kernel: Add support for block headers

Introduces btck_BlockHeader type with accessor methods and btck_chainstate_manager_process_block_header() for validating headers without full blocks. Also, adds btck_chainstate_manager_get_best_entry() to query the header with most cumulative proof-of-work.

Co-authored-by: TheCharlatan <seb.kung@gmail.com>
This commit is contained in:
yuvicc 2025-11-18 19:37:40 +05:30
parent b851ff6cae
commit 9a9d797ef6
No known key found for this signature in database
GPG Key ID: 9711CE1B203EAE8C
4 changed files with 370 additions and 0 deletions

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)
{
@ -1044,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)
{
{
@ -1112,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 {
@ -1143,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;
@ -1279,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()));
@ -1301,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.
*
@ -1635,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

@ -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
@ -1144,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())};
@ -1156,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

@ -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.