mirror of https://github.com/bitcoin/bitcoin.git
kernel: Add validation interface to C header
This adds the infrastructure required to process validation events. For now the external validation interface only has support for the `BlockChecked` , `NewPoWValidBlock`, `BlockConnected`, and `BlockDisconnected` callback. Support for the other internal validation interface methods can be added in the future. The validation interface follows an architecture for defining its callbacks and ownership that is similar to the notifications. The task runner is created internally with a context, which itself internally creates a unique ValidationSignals object. When the user creates a new chainstate manager the validation signals are internally passed to the chainstate manager through the context. The callbacks block any further validation execution when they are called. It is up to the user to either multiplex them, or use them otherwise in a multithreaded mechanism to make processing the validation events non-blocking. A validation interface can register for validation events with a context. Internally the passed in validation interface is registerd with the validation signals of a context.
This commit is contained in:
parent
8d9088c5db
commit
403fb9b120
|
@ -31,8 +31,10 @@
|
|||
#include <util/fs.h>
|
||||
#include <util/result.h>
|
||||
#include <util/signalinterrupt.h>
|
||||
#include <util/task_runner.h>
|
||||
#include <util/translation.h>
|
||||
#include <validation.h>
|
||||
#include <validationinterface.h>
|
||||
|
||||
#include <cassert>
|
||||
#include <cstddef>
|
||||
|
@ -47,6 +49,8 @@
|
|||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
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<std::string(const char*)> G_TRANSLATION_FUN{nullptr};
|
||||
|
@ -135,6 +139,7 @@ struct Handle {
|
|||
|
||||
struct btck_BlockTreeEntry: Handle<btck_BlockTreeEntry, CBlockIndex> {};
|
||||
struct btck_Block : Handle<btck_Block, std::shared_ptr<const CBlock>> {};
|
||||
struct btck_BlockValidationState : Handle<btck_BlockValidationState, BlockValidationState> {};
|
||||
|
||||
namespace {
|
||||
|
||||
|
@ -326,10 +331,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<const CBlock>& block, const BlockValidationState& stateIn) override
|
||||
{
|
||||
if (m_cbs.block_checked) {
|
||||
m_cbs.block_checked(m_cbs.user_data,
|
||||
btck_Block::ref(new std::shared_ptr<const CBlock>{block}),
|
||||
btck_BlockValidationState::ref(&stateIn));
|
||||
}
|
||||
}
|
||||
|
||||
void NewPoWValidBlock(const CBlockIndex* pindex, const std::shared_ptr<const CBlock>& block) override
|
||||
{
|
||||
if (m_cbs.pow_valid_block) {
|
||||
m_cbs.pow_valid_block(m_cbs.user_data,
|
||||
btck_BlockTreeEntry::ref(pindex),
|
||||
btck_Block::ref(new std::shared_ptr<const CBlock>{block}));
|
||||
}
|
||||
}
|
||||
|
||||
void BlockConnected(ChainstateRole role, const std::shared_ptr<const CBlock>& block, const CBlockIndex* pindex) override
|
||||
{
|
||||
if (m_cbs.block_connected) {
|
||||
m_cbs.block_connected(m_cbs.user_data,
|
||||
btck_Block::ref(new std::shared_ptr<const CBlock>{block}),
|
||||
btck_BlockTreeEntry::ref(pindex));
|
||||
}
|
||||
}
|
||||
|
||||
void BlockDisconnected(const std::shared_ptr<const CBlock>& block, const CBlockIndex* pindex) override
|
||||
{
|
||||
if (m_cbs.block_disconnected) {
|
||||
m_cbs.block_disconnected(m_cbs.user_data,
|
||||
btck_Block::ref(new std::shared_ptr<const CBlock>{block}),
|
||||
btck_BlockTreeEntry::ref(pindex));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
std::shared_ptr<KernelValidationInterface> m_validation_interface GUARDED_BY(m_mutex);
|
||||
};
|
||||
|
||||
class Context
|
||||
|
@ -341,11 +401,16 @@ public:
|
|||
|
||||
std::unique_ptr<util::SignalInterrupt> m_interrupt;
|
||||
|
||||
std::unique_ptr<ValidationSignals> m_signals;
|
||||
|
||||
std::unique_ptr<const CChainParams> m_chainparams;
|
||||
|
||||
std::shared_ptr<KernelValidationInterface> m_validation_interface;
|
||||
|
||||
Context(const ContextOptions* options, bool& sane)
|
||||
: m_context{std::make_unique<kernel::Context>()},
|
||||
m_interrupt{std::make_unique<util::SignalInterrupt>()}
|
||||
m_interrupt{std::make_unique<util::SignalInterrupt>()},
|
||||
m_signals{std::make_unique<ValidationSignals>(std::make_unique<ImmediateTaskRunner>())}
|
||||
{
|
||||
if (options) {
|
||||
LOCK(options->m_mutex);
|
||||
|
@ -355,6 +420,10 @@ public:
|
|||
if (options->m_notifications) {
|
||||
m_notifications = options->m_notifications;
|
||||
}
|
||||
if (options->m_validation_interface) {
|
||||
m_validation_interface = options->m_validation_interface;
|
||||
m_signals->RegisterSharedValidationInterface(m_validation_interface);
|
||||
}
|
||||
}
|
||||
|
||||
if (!m_chainparams) {
|
||||
|
@ -369,6 +438,11 @@ public:
|
|||
sane = false;
|
||||
}
|
||||
}
|
||||
|
||||
~Context()
|
||||
{
|
||||
m_signals->UnregisterSharedValidationInterface(m_validation_interface);
|
||||
}
|
||||
};
|
||||
|
||||
//! Helper struct to wrap the ChainstateManager-related Options
|
||||
|
@ -383,7 +457,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,
|
||||
|
@ -650,6 +725,12 @@ void btck_context_options_set_notifications(btck_ContextOptions* options, btck_N
|
|||
btck_ContextOptions::get(options).m_notifications = std::make_shared<KernelNotifications>(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<KernelValidationInterface>(vi_cbs);
|
||||
}
|
||||
|
||||
void btck_context_options_destroy(btck_ContextOptions* options)
|
||||
{
|
||||
delete options;
|
||||
|
|
|
@ -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
|
||||
|
@ -148,6 +151,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;
|
||||
|
@ -190,6 +197,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))
|
||||
|
@ -225,6 +240,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, const btck_BlockTreeEntry* entry, btck_Block* block);
|
||||
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
|
||||
|
@ -659,6 +701,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.
|
||||
*/
|
||||
|
@ -820,7 +876,9 @@ BITCOINKERNEL_API int btck_chainstate_manager_import_blocks(
|
|||
* 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.
|
||||
|
|
|
@ -605,6 +605,35 @@ 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;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
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<btck_ChainParameters, btck_chain_parameters_copy, btck_chain_parameters_destroy>
|
||||
{
|
||||
public:
|
||||
|
@ -645,6 +674,24 @@ public:
|
|||
});
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void SetValidationInterface(std::shared_ptr<T> validation_interface)
|
||||
{
|
||||
static_assert(std::is_base_of_v<ValidationInterface<T>, T>);
|
||||
auto heap_vi = std::make_unique<std::shared_ptr<T>>(std::move(validation_interface));
|
||||
using user_type = std::shared_ptr<T>*;
|
||||
btck_context_options_set_validation_interface(
|
||||
get(),
|
||||
btck_ValidationInterfaceCallbacks{
|
||||
.user_data = heap_vi.release(),
|
||||
.user_data_destroy = +[](void* user_data) { delete static_cast<user_type>(user_data); },
|
||||
.block_checked = +[](void* user_data, btck_Block* block, const btck_BlockValidationState* state) { (*static_cast<user_type>(user_data))->BlockChecked(Block{block}, BlockValidationState{state}); },
|
||||
.pow_valid_block = +[](void* user_data, const btck_BlockTreeEntry* entry, btck_Block* block) { (*static_cast<user_type>(user_data))->PowValidBlock(BlockTreeEntry{entry}, Block{block}); },
|
||||
.block_connected = +[](void* user_data, btck_Block* block, const btck_BlockTreeEntry* entry) { (*static_cast<user_type>(user_data))->BlockConnected(Block{block}, BlockTreeEntry{entry}); },
|
||||
.block_disconnected = +[](void* user_data, btck_Block* block, const btck_BlockTreeEntry* entry) { (*static_cast<user_type>(user_data))->BlockDisconnected(Block{block}, BlockTreeEntry{entry}); },
|
||||
});
|
||||
}
|
||||
|
||||
friend class Context;
|
||||
};
|
||||
|
||||
|
|
|
@ -131,6 +131,30 @@ public:
|
|||
}
|
||||
};
|
||||
|
||||
class TestValidationInterface : public ValidationInterface<TestValidationInterface>
|
||||
{
|
||||
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,
|
||||
|
@ -489,12 +513,15 @@ BOOST_AUTO_TEST_CASE(btck_block)
|
|||
CheckRange(block_tx.Transactions(), block_tx.CountTransactions());
|
||||
}
|
||||
|
||||
Context create_context(std::shared_ptr<TestKernelNotifications> notifications, ChainType chain_type)
|
||||
Context create_context(std::shared_ptr<TestKernelNotifications> notifications, ChainType chain_type, std::shared_ptr<TestValidationInterface> 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;
|
||||
}
|
||||
|
@ -599,7 +626,8 @@ void chainman_reindex_chainstate_test(TestDirectory& test_directory)
|
|||
void chainman_mainnet_validation_test(TestDirectory& test_directory)
|
||||
{
|
||||
auto notifications{std::make_shared<TestKernelNotifications>()};
|
||||
auto context{create_context(notifications, ChainType::MAINNET)};
|
||||
auto validation_interface{std::make_shared<TestValidationInterface>()};
|
||||
auto context{create_context(notifications, ChainType::MAINNET, validation_interface)};
|
||||
auto chainman{create_chainman(test_directory, false, false, false, false, context)};
|
||||
|
||||
{
|
||||
|
|
Loading…
Reference in New Issue