From 1bf825fe05a275574bf8087d425a21aaa7af69b1 Mon Sep 17 00:00:00 2001 From: TheCharlatan Date: Wed, 5 Jun 2024 22:05:40 +0200 Subject: [PATCH] kernel: Add block index utility functions to C header Adds further functions useful for traversing the block index and retrieving block information. This includes getting the block height and hash. --- src/kernel/bitcoinkernel.cpp | 70 ++++++++++++++++++++- src/kernel/bitcoinkernel.h | 97 ++++++++++++++++++++++++++++++ src/kernel/bitcoinkernel_wrapper.h | 47 +++++++++++++++ src/test/kernel/test_kernel.cpp | 25 ++++++++ 4 files changed, 238 insertions(+), 1 deletion(-) diff --git a/src/kernel/bitcoinkernel.cpp b/src/kernel/bitcoinkernel.cpp index e8851f44fbd63..88e0e34a3efad 100644 --- a/src/kernel/bitcoinkernel.cpp +++ b/src/kernel/bitcoinkernel.cpp @@ -976,12 +976,58 @@ kernel_BlockIndex* kernel_get_block_index_from_tip(const kernel_Context* context return reinterpret_cast(WITH_LOCK(::cs_main, return chainman->ActiveChain().Tip())); } +kernel_BlockIndex* kernel_get_block_index_from_genesis(const kernel_Context* context_, kernel_ChainstateManager* chainman_) +{ + auto chainman{cast_chainstate_manager(chainman_)}; + return reinterpret_cast(WITH_LOCK(::cs_main, return chainman->ActiveChain().Genesis())); +} + +kernel_BlockIndex* kernel_get_block_index_from_hash(const kernel_Context* context_, kernel_ChainstateManager* chainman_, kernel_BlockHash* block_hash) +{ + auto chainman{cast_chainstate_manager(chainman_)}; + + auto hash = uint256{Span{(*block_hash).hash, 32}}; + auto block_index = WITH_LOCK(::cs_main, return chainman->m_blockman.LookupBlockIndex(hash)); + if (!block_index) { + LogDebug(BCLog::KERNEL, "A block with the given hash is not indexed."); + return nullptr; + } + return reinterpret_cast(block_index); +} + +kernel_BlockIndex* kernel_get_block_index_from_height(const kernel_Context* context_, kernel_ChainstateManager* chainman_, int height) +{ + auto chainman{cast_chainstate_manager(chainman_)}; + + LOCK(cs_main); + + if (height < 0 || height > chainman->ActiveChain().Height()) { + LogDebug(BCLog::KERNEL, "Block height is out of range."); + return nullptr; + } + return reinterpret_cast(chainman->ActiveChain()[height]); +} + +kernel_BlockIndex* kernel_get_next_block_index(const kernel_Context* context_, kernel_ChainstateManager* chainman_, const kernel_BlockIndex* block_index_) +{ + const auto block_index{cast_const_block_index(block_index_)}; + auto chainman{cast_chainstate_manager(chainman_)}; + + auto next_block_index{WITH_LOCK(::cs_main, return chainman->ActiveChain().Next(block_index))}; + + if (!next_block_index) { + LogTrace(BCLog::KERNEL, "The block index is the tip of the current chain, it does not have a next."); + } + + return reinterpret_cast(next_block_index); +} + kernel_BlockIndex* kernel_get_previous_block_index(const kernel_BlockIndex* block_index_) { const CBlockIndex* block_index{cast_const_block_index(block_index_)}; if (!block_index->pprev) { - LogInfo("Genesis block has no previous."); + LogTrace(BCLog::KERNEL, "The block index is the genesis, it has no previous."); return nullptr; } @@ -1069,6 +1115,28 @@ kernel_TransactionOutput* kernel_get_undo_output_by_index(const kernel_BlockUndo return reinterpret_cast(prevout); } +int32_t kernel_block_index_get_height(const kernel_BlockIndex* block_index_) +{ + auto block_index{cast_const_block_index(block_index_)}; + return block_index->nHeight; +} + +kernel_BlockHash* kernel_block_index_get_block_hash(const kernel_BlockIndex* block_index_) +{ + auto block_index{cast_const_block_index(block_index_)}; + if (block_index->phashBlock == nullptr) { + return nullptr; + } + auto block_hash = new kernel_BlockHash{}; + std::memcpy(block_hash->hash, block_index->phashBlock->begin(), sizeof(*block_index->phashBlock)); + return block_hash; +} + +void kernel_block_hash_destroy(kernel_BlockHash* hash) +{ + if (hash) delete hash; +} + kernel_ScriptPubkey* kernel_copy_script_pubkey_from_output(kernel_TransactionOutput* output_) { auto output{cast_transaction_output(output_)}; diff --git a/src/kernel/bitcoinkernel.h b/src/kernel/bitcoinkernel.h index 040f3e7bd0a5c..296cd8fe65f0c 100644 --- a/src/kernel/bitcoinkernel.h +++ b/src/kernel/bitcoinkernel.h @@ -420,6 +420,13 @@ typedef enum { kernel_CHAIN_TYPE_REGTEST, } kernel_ChainType; +/** + * A type-safe block identifier. + */ +typedef struct { + unsigned char hash[32]; +} kernel_BlockHash; + /** * Convenience struct for holding serialized data. */ @@ -1049,6 +1056,63 @@ kernel_BlockIndex* BITCOINKERNEL_WARN_UNUSED_RESULT kernel_get_block_index_from_ kernel_ChainstateManager* chainstate_manager ) BITCOINKERNEL_ARG_NONNULL(1, 2); +/** + * @brief Get the block index entry of the genesis block. + * + * @param[in] context Non-null. + * @param[in] chainstate_manager Non-null. + * @return The block index of the genesis block, or null on error. + */ +kernel_BlockIndex* BITCOINKERNEL_WARN_UNUSED_RESULT kernel_get_block_index_from_genesis( + const kernel_Context* context, + kernel_ChainstateManager* chainstate_manager +) BITCOINKERNEL_ARG_NONNULL(1, 2); + +/** + * @brief Retrieve a block index by its block hash. + * + * @param[in] context Non-null. + * @param[in] chainstate_manager Non-null. + * @param[in] block_hash Non-null. + * @return The block index of the block with the passed in hash, or null on error. + */ +kernel_BlockIndex* BITCOINKERNEL_WARN_UNUSED_RESULT kernel_get_block_index_from_hash( + const kernel_Context* context, + kernel_ChainstateManager* chainstate_manager, + kernel_BlockHash* block_hash +) BITCOINKERNEL_ARG_NONNULL(1, 2, 3); + +/** + * @brief Retrieve a block index by its height in the currently active chain. + * Once retrieved there is no guarantee that it remains in the active chain. + * + * @param[in] context Non-null. + * @param[in] chainstate_manager Non-null. + * @param[in] block_height Height in the chain of the to be retrieved block index. + * @return The block index at a certain height in the currently active chain, or null on error. + */ +kernel_BlockIndex* BITCOINKERNEL_WARN_UNUSED_RESULT kernel_get_block_index_from_height( + const kernel_Context* context, + kernel_ChainstateManager* chainstate_manager, + int block_height +) BITCOINKERNEL_ARG_NONNULL(1, 2); + +/** + * @brief Return the next block index in the currently active chain, or null if + * the current block index is the tip, or is not in the currently active + * chain. + * + * @param[in] context Non-null. + * @param[in] block_index Non-null. + * @param[in] chainstate_manager Non-null. + * @return The next block index in the currently active chain, or null on error. + */ +kernel_BlockIndex* BITCOINKERNEL_WARN_UNUSED_RESULT kernel_get_next_block_index( + const kernel_Context* context, + kernel_ChainstateManager* chainstate_manager, + const kernel_BlockIndex* block_index +) BITCOINKERNEL_ARG_NONNULL(1, 2, 3); + /** * @brief Returns the previous block index in the chain, or null if the current * block index entry is the genesis block. @@ -1060,6 +1124,17 @@ kernel_BlockIndex* BITCOINKERNEL_WARN_UNUSED_RESULT kernel_get_previous_block_in const kernel_BlockIndex* block_index ) BITCOINKERNEL_ARG_NONNULL(1); +/** + * @brief Return the height of a certain block index. + * + * @param[in] block_index Non-null. + * @return The block height. + */ +int32_t BITCOINKERNEL_WARN_UNUSED_RESULT kernel_block_index_get_height( + const kernel_BlockIndex* block_index +) BITCOINKERNEL_ARG_NONNULL(1); + + /** * @brief Destroy the block index. */ @@ -1134,6 +1209,28 @@ void kernel_block_undo_destroy(kernel_BlockUndo* block_undo); ///@} +/** @name BlockHash + * Functions for working with block hashes. + */ +///@{ + +/** + * @brief Return the block hash associated with a block index. + * + * @param[in] block_index Non-null. + * @return The block hash. + */ +kernel_BlockHash* BITCOINKERNEL_WARN_UNUSED_RESULT kernel_block_index_get_block_hash( + const kernel_BlockIndex* block_index +) BITCOINKERNEL_ARG_NONNULL(1); + +/** + * Destroy the block hash. + */ +void kernel_block_hash_destroy(kernel_BlockHash* block_hash); + +///@} + #ifdef __cplusplus } // extern "C" #endif // __cplusplus diff --git a/src/kernel/bitcoinkernel_wrapper.h b/src/kernel/bitcoinkernel_wrapper.h index ac4afbd7c0bef..9a7f48edf118c 100644 --- a/src/kernel/bitcoinkernel_wrapper.h +++ b/src/kernel/bitcoinkernel_wrapper.h @@ -533,6 +533,13 @@ class BlockUndo } }; +struct BlockHashDeleter { + void operator()(kernel_BlockHash* ptr) const + { + kernel_block_hash_destroy(ptr); + } +}; + class BlockIndex { private: @@ -558,6 +565,22 @@ class BlockIndex return index; } + int32_t GetHeight() const noexcept + { + if (!m_block_index) { + return -1; + } + return kernel_block_index_get_height(m_block_index.get()); + } + + std::unique_ptr GetHash() const noexcept + { + if (!m_block_index) { + return nullptr; + } + return std::unique_ptr(kernel_block_index_get_block_hash(m_block_index.get())); + } + operator bool() const noexcept { return m_block_index && m_block_index.get(); @@ -613,6 +636,30 @@ class ChainMan return kernel_get_block_index_from_tip(m_context.m_context.get(), m_chainman); } + BlockIndex GetBlockIndexFromGenesis() const noexcept + { + return kernel_get_block_index_from_genesis(m_context.m_context.get(), m_chainman); + } + + BlockIndex GetBlockIndexByHash(kernel_BlockHash* block_hash) const noexcept + { + return kernel_get_block_index_from_hash(m_context.m_context.get(), m_chainman, block_hash); + } + + std::optional GetBlockIndexByHeight(int height) const noexcept + { + auto index{kernel_get_block_index_from_height(m_context.m_context.get(), m_chainman, height)}; + if (!index) return std::nullopt; + return index; + } + + std::optional GetNextBlockIndex(BlockIndex& block_index) const noexcept + { + auto index{kernel_get_next_block_index(m_context.m_context.get(), m_chainman, block_index.m_block_index.get())}; + if (!index) return std::nullopt; + return index; + } + std::optional ReadBlock(BlockIndex& block_index) const noexcept { auto block{kernel_read_block_from_disk(m_context.m_context.get(), m_chainman, block_index.m_block_index.get())}; diff --git a/src/test/kernel/test_kernel.cpp b/src/test/kernel/test_kernel.cpp index d074d29a6e187..7953f28678564 100644 --- a/src/test/kernel/test_kernel.cpp +++ b/src/test/kernel/test_kernel.cpp @@ -554,6 +554,31 @@ void chainman_reindex_test(TestDirectory& test_directory) std::vector import_files; assert(chainman->ImportBlocks(import_files)); + + // Sanity check some block retrievals + auto genesis_index{chainman->GetBlockIndexFromGenesis()}; + auto genesis_block_raw{chainman->ReadBlock(genesis_index).value().GetBlockData()}; + auto first_index{chainman->GetBlockIndexByHeight(0).value()}; + auto first_block_raw{chainman->ReadBlock(genesis_index).value().GetBlockData()}; + assert(genesis_block_raw == first_block_raw); + auto height{first_index.GetHeight()}; + assert(height == 0); + + auto next_index{chainman->GetNextBlockIndex(first_index).value()}; + auto next_block_string{chainman->ReadBlock(next_index).value().GetBlockData()}; + auto tip_index{chainman->GetBlockIndexFromTip()}; + auto tip_block_string{chainman->ReadBlock(tip_index).value().GetBlockData()}; + auto second_index{chainman->GetBlockIndexByHeight(1).value()}; + auto second_block_string{chainman->ReadBlock(second_index).value().GetBlockData()}; + auto second_height{second_index.GetHeight()}; + assert(second_height == 1); + assert(next_block_string == tip_block_string); + assert(next_block_string == second_block_string); + + auto hash{second_index.GetHash()}; + auto another_second_index{chainman->GetBlockIndexByHash(hash.get())}; + auto another_second_height{another_second_index.GetHeight()}; + assert(second_height == another_second_height); } void chainman_reindex_chainstate_test(TestDirectory& test_directory)