Merge bitcoin/bitcoin#33908: kernel: add context‑free block validation API (btck_check_block_context_free) with POW/Merkle flags

0587c56091 kernel: Expose context-free block validation (w0xlt)
71f827c3c2 kernel: Expose consensus parameters (`btck_ConsensusParams`) (w0xlt)

Pull request description:

  This PR exposes Bitcoin Core’s context‑free block checks to library users via a new C API entry point, `btck_check_block_context_free`.

  Callers can validate a block’s structure (size/weight, coinbase rules, per‑tx context‑free checks) and optionally re‑run Proof‑of‑Work and Merkle‑root verification without touching chainstate, the block index, or the UTXO set.

  Rationale
  Clients embedding the kernel need a pure block sanity check without requiring node state or disk writes (candidate block validation, for example). This API offers that surface in a single call, with optional PoW/Merkle toggles to avoid redundant work when the header has already been validated or when Merkle verification is deferred.

ACKs for top commit:
  yuvicc:
    re-ACK 0587c56091
  achow101:
    ACK 0587c56091
  sedited:
    ACK 0587c56091

Tree-SHA512: 6bd53e4964909335d1f2fee30ff96c95a8dd2c84bcdfe11c50ba369301822e5dea9bbe2376bb6d6b4652875152071eb0446657042b00429f29581da4fcea71a9
This commit is contained in:
Ava Chow
2026-04-02 15:28:07 -07:00
4 changed files with 167 additions and 0 deletions

View File

@@ -50,6 +50,10 @@
#include <utility>
#include <vector>
namespace Consensus {
struct Params;
} // namespace Consensus
using kernel::ChainstateRole;
using util::ImmediateTaskRunner;
@@ -496,6 +500,7 @@ struct btck_TransactionOutPoint: Handle<btck_TransactionOutPoint, COutPoint> {};
struct btck_Txid: Handle<btck_Txid, Txid> {};
struct btck_PrecomputedTransactionData : Handle<btck_PrecomputedTransactionData, PrecomputedTransactionData> {};
struct btck_BlockHeader: Handle<btck_BlockHeader, CBlockHeader> {};
struct btck_ConsensusParams: Handle<btck_ConsensusParams, Consensus::Params> {};
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;
@@ -1119,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();

View File

@@ -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.
*/
@@ -1247,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.
*

View File

@@ -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 <typename T>
struct is_bitmask_enum : std::false_type {
};
@@ -105,6 +112,10 @@ template <>
struct is_bitmask_enum<ScriptVerificationFlags> : std::true_type {
};
template <>
struct is_bitmask_enum<BlockCheckFlags> : std::true_type {
};
template <typename T>
concept BitmaskEnum = is_bitmask_enum<T>::value;
@@ -370,6 +381,7 @@ public:
class PrecomputedTransactionData;
class Transaction;
class TransactionOutput;
class BlockValidationState;
template <typename Derived>
class ScriptPubkeyApi
@@ -777,6 +789,12 @@ public:
: Handle{header} {}
};
class ConsensusParamsView : public View<btck_ConsensusParams>
{
public:
explicit ConsensusParamsView(const btck_ConsensusParams* ptr) : View{ptr} {}
};
class Block : public Handle<btck_Block, btck_block_copy, btck_block_destroy>
{
public:
@@ -797,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
@@ -952,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<btck_BlockCheckFlags>(flags), state.get()) == 1;
}
class ValidationInterface
{
public:
@@ -971,6 +1000,11 @@ class ChainParams : public Handle<btck_ChainParameters, btck_chain_parameters_co
public:
ChainParams(ChainType chain_type)
: Handle{btck_chain_parameters_create(static_cast<btck_ChainType>(chain_type))} {}
ConsensusParamsView GetConsensusParams() const
{
return ConsensusParamsView{btck_chain_parameters_get_consensus_params(get())};
}
};
class ContextOptions : public UniqueHandle<btck_ContextOptions, btck_context_options_destroy>

View File

@@ -10,6 +10,7 @@
#include <boost/test/included/unit_test.hpp>
#include <test/kernel/block_data.h>
#include <test/util/common.h>
#include <charconv>
#include <cstdint>
@@ -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"}};