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