Merge bitcoin/bitcoin#33822: kernel: Add block header support and validation

9a9d797ef6ed8e1b3e876fc93cf1a6395ab270e9 kernel: Add support for block headers (yuvicc)
b851ff6cae71934bf2389d109908339d60ec6e5b  kernel: Add Handle/View pattern for BlockValidationState (yuvicc)

Pull request description:

  Adds a new `btck_BlockHeader` type and associated functions to create, access, and validate block headers. Block headers will have their own type (`btck_BlockHeader`) that can be created from raw data, copied, and queried for all the standard header fields (hash, prev hash, timestamp, bits, version, nonce). We can also extract headers from full blocks or block tree entries.

  The first commit here refactors `BlockValidationState` to use Handle/View pattern so external code can own them, which is required for the header processing in the API.

   #### New Block Header API

    - **`btck_BlockHeader` type**: Opaque handle for block headers
    - **Header methods**:
      - `btck_block_header_create()`: Create header from 80-byte serialized data
      - `btck_block_header_copy()`: Copy block headers
      - `btck_block_header_destroy()`: Destroy header object
      - `btck_block_header_get_hash()`: Calculate block hash
      - `btck_block_header_get_prev_hash()`: Get previous block hash
      - `btck_block_header_get_timestamp()`: Get block timestamp
      - `btck_block_header_get_bits()`: Get difficulty target (compact format)
      - `btck_block_header_get_version()`: Get block version
      - `btck_block_header_get_nonce()`: Get proof-of-work nonce

      - `btck_block_get_header()`: Extract header from a full block
      - `btck_block_tree_entry_get_block_header()`: Get header associated with a block tree entry

    - **Header Processing Methods:**
      - **`btck_chainstate_manager_process_block_header()`**: Validates and processes a block header without requiring the full block. This performs proof-of-work verification, timestamp validation, and updates the internal chain state.
      - **`btck_chainstate_manager_get_best_entry()`**: Returns the block tree entry with the most cumulative proof-of-work.

  Why `btck_chainstate_manager_get_best_entry()` is included alongside header validation? Just as we have logic to get the tip for block validation (so you can request more blocks extending your best from your peers), we need the equivalent for header validation. To make header validation worthwhile, knowing what the best current header is seems useful—it tells you what headers to request next from peers.

    ### Testing

    Added tests in `test_kernel.cpp` that cover creating headers from raw data, extracting all header fields, and processing headers through the chainstate manager.

    CC sedited

ACKs for top commit:
  stringintech:
    re-ACK 9a9d797e
  sedited:
    Re-ACK 9a9d797ef6ed8e1b3e876fc93cf1a6395ab270e9
  janb84:
    ACK 9a9d797ef6ed8e1b3e876fc93cf1a6395ab270e9

Tree-SHA512: 1dde9ef860543c906d1bb5e604f0d2956e7382fcbb55090686261b2277270a1fd3826f02ecf1749b2774da66e88f686c7845172b4c68b62259e7a7aee0825fa2
This commit is contained in:
merge-script 2026-01-23 13:04:05 +00:00
commit 891030ac8b
No known key found for this signature in database
GPG Key ID: 2EEB9F5CC09526C1
5 changed files with 437 additions and 18 deletions

View File

@ -57,7 +57,7 @@ public:
std::optional<std::string> m_expected_valid_block = std::nullopt;
void BlockChecked(const Block block, const BlockValidationState state) override
void BlockChecked(Block block, BlockValidationStateView state) override
{
auto mode{state.GetValidationMode()};
switch (mode) {

View File

@ -496,6 +496,7 @@ struct btck_TransactionInput : Handle<btck_TransactionInput, CTxIn> {};
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> {};
btck_Transaction* btck_transaction_create(const void* raw_transaction, size_t raw_transaction_len)
{
@ -885,6 +886,21 @@ const btck_BlockTreeEntry* btck_block_tree_entry_get_previous(const btck_BlockTr
return btck_BlockTreeEntry::ref(btck_BlockTreeEntry::get(entry).pprev);
}
btck_BlockValidationState* btck_block_validation_state_create()
{
return btck_BlockValidationState::create();
}
btck_BlockValidationState* btck_block_validation_state_copy(const btck_BlockValidationState* state)
{
return btck_BlockValidationState::copy(state);
}
void btck_block_validation_state_destroy(btck_BlockValidationState* state)
{
delete state;
}
btck_ValidationMode btck_block_validation_state_get_validation_mode(const btck_BlockValidationState* block_validation_state_)
{
auto& block_validation_state = btck_BlockValidationState::get(block_validation_state_);
@ -1029,6 +1045,12 @@ const btck_BlockTreeEntry* btck_chainstate_manager_get_block_tree_entry_by_hash(
return btck_BlockTreeEntry::ref(block_index);
}
const btck_BlockTreeEntry* btck_chainstate_manager_get_best_entry(const btck_ChainstateManager* chainstate_manager)
{
auto& chainman = *btck_ChainstateManager::get(chainstate_manager).m_chainman;
return btck_BlockTreeEntry::ref(WITH_LOCK(chainman.GetMutex(), return chainman.m_best_header));
}
void btck_chainstate_manager_destroy(btck_ChainstateManager* chainman)
{
{
@ -1097,6 +1119,12 @@ const btck_Transaction* btck_block_get_transaction_at(const btck_Block* block, s
return btck_Transaction::ref(&btck_Block::get(block)->vtx[index]);
}
btck_BlockHeader* btck_block_get_header(const btck_Block* block)
{
const auto& block_ptr = btck_Block::get(block);
return btck_BlockHeader::create(static_cast<const CBlockHeader&>(*block_ptr));
}
int btck_block_to_bytes(const btck_Block* block, btck_WriteBytes writer, void* user_data)
{
try {
@ -1128,6 +1156,11 @@ btck_Block* btck_block_read(const btck_ChainstateManager* chainman, const btck_B
return btck_Block::create(block);
}
btck_BlockHeader* btck_block_tree_entry_get_block_header(const btck_BlockTreeEntry* entry)
{
return btck_BlockHeader::create(btck_BlockTreeEntry::get(entry).GetBlockHeader());
}
int32_t btck_block_tree_entry_get_height(const btck_BlockTreeEntry* entry)
{
return btck_BlockTreeEntry::get(entry).nHeight;
@ -1264,6 +1297,22 @@ int btck_chainstate_manager_process_block(
return result ? 0 : -1;
}
int btck_chainstate_manager_process_block_header(
btck_ChainstateManager* chainstate_manager,
const btck_BlockHeader* header,
btck_BlockValidationState* state)
{
try {
auto& chainman = btck_ChainstateManager::get(chainstate_manager).m_chainman;
auto result = chainman->ProcessNewBlockHeaders({&btck_BlockHeader::get(header), 1}, /*min_pow_checked=*/true, btck_BlockValidationState::get(state), /*ppindex=*/nullptr);
return result ? 0 : -1;
} catch (const std::exception& e) {
LogError("Failed to process block header: %s", e.what());
return -1;
}
}
const btck_Chain* btck_chainstate_manager_get_active_chain(const btck_ChainstateManager* chainman)
{
return btck_Chain::ref(&WITH_LOCK(btck_ChainstateManager::get(chainman).m_chainman->GetMutex(), return btck_ChainstateManager::get(chainman).m_chainman->ActiveChain()));
@ -1286,3 +1335,61 @@ int btck_chain_contains(const btck_Chain* chain, const btck_BlockTreeEntry* entr
LOCK(::cs_main);
return btck_Chain::get(chain).Contains(&btck_BlockTreeEntry::get(entry)) ? 1 : 0;
}
btck_BlockHeader* btck_block_header_create(const void* raw_block_header, size_t raw_block_header_len)
{
if (raw_block_header == nullptr && raw_block_header_len != 0) {
return nullptr;
}
auto header{std::make_unique<CBlockHeader>()};
DataStream stream{std::span{reinterpret_cast<const std::byte*>(raw_block_header), raw_block_header_len}};
try {
stream >> *header;
} catch (...) {
LogError("Block header decode failed.");
return nullptr;
}
return btck_BlockHeader::ref(header.release());
}
btck_BlockHeader* btck_block_header_copy(const btck_BlockHeader* header)
{
return btck_BlockHeader::copy(header);
}
btck_BlockHash* btck_block_header_get_hash(const btck_BlockHeader* header)
{
return btck_BlockHash::create(btck_BlockHeader::get(header).GetHash());
}
const btck_BlockHash* btck_block_header_get_prev_hash(const btck_BlockHeader* header)
{
return btck_BlockHash::ref(&btck_BlockHeader::get(header).hashPrevBlock);
}
uint32_t btck_block_header_get_timestamp(const btck_BlockHeader* header)
{
return btck_BlockHeader::get(header).nTime;
}
uint32_t btck_block_header_get_bits(const btck_BlockHeader* header)
{
return btck_BlockHeader::get(header).nBits;
}
int32_t btck_block_header_get_version(const btck_BlockHeader* header)
{
return btck_BlockHeader::get(header).nVersion;
}
uint32_t btck_block_header_get_nonce(const btck_BlockHeader* header)
{
return btck_BlockHeader::get(header).nNonce;
}
void btck_block_header_destroy(btck_BlockHeader* header)
{
delete header;
}

View File

@ -292,8 +292,18 @@ typedef struct btck_TransactionOutPoint btck_TransactionOutPoint;
*/
typedef struct btck_PrecomputedTransactionData btck_PrecomputedTransactionData;
/**
* Opaque data structure for holding a btck_Txid.
*
* This is a type-safe identifier for a transaction.
*/
typedef struct btck_Txid btck_Txid;
/**
* Opaque data structure for holding a btck_BlockHeader.
*/
typedef struct btck_BlockHeader btck_BlockHeader;
/** Current sync state passed to tip changed callbacks. */
typedef uint8_t btck_SynchronizationState;
#define btck_SynchronizationState_INIT_REINDEX ((btck_SynchronizationState)(0))
@ -958,6 +968,15 @@ BITCOINKERNEL_API void btck_context_destroy(btck_Context* context);
BITCOINKERNEL_API const btck_BlockTreeEntry* BITCOINKERNEL_WARN_UNUSED_RESULT btck_block_tree_entry_get_previous(
const btck_BlockTreeEntry* block_tree_entry) BITCOINKERNEL_ARG_NONNULL(1);
/**
* @brief Return the btck_BlockHeader associated with this entry.
*
* @param[in] block_tree_entry Non-null.
* @return btck_BlockHeader.
*/
BITCOINKERNEL_API btck_BlockHeader* BITCOINKERNEL_WARN_UNUSED_RESULT btck_block_tree_entry_get_block_header(
const btck_BlockTreeEntry* block_tree_entry) BITCOINKERNEL_ARG_NONNULL(1);
/**
* @brief Return the height of a certain block tree entry.
*
@ -1083,6 +1102,29 @@ BITCOINKERNEL_API void btck_chainstate_manager_options_destroy(btck_ChainstateMa
BITCOINKERNEL_API btck_ChainstateManager* BITCOINKERNEL_WARN_UNUSED_RESULT btck_chainstate_manager_create(
const btck_ChainstateManagerOptions* chainstate_manager_options) BITCOINKERNEL_ARG_NONNULL(1);
/**
* @brief Get the btck_BlockTreeEntry whose associated btck_BlockHeader has the most
* known cumulative proof of work.
*
* @param[in] chainstate_manager Non-null.
* @return The btck_BlockTreeEntry.
*/
BITCOINKERNEL_API const btck_BlockTreeEntry* BITCOINKERNEL_WARN_UNUSED_RESULT btck_chainstate_manager_get_best_entry(
const btck_ChainstateManager* chainstate_manager) BITCOINKERNEL_ARG_NONNULL(1);
/**
* @brief Processes and validates the provided btck_BlockHeader.
*
* @param[in] chainstate_manager Non-null.
* @param[in] header Non-null btck_BlockHeader to be validated.
* @param[out] block_validation_state The result of the btck_BlockHeader validation.
* @return 0 if btck_BlockHeader processing completed successfully, non-zero on error.
*/
BITCOINKERNEL_API int BITCOINKERNEL_WARN_UNUSED_RESULT btck_chainstate_manager_process_block_header(
btck_ChainstateManager* chainstate_manager,
const btck_BlockHeader* header,
btck_BlockValidationState* block_validation_state) BITCOINKERNEL_ARG_NONNULL(1, 2, 3);
/**
* @brief Triggers the start of a reindex if the wipe options were previously
* set for the chainstate manager. Can also import an array of existing block
@ -1214,6 +1256,17 @@ BITCOINKERNEL_API size_t BITCOINKERNEL_WARN_UNUSED_RESULT btck_block_count_trans
BITCOINKERNEL_API const btck_Transaction* BITCOINKERNEL_WARN_UNUSED_RESULT btck_block_get_transaction_at(
const btck_Block* block, size_t transaction_index) BITCOINKERNEL_ARG_NONNULL(1);
/**
* @brief Get the btck_BlockHeader from the block.
*
* Creates a new btck_BlockHeader object from the block's header data.
*
* @param[in] block Non-null btck_Block
* @return btck_BlockHeader.
*/
BITCOINKERNEL_API btck_BlockHeader* BITCOINKERNEL_WARN_UNUSED_RESULT btck_block_get_header(
const btck_Block* block) BITCOINKERNEL_ARG_NONNULL(1);
/**
* @brief Calculate and return the hash of a block.
*
@ -1251,17 +1304,37 @@ BITCOINKERNEL_API void btck_block_destroy(btck_Block* block);
///@{
/**
* Returns the validation mode from an opaque block validation state pointer.
* Create a new btck_BlockValidationState.
*/
BITCOINKERNEL_API btck_BlockValidationState* BITCOINKERNEL_WARN_UNUSED_RESULT btck_block_validation_state_create();
/**
* Returns the validation mode from an opaque btck_BlockValidationState pointer.
*/
BITCOINKERNEL_API btck_ValidationMode btck_block_validation_state_get_validation_mode(
const btck_BlockValidationState* block_validation_state) BITCOINKERNEL_ARG_NONNULL(1);
/**
* Returns the validation result from an opaque block validation state pointer.
* Returns the validation result from an opaque btck_BlockValidationState pointer.
*/
BITCOINKERNEL_API btck_BlockValidationResult btck_block_validation_state_get_block_validation_result(
const btck_BlockValidationState* block_validation_state) BITCOINKERNEL_ARG_NONNULL(1);
/**
* @brief Copies the btck_BlockValidationState.
*
* @param[in] block_validation_state Non-null.
* @return The copied btck_BlockValidationState.
*/
BITCOINKERNEL_API btck_BlockValidationState* BITCOINKERNEL_WARN_UNUSED_RESULT btck_block_validation_state_copy(
const btck_BlockValidationState* block_validation_state) BITCOINKERNEL_ARG_NONNULL(1);
/**
* Destroy the btck_BlockValidationState.
*/
BITCOINKERNEL_API void btck_block_validation_state_destroy(
btck_BlockValidationState* block_validation_state) BITCOINKERNEL_ARG_NONNULL(1);
///@}
/** @name Chain
@ -1615,6 +1688,93 @@ BITCOINKERNEL_API void btck_block_hash_destroy(btck_BlockHash* block_hash);
///@}
/**
* @name Block Header
* Functions for working with block headers.
*/
///@{
/**
* @brief Create a btck_BlockHeader from serialized data.
*
* @param[in] raw_block_header Non-null, serialized header data (80 bytes)
* @param[in] raw_block_header_len Length of serialized header (must be 80)
* @return btck_BlockHeader, or null on error.
*/
BITCOINKERNEL_API btck_BlockHeader* BITCOINKERNEL_WARN_UNUSED_RESULT btck_block_header_create(
const void* raw_block_header, size_t raw_block_header_len);
/**
* @brief Copy a btck_BlockHeader.
*
* @param[in] header Non-null btck_BlockHeader.
* @return Copied btck_BlockHeader.
*/
BITCOINKERNEL_API btck_BlockHeader* BITCOINKERNEL_WARN_UNUSED_RESULT btck_block_header_copy(
const btck_BlockHeader* header) BITCOINKERNEL_ARG_NONNULL(1);
/**
* @brief Get the btck_BlockHash.
*
* @param[in] header Non-null header
* @return btck_BlockHash.
*/
BITCOINKERNEL_API btck_BlockHash* BITCOINKERNEL_WARN_UNUSED_RESULT btck_block_header_get_hash(
const btck_BlockHeader* header) BITCOINKERNEL_ARG_NONNULL(1);
/**
* @brief Get the previous btck_BlockHash from btck_BlockHeader. The returned hash
* is unowned and only valid for the lifetime of the btck_BlockHeader.
*
* @param[in] header Non-null btck_BlockHeader
* @return Previous btck_BlockHash
*/
BITCOINKERNEL_API const btck_BlockHash* BITCOINKERNEL_WARN_UNUSED_RESULT btck_block_header_get_prev_hash(
const btck_BlockHeader* header) BITCOINKERNEL_ARG_NONNULL(1);
/**
* @brief Get the timestamp from btck_BlockHeader.
*
* @param[in] header Non-null btck_BlockHeader
* @return Block timestamp (Unix epoch seconds)
*/
BITCOINKERNEL_API uint32_t BITCOINKERNEL_WARN_UNUSED_RESULT btck_block_header_get_timestamp(
const btck_BlockHeader* header) BITCOINKERNEL_ARG_NONNULL(1);
/**
* @brief Get the nBits difficulty target from btck_BlockHeader.
*
* @param[in] header Non-null btck_BlockHeader
* @return Difficulty target (compact format)
*/
BITCOINKERNEL_API uint32_t BITCOINKERNEL_WARN_UNUSED_RESULT btck_block_header_get_bits(
const btck_BlockHeader* header) BITCOINKERNEL_ARG_NONNULL(1);
/**
* @brief Get the version from btck_BlockHeader.
*
* @param[in] header Non-null btck_BlockHeader
* @return Block version
*/
BITCOINKERNEL_API int32_t BITCOINKERNEL_WARN_UNUSED_RESULT btck_block_header_get_version(
const btck_BlockHeader* header) BITCOINKERNEL_ARG_NONNULL(1);
/**
* @brief Get the nonce from btck_BlockHeader.
*
* @param[in] header Non-null btck_BlockHeader
* @return Nonce
*/
BITCOINKERNEL_API uint32_t BITCOINKERNEL_WARN_UNUSED_RESULT btck_block_header_get_nonce(
const btck_BlockHeader* header) BITCOINKERNEL_ARG_NONNULL(1);
/**
* Destroy the btck_BlockHeader.
*/
BITCOINKERNEL_API void btck_block_header_destroy(btck_BlockHeader* header);
///@}
#ifdef __cplusplus
} // extern "C"
#endif // __cplusplus

View File

@ -685,7 +685,7 @@ public:
}
};
class BlockHashView: public View<btck_BlockHash>, public BlockHashApi<BlockHashView>
class BlockHashView : public View<btck_BlockHash>, public BlockHashApi<BlockHashView>
{
public:
explicit BlockHashView(const btck_BlockHash* ptr) : View{ptr} {}
@ -704,6 +704,69 @@ public:
: Handle{view} {}
};
template <typename Derived>
class BlockHeaderApi
{
private:
auto impl() const
{
return static_cast<const Derived*>(this)->get();
}
friend Derived;
BlockHeaderApi() = default;
public:
BlockHash Hash() const
{
return BlockHash{btck_block_header_get_hash(impl())};
}
BlockHashView PrevHash() const
{
return BlockHashView{btck_block_header_get_prev_hash(impl())};
}
uint32_t Timestamp() const
{
return btck_block_header_get_timestamp(impl());
}
uint32_t Bits() const
{
return btck_block_header_get_bits(impl());
}
int32_t Version() const
{
return btck_block_header_get_version(impl());
}
uint32_t Nonce() const
{
return btck_block_header_get_nonce(impl());
}
};
class BlockHeaderView : public View<btck_BlockHeader>, public BlockHeaderApi<BlockHeaderView>
{
public:
explicit BlockHeaderView(const btck_BlockHeader* ptr) : View{ptr} {}
};
class BlockHeader : public Handle<btck_BlockHeader, btck_block_header_copy, btck_block_header_destroy>, public BlockHeaderApi<BlockHeader>
{
public:
explicit BlockHeader(std::span<const std::byte> raw_header)
: Handle{btck_block_header_create(reinterpret_cast<const unsigned char*>(raw_header.data()), raw_header.size())} {}
BlockHeader(const BlockHeaderView& view)
: Handle{view} {}
BlockHeader(btck_BlockHeader* header)
: Handle{header} {}
};
class Block : public Handle<btck_Block, btck_block_copy, btck_block_destroy>
{
public:
@ -731,6 +794,11 @@ public:
return BlockHash{btck_block_get_hash(get())};
}
BlockHeader GetHeader() const
{
return BlockHeader{btck_block_get_header(get())};
}
std::vector<std::byte> ToBytes() const
{
return write_bytes(get(), btck_block_to_bytes);
@ -809,6 +877,11 @@ public:
{
return BlockHashView{btck_block_tree_entry_get_block_hash(get())};
}
BlockHeader GetHeader() const
{
return BlockHeader{btck_block_tree_entry_get_block_header(get())};
}
};
class KernelNotifications
@ -831,36 +904,50 @@ public:
virtual void FatalErrorHandler(std::string_view error) {}
};
class BlockValidationState
template <typename Derived>
class BlockValidationStateApi
{
private:
const btck_BlockValidationState* m_state;
auto impl() const
{
return static_cast<const Derived*>(this)->get();
}
friend Derived;
BlockValidationStateApi() = default;
public:
BlockValidationState(const btck_BlockValidationState* state) : m_state{state} {}
BlockValidationState(const BlockValidationState&) = delete;
BlockValidationState& operator=(const BlockValidationState&) = delete;
BlockValidationState(BlockValidationState&&) = delete;
BlockValidationState& operator=(BlockValidationState&&) = delete;
ValidationMode GetValidationMode() const
{
return static_cast<ValidationMode>(btck_block_validation_state_get_validation_mode(m_state));
return static_cast<ValidationMode>(btck_block_validation_state_get_validation_mode(impl()));
}
BlockValidationResult GetBlockValidationResult() const
{
return static_cast<BlockValidationResult>(btck_block_validation_state_get_block_validation_result(m_state));
return static_cast<BlockValidationResult>(btck_block_validation_state_get_block_validation_result(impl()));
}
};
class BlockValidationStateView : public View<btck_BlockValidationState>, public BlockValidationStateApi<BlockValidationStateView>
{
public:
explicit BlockValidationStateView(const btck_BlockValidationState* ptr) : View{ptr} {}
};
class BlockValidationState : public Handle<btck_BlockValidationState, btck_block_validation_state_copy, btck_block_validation_state_destroy>, public BlockValidationStateApi<BlockValidationState>
{
public:
explicit BlockValidationState() : Handle{btck_block_validation_state_create()} {}
BlockValidationState(const BlockValidationStateView& view) : Handle{view} {}
};
class ValidationInterface
{
public:
virtual ~ValidationInterface() = default;
virtual void BlockChecked(Block block, const BlockValidationState state) {}
virtual void BlockChecked(Block block, BlockValidationStateView state) {}
virtual void PowValidBlock(BlockTreeEntry entry, Block block) {}
@ -918,7 +1005,7 @@ public:
btck_ValidationInterfaceCallbacks{
.user_data = heap_vi.release(),
.user_data_destroy = +[](void* user_data) { delete static_cast<user_type>(user_data); },
.block_checked = +[](void* user_data, btck_Block* block, const btck_BlockValidationState* state) { (*static_cast<user_type>(user_data))->BlockChecked(Block{block}, BlockValidationState{state}); },
.block_checked = +[](void* user_data, btck_Block* block, const btck_BlockValidationState* state) { (*static_cast<user_type>(user_data))->BlockChecked(Block{block}, BlockValidationStateView{state}); },
.pow_valid_block = +[](void* user_data, btck_Block* block, const btck_BlockTreeEntry* entry) { (*static_cast<user_type>(user_data))->PowValidBlock(BlockTreeEntry{entry}, Block{block}); },
.block_connected = +[](void* user_data, btck_Block* block, const btck_BlockTreeEntry* entry) { (*static_cast<user_type>(user_data))->BlockConnected(Block{block}, BlockTreeEntry{entry}); },
.block_disconnected = +[](void* user_data, btck_Block* block, const btck_BlockTreeEntry* entry) { (*static_cast<user_type>(user_data))->BlockDisconnected(Block{block}, BlockTreeEntry{entry}); },
@ -1130,6 +1217,11 @@ public:
return res == 0;
}
bool ProcessBlockHeader(const BlockHeader& header, BlockValidationState& state)
{
return btck_chainstate_manager_process_block_header(get(), header.get(), state.get()) == 0;
}
ChainView GetChain() const
{
return ChainView{btck_chainstate_manager_get_active_chain(get())};
@ -1142,6 +1234,11 @@ public:
return entry;
}
BlockTreeEntry GetBestEntry() const
{
return btck_chainstate_manager_get_best_entry(get());
}
std::optional<Block> ReadBlock(const BlockTreeEntry& entry) const
{
auto block{btck_block_read(get(), entry.get())};

View File

@ -145,7 +145,7 @@ class TestValidationInterface : public ValidationInterface
public:
std::optional<std::vector<std::byte>> m_expected_valid_block = std::nullopt;
void BlockChecked(Block block, const BlockValidationState state) override
void BlockChecked(Block block, BlockValidationStateView state) override
{
if (m_expected_valid_block.has_value()) {
auto ser_block{block.ToBytes()};
@ -659,6 +659,39 @@ BOOST_AUTO_TEST_CASE(btck_context_tests)
}
}
BOOST_AUTO_TEST_CASE(btck_block_header_tests)
{
// Block header format: version(4) + prev_hash(32) + merkle_root(32) + timestamp(4) + bits(4) + nonce(4) = 80 bytes
BlockHeader header_0{hex_string_to_byte_vec("00e07a26beaaeee2e71d7eb19279545edbaf15de0999983626ec00000000000000000000579cf78b65229bfb93f4a11463af2eaa5ad91780f27f5d147a423bea5f7e4cdf2a47e268b4dd01173a9662ee")};
BOOST_CHECK_EQUAL(byte_span_to_hex_string_reversed(header_0.Hash().ToBytes()), "00000000000000000000325c7e14a4ee3b4fcb2343089a839287308a0ddbee4f");
BlockHeader header_1{hex_string_to_byte_vec("00c00020e7cb7b4de21d26d55bd384017b8bb9333ac3b2b55bed00000000000000000000d91b4484f801b99f03d36b9d26cfa83420b67f81da12d7e6c1e7f364e743c5ba9946e268b4dd011799c8533d")};
CheckHandle(header_0, header_1);
// Test error handling for invalid data
BOOST_CHECK_THROW(BlockHeader{hex_string_to_byte_vec("00")}, std::runtime_error);
BOOST_CHECK_THROW(BlockHeader{hex_string_to_byte_vec("")}, std::runtime_error);
// Test all header field accessors using mainnet block 1
auto mainnet_block_1_header = hex_string_to_byte_vec("010000006fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000982051fd1e4ba744bbbe680e1fee14677ba1a3c3540bf7b1cdb606e857233e0e61bc6649ffff001d01e36299");
BlockHeader header{mainnet_block_1_header};
BOOST_CHECK_EQUAL(header.Version(), 1);
BOOST_CHECK_EQUAL(header.Timestamp(), 1231469665);
BOOST_CHECK_EQUAL(header.Bits(), 0x1d00ffff);
BOOST_CHECK_EQUAL(header.Nonce(), 2573394689);
BOOST_CHECK_EQUAL(byte_span_to_hex_string_reversed(header.Hash().ToBytes()), "00000000839a8e6886ab5951d76f411475428afc90947ee320161bbf18eb6048");
auto prev_hash = header.PrevHash();
BOOST_CHECK_EQUAL(byte_span_to_hex_string_reversed(prev_hash.ToBytes()), "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f");
auto raw_block = hex_string_to_byte_vec("010000006fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000982051fd1e4ba744bbbe680e1fee14677ba1a3c3540bf7b1cdb606e857233e0e61bc6649ffff001d01e362990101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0704ffff001d0104ffffffff0100f2052a0100000043410496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52da7589379515d4e0a604f8141781e62294721166bf621e73a82cbf2342c858eeac00000000");
Block block{raw_block};
BlockHeader block_header{block.GetHeader()};
BOOST_CHECK_EQUAL(block_header.Version(), 1);
BOOST_CHECK_EQUAL(block_header.Timestamp(), 1231469665);
BOOST_CHECK_EQUAL(block_header.Bits(), 0x1d00ffff);
BOOST_CHECK_EQUAL(block_header.Nonce(), 2573394689);
BOOST_CHECK_EQUAL(byte_span_to_hex_string_reversed(block_header.Hash().ToBytes()), "00000000839a8e6886ab5951d76f411475428afc90947ee320161bbf18eb6048");
}
BOOST_AUTO_TEST_CASE(btck_block)
{
Block block{hex_string_to_byte_vec(REGTEST_BLOCK_DATA[0])};
@ -819,8 +852,13 @@ void chainman_mainnet_validation_test(TestDirectory& test_directory)
// mainnet block 1
auto raw_block = hex_string_to_byte_vec("010000006fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000982051fd1e4ba744bbbe680e1fee14677ba1a3c3540bf7b1cdb606e857233e0e61bc6649ffff001d01e362990101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0704ffff001d0104ffffffff0100f2052a0100000043410496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52da7589379515d4e0a604f8141781e62294721166bf621e73a82cbf2342c858eeac00000000");
Block block{raw_block};
BlockHeader header{block.GetHeader()};
TransactionView tx{block.GetTransaction(block.CountTransactions() - 1)};
BOOST_CHECK_EQUAL(byte_span_to_hex_string_reversed(tx.Txid().ToBytes()), "0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098");
BOOST_CHECK_EQUAL(header.Version(), 1);
BOOST_CHECK_EQUAL(header.Timestamp(), 1231469665);
BOOST_CHECK_EQUAL(header.Bits(), 0x1d00ffff);
BOOST_CHECK_EQUAL(header.Nonce(), 2573394689);
BOOST_CHECK_EQUAL(tx.CountInputs(), 1);
Transaction tx2 = tx;
BOOST_CHECK_EQUAL(tx2.CountInputs(), 1);
@ -960,6 +998,23 @@ BOOST_AUTO_TEST_CASE(btck_chainman_regtest_tests)
auto notifications{std::make_shared<TestKernelNotifications>()};
auto context{create_context(notifications, ChainType::REGTEST)};
{
auto chainman{create_chainman(test_directory, false, false, false, false, context)};
for (const auto& data : REGTEST_BLOCK_DATA) {
Block block{hex_string_to_byte_vec(data)};
BlockHeader header = block.GetHeader();
BlockValidationState state{};
BOOST_CHECK(state.GetBlockValidationResult() == BlockValidationResult::UNSET);
BOOST_CHECK(chainman->ProcessBlockHeader(header, state));
BOOST_CHECK(state.GetValidationMode() == ValidationMode::VALID);
BlockTreeEntry entry{*chainman->GetBlockTreeEntry(header.Hash())};
BOOST_CHECK(!chainman->GetChain().Contains(entry));
BlockTreeEntry best_entry{chainman->GetBestEntry()};
BlockHash hash{entry.GetHash()};
BOOST_CHECK(hash == best_entry.GetHeader().Hash());
}
}
// Validate 206 regtest blocks in total.
// Stop halfway to check that it is possible to continue validating starting
// from prior state.