mirror of https://github.com/bitcoin/bitcoin.git
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:
parent
e3d4d2461e
commit
3a2b8d855d
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue