kernel: Add functions to read block from disk to C header

This adds functions for reading a block from disk with a retrieved block
tree entry. External services that wish to build their own index, or
analyze blocks can use this to retrieve block data.

The block tree can now be traversed from the tip backwards. This is
guaranteed to work, since the chainstate maintains an internal block
tree index in memory and every block (besides the genesis) has an
ancestor.

The user can use this function to iterate through all blocks in the
chain (starting from the tip). The tip is retrieved from a separate
`Chain` object, which allows distinguishing whether entries are
currently in the best chain. Once the block tree entry for the genesis
block is reached a nullptr is returned if the user attempts to get the
previous entry.
This commit is contained in:
TheCharlatan 2024-06-01 13:03:31 +02:00
parent e3d4d2461e
commit 3a2b8d855d
No known key found for this signature in database
GPG Key ID: 9B79B45691DB4173
4 changed files with 175 additions and 0 deletions

View File

@ -491,6 +491,7 @@ struct btck_Context : Handle<btck_Context, std::shared_ptr<const Context>> {};
struct btck_ChainParameters : Handle<btck_ChainParameters, std::unique_ptr<const 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)
{
@ -763,6 +764,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_);
@ -978,6 +989,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,
@ -990,3 +1011,18 @@ 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)
{
return btck_BlockTreeEntry::ref(btck_Chain::get(chain).Tip());
}
int btck_chain_get_height(const btck_Chain* chain)
{
return btck_Chain::get(chain).Height();
}

View File

@ -205,6 +205,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))
@ -785,6 +791,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.
*/
@ -913,6 +936,20 @@ 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 and state transitions within the
* chainstate manager, e.g. when processing blocks, will also change the chain.
* 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.
*/
@ -925,6 +962,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.
*
@ -1006,6 +1055,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 int BITCOINKERNEL_WARN_UNUSED_RESULT btck_chain_get_height(
const btck_Chain* chain) BITCOINKERNEL_ARG_NONNULL(1);
///@}
#ifdef __cplusplus
} // extern "C"
#endif // __cplusplus

View File

@ -9,6 +9,7 @@
#include <functional>
#include <memory>
#include <optional>
#include <span>
#include <stdexcept>
#include <string>
@ -605,6 +606,15 @@ public:
: View{entry}
{
}
std::optional<BlockTreeEntry> GetPrevious() const
{
auto entry{btck_block_tree_entry_get_previous(get())};
if (!entry) return std::nullopt;
return entry;
}
friend class ChainMan;
};
template <typename T>
@ -776,6 +786,22 @@ public:
friend class ChainMan;
};
class ChainView : public View<btck_Chain>
{
public:
explicit ChainView(const btck_Chain* ptr) : View{ptr} {}
BlockTreeEntry Tip() const
{
return btck_chain_get_tip(get());
}
int Height() const
{
return btck_chain_get_height(get());
}
};
class ChainMan : UniqueHandle<btck_ChainstateManager, btck_chainstate_manager_destroy>
{
public:
@ -805,6 +831,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

View File

@ -723,6 +723,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);
@ -796,4 +810,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(), as_bytes(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(), as_bytes(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));
}