kernel: Add notifications context option to C header
What changed, and why it matters
This commit adds a new public API feature to the Bitcoin Core kernel library that lets outside programs register callback functions to receive notifications about new blocks, sync progress, warnings, and serious errors. It is a feature addition, not a fix for a known security bug. There is no evidence in the commit or supplied references that this change addresses a security issue or introduces a vulnerability.
No security action required. Review as normal API/feature code. If auditing, verify that user_data lifetime and callback re-entrancy expectations are documented clearly for C consumers.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The change exposes the existing internal kernel::Notifications interface through the C ABI (bitcoinkernel.h) and the C++ wrapper (bitcoinkernel_wrapper.h). It adds btck_NotificationInterfaceCallbacks, related enums, a btck_BlockTreeEntry opaque type, and btck_context_options_set_notifications(). The implementation in bitcoinkernel.cpp creates a KernelNotifications adapter that forwards C++ virtual notifications to the user-supplied C callbacks, with optional user_data destruction. A default no-op notification object is created if none is supplied. Tests are updated to exercise the new callbacks.
Changed components
src/kernel/bitcoinkernel.hsrc/kernel/bitcoinkernel.cppsrc/kernel/bitcoinkernel_wrapper.hsrc/test/kernel/test_kernel.cppInspect captured patch +270 / −3
diff --git a/src/kernel/bitcoinkernel.cpp b/src/kernel/bitcoinkernel.cpp
index 35e03372..2670287a 100644
--- a/src/kernel/bitcoinkernel.cpp
+++ b/src/kernel/bitcoinkernel.cpp
@@ -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;
@@ -224,9 +258,61 @@ struct LoggingConnection {
}
};
+class KernelNotifications final : 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
@@ -234,7 +320,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;
@@ -242,7 +328,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) {
@@ -250,11 +335,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;
@@ -511,6 +603,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;
diff --git a/src/kernel/bitcoinkernel.h b/src/kernel/bitcoinkernel.h
index f901dedd..25f39a11 100644
--- a/src/kernel/bitcoinkernel.h
+++ b/src/kernel/bitcoinkernel.h
@@ -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
@@ -148,6 +153,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 */
/**
@@ -161,6 +190,43 @@ 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.
+ *
+ * If user_data_destroy is provided, the kernel will automatically call this
+ * callback to clean up user_data when the notification interface object is destroyed.
+ * If user_data_destroy is NULL, it is 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.
*/
@@ -572,6 +638,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.
*/
@@ -591,7 +668,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);
diff --git a/src/kernel/bitcoinkernel_wrapper.h b/src/kernel/bitcoinkernel_wrapper.h
index b550c3a5..8e30b6ac 100644
--- a/src/kernel/bitcoinkernel_wrapper.h
+++ b/src/kernel/bitcoinkernel_wrapper.h
@@ -46,6 +46,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_OK,
ERROR_INVALID_FLAGS_COMBINATION = btck_ScriptVerifyStatus_ERROR_INVALID_FLAGS_COMBINATION,
@@ -531,6 +542,35 @@ public:
}
};
+class BlockTreeEntry : View<btck_BlockTreeEntry>
+{
+public:
+ BlockTreeEntry(const btck_BlockTreeEntry* entry)
+ : View{entry}
+ {
+ }
+};
+
+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:
@@ -547,6 +587,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>);
+ 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}); },
+ });
+ }
};
class Context : public Handle<btck_Context, btck_context_copy, btck_context_destroy>
diff --git a/src/test/kernel/test_kernel.cpp b/src/test/kernel/test_kernel.cpp
index 8fc0308c..ef669cb6 100644
--- a/src/test/kernel/test_kernel.cpp
+++ b/src/test/kernel/test_kernel.cpp
@@ -60,6 +60,35 @@ public:
}
};
+class TestKernelNotifications : public KernelNotifications
+{
+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,
@@ -401,6 +430,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};
}
}
Why this scored 18/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.