diff --git a/src/kernel/bitcoinkernel.cpp b/src/kernel/bitcoinkernel.cpp index 753489373d3..21193ce4948 100644 --- a/src/kernel/bitcoinkernel.cpp +++ b/src/kernel/bitcoinkernel.cpp @@ -484,6 +484,7 @@ struct btck_Context : Handle> {}; struct btck_ChainParameters : Handle {}; struct btck_ChainstateManagerOptions : Handle {}; struct btck_ChainstateManager : Handle {}; +struct btck_Chain : Handle {}; btck_Transaction* btck_transaction_create(const void* raw_transaction, size_t raw_transaction_len) { @@ -769,6 +770,16 @@ void btck_context_destroy(btck_Context* context) delete context; } +const btck_BlockTreeEntry* btck_block_tree_entry_get_previous(const btck_BlockTreeEntry* entry) +{ + if (!btck_BlockTreeEntry::get(entry).pprev) { + LogInfo("Genesis block has no previous."); + return nullptr; + } + + return btck_BlockTreeEntry::ref(btck_BlockTreeEntry::get(entry).pprev); +} + 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_); @@ -983,6 +994,16 @@ void btck_block_destroy(btck_Block* block) delete block; } +btck_Block* btck_block_read(const btck_ChainstateManager* chainman, const btck_BlockTreeEntry* entry) +{ + auto block{std::make_shared()}; + if (!btck_ChainstateManager::get(chainman).m_chainman->m_blockman.ReadBlock(*block, btck_BlockTreeEntry::get(entry))) { + LogError("Failed to read block."); + return nullptr; + } + return btck_Block::create(block); +} + int btck_chainstate_manager_process_block( btck_ChainstateManager* chainman, const btck_Block* block, @@ -995,3 +1016,20 @@ int btck_chainstate_manager_process_block( } return result ? 0 : -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())); +} + +const btck_BlockTreeEntry* btck_chain_get_tip(const btck_Chain* chain) +{ + LOCK(::cs_main); + return btck_BlockTreeEntry::ref(btck_Chain::get(chain).Tip()); +} + +int btck_chain_get_height(const btck_Chain* chain) +{ + LOCK(::cs_main); + return btck_Chain::get(chain).Height(); +} diff --git a/src/kernel/bitcoinkernel.h b/src/kernel/bitcoinkernel.h index 62db5daa3d8..e86945147ad 100644 --- a/src/kernel/bitcoinkernel.h +++ b/src/kernel/bitcoinkernel.h @@ -206,6 +206,12 @@ typedef struct btck_Block btck_Block; */ typedef struct btck_BlockValidationState btck_BlockValidationState; +/** + * Opaque data structure for holding the currently known best-chain associated + * with a chainstate. + */ +typedef struct btck_Chain btck_Chain; + /** Current sync state passed to tip changed callbacks. */ typedef uint8_t btck_SynchronizationState; #define btck_SynchronizationState_INIT_REINDEX ((btck_SynchronizationState)(0)) @@ -800,6 +806,23 @@ BITCOINKERNEL_API void btck_context_destroy(btck_Context* context); ///@} +/** @name BlockTreeEntry + * Functions for working with block tree entries. + */ +///@{ + +/** + * @brief Returns the previous block tree entry in the chain, or null if the current + * block tree entry is the genesis block. + * + * @param[in] block_tree_entry Non-null. + * @return The previous block tree entry, or null on error or if the current block tree entry is the genesis block. + */ +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); + +///@} + /** @name ChainstateManagerOptions * Functions for working with chainstate manager options. */ @@ -931,6 +954,23 @@ BITCOINKERNEL_API int BITCOINKERNEL_WARN_UNUSED_RESULT btck_chainstate_manager_p const btck_Block* block, int* new_block) BITCOINKERNEL_ARG_NONNULL(1, 2, 3); +/** + * @brief Returns the best known currently active chain. Its lifetime is + * dependent on the chainstate manager. It can be thought of as a view on a + * vector of block tree entries that form the best chain. The returned chain + * reference always points to the currently active best chain. However, state + * transitions within the chainstate manager (e.g., processing blocks) will + * update the chain's contents. Data retrieved from this chain is only + * consistent up to the point when new data is processed in the chainstate + * manager. It is the user's responsibility to guard against these + * inconsistencies. + * + * @param[in] chainstate_manager Non-null. + * @return The chain. + */ +BITCOINKERNEL_API const btck_Chain* BITCOINKERNEL_WARN_UNUSED_RESULT btck_chainstate_manager_get_active_chain( + const btck_ChainstateManager* chainstate_manager) BITCOINKERNEL_ARG_NONNULL(1); + /** * Destroy the chainstate manager. */ @@ -943,6 +983,18 @@ BITCOINKERNEL_API void btck_chainstate_manager_destroy(btck_ChainstateManager* c */ ///@{ +/** + * @brief Reads the block the passed in block tree entry points to from disk and + * returns it. + * + * @param[in] chainstate_manager Non-null. + * @param[in] block_tree_entry Non-null. + * @return The read out block, or null on error. + */ +BITCOINKERNEL_API btck_Block* BITCOINKERNEL_WARN_UNUSED_RESULT btck_block_read( + const btck_ChainstateManager* chainstate_manager, + const btck_BlockTreeEntry* block_tree_entry) BITCOINKERNEL_ARG_NONNULL(1, 2); + /** * @brief Parse a serialized raw block into a new block object. * @@ -1024,6 +1076,32 @@ BITCOINKERNEL_API btck_BlockValidationResult btck_block_validation_state_get_blo ///@} +/** @name Chain + * Functions for working with the chain + */ +///@{ + +/** + * @brief Get the block tree entry of the current chain tip. Once returned, + * there is no guarantee that it remains in the active chain. + * + * @param[in] chain Non-null. + * @return The block tree entry of the current tip, or null if the chain is empty. + */ +BITCOINKERNEL_API const btck_BlockTreeEntry* BITCOINKERNEL_WARN_UNUSED_RESULT btck_chain_get_tip( + const btck_Chain* chain) BITCOINKERNEL_ARG_NONNULL(1); + +/** + * @brief Return the height of the tip of the chain. + * + * @param[in] chain Non-null. + * @return The current height. + */ +BITCOINKERNEL_API int32_t BITCOINKERNEL_WARN_UNUSED_RESULT btck_chain_get_height( + const btck_Chain* chain) BITCOINKERNEL_ARG_NONNULL(1); + +///@} + #ifdef __cplusplus } // extern "C" #endif // __cplusplus diff --git a/src/kernel/bitcoinkernel_wrapper.h b/src/kernel/bitcoinkernel_wrapper.h index 53ecee3e460..983a5e0737e 100644 --- a/src/kernel/bitcoinkernel_wrapper.h +++ b/src/kernel/bitcoinkernel_wrapper.h @@ -9,6 +9,7 @@ #include #include +#include #include #include #include @@ -598,13 +599,20 @@ public: } }; -class BlockTreeEntry : View +class BlockTreeEntry : public View { public: BlockTreeEntry(const btck_BlockTreeEntry* entry) : View{entry} { } + + std::optional GetPrevious() const + { + auto entry{btck_block_tree_entry_get_previous(get())}; + if (!entry) return std::nullopt; + return entry; + } }; class KernelNotifications @@ -766,6 +774,22 @@ public: } }; +class ChainView : public View +{ +public: + explicit ChainView(const btck_Chain* ptr) : View{ptr} {} + + BlockTreeEntry Tip() const + { + return btck_chain_get_tip(get()); + } + + int32_t Height() const + { + return btck_chain_get_height(get()); + } +}; + class ChainMan : UniqueHandle { public: @@ -795,6 +819,18 @@ public: if (new_block) *new_block = _new_block == 1; return res == 0; } + + ChainView GetChain() const + { + return ChainView{btck_chainstate_manager_get_active_chain(get())}; + } + + std::optional ReadBlock(const BlockTreeEntry& entry) const + { + auto block{btck_block_read(get(), entry.get())}; + if (!block) return std::nullopt; + return block; + } }; } // namespace btck diff --git a/src/test/kernel/test_kernel.cpp b/src/test/kernel/test_kernel.cpp index fcb614c58a9..29765812931 100644 --- a/src/test/kernel/test_kernel.cpp +++ b/src/test/kernel/test_kernel.cpp @@ -693,6 +693,20 @@ void chainman_mainnet_validation_test(TestDirectory& test_directory) BOOST_CHECK(!chainman->ProcessBlock(invalid_block, &new_block)); BOOST_CHECK(!new_block); + auto chain{chainman->GetChain()}; + BOOST_CHECK_EQUAL(chain.Height(), 1); + auto tip{chain.Tip()}; + auto read_block{chainman->ReadBlock(tip)}; + BOOST_REQUIRE(read_block); + check_equal(read_block.value().ToBytes(), raw_block); + + // Check that we can read the previous block + auto tip_2{tip.GetPrevious()}; + auto read_block_2{chainman->ReadBlock(tip_2.value())}; + + // It should be an error if we go another block back, since the genesis has no ancestor + BOOST_CHECK(!tip_2.value().GetPrevious()); + // If we try to validate it again, it should be a duplicate BOOST_CHECK(chainman->ProcessBlock(block, &new_block)); BOOST_CHECK(!new_block); @@ -758,4 +772,16 @@ BOOST_AUTO_TEST_CASE(btck_chainman_regtest_tests) BOOST_CHECK(chainman->ProcessBlock(block, &new_block)); BOOST_CHECK(new_block); } + + auto chain = chainman->GetChain(); + auto tip = chain.Tip(); + auto read_block = chainman->ReadBlock(tip).value(); + check_equal(read_block.ToBytes(), hex_string_to_byte_vec(REGTEST_BLOCK_DATA[REGTEST_BLOCK_DATA.size() - 1])); + + auto tip_2 = tip.GetPrevious().value(); + auto read_block_2 = chainman->ReadBlock(tip_2).value(); + check_equal(read_block_2.ToBytes(), hex_string_to_byte_vec(REGTEST_BLOCK_DATA[REGTEST_BLOCK_DATA.size() - 2])); + + std::filesystem::remove_all(test_directory.m_directory / "blocks" / "blk00000.dat"); + BOOST_CHECK(!chainman->ReadBlock(tip_2)); }