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 <yuvichh01@gmail.com>
This commit is contained in:
w0xlt
2025-11-19 13:16:19 -08:00
parent 71f827c3c2
commit 0587c56091
4 changed files with 130 additions and 0 deletions

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"}};