diff --git a/src/kernel/bitcoinkernel.cpp b/src/kernel/bitcoinkernel.cpp index 1c146df0cb0..9c96a1c4900 100644 --- a/src/kernel/bitcoinkernel.cpp +++ b/src/kernel/bitcoinkernel.cpp @@ -31,8 +31,10 @@ #include #include #include +#include #include #include +#include #include #include @@ -47,6 +49,8 @@ #include #include +using util::ImmediateTaskRunner; + // Define G_TRANSLATION_FUN symbol in libbitcoinkernel library so users of the // library aren't required to export this symbol extern const std::function G_TRANSLATION_FUN{nullptr}; @@ -135,6 +139,7 @@ struct Handle { struct btck_BlockTreeEntry: Handle {}; struct btck_Block : Handle> {}; +struct btck_BlockValidationState : Handle {}; namespace { @@ -317,10 +322,65 @@ public: } }; +class KernelValidationInterface final : public CValidationInterface +{ +public: + btck_ValidationInterfaceCallbacks m_cbs; + + explicit KernelValidationInterface(const btck_ValidationInterfaceCallbacks vi_cbs) : m_cbs{vi_cbs} {} + + ~KernelValidationInterface() + { + if (m_cbs.user_data && m_cbs.user_data_destroy) { + m_cbs.user_data_destroy(m_cbs.user_data); + } + m_cbs.user_data = nullptr; + m_cbs.user_data_destroy = nullptr; + } + +protected: + void BlockChecked(const std::shared_ptr& block, const BlockValidationState& stateIn) override + { + if (m_cbs.block_checked) { + m_cbs.block_checked(m_cbs.user_data, + btck_Block::copy(btck_Block::ref(&block)), + btck_BlockValidationState::ref(&stateIn)); + } + } + + void NewPoWValidBlock(const CBlockIndex* pindex, const std::shared_ptr& block) override + { + if (m_cbs.pow_valid_block) { + m_cbs.pow_valid_block(m_cbs.user_data, + btck_Block::copy(btck_Block::ref(&block)), + btck_BlockTreeEntry::ref(pindex)); + } + } + + void BlockConnected(ChainstateRole role, const std::shared_ptr& block, const CBlockIndex* pindex) override + { + if (m_cbs.block_connected) { + m_cbs.block_connected(m_cbs.user_data, + btck_Block::copy(btck_Block::ref(&block)), + btck_BlockTreeEntry::ref(pindex)); + } + } + + void BlockDisconnected(const std::shared_ptr& block, const CBlockIndex* pindex) override + { + if (m_cbs.block_disconnected) { + m_cbs.block_disconnected(m_cbs.user_data, + btck_Block::copy(btck_Block::ref(&block)), + btck_BlockTreeEntry::ref(pindex)); + } + } +}; + struct ContextOptions { mutable Mutex m_mutex; std::unique_ptr m_chainparams GUARDED_BY(m_mutex); std::shared_ptr m_notifications GUARDED_BY(m_mutex); + std::shared_ptr m_validation_interface GUARDED_BY(m_mutex); }; class Context @@ -332,8 +392,12 @@ public: std::unique_ptr m_interrupt; + std::unique_ptr m_signals; + std::unique_ptr m_chainparams; + std::shared_ptr m_validation_interface; + Context(const ContextOptions* options, bool& sane) : m_context{std::make_unique()}, m_interrupt{std::make_unique()} @@ -346,6 +410,11 @@ public: if (options->m_notifications) { m_notifications = options->m_notifications; } + if (options->m_validation_interface) { + m_signals = std::make_unique(std::make_unique()); + m_validation_interface = options->m_validation_interface; + m_signals->RegisterSharedValidationInterface(m_validation_interface); + } } if (!m_chainparams) { @@ -360,6 +429,13 @@ public: sane = false; } } + + ~Context() + { + if (m_signals) { + m_signals->UnregisterSharedValidationInterface(m_validation_interface); + } + } }; //! Helper struct to wrap the ChainstateManager-related Options @@ -374,7 +450,8 @@ struct ChainstateManagerOptions { : m_chainman_options{ChainstateManager::Options{ .chainparams = *context->m_chainparams, .datadir = data_dir, - .notifications = *context->m_notifications}}, + .notifications = *context->m_notifications, + .signals = context->m_signals.get()}}, m_blockman_options{node::BlockManager::Options{ .chainparams = *context->m_chainparams, .blocks_dir = blocks_dir, @@ -654,6 +731,12 @@ void btck_context_options_set_notifications(btck_ContextOptions* options, btck_N btck_ContextOptions::get(options).m_notifications = std::make_shared(notifications); } +void btck_context_options_set_validation_interface(btck_ContextOptions* options, btck_ValidationInterfaceCallbacks vi_cbs) +{ + LOCK(btck_ContextOptions::get(options).m_mutex); + btck_ContextOptions::get(options).m_validation_interface = std::make_shared(vi_cbs); +} + void btck_context_options_destroy(btck_ContextOptions* options) { delete options; diff --git a/src/kernel/bitcoinkernel.h b/src/kernel/bitcoinkernel.h index 8d7b2898251..b41ff90e744 100644 --- a/src/kernel/bitcoinkernel.h +++ b/src/kernel/bitcoinkernel.h @@ -67,6 +67,9 @@ extern "C" { * functions, e.g. for scripts, may communicate more detailed error information * through status code out parameters. * + * Fine-grained validation information is communicated through the validation + * interface. + * * 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 @@ -149,6 +152,10 @@ typedef struct btck_ContextOptions btck_ContextOptions; * other validation objects are instantiated from it, the context is kept in * memory for the duration of their lifetimes. * + * The processing of validation events is done through an internal task runner + * owned by the context. It passes events through the registered validation + * interface callbacks. + * * A constructed context can be safely used from multiple threads. */ typedef struct btck_Context btck_Context; @@ -191,6 +198,14 @@ typedef struct btck_ChainstateManager btck_ChainstateManager; */ typedef struct btck_Block btck_Block; +/** + * Opaque data structure for holding the state of a block during validation. + * + * Contains information indicating whether validation was successful, and if not + * which step during block validation failed. + */ +typedef struct btck_BlockValidationState btck_BlockValidationState; + /** Current sync state passed to tip changed callbacks. */ typedef uint8_t btck_SynchronizationState; #define btck_SynchronizationState_INIT_REINDEX ((btck_SynchronizationState)(0)) @@ -226,6 +241,33 @@ 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); +/** + * Function signatures for the validation interface. + */ +typedef void (*btck_ValidationInterfaceBlockChecked)(void* user_data, btck_Block* block, const btck_BlockValidationState* state); +typedef void (*btck_ValidationInterfacePoWValidBlock)(void* user_data, btck_Block* block, const btck_BlockTreeEntry* entry); +typedef void (*btck_ValidationInterfaceBlockConnected)(void* user_data, btck_Block* block, const btck_BlockTreeEntry* entry); +typedef void (*btck_ValidationInterfaceBlockDisconnected)(void* user_data, btck_Block* block, const btck_BlockTreeEntry* entry); + +/** + * Holds the validation interface callbacks. The user data pointer may be used + * to point to user-defined structures to make processing the validation + * callbacks easier. Note that these callbacks block any further validation + * execution when they are called. + */ +typedef struct { + void* user_data; //!< Holds a user-defined opaque structure that is passed to the validation + //!< interface 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_ValidationInterfaceBlockChecked block_checked; //!< Called when a new block has been fully validated. Contains the + //!< result of its validation. + btck_ValidationInterfacePoWValidBlock pow_valid_block; //!< Called when a new block extends the header chain and has a valid transaction + //!< and segwit merkle root. + btck_ValidationInterfaceBlockConnected block_connected; //!< Called when a block is valid and has now been connected to the best chain. + btck_ValidationInterfaceBlockDisconnected block_disconnected; //!< Called during a re-org when a block has been removed from the best chain. +} btck_ValidationInterfaceCallbacks; + /** * A struct for holding the kernel notification callbacks. The user data * pointer may be used to point to user-defined structures to make processing @@ -674,6 +716,20 @@ BITCOINKERNEL_API void btck_context_options_set_notifications( btck_ContextOptions* context_options, btck_NotificationInterfaceCallbacks notifications) BITCOINKERNEL_ARG_NONNULL(1); +/** + * @brief Set the validation interface callbacks for the context options. The + * context created with the options will be configured for these validation + * interface callbacks. The callbacks will then be triggered from validation + * events issued by the chainstate manager created from the same context. + * + * @param[in] context_options Non-null, previously created with btck_context_options_create. + * @param[in] validation_interface_callbacks The callbacks used for passing validation information to the + * user. + */ +BITCOINKERNEL_API void btck_context_options_set_validation_interface( + btck_ContextOptions* context_options, + btck_ValidationInterfaceCallbacks validation_interface_callbacks) BITCOINKERNEL_ARG_NONNULL(1); + /** * Destroy the context options. */ @@ -835,7 +891,9 @@ BITCOINKERNEL_API int BITCOINKERNEL_WARN_UNUSED_RESULT btck_chainstate_manager_i * manager. Processing first does checks on the block, and if these passed, * saves it to disk. It then validates the block against the utxo set. If it is * valid, the chain is extended with it. The return value is not indicative of - * the block's validity. + * the block's validity. Detailed information on the validity of the block can + * be retrieved by registering the `block_checked` callback in the validation + * interface. * * @param[in] chainstate_manager Non-null. * @param[in] block Non-null, block to be validated. diff --git a/src/kernel/bitcoinkernel_wrapper.h b/src/kernel/bitcoinkernel_wrapper.h index 4c378d3a8fa..7105f9e95d6 100644 --- a/src/kernel/bitcoinkernel_wrapper.h +++ b/src/kernel/bitcoinkernel_wrapper.h @@ -604,6 +604,34 @@ public: virtual void FatalErrorHandler(std::string_view error) {} }; +class BlockValidationState +{ +private: + const btck_BlockValidationState* m_state; + +public: + BlockValidationState(const btck_BlockValidationState* state) : m_state{state} {} + + BlockValidationState(const BlockValidationState&) = delete; + BlockValidationState& operator=(const BlockValidationState&) = delete; + BlockValidationState(BlockValidationState&&) = delete; + BlockValidationState& operator=(BlockValidationState&&) = delete; +}; + +class ValidationInterface +{ +public: + virtual ~ValidationInterface() = default; + + virtual void BlockChecked(Block block, const BlockValidationState state) {} + + virtual void PowValidBlock(BlockTreeEntry entry, Block block) {} + + virtual void BlockConnected(Block block, BlockTreeEntry entry) {} + + virtual void BlockDisconnected(Block block, BlockTreeEntry entry) {} +}; + class ChainParams : public Handle { public: @@ -641,6 +669,24 @@ public: .fatal_error = +[](void* user_data, const char* error, size_t error_len) { (*static_cast(user_data))->FatalErrorHandler({error, error_len}); }, }); } + + template + void SetValidationInterface(std::shared_ptr validation_interface) + { + static_assert(std::is_base_of_v); + auto heap_vi = std::make_unique>(std::move(validation_interface)); + using user_type = std::shared_ptr*; + btck_context_options_set_validation_interface( + get(), + btck_ValidationInterfaceCallbacks{ + .user_data = heap_vi.release(), + .user_data_destroy = +[](void* user_data) { delete static_cast(user_data); }, + .block_checked = +[](void* user_data, btck_Block* block, const btck_BlockValidationState* state) { (*static_cast(user_data))->BlockChecked(Block{block}, BlockValidationState{state}); }, + .pow_valid_block = +[](void* user_data, btck_Block* block, const btck_BlockTreeEntry* entry) { (*static_cast(user_data))->PowValidBlock(BlockTreeEntry{entry}, Block{block}); }, + .block_connected = +[](void* user_data, btck_Block* block, const btck_BlockTreeEntry* entry) { (*static_cast(user_data))->BlockConnected(Block{block}, BlockTreeEntry{entry}); }, + .block_disconnected = +[](void* user_data, btck_Block* block, const btck_BlockTreeEntry* entry) { (*static_cast(user_data))->BlockDisconnected(Block{block}, BlockTreeEntry{entry}); }, + }); + } }; class Context : public Handle diff --git a/src/test/kernel/test_kernel.cpp b/src/test/kernel/test_kernel.cpp index cfe3b2d2d4a..4cf3df7f57e 100644 --- a/src/test/kernel/test_kernel.cpp +++ b/src/test/kernel/test_kernel.cpp @@ -126,6 +126,30 @@ public: } }; +class TestValidationInterface : public ValidationInterface +{ +public: + void BlockChecked(Block block, const BlockValidationState state) override + { + std::cout << "Block checked." << std::endl; + } + + void BlockConnected(Block block, BlockTreeEntry entry) override + { + std::cout << "Block connected." << std::endl; + } + + void PowValidBlock(BlockTreeEntry entry, Block block) override + { + std::cout << "Block passed pow verification" << std::endl; + } + + void BlockDisconnected(Block block, BlockTreeEntry entry) override + { + std::cout << "Block disconnected." << std::endl; + } +}; + void run_verify_test( const ScriptPubkey& spent_script_pubkey, const Transaction& spending_tx, @@ -481,12 +505,15 @@ BOOST_AUTO_TEST_CASE(btck_block) CheckRange(block_tx.Transactions(), block_tx.CountTransactions()); } -Context create_context(std::shared_ptr notifications, ChainType chain_type) +Context create_context(std::shared_ptr notifications, ChainType chain_type, std::shared_ptr validation_interface = nullptr) { ContextOptions options{}; ChainParams params{chain_type}; options.SetChainParams(params); options.SetNotifications(notifications); + if (validation_interface) { + options.SetValidationInterface(validation_interface); + } auto context{Context{options}}; return context; } @@ -571,7 +598,8 @@ void chainman_reindex_chainstate_test(TestDirectory& test_directory) void chainman_mainnet_validation_test(TestDirectory& test_directory) { auto notifications{std::make_shared()}; - auto context{create_context(notifications, ChainType::MAINNET)}; + auto validation_interface{std::make_shared()}; + auto context{create_context(notifications, ChainType::MAINNET, validation_interface)}; auto chainman{create_chainman(test_directory, false, false, false, false, context)}; {