kernel: Add notifications context option to C header

The notifications are used for notifying on connected blocks and on
warning and fatal error conditions.

The user of the C header may define callbacks that gets passed to the
internal notification object in the
`kernel_NotificationInterfaceCallbacks` struct.

Each of the callbacks take a `user_data` argument that gets populated
from the `user_data` value in the struct. It can be used to recreate the
structure containing the callbacks on the user's side, or to give the
callbacks additional contextual information.
This commit is contained in:
TheCharlatan 2024-06-03 14:51:29 +02:00
parent 86cd091d74
commit 96651a3f86
No known key found for this signature in database
GPG Key ID: 9B79B45691DB4173
4 changed files with 267 additions and 3 deletions

View File

@ -12,6 +12,7 @@
#include <kernel/context.h>
#include <kernel/cs_main.h>
#include <kernel/notifications_interface.h>
#include <kernel/warning.h>
#include <logging.h>
#include <primitives/transaction.h>
#include <script/interpreter.h>
@ -23,6 +24,7 @@
#include <util/result.h>
#include <util/signalinterrupt.h>
#include <util/translation.h>
#include <validation.h>
#include <cassert>
#include <cstddef>
@ -36,6 +38,8 @@
#include <utility>
#include <vector>
class CBlockIndex;
// Define G_TRANSLATION_FUN symbol in libbitcoinkernel library so users of the
// library aren't required to export this symbol
extern const std::function<std::string(const char*)> G_TRANSLATION_FUN{nullptr};
@ -120,6 +124,12 @@ struct Handle {
}
};
} // namespace
struct btck_BlockTreeEntry: Handle<btck_BlockTreeEntry, CBlockIndex> {};
namespace {
BCLog::Level get_bclog_level(btck_LogLevel level)
{
switch (level) {
@ -176,6 +186,30 @@ BCLog::LogFlags get_bclog_flag(btck_LogCategory category)
assert(false);
}
btck_SynchronizationState cast_state(SynchronizationState state)
{
switch (state) {
case SynchronizationState::INIT_REINDEX:
return btck_SynchronizationState_INIT_REINDEX;
case SynchronizationState::INIT_DOWNLOAD:
return btck_SynchronizationState_INIT_DOWNLOAD;
case SynchronizationState::POST_INIT:
return btck_SynchronizationState_POST_INIT;
} // no default case, so the compiler can warn about missing cases
assert(false);
}
btck_Warning cast_btck_warning(kernel::Warning warning)
{
switch (warning) {
case kernel::Warning::UNKNOWN_NEW_RULES_ACTIVATED:
return btck_Warning_UNKNOWN_NEW_RULES_ACTIVATED;
case kernel::Warning::LARGE_WORK_INVALID_CHAIN:
return btck_Warning_LARGE_WORK_INVALID_CHAIN;
} // no default case, so the compiler can warn about missing cases
assert(false);
}
struct LoggingConnection {
std::unique_ptr<std::list<std::function<void(const std::string&)>>::iterator> m_connection;
void* m_user_data;
@ -233,9 +267,61 @@ struct LoggingConnection {
}
};
class KernelNotifications : public kernel::Notifications
{
private:
btck_NotificationInterfaceCallbacks m_cbs;
public:
KernelNotifications(btck_NotificationInterfaceCallbacks cbs)
: m_cbs{cbs}
{
}
~KernelNotifications()
{
if (m_cbs.user_data && m_cbs.user_data_destroy) {
m_cbs.user_data_destroy(m_cbs.user_data);
}
m_cbs.user_data_destroy = nullptr;
m_cbs.user_data = nullptr;
}
kernel::InterruptResult blockTip(SynchronizationState state, const CBlockIndex& index, double verification_progress) override
{
if (m_cbs.block_tip) m_cbs.block_tip(m_cbs.user_data, cast_state(state), btck_BlockTreeEntry::ref(&index), verification_progress);
return {};
}
void headerTip(SynchronizationState state, int64_t height, int64_t timestamp, bool presync) override
{
if (m_cbs.header_tip) m_cbs.header_tip(m_cbs.user_data, cast_state(state), height, timestamp, presync ? 1 : 0);
}
void progress(const bilingual_str& title, int progress_percent, bool resume_possible) override
{
if (m_cbs.progress) m_cbs.progress(m_cbs.user_data, title.original.c_str(), title.original.length(), progress_percent, resume_possible ? 1 : 0);
}
void warningSet(kernel::Warning id, const bilingual_str& message) override
{
if (m_cbs.warning_set) m_cbs.warning_set(m_cbs.user_data, cast_btck_warning(id), message.original.c_str(), message.original.length());
}
void warningUnset(kernel::Warning id) override
{
if (m_cbs.warning_unset) m_cbs.warning_unset(m_cbs.user_data, cast_btck_warning(id));
}
void flushError(const bilingual_str& message) override
{
if (m_cbs.flush_error) m_cbs.flush_error(m_cbs.user_data, message.original.c_str(), message.original.length());
}
void fatalError(const bilingual_str& message) override
{
if (m_cbs.fatal_error) m_cbs.fatal_error(m_cbs.user_data, message.original.c_str(), message.original.length());
}
};
struct ContextOptions {
mutable Mutex m_mutex;
std::unique_ptr<const CChainParams> m_chainparams GUARDED_BY(m_mutex);
std::shared_ptr<KernelNotifications> m_notifications GUARDED_BY(m_mutex);
};
class Context
@ -243,7 +329,7 @@ class Context
public:
std::unique_ptr<kernel::Context> m_context;
std::unique_ptr<kernel::Notifications> m_notifications;
std::shared_ptr<KernelNotifications> m_notifications;
std::unique_ptr<util::SignalInterrupt> m_interrupt;
@ -251,7 +337,6 @@ public:
Context(const ContextOptions* options, bool& sane)
: m_context{std::make_unique<kernel::Context>()},
m_notifications{std::make_unique<kernel::Notifications>()},
m_interrupt{std::make_unique<util::SignalInterrupt>()}
{
if (options) {
@ -259,11 +344,18 @@ public:
if (options->m_chainparams) {
m_chainparams = std::make_unique<const CChainParams>(*options->m_chainparams);
}
if (options->m_notifications) {
m_notifications = options->m_notifications;
}
}
if (!m_chainparams) {
m_chainparams = CChainParams::Main();
}
if (!m_notifications) {
m_notifications = std::make_shared<KernelNotifications>(btck_NotificationInterfaceCallbacks{
nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr});
}
if (!kernel::SanityChecks(*m_context)) {
sane = false;
@ -507,6 +599,13 @@ void btck_context_options_set_chainparams(btck_ContextOptions* options, const bt
btck_ContextOptions::get(options).m_chainparams = std::make_unique<const CChainParams>(*btck_ChainParameters::get(chain_parameters));
}
void btck_context_options_set_notifications(btck_ContextOptions* options, btck_NotificationInterfaceCallbacks notifications)
{
// The KernelNotifications are copy-initialized, so the caller can free them again.
LOCK(btck_ContextOptions::get(options).m_mutex);
btck_ContextOptions::get(options).m_notifications = std::make_shared<KernelNotifications>(notifications);
}
void btck_context_options_destroy(btck_ContextOptions* options)
{
delete options;

View File

@ -67,6 +67,11 @@ extern "C" {
* functions, e.g. for scripts, may communicate more detailed error information
* through status code out parameters.
*
* The kernel notifications issue callbacks for errors. These are usually
* indicative of a system error. If such an error is issued, it is recommended
* to halt and tear down the existing kernel objects. Remediating the error may
* require system intervention by the user.
*
* @section pointer Pointer and argument conventions
*
* The user is responsible for de-allocating the memory owned by pointers
@ -147,6 +152,30 @@ typedef struct btck_ContextOptions btck_ContextOptions;
*/
typedef struct btck_Context btck_Context;
/**
* Opaque data structure for holding a block tree entry.
*
* This is a pointer to an element in the block index currently in memory of
* the chainstate manager. It is valid for the lifetime of the chainstate
* manager it was retrieved from. The entry is part of a tree-like structure
* that is maintained internally. Every entry, besides the genesis, points to a
* single parent. Multiple entries may share a parent, thus forming a tree.
* Each entry corresponds to a single block and may be used to retrieve its
* data and validation status.
*/
typedef struct btck_BlockTreeEntry btck_BlockTreeEntry;
/** Current sync state passed to tip changed callbacks. */
typedef uint8_t btck_SynchronizationState;
#define btck_SynchronizationState_INIT_REINDEX ((btck_SynchronizationState)(0))
#define btck_SynchronizationState_INIT_DOWNLOAD ((btck_SynchronizationState)(1))
#define btck_SynchronizationState_POST_INIT ((btck_SynchronizationState)(2))
/** Possible warning types issued by validation. */
typedef uint8_t btck_Warning;
#define btck_Warning_UNKNOWN_NEW_RULES_ACTIVATED ((btck_Warning)(0))
#define btck_Warning_LARGE_WORK_INVALID_CHAIN ((btck_Warning)(1))
/** Callback function types */
/**
@ -160,6 +189,39 @@ typedef void (*btck_LogCallback)(void* user_data, const char* message, size_t me
*/
typedef void (*btck_DestroyCallback)(void* user_data);
/**
* Function signatures for the kernel notifications.
*/
typedef void (*btck_NotifyBlockTip)(void* user_data, btck_SynchronizationState state, const btck_BlockTreeEntry* entry, double verification_progress);
typedef void (*btck_NotifyHeaderTip)(void* user_data, btck_SynchronizationState state, int64_t height, int64_t timestamp, int presync);
typedef void (*btck_NotifyProgress)(void* user_data, const char* title, size_t title_len, int progress_percent, int resume_possible);
typedef void (*btck_NotifyWarningSet)(void* user_data, btck_Warning warning, const char* message, size_t message_len);
typedef void (*btck_NotifyWarningUnset)(void* user_data, btck_Warning warning);
typedef void (*btck_NotifyFlushError)(void* user_data, const char* message, size_t message_len);
typedef void (*btck_NotifyFatalError)(void* user_data, const char* message, size_t message_len);
/**
* A struct for holding the kernel notification callbacks. The user data
* pointer may be used to point to user-defined structures to make processing
* the notifications easier. Note that this makes it the user's responsibility
* to ensure that the user_data outlives the kernel objects. Notifications can
* occur even as kernel objects are deleted, so care has to be taken to ensure
* safe unwinding.
*/
typedef struct {
void* user_data; //!< Holds a user-defined opaque structure that is passed to the notification callbacks.
//!< If user_data_destroy is also defined ownership of the user_data is passed to the
//!< created context options and subsequently context.
btck_DestroyCallback user_data_destroy; //!< Frees the provided user data structure.
btck_NotifyBlockTip block_tip; //!< The chain's tip was updated to the provided block entry.
btck_NotifyHeaderTip header_tip; //!< A new best block header was added.
btck_NotifyProgress progress; //!< Reports on current block synchronization progress.
btck_NotifyWarningSet warning_set; //!< A warning issued by the kernel library during validation.
btck_NotifyWarningUnset warning_unset; //!< A previous condition leading to the issuance of a warning is no longer given.
btck_NotifyFlushError flush_error; //!< An error encountered when flushing data to disk.
btck_NotifyFatalError fatal_error; //!< A un-recoverable system error encountered by the library.
} btck_NotificationInterfaceCallbacks;
/**
* A collection of logging categories that may be encountered by kernel code.
*/
@ -561,6 +623,17 @@ BITCOINKERNEL_API void btck_context_options_set_chainparams(
btck_ContextOptions* context_options,
const btck_ChainParameters* chain_parameters) BITCOINKERNEL_ARG_NONNULL(1, 2);
/**
* @brief Set the kernel notifications for the context options. The context
* created with the options will be configured with these notifications.
*
* @param[in] context_options Non-null, previously created by @ref btck_context_options_create.
* @param[in] notifications Is set to the context options.
*/
BITCOINKERNEL_API void btck_context_options_set_notifications(
btck_ContextOptions* context_options,
btck_NotificationInterfaceCallbacks notifications) BITCOINKERNEL_ARG_NONNULL(1);
/**
* Destroy the context options.
*/
@ -580,7 +653,7 @@ BITCOINKERNEL_API void btck_context_options_destroy(btck_ContextOptions* context
* kernel notification callbacks.
*
* @param[in] context_options Nullable, created by @ref btck_context_options_create.
* @return The allocated kernel context, or null on error.
* @return The allocated context, or null on error.
*/
BITCOINKERNEL_API btck_Context* BITCOINKERNEL_WARN_UNUSED_RESULT btck_context_create(
const btck_ContextOptions* context_options);

View File

@ -49,6 +49,17 @@ enum class ChainType : btck_ChainType {
REGTEST = btck_ChainType_REGTEST
};
enum class SynchronizationState : btck_SynchronizationState {
INIT_REINDEX = btck_SynchronizationState_INIT_REINDEX,
INIT_DOWNLOAD = btck_SynchronizationState_INIT_DOWNLOAD,
POST_INIT = btck_SynchronizationState_POST_INIT
};
enum class Warning : btck_Warning {
UNKNOWN_NEW_RULES_ACTIVATED = btck_Warning_UNKNOWN_NEW_RULES_ACTIVATED,
LARGE_WORK_INVALID_CHAIN = btck_Warning_LARGE_WORK_INVALID_CHAIN
};
enum class ScriptVerifyStatus : btck_ScriptVerifyStatus {
OK = btck_ScriptVerifyStatus_SCRIPT_VERIFY_OK,
ERROR_INVALID_FLAGS_COMBINATION = btck_ScriptVerifyStatus_ERROR_INVALID_FLAGS_COMBINATION,
@ -526,6 +537,36 @@ public:
}
};
class BlockTreeEntry : View<btck_BlockTreeEntry>
{
public:
BlockTreeEntry(const btck_BlockTreeEntry* entry)
: View{entry}
{
}
};
template <typename T>
class KernelNotifications
{
public:
virtual ~KernelNotifications() = default;
virtual void BlockTipHandler(SynchronizationState state, BlockTreeEntry entry, double verification_progress) {}
virtual void HeaderTipHandler(SynchronizationState state, int64_t height, int64_t timestamp, bool presync) {}
virtual void ProgressHandler(std::string_view title, int progress_percent, bool resume_possible) {}
virtual void WarningSetHandler(Warning warning, std::string_view message) {}
virtual void WarningUnsetHandler(Warning warning) {}
virtual void FlushErrorHandler(std::string_view error) {}
virtual void FatalErrorHandler(std::string_view error) {}
};
class ChainParams : public Handle<btck_ChainParameters, btck_chain_parameters_copy, btck_chain_parameters_destroy>
{
public:
@ -545,6 +586,27 @@ public:
btck_context_options_set_chainparams(get(), chain_params.get());
}
template <typename T>
void SetNotifications(std::shared_ptr<T> notifications)
{
static_assert(std::is_base_of_v<KernelNotifications<T>, T>);
auto heap_notifications = std::make_unique<std::shared_ptr<T>>(std::move(notifications));
using user_type = std::shared_ptr<T>*;
btck_context_options_set_notifications(
get(),
btck_NotificationInterfaceCallbacks{
.user_data = heap_notifications.release(),
.user_data_destroy = +[](void* user_data) { delete static_cast<user_type>(user_data); },
.block_tip = +[](void* user_data, btck_SynchronizationState state, const btck_BlockTreeEntry* entry, double verification_progress) { (*static_cast<user_type>(user_data))->BlockTipHandler(static_cast<SynchronizationState>(state), BlockTreeEntry{entry}, verification_progress); },
.header_tip = +[](void* user_data, btck_SynchronizationState state, int64_t height, int64_t timestamp, int presync) { (*static_cast<user_type>(user_data))->HeaderTipHandler(static_cast<SynchronizationState>(state), height, timestamp, presync == 1); },
.progress = +[](void* user_data, const char* title, size_t title_len, int progress_percent, int resume_possible) { (*static_cast<user_type>(user_data))->ProgressHandler({title, title_len}, progress_percent, resume_possible == 1); },
.warning_set = +[](void* user_data, btck_Warning warning, const char* message, size_t message_len) { (*static_cast<user_type>(user_data))->WarningSetHandler(static_cast<Warning>(warning), {message, message_len}); },
.warning_unset = +[](void* user_data, btck_Warning warning) { (*static_cast<user_type>(user_data))->WarningUnsetHandler(static_cast<Warning>(warning)); },
.flush_error = +[](void* user_data, const char* error, size_t error_len) { (*static_cast<user_type>(user_data))->FlushErrorHandler({error, error_len}); },
.fatal_error = +[](void* user_data, const char* error, size_t error_len) { (*static_cast<user_type>(user_data))->FatalErrorHandler({error, error_len}); },
});
}
friend class Context;
};

View File

@ -60,6 +60,35 @@ public:
}
};
class TestKernelNotifications : public KernelNotifications<TestKernelNotifications>
{
public:
void HeaderTipHandler(SynchronizationState state, int64_t height, int64_t timestamp, bool presync) override
{
BOOST_CHECK_GT(timestamp, 0);
}
void WarningSetHandler(Warning warning, std::string_view message) override
{
std::cout << "Kernel warning is set: " << message << std::endl;
}
void WarningUnsetHandler(Warning warning) override
{
std::cout << "Kernel warning was unset." << std::endl;
}
void FlushErrorHandler(std::string_view error) override
{
std::cout << error << std::endl;
}
void FatalErrorHandler(std::string_view error) override
{
std::cout << error << std::endl;
}
};
void run_verify_test(
const ScriptPubkey& spent_script_pubkey,
const Transaction& spending_tx,
@ -404,6 +433,7 @@ BOOST_AUTO_TEST_CASE(btck_context_tests)
ChainParams regtest_params{ChainType::REGTEST};
CheckHandle(params, regtest_params);
options.SetChainParams(params);
options.SetNotifications(std::make_shared<TestKernelNotifications>());
Context context{options};
}
}