kernel: Add functions to read block from disk to C header
What changed, and why it matters
This commit adds new read-only functions to Bitcoin Core's libbitcoinkernel library so external programs can walk backward through the blockchain from the current tip and read raw block data from disk. It is a feature addition, not a fix for a known security problem. There is no evidence in the commit or supplied references that it addresses a vulnerability.
No security action required. Treat as normal feature review: verify API lifetime/locking documentation is adequate and that consumers cannot trigger use-after-free by holding Chain/BlockTreeEntry references across chainstate mutations.
Security signals we found
No security-relevant signals present in commit message or diff
New read-only kernel API surface increases reach but does not introduce privileged operations
Tests include negative path: missing blk file returns null/failure
Evidence from the diff
The patch exposes C APIs (btck_block_read, btck_chainstate_manager_get_active_chain, btck_chain_get_tip, btck_chain_get_height, btck_block_tree_entry_get_previous) and matching C++ wrappers to let libbitcoinkernel consumers traverse the active chain and read blocks via BlockManager::ReadBlock. It also adds unit tests verifying tip/previous traversal and that missing block files return null. The code is additive and read-only; it does not alter validation rules, network behavior, or consensus-critical paths.
Changed components
src/kernel/bitcoinkernel.cppsrc/kernel/bitcoinkernel.hsrc/kernel/bitcoinkernel_wrapper.hsrc/test/kernel/test_kernel.cppInspect captured patch +179 / −1
diff --git a/src/kernel/bitcoinkernel.cpp b/src/kernel/bitcoinkernel.cpp
index 75348937..21193ce4 100644
--- a/src/kernel/bitcoinkernel.cpp
+++ b/src/kernel/bitcoinkernel.cpp
@@ -484,6 +484,7 @@ struct btck_Context : Handle<btck_Context, std::shared_ptr<const Context>> {};
struct btck_ChainParameters : Handle<btck_ChainParameters, CChainParams> {};
struct btck_ChainstateManagerOptions : Handle<btck_ChainstateManagerOptions, ChainstateManagerOptions> {};
struct btck_ChainstateManager : Handle<btck_ChainstateManager, ChainMan> {};
+struct btck_Chain : Handle<btck_Chain, CChain> {};
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<CBlock>()};
+ 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 62db5daa..e8694514 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 53ecee3e..983a5e07 100644
--- a/src/kernel/bitcoinkernel_wrapper.h
+++ b/src/kernel/bitcoinkernel_wrapper.h
@@ -9,6 +9,7 @@
#include <functional>
#include <memory>
+#include <optional>
#include <span>
#include <stdexcept>
#include <string>
@@ -598,13 +599,20 @@ public:
}
};
-class BlockTreeEntry : View<btck_BlockTreeEntry>
+class BlockTreeEntry : public View<btck_BlockTreeEntry>
{
public:
BlockTreeEntry(const btck_BlockTreeEntry* entry)
: View{entry}
{
}
+
+ std::optional<BlockTreeEntry> 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<btck_Chain>
+{
+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<btck_ChainstateManager, btck_chainstate_manager_destroy>
{
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<Block> 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 fcb614c5..29765812 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));
}
Why this scored 18/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.