Skip to content

Commit aa262da

Browse files
committed
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. 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. 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. I.e. for a synchronous mechanism, the user executes instructions directly at the end of the callback function: ```mermaid sequenceDiagram participant V as Validation participant C as Callback V->>C: Call callback Note over C: Process event (blocks) C-->>V: Return Note over V: Validation resumes ``` To avoid blocking, the user can submit the data to e.g. a worker thread or event manager, so processing happens asynchronously: ```mermaid sequenceDiagram participant V as Validation participant C as Callback participant W as Worker Thread V->>C: Call callback C->>W: Submit to worker thread C-->>V: Return immediately Note over V: Validation continues Note over W: Process event async ```
1 parent d27e277 commit aa262da

File tree

4 files changed

+219
-4
lines changed

4 files changed

+219
-4
lines changed

src/kernel/bitcoinkernel.cpp

Lines changed: 84 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,10 @@
3131
#include <util/fs.h>
3232
#include <util/result.h>
3333
#include <util/signalinterrupt.h>
34+
#include <util/task_runner.h>
3435
#include <util/translation.h>
3536
#include <validation.h>
37+
#include <validationinterface.h>
3638

3739
#include <cassert>
3840
#include <cstddef>
@@ -47,6 +49,8 @@
4749
#include <utility>
4850
#include <vector>
4951

52+
using util::ImmediateTaskRunner;
53+
5054
// Define G_TRANSLATION_FUN symbol in libbitcoinkernel library so users of the
5155
// library aren't required to export this symbol
5256
extern const std::function<std::string(const char*)> G_TRANSLATION_FUN{nullptr};
@@ -135,6 +139,7 @@ struct Handle {
135139

136140
struct btck_BlockTreeEntry: Handle<btck_BlockTreeEntry, CBlockIndex> {};
137141
struct btck_Block : Handle<btck_Block, std::shared_ptr<const CBlock>> {};
142+
struct btck_BlockValidationState : Handle<btck_BlockValidationState, BlockValidationState> {};
138143

139144
namespace {
140145

@@ -317,10 +322,65 @@ class KernelNotifications final : public kernel::Notifications
317322
}
318323
};
319324

325+
class KernelValidationInterface final : public CValidationInterface
326+
{
327+
public:
328+
btck_ValidationInterfaceCallbacks m_cbs;
329+
330+
explicit KernelValidationInterface(const btck_ValidationInterfaceCallbacks vi_cbs) : m_cbs{vi_cbs} {}
331+
332+
~KernelValidationInterface()
333+
{
334+
if (m_cbs.user_data && m_cbs.user_data_destroy) {
335+
m_cbs.user_data_destroy(m_cbs.user_data);
336+
}
337+
m_cbs.user_data = nullptr;
338+
m_cbs.user_data_destroy = nullptr;
339+
}
340+
341+
protected:
342+
void BlockChecked(const std::shared_ptr<const CBlock>& block, const BlockValidationState& stateIn) override
343+
{
344+
if (m_cbs.block_checked) {
345+
m_cbs.block_checked(m_cbs.user_data,
346+
btck_Block::copy(btck_Block::ref(&block)),
347+
btck_BlockValidationState::ref(&stateIn));
348+
}
349+
}
350+
351+
void NewPoWValidBlock(const CBlockIndex* pindex, const std::shared_ptr<const CBlock>& block) override
352+
{
353+
if (m_cbs.pow_valid_block) {
354+
m_cbs.pow_valid_block(m_cbs.user_data,
355+
btck_Block::copy(btck_Block::ref(&block)),
356+
btck_BlockTreeEntry::ref(pindex));
357+
}
358+
}
359+
360+
void BlockConnected(ChainstateRole role, const std::shared_ptr<const CBlock>& block, const CBlockIndex* pindex) override
361+
{
362+
if (m_cbs.block_connected) {
363+
m_cbs.block_connected(m_cbs.user_data,
364+
btck_Block::copy(btck_Block::ref(&block)),
365+
btck_BlockTreeEntry::ref(pindex));
366+
}
367+
}
368+
369+
void BlockDisconnected(const std::shared_ptr<const CBlock>& block, const CBlockIndex* pindex) override
370+
{
371+
if (m_cbs.block_disconnected) {
372+
m_cbs.block_disconnected(m_cbs.user_data,
373+
btck_Block::copy(btck_Block::ref(&block)),
374+
btck_BlockTreeEntry::ref(pindex));
375+
}
376+
}
377+
};
378+
320379
struct ContextOptions {
321380
mutable Mutex m_mutex;
322381
std::unique_ptr<const CChainParams> m_chainparams GUARDED_BY(m_mutex);
323382
std::shared_ptr<KernelNotifications> m_notifications GUARDED_BY(m_mutex);
383+
std::shared_ptr<KernelValidationInterface> m_validation_interface GUARDED_BY(m_mutex);
324384
};
325385

326386
class Context
@@ -332,8 +392,12 @@ class Context
332392

333393
std::unique_ptr<util::SignalInterrupt> m_interrupt;
334394

395+
std::unique_ptr<ValidationSignals> m_signals;
396+
335397
std::unique_ptr<const CChainParams> m_chainparams;
336398

399+
std::shared_ptr<KernelValidationInterface> m_validation_interface;
400+
337401
Context(const ContextOptions* options, bool& sane)
338402
: m_context{std::make_unique<kernel::Context>()},
339403
m_interrupt{std::make_unique<util::SignalInterrupt>()}
@@ -346,6 +410,11 @@ class Context
346410
if (options->m_notifications) {
347411
m_notifications = options->m_notifications;
348412
}
413+
if (options->m_validation_interface) {
414+
m_signals = std::make_unique<ValidationSignals>(std::make_unique<ImmediateTaskRunner>());
415+
m_validation_interface = options->m_validation_interface;
416+
m_signals->RegisterSharedValidationInterface(m_validation_interface);
417+
}
349418
}
350419

351420
if (!m_chainparams) {
@@ -360,6 +429,13 @@ class Context
360429
sane = false;
361430
}
362431
}
432+
433+
~Context()
434+
{
435+
if (m_signals) {
436+
m_signals->UnregisterSharedValidationInterface(m_validation_interface);
437+
}
438+
}
363439
};
364440

365441
//! Helper struct to wrap the ChainstateManager-related Options
@@ -374,7 +450,8 @@ struct ChainstateManagerOptions {
374450
: m_chainman_options{ChainstateManager::Options{
375451
.chainparams = *context->m_chainparams,
376452
.datadir = data_dir,
377-
.notifications = *context->m_notifications}},
453+
.notifications = *context->m_notifications,
454+
.signals = context->m_signals.get()}},
378455
m_blockman_options{node::BlockManager::Options{
379456
.chainparams = *context->m_chainparams,
380457
.blocks_dir = blocks_dir,
@@ -654,6 +731,12 @@ void btck_context_options_set_notifications(btck_ContextOptions* options, btck_N
654731
btck_ContextOptions::get(options).m_notifications = std::make_shared<KernelNotifications>(notifications);
655732
}
656733

734+
void btck_context_options_set_validation_interface(btck_ContextOptions* options, btck_ValidationInterfaceCallbacks vi_cbs)
735+
{
736+
LOCK(btck_ContextOptions::get(options).m_mutex);
737+
btck_ContextOptions::get(options).m_validation_interface = std::make_shared<KernelValidationInterface>(vi_cbs);
738+
}
739+
657740
void btck_context_options_destroy(btck_ContextOptions* options)
658741
{
659742
delete options;

src/kernel/bitcoinkernel.h

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,9 @@ extern "C" {
6767
* functions, e.g. for scripts, may communicate more detailed error information
6868
* through status code out parameters.
6969
*
70+
* Fine-grained validation information is communicated through the validation
71+
* interface.
72+
*
7073
* The kernel notifications issue callbacks for errors. These are usually
7174
* indicative of a system error. If such an error is issued, it is recommended
7275
* to halt and tear down the existing kernel objects. Remediating the error may
@@ -149,6 +152,10 @@ typedef struct btck_ContextOptions btck_ContextOptions;
149152
* other validation objects are instantiated from it, the context is kept in
150153
* memory for the duration of their lifetimes.
151154
*
155+
* The processing of validation events is done through an internal task runner
156+
* owned by the context. It passes events through the registered validation
157+
* interface callbacks.
158+
*
152159
* A constructed context can be safely used from multiple threads.
153160
*/
154161
typedef struct btck_Context btck_Context;
@@ -191,6 +198,14 @@ typedef struct btck_ChainstateManager btck_ChainstateManager;
191198
*/
192199
typedef struct btck_Block btck_Block;
193200

201+
/**
202+
* Opaque data structure for holding the state of a block during validation.
203+
*
204+
* Contains information indicating whether validation was successful, and if not
205+
* which step during block validation failed.
206+
*/
207+
typedef struct btck_BlockValidationState btck_BlockValidationState;
208+
194209
/** Current sync state passed to tip changed callbacks. */
195210
typedef uint8_t btck_SynchronizationState;
196211
#define btck_SynchronizationState_INIT_REINDEX ((btck_SynchronizationState)(0))
@@ -226,6 +241,33 @@ typedef void (*btck_NotifyWarningUnset)(void* user_data, btck_Warning warning);
226241
typedef void (*btck_NotifyFlushError)(void* user_data, const char* message, size_t message_len);
227242
typedef void (*btck_NotifyFatalError)(void* user_data, const char* message, size_t message_len);
228243

244+
/**
245+
* Function signatures for the validation interface.
246+
*/
247+
typedef void (*btck_ValidationInterfaceBlockChecked)(void* user_data, btck_Block* block, const btck_BlockValidationState* state);
248+
typedef void (*btck_ValidationInterfacePoWValidBlock)(void* user_data, btck_Block* block, const btck_BlockTreeEntry* entry);
249+
typedef void (*btck_ValidationInterfaceBlockConnected)(void* user_data, btck_Block* block, const btck_BlockTreeEntry* entry);
250+
typedef void (*btck_ValidationInterfaceBlockDisconnected)(void* user_data, btck_Block* block, const btck_BlockTreeEntry* entry);
251+
252+
/**
253+
* Holds the validation interface callbacks. The user data pointer may be used
254+
* to point to user-defined structures to make processing the validation
255+
* callbacks easier. Note that these callbacks block any further validation
256+
* execution when they are called.
257+
*/
258+
typedef struct {
259+
void* user_data; //!< Holds a user-defined opaque structure that is passed to the validation
260+
//!< interface callbacks. If user_data_destroy is also defined ownership of the
261+
//!< user_data is passed to the created context options and subsequently context.
262+
btck_DestroyCallback user_data_destroy; //!< Frees the provided user data structure.
263+
btck_ValidationInterfaceBlockChecked block_checked; //!< Called when a new block has been fully validated. Contains the
264+
//!< result of its validation.
265+
btck_ValidationInterfacePoWValidBlock pow_valid_block; //!< Called when a new block extends the header chain and has a valid transaction
266+
//!< and segwit merkle root.
267+
btck_ValidationInterfaceBlockConnected block_connected; //!< Called when a block is valid and has now been connected to the best chain.
268+
btck_ValidationInterfaceBlockDisconnected block_disconnected; //!< Called during a re-org when a block has been removed from the best chain.
269+
} btck_ValidationInterfaceCallbacks;
270+
229271
/**
230272
* A struct for holding the kernel notification callbacks. The user data
231273
* 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(
674716
btck_ContextOptions* context_options,
675717
btck_NotificationInterfaceCallbacks notifications) BITCOINKERNEL_ARG_NONNULL(1);
676718

719+
/**
720+
* @brief Set the validation interface callbacks for the context options. The
721+
* context created with the options will be configured for these validation
722+
* interface callbacks. The callbacks will then be triggered from validation
723+
* events issued by the chainstate manager created from the same context.
724+
*
725+
* @param[in] context_options Non-null, previously created with btck_context_options_create.
726+
* @param[in] validation_interface_callbacks The callbacks used for passing validation information to the
727+
* user.
728+
*/
729+
BITCOINKERNEL_API void btck_context_options_set_validation_interface(
730+
btck_ContextOptions* context_options,
731+
btck_ValidationInterfaceCallbacks validation_interface_callbacks) BITCOINKERNEL_ARG_NONNULL(1);
732+
677733
/**
678734
* Destroy the context options.
679735
*/
@@ -835,7 +891,9 @@ BITCOINKERNEL_API int BITCOINKERNEL_WARN_UNUSED_RESULT btck_chainstate_manager_i
835891
* manager. Processing first does checks on the block, and if these passed,
836892
* saves it to disk. It then validates the block against the utxo set. If it is
837893
* valid, the chain is extended with it. The return value is not indicative of
838-
* the block's validity.
894+
* the block's validity. Detailed information on the validity of the block can
895+
* be retrieved by registering the `block_checked` callback in the validation
896+
* interface.
839897
*
840898
* @param[in] chainstate_manager Non-null.
841899
* @param[in] block Non-null, block to be validated.

src/kernel/bitcoinkernel_wrapper.h

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -604,6 +604,34 @@ class KernelNotifications
604604
virtual void FatalErrorHandler(std::string_view error) {}
605605
};
606606

607+
class BlockValidationState
608+
{
609+
private:
610+
const btck_BlockValidationState* m_state;
611+
612+
public:
613+
BlockValidationState(const btck_BlockValidationState* state) : m_state{state} {}
614+
615+
BlockValidationState(const BlockValidationState&) = delete;
616+
BlockValidationState& operator=(const BlockValidationState&) = delete;
617+
BlockValidationState(BlockValidationState&&) = delete;
618+
BlockValidationState& operator=(BlockValidationState&&) = delete;
619+
};
620+
621+
class ValidationInterface
622+
{
623+
public:
624+
virtual ~ValidationInterface() = default;
625+
626+
virtual void BlockChecked(Block block, const BlockValidationState state) {}
627+
628+
virtual void PowValidBlock(BlockTreeEntry entry, Block block) {}
629+
630+
virtual void BlockConnected(Block block, BlockTreeEntry entry) {}
631+
632+
virtual void BlockDisconnected(Block block, BlockTreeEntry entry) {}
633+
};
634+
607635
class ChainParams : public Handle<btck_ChainParameters, btck_chain_parameters_copy, btck_chain_parameters_destroy>
608636
{
609637
public:
@@ -641,6 +669,24 @@ class ContextOptions : public UniqueHandle<btck_ContextOptions, btck_context_opt
641669
.fatal_error = +[](void* user_data, const char* error, size_t error_len) { (*static_cast<user_type>(user_data))->FatalErrorHandler({error, error_len}); },
642670
});
643671
}
672+
673+
template <typename T>
674+
void SetValidationInterface(std::shared_ptr<T> validation_interface)
675+
{
676+
static_assert(std::is_base_of_v<ValidationInterface, T>);
677+
auto heap_vi = std::make_unique<std::shared_ptr<T>>(std::move(validation_interface));
678+
using user_type = std::shared_ptr<T>*;
679+
btck_context_options_set_validation_interface(
680+
get(),
681+
btck_ValidationInterfaceCallbacks{
682+
.user_data = heap_vi.release(),
683+
.user_data_destroy = +[](void* user_data) { delete static_cast<user_type>(user_data); },
684+
.block_checked = +[](void* user_data, btck_Block* block, const btck_BlockValidationState* state) { (*static_cast<user_type>(user_data))->BlockChecked(Block{block}, BlockValidationState{state}); },
685+
.pow_valid_block = +[](void* user_data, btck_Block* block, const btck_BlockTreeEntry* entry) { (*static_cast<user_type>(user_data))->PowValidBlock(BlockTreeEntry{entry}, Block{block}); },
686+
.block_connected = +[](void* user_data, btck_Block* block, const btck_BlockTreeEntry* entry) { (*static_cast<user_type>(user_data))->BlockConnected(Block{block}, BlockTreeEntry{entry}); },
687+
.block_disconnected = +[](void* user_data, btck_Block* block, const btck_BlockTreeEntry* entry) { (*static_cast<user_type>(user_data))->BlockDisconnected(Block{block}, BlockTreeEntry{entry}); },
688+
});
689+
}
644690
};
645691

646692
class Context : public Handle<btck_Context, btck_context_copy, btck_context_destroy>

src/test/kernel/test_kernel.cpp

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,30 @@ class TestKernelNotifications : public KernelNotifications
126126
}
127127
};
128128

129+
class TestValidationInterface : public ValidationInterface
130+
{
131+
public:
132+
void BlockChecked(Block block, const BlockValidationState state) override
133+
{
134+
std::cout << "Block checked." << std::endl;
135+
}
136+
137+
void BlockConnected(Block block, BlockTreeEntry entry) override
138+
{
139+
std::cout << "Block connected." << std::endl;
140+
}
141+
142+
void PowValidBlock(BlockTreeEntry entry, Block block) override
143+
{
144+
std::cout << "Block passed pow verification" << std::endl;
145+
}
146+
147+
void BlockDisconnected(Block block, BlockTreeEntry entry) override
148+
{
149+
std::cout << "Block disconnected." << std::endl;
150+
}
151+
};
152+
129153
void run_verify_test(
130154
const ScriptPubkey& spent_script_pubkey,
131155
const Transaction& spending_tx,
@@ -481,12 +505,15 @@ BOOST_AUTO_TEST_CASE(btck_block)
481505
CheckRange(block_tx.Transactions(), block_tx.CountTransactions());
482506
}
483507

484-
Context create_context(std::shared_ptr<TestKernelNotifications> notifications, ChainType chain_type)
508+
Context create_context(std::shared_ptr<TestKernelNotifications> notifications, ChainType chain_type, std::shared_ptr<TestValidationInterface> validation_interface = nullptr)
485509
{
486510
ContextOptions options{};
487511
ChainParams params{chain_type};
488512
options.SetChainParams(params);
489513
options.SetNotifications(notifications);
514+
if (validation_interface) {
515+
options.SetValidationInterface(validation_interface);
516+
}
490517
auto context{Context{options}};
491518
return context;
492519
}
@@ -571,7 +598,8 @@ void chainman_reindex_chainstate_test(TestDirectory& test_directory)
571598
void chainman_mainnet_validation_test(TestDirectory& test_directory)
572599
{
573600
auto notifications{std::make_shared<TestKernelNotifications>()};
574-
auto context{create_context(notifications, ChainType::MAINNET)};
601+
auto validation_interface{std::make_shared<TestValidationInterface>()};
602+
auto context{create_context(notifications, ChainType::MAINNET, validation_interface)};
575603
auto chainman{create_chainman(test_directory, false, false, false, false, context)};
576604

577605
{

0 commit comments

Comments
 (0)