From 71f827c3c2b83f181d8eff7749b8c1f71987f5e0 Mon Sep 17 00:00:00 2001 From: w0xlt <94266259+w0xlt@users.noreply.github.com> Date: Wed, 19 Nov 2025 10:40:35 -0800 Subject: [PATCH 1/2] kernel: Expose consensus parameters (`btck_ConsensusParams`) Library users currently need to maintain a full context object to perform context-free block validation. Exposing an opaque `btck_ConsensusParams` struct allows callers to supply only the required consensus parameters, resulting in a lighter-weight API and a clearer expression of the actual validation behavior. Co-authored-by: yuvicc --- src/kernel/bitcoinkernel.cpp | 10 ++++++++++ src/kernel/bitcoinkernel.h | 16 ++++++++++++++++ src/kernel/bitcoinkernel_wrapper.h | 11 +++++++++++ 3 files changed, 37 insertions(+) diff --git a/src/kernel/bitcoinkernel.cpp b/src/kernel/bitcoinkernel.cpp index 4216bf921ff..b1180894738 100644 --- a/src/kernel/bitcoinkernel.cpp +++ b/src/kernel/bitcoinkernel.cpp @@ -50,6 +50,10 @@ #include #include +namespace Consensus { +struct Params; +} // namespace Consensus + using kernel::ChainstateRole; using util::ImmediateTaskRunner; @@ -496,6 +500,7 @@ struct btck_TransactionOutPoint: Handle {}; struct btck_Txid: Handle {}; struct btck_PrecomputedTransactionData : Handle {}; struct btck_BlockHeader: Handle {}; +struct btck_ConsensusParams: Handle {}; btck_Transaction* btck_transaction_create(const void* raw_transaction, size_t raw_transaction_len) { @@ -823,6 +828,11 @@ btck_ChainParameters* btck_chain_parameters_copy(const btck_ChainParameters* cha return btck_ChainParameters::copy(chain_parameters); } +const btck_ConsensusParams* btck_chain_parameters_get_consensus_params(const btck_ChainParameters* chain_parameters) +{ + return btck_ConsensusParams::ref(&btck_ChainParameters::get(chain_parameters).GetConsensus()); +} + void btck_chain_parameters_destroy(btck_ChainParameters* chain_parameters) { delete chain_parameters; diff --git a/src/kernel/bitcoinkernel.h b/src/kernel/bitcoinkernel.h index fe5bc3f91b5..971c5380ec7 100644 --- a/src/kernel/bitcoinkernel.h +++ b/src/kernel/bitcoinkernel.h @@ -225,6 +225,11 @@ typedef struct btck_Block btck_Block; */ typedef struct btck_BlockValidationState btck_BlockValidationState; +/** + * Opaque data structure for holding the Consensus Params. + */ +typedef struct btck_ConsensusParams btck_ConsensusParams; + /** * Opaque data structure for holding the currently known best-chain associated * with a chainstate. @@ -864,6 +869,17 @@ BITCOINKERNEL_API btck_ChainParameters* BITCOINKERNEL_WARN_UNUSED_RESULT btck_ch BITCOINKERNEL_API btck_ChainParameters* BITCOINKERNEL_WARN_UNUSED_RESULT btck_chain_parameters_copy( const btck_ChainParameters* chain_parameters) BITCOINKERNEL_ARG_NONNULL(1); +/** + * @brief Get btck_ConsensusParams from btck_ChainParameters. The returned + * btck_ConsensusParams pointer is valid only for the lifetime of the + * btck_ChainParameters object and must not be destroyed by the caller. + * + * @param[in] chain_parameters Non-null. + * @return The btck_ConsensusParams. + */ +BITCOINKERNEL_API const btck_ConsensusParams* BITCOINKERNEL_WARN_UNUSED_RESULT btck_chain_parameters_get_consensus_params( + const btck_ChainParameters* chain_parameters) BITCOINKERNEL_ARG_NONNULL(1); + /** * Destroy the chain parameters. */ diff --git a/src/kernel/bitcoinkernel_wrapper.h b/src/kernel/bitcoinkernel_wrapper.h index fd234857875..5f22fb8ffb6 100644 --- a/src/kernel/bitcoinkernel_wrapper.h +++ b/src/kernel/bitcoinkernel_wrapper.h @@ -777,6 +777,12 @@ public: : Handle{header} {} }; +class ConsensusParamsView : public View +{ +public: + explicit ConsensusParamsView(const btck_ConsensusParams* ptr) : View{ptr} {} +}; + class Block : public Handle { public: @@ -971,6 +977,11 @@ class ChainParams : public Handle(chain_type))} {} + + ConsensusParamsView GetConsensusParams() const + { + return ConsensusParamsView{btck_chain_parameters_get_consensus_params(get())}; + } }; class ContextOptions : public UniqueHandle From 0587c56091faf7851b0dd1b6fcbe660ee55f1f10 Mon Sep 17 00:00:00 2001 From: w0xlt <94266259+w0xlt@users.noreply.github.com> Date: Wed, 19 Nov 2025 13:16:19 -0800 Subject: [PATCH 2/2] kernel: Expose context-free block validation This introduces a context-free validation entry point for full blocks in the kernel C and C++ APIs. * Add `btck_block_check`, a C function that wraps `CheckBlock` and runs header and body checks for a `btck_Block` using `btck_ConsensusParams`. Callers provide a `btck_BlockValidationState` to receive the result and supply a `btck_BlockCheckFlags` bitmask to control POW and merkle-root verification. * Add `btck_BlockCheckFlags` in the C API, plus the corresponding `BlockCheckFlags` scoped enum in the C++ wrapper, including a `*_ALL` convenience value. * Add `Block::Check()` to the C++ wrapper to mirror the new C function and return a bool while filling a `BlockValidationState`. * Add a test `(btck_check_block_context_free)` that verifies a known valid mainnet block passes with `BlockCheckFlags::ALL` and that truncated block data fails deserialization. Co-authored-by: yuvicc --- src/kernel/bitcoinkernel.cpp | 13 +++++++ src/kernel/bitcoinkernel.h | 34 +++++++++++++++++ src/kernel/bitcoinkernel_wrapper.h | 23 ++++++++++++ src/test/kernel/test_kernel.cpp | 60 ++++++++++++++++++++++++++++++ 4 files changed, 130 insertions(+) diff --git a/src/kernel/bitcoinkernel.cpp b/src/kernel/bitcoinkernel.cpp index b1180894738..3295fe153f2 100644 --- a/src/kernel/bitcoinkernel.cpp +++ b/src/kernel/bitcoinkernel.cpp @@ -1129,6 +1129,19 @@ btck_Block* btck_block_copy(const btck_Block* block) return btck_Block::copy(block); } +int btck_block_check(const btck_Block* block, const btck_ConsensusParams* consensus_params, btck_BlockCheckFlags flags, btck_BlockValidationState* validation_state) +{ + auto& state = btck_BlockValidationState::get(validation_state); + state = BlockValidationState{}; + + const bool check_pow = (flags & btck_BlockCheckFlags_POW) != 0; + const bool check_merkle = (flags & btck_BlockCheckFlags_MERKLE) != 0; + + const bool result = CheckBlock(*btck_Block::get(block), state, btck_ConsensusParams::get(consensus_params), /*fCheckPOW=*/check_pow, /*fCheckMerkleRoot=*/check_merkle); + + return result ? 1 : 0; +} + size_t btck_block_count_transactions(const btck_Block* block) { return btck_Block::get(block)->vtx.size(); diff --git a/src/kernel/bitcoinkernel.h b/src/kernel/bitcoinkernel.h index 971c5380ec7..a7a861d4d12 100644 --- a/src/kernel/bitcoinkernel.h +++ b/src/kernel/bitcoinkernel.h @@ -1263,6 +1263,40 @@ BITCOINKERNEL_API btck_Block* BITCOINKERNEL_WARN_UNUSED_RESULT btck_block_create BITCOINKERNEL_API btck_Block* BITCOINKERNEL_WARN_UNUSED_RESULT btck_block_copy( const btck_Block* block) BITCOINKERNEL_ARG_NONNULL(1); +/** Bitflags to control context-free block checks (optional). */ +typedef uint32_t btck_BlockCheckFlags; +#define btck_BlockCheckFlags_BASE ((btck_BlockCheckFlags)0) //!< run the base context-free block checks only +#define btck_BlockCheckFlags_POW ((btck_BlockCheckFlags)(1U << 0)) //!< run CheckProofOfWork via CheckBlockHeader +#define btck_BlockCheckFlags_MERKLE ((btck_BlockCheckFlags)(1U << 1)) //!< verify merkle root (and mutation detection) +#define btck_BlockCheckFlags_ALL ((btck_BlockCheckFlags)(btck_BlockCheckFlags_POW | btck_BlockCheckFlags_MERKLE)) //!< enable all optional context-free block checks + +/** + * @brief Perform context-free validation checks on a btck_Block. + * + * Runs the base context-free block checks (size limits, coinbase structure, + * transaction checks, and sigop limits) using the supplied + * btck_ConsensusParams. The proof-of-work and merkle-root checks are optional + * and can be toggled via @p flags. Note that this does not include any + * transaction script, timestamps, order, or other checks that may require more + * context. + * + * @param[in] block Non-null, btck_Block to validate. + * @param[in] consensus_params Non-null, btck_ConsensusParams for validation. + * @param[in] flags Bitmask of btck_BlockCheckFlags controlling the + * optional POW and merkle-root checks. Use + * btck_BlockCheckFlags_BASE to run only the base + * checks. + * @param[in,out] validation_state Non-null, previously created with + * btck_block_validation_state_create and updated + * in-place with the validation result. + * @return 1 if the btck_Block passed the checks, 0 otherwise. + */ +BITCOINKERNEL_API int BITCOINKERNEL_WARN_UNUSED_RESULT btck_block_check( + const btck_Block* block, + const btck_ConsensusParams* consensus_params, + btck_BlockCheckFlags flags, + btck_BlockValidationState* validation_state) BITCOINKERNEL_ARG_NONNULL(1, 2, 4); + /** * @brief Count the number of transactions contained in a block. * diff --git a/src/kernel/bitcoinkernel_wrapper.h b/src/kernel/bitcoinkernel_wrapper.h index 5f22fb8ffb6..269bcdc1c9e 100644 --- a/src/kernel/bitcoinkernel_wrapper.h +++ b/src/kernel/bitcoinkernel_wrapper.h @@ -97,6 +97,13 @@ enum class ScriptVerificationFlags : btck_ScriptVerificationFlags { ALL = btck_ScriptVerificationFlags_ALL }; +enum class BlockCheckFlags : btck_BlockCheckFlags { + BASE = btck_BlockCheckFlags_BASE, + POW = btck_BlockCheckFlags_POW, + MERKLE = btck_BlockCheckFlags_MERKLE, + ALL = btck_BlockCheckFlags_ALL +}; + template struct is_bitmask_enum : std::false_type { }; @@ -105,6 +112,10 @@ template <> struct is_bitmask_enum : std::true_type { }; +template <> +struct is_bitmask_enum : std::true_type { +}; + template concept BitmaskEnum = is_bitmask_enum::value; @@ -370,6 +381,7 @@ public: class PrecomputedTransactionData; class Transaction; class TransactionOutput; +class BlockValidationState; template class ScriptPubkeyApi @@ -803,6 +815,10 @@ public: return TransactionView{btck_block_get_transaction_at(get(), index)}; } + bool Check(const ConsensusParamsView& consensus_params, + BlockCheckFlags flags, + BlockValidationState& state) const; + MAKE_RANGE_METHOD(Transactions, Block, &Block::CountTransactions, &Block::GetTransaction, *this) BlockHash GetHash() const @@ -958,6 +974,13 @@ public: BlockValidationState(const BlockValidationStateView& view) : Handle{view} {} }; +inline bool Block::Check(const ConsensusParamsView& consensus_params, + BlockCheckFlags flags, + BlockValidationState& state) const +{ + return btck_block_check(get(), consensus_params.get(), static_cast(flags), state.get()) == 1; +} + class ValidationInterface { public: diff --git a/src/test/kernel/test_kernel.cpp b/src/test/kernel/test_kernel.cpp index d4a8647b662..5151f5b3bf5 100644 --- a/src/test/kernel/test_kernel.cpp +++ b/src/test/kernel/test_kernel.cpp @@ -10,6 +10,7 @@ #include #include +#include #include #include @@ -913,6 +914,65 @@ void chainman_mainnet_validation_test(TestDirectory& test_directory) BOOST_CHECK(!new_block); } +BOOST_AUTO_TEST_CASE(btck_check_block_context_free) +{ + constexpr size_t MERKLE_ROOT_OFFSET{4 + 32}; + constexpr size_t NBITS_OFFSET{4 + 32 + 32 + 4}; + constexpr size_t COINBASE_PREVOUT_N_OFFSET{4 + 32 + 32 + 4 + 4 + 4 + 1 + 4 + 1 + 32}; + + // Mainnet block 1 + auto raw_block = hex_string_to_byte_vec("010000006fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000982051fd1e4ba744bbbe680e1fee14677ba1a3c3540bf7b1cdb606e857233e0e61bc6649ffff001d01e362990101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0704ffff001d0104ffffffff0100f2052a0100000043410496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52da7589379515d4e0a604f8141781e62294721166bf621e73a82cbf2342c858eeac00000000"); + + // Context-free block checks still need consensus params for the optional + // proof-of-work validation path. + ChainParams mainnet_params{ChainType::MAINNET}; + auto consensus_params = mainnet_params.GetConsensusParams(); + + Block block{raw_block}; + BlockValidationState state; + + BOOST_CHECK(block.Check(consensus_params, BlockCheckFlags::BASE, state)); + BOOST_CHECK(state.GetValidationMode() == ValidationMode::VALID); + + BOOST_CHECK(block.Check(consensus_params, BlockCheckFlags::ALL, state)); + BOOST_CHECK(state.GetValidationMode() == ValidationMode::VALID); + + auto bad_merkle_block_data = raw_block; + bad_merkle_block_data[MERKLE_ROOT_OFFSET] ^= std::byte{0x01}; + Block bad_merkle_block{bad_merkle_block_data}; + + BOOST_CHECK(!bad_merkle_block.Check(consensus_params, BlockCheckFlags::MERKLE, state)); + BOOST_CHECK(state.GetValidationMode() == ValidationMode::INVALID); + BOOST_CHECK(state.GetBlockValidationResult() == BlockValidationResult::MUTATED); + + BOOST_CHECK(bad_merkle_block.Check(consensus_params, BlockCheckFlags::BASE, state)); + BOOST_CHECK(state.GetValidationMode() == ValidationMode::VALID); + + auto bad_pow_block_data = raw_block; + bad_pow_block_data[NBITS_OFFSET + 3] = std::byte{0x1c}; + Block bad_pow_block{bad_pow_block_data}; + + BOOST_CHECK(!bad_pow_block.Check(consensus_params, BlockCheckFlags::POW, state)); + BOOST_CHECK(state.GetValidationMode() == ValidationMode::INVALID); + BOOST_CHECK(state.GetBlockValidationResult() == BlockValidationResult::INVALID_HEADER); + + BOOST_CHECK(bad_pow_block.Check(consensus_params, BlockCheckFlags::MERKLE, state)); + BOOST_CHECK(state.GetValidationMode() == ValidationMode::VALID); + + auto bad_base_block_data = raw_block; + bad_base_block_data[COINBASE_PREVOUT_N_OFFSET] = std::byte{0x00}; + Block bad_base_block{bad_base_block_data}; + + BOOST_CHECK(!bad_base_block.Check(consensus_params, BlockCheckFlags::BASE, state)); + BOOST_CHECK(state.GetValidationMode() == ValidationMode::INVALID); + BOOST_CHECK(state.GetBlockValidationResult() == BlockValidationResult::CONSENSUS); + + // Test with invalid truncated block data. + auto truncated_block_data = hex_string_to_byte_vec("010000006fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000982051fd1e4ba744bbbe680e1fee14677ba1a3c3540bf7b1cdb606e857233e0e61bc6649ffff001d01e36299"); + BOOST_CHECK_EXCEPTION(Block{truncated_block_data}, std::runtime_error, + HasReason{"failed to instantiate btck object"}); +} + BOOST_AUTO_TEST_CASE(btck_chainman_mainnet_tests) { auto test_directory{TestDirectory{"mainnet_test_bitcoin_kernel"}};