Skip to content

Commit 09d0f62

Browse files
committed
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.
1 parent a263a4c commit 09d0f62

File tree

4 files changed

+179
-1
lines changed

4 files changed

+179
-1
lines changed

src/kernel/bitcoinkernel.cpp

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -484,6 +484,7 @@ struct btck_Context : Handle<btck_Context, std::shared_ptr<const Context>> {};
484484
struct btck_ChainParameters : Handle<btck_ChainParameters, CChainParams> {};
485485
struct btck_ChainstateManagerOptions : Handle<btck_ChainstateManagerOptions, ChainstateManagerOptions> {};
486486
struct btck_ChainstateManager : Handle<btck_ChainstateManager, ChainMan> {};
487+
struct btck_Chain : Handle<btck_Chain, CChain> {};
487488

488489
btck_Transaction* btck_transaction_create(const void* raw_transaction, size_t raw_transaction_len)
489490
{
@@ -769,6 +770,16 @@ void btck_context_destroy(btck_Context* context)
769770
delete context;
770771
}
771772

773+
const btck_BlockTreeEntry* btck_block_tree_entry_get_previous(const btck_BlockTreeEntry* entry)
774+
{
775+
if (!btck_BlockTreeEntry::get(entry).pprev) {
776+
LogInfo("Genesis block has no previous.");
777+
return nullptr;
778+
}
779+
780+
return btck_BlockTreeEntry::ref(btck_BlockTreeEntry::get(entry).pprev);
781+
}
782+
772783
btck_ValidationMode btck_block_validation_state_get_validation_mode(const btck_BlockValidationState* block_validation_state_)
773784
{
774785
auto& block_validation_state = btck_BlockValidationState::get(block_validation_state_);
@@ -983,6 +994,16 @@ void btck_block_destroy(btck_Block* block)
983994
delete block;
984995
}
985996

997+
btck_Block* btck_block_read(const btck_ChainstateManager* chainman, const btck_BlockTreeEntry* entry)
998+
{
999+
auto block{std::make_shared<CBlock>()};
1000+
if (!btck_ChainstateManager::get(chainman).m_chainman->m_blockman.ReadBlock(*block, btck_BlockTreeEntry::get(entry))) {
1001+
LogError("Failed to read block.");
1002+
return nullptr;
1003+
}
1004+
return btck_Block::create(block);
1005+
}
1006+
9861007
int btck_chainstate_manager_process_block(
9871008
btck_ChainstateManager* chainman,
9881009
const btck_Block* block,
@@ -995,3 +1016,20 @@ int btck_chainstate_manager_process_block(
9951016
}
9961017
return result ? 0 : -1;
9971018
}
1019+
1020+
const btck_Chain* btck_chainstate_manager_get_active_chain(const btck_ChainstateManager* chainman)
1021+
{
1022+
return btck_Chain::ref(&WITH_LOCK(btck_ChainstateManager::get(chainman).m_chainman->GetMutex(), return btck_ChainstateManager::get(chainman).m_chainman->ActiveChain()));
1023+
}
1024+
1025+
const btck_BlockTreeEntry* btck_chain_get_tip(const btck_Chain* chain)
1026+
{
1027+
LOCK(::cs_main);
1028+
return btck_BlockTreeEntry::ref(btck_Chain::get(chain).Tip());
1029+
}
1030+
1031+
int btck_chain_get_height(const btck_Chain* chain)
1032+
{
1033+
LOCK(::cs_main);
1034+
return btck_Chain::get(chain).Height();
1035+
}

src/kernel/bitcoinkernel.h

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,12 @@ typedef struct btck_Block btck_Block;
206206
*/
207207
typedef struct btck_BlockValidationState btck_BlockValidationState;
208208

209+
/**
210+
* Opaque data structure for holding the currently known best-chain associated
211+
* with a chainstate.
212+
*/
213+
typedef struct btck_Chain btck_Chain;
214+
209215
/** Current sync state passed to tip changed callbacks. */
210216
typedef uint8_t btck_SynchronizationState;
211217
#define btck_SynchronizationState_INIT_REINDEX ((btck_SynchronizationState)(0))
@@ -800,6 +806,23 @@ BITCOINKERNEL_API void btck_context_destroy(btck_Context* context);
800806

801807
///@}
802808

809+
/** @name BlockTreeEntry
810+
* Functions for working with block tree entries.
811+
*/
812+
///@{
813+
814+
/**
815+
* @brief Returns the previous block tree entry in the chain, or null if the current
816+
* block tree entry is the genesis block.
817+
*
818+
* @param[in] block_tree_entry Non-null.
819+
* @return The previous block tree entry, or null on error or if the current block tree entry is the genesis block.
820+
*/
821+
BITCOINKERNEL_API const btck_BlockTreeEntry* BITCOINKERNEL_WARN_UNUSED_RESULT btck_block_tree_entry_get_previous(
822+
const btck_BlockTreeEntry* block_tree_entry) BITCOINKERNEL_ARG_NONNULL(1);
823+
824+
///@}
825+
803826
/** @name ChainstateManagerOptions
804827
* Functions for working with chainstate manager options.
805828
*/
@@ -931,6 +954,23 @@ BITCOINKERNEL_API int BITCOINKERNEL_WARN_UNUSED_RESULT btck_chainstate_manager_p
931954
const btck_Block* block,
932955
int* new_block) BITCOINKERNEL_ARG_NONNULL(1, 2, 3);
933956

957+
/**
958+
* @brief Returns the best known currently active chain. Its lifetime is
959+
* dependent on the chainstate manager. It can be thought of as a view on a
960+
* vector of block tree entries that form the best chain. The returned chain
961+
* reference always points to the currently active best chain. However, state
962+
* transitions within the chainstate manager (e.g., processing blocks) will
963+
* update the chain's contents. Data retrieved from this chain is only
964+
* consistent up to the point when new data is processed in the chainstate
965+
* manager. It is the user's responsibility to guard against these
966+
* inconsistencies.
967+
*
968+
* @param[in] chainstate_manager Non-null.
969+
* @return The chain.
970+
*/
971+
BITCOINKERNEL_API const btck_Chain* BITCOINKERNEL_WARN_UNUSED_RESULT btck_chainstate_manager_get_active_chain(
972+
const btck_ChainstateManager* chainstate_manager) BITCOINKERNEL_ARG_NONNULL(1);
973+
934974
/**
935975
* Destroy the chainstate manager.
936976
*/
@@ -943,6 +983,18 @@ BITCOINKERNEL_API void btck_chainstate_manager_destroy(btck_ChainstateManager* c
943983
*/
944984
///@{
945985

986+
/**
987+
* @brief Reads the block the passed in block tree entry points to from disk and
988+
* returns it.
989+
*
990+
* @param[in] chainstate_manager Non-null.
991+
* @param[in] block_tree_entry Non-null.
992+
* @return The read out block, or null on error.
993+
*/
994+
BITCOINKERNEL_API btck_Block* BITCOINKERNEL_WARN_UNUSED_RESULT btck_block_read(
995+
const btck_ChainstateManager* chainstate_manager,
996+
const btck_BlockTreeEntry* block_tree_entry) BITCOINKERNEL_ARG_NONNULL(1, 2);
997+
946998
/**
947999
* @brief Parse a serialized raw block into a new block object.
9481000
*
@@ -1024,6 +1076,32 @@ BITCOINKERNEL_API btck_BlockValidationResult btck_block_validation_state_get_blo
10241076

10251077
///@}
10261078

1079+
/** @name Chain
1080+
* Functions for working with the chain
1081+
*/
1082+
///@{
1083+
1084+
/**
1085+
* @brief Get the block tree entry of the current chain tip. Once returned,
1086+
* there is no guarantee that it remains in the active chain.
1087+
*
1088+
* @param[in] chain Non-null.
1089+
* @return The block tree entry of the current tip, or null if the chain is empty.
1090+
*/
1091+
BITCOINKERNEL_API const btck_BlockTreeEntry* BITCOINKERNEL_WARN_UNUSED_RESULT btck_chain_get_tip(
1092+
const btck_Chain* chain) BITCOINKERNEL_ARG_NONNULL(1);
1093+
1094+
/**
1095+
* @brief Return the height of the tip of the chain.
1096+
*
1097+
* @param[in] chain Non-null.
1098+
* @return The current height.
1099+
*/
1100+
BITCOINKERNEL_API int32_t BITCOINKERNEL_WARN_UNUSED_RESULT btck_chain_get_height(
1101+
const btck_Chain* chain) BITCOINKERNEL_ARG_NONNULL(1);
1102+
1103+
///@}
1104+
10271105
#ifdef __cplusplus
10281106
} // extern "C"
10291107
#endif // __cplusplus

src/kernel/bitcoinkernel_wrapper.h

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
#include <functional>
1111
#include <memory>
12+
#include <optional>
1213
#include <span>
1314
#include <stdexcept>
1415
#include <string>
@@ -598,13 +599,20 @@ class Logger : UniqueHandle<btck_LoggingConnection, btck_logging_connection_dest
598599
}
599600
};
600601

601-
class BlockTreeEntry : View<btck_BlockTreeEntry>
602+
class BlockTreeEntry : public View<btck_BlockTreeEntry>
602603
{
603604
public:
604605
BlockTreeEntry(const btck_BlockTreeEntry* entry)
605606
: View{entry}
606607
{
607608
}
609+
610+
std::optional<BlockTreeEntry> GetPrevious() const
611+
{
612+
auto entry{btck_block_tree_entry_get_previous(get())};
613+
if (!entry) return std::nullopt;
614+
return entry;
615+
}
608616
};
609617

610618
class KernelNotifications
@@ -766,6 +774,22 @@ class ChainstateManagerOptions : public UniqueHandle<btck_ChainstateManagerOptio
766774
}
767775
};
768776

777+
class ChainView : public View<btck_Chain>
778+
{
779+
public:
780+
explicit ChainView(const btck_Chain* ptr) : View{ptr} {}
781+
782+
BlockTreeEntry Tip() const
783+
{
784+
return btck_chain_get_tip(get());
785+
}
786+
787+
int32_t Height() const
788+
{
789+
return btck_chain_get_height(get());
790+
}
791+
};
792+
769793
class ChainMan : UniqueHandle<btck_ChainstateManager, btck_chainstate_manager_destroy>
770794
{
771795
public:
@@ -795,6 +819,18 @@ class ChainMan : UniqueHandle<btck_ChainstateManager, btck_chainstate_manager_de
795819
if (new_block) *new_block = _new_block == 1;
796820
return res == 0;
797821
}
822+
823+
ChainView GetChain() const
824+
{
825+
return ChainView{btck_chainstate_manager_get_active_chain(get())};
826+
}
827+
828+
std::optional<Block> ReadBlock(const BlockTreeEntry& entry) const
829+
{
830+
auto block{btck_block_read(get(), entry.get())};
831+
if (!block) return std::nullopt;
832+
return block;
833+
}
798834
};
799835

800836
} // namespace btck

src/test/kernel/test_kernel.cpp

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -693,6 +693,20 @@ void chainman_mainnet_validation_test(TestDirectory& test_directory)
693693
BOOST_CHECK(!chainman->ProcessBlock(invalid_block, &new_block));
694694
BOOST_CHECK(!new_block);
695695

696+
auto chain{chainman->GetChain()};
697+
BOOST_CHECK_EQUAL(chain.Height(), 1);
698+
auto tip{chain.Tip()};
699+
auto read_block{chainman->ReadBlock(tip)};
700+
BOOST_REQUIRE(read_block);
701+
check_equal(read_block.value().ToBytes(), raw_block);
702+
703+
// Check that we can read the previous block
704+
auto tip_2{tip.GetPrevious()};
705+
auto read_block_2{chainman->ReadBlock(tip_2.value())};
706+
707+
// It should be an error if we go another block back, since the genesis has no ancestor
708+
BOOST_CHECK(!tip_2.value().GetPrevious());
709+
696710
// If we try to validate it again, it should be a duplicate
697711
BOOST_CHECK(chainman->ProcessBlock(block, &new_block));
698712
BOOST_CHECK(!new_block);
@@ -758,4 +772,16 @@ BOOST_AUTO_TEST_CASE(btck_chainman_regtest_tests)
758772
BOOST_CHECK(chainman->ProcessBlock(block, &new_block));
759773
BOOST_CHECK(new_block);
760774
}
775+
776+
auto chain = chainman->GetChain();
777+
auto tip = chain.Tip();
778+
auto read_block = chainman->ReadBlock(tip).value();
779+
check_equal(read_block.ToBytes(), hex_string_to_byte_vec(REGTEST_BLOCK_DATA[REGTEST_BLOCK_DATA.size() - 1]));
780+
781+
auto tip_2 = tip.GetPrevious().value();
782+
auto read_block_2 = chainman->ReadBlock(tip_2).value();
783+
check_equal(read_block_2.ToBytes(), hex_string_to_byte_vec(REGTEST_BLOCK_DATA[REGTEST_BLOCK_DATA.size() - 2]));
784+
785+
std::filesystem::remove_all(test_directory.m_directory / "blocks" / "blk00000.dat");
786+
BOOST_CHECK(!chainman->ReadBlock(tip_2));
761787
}

0 commit comments

Comments
 (0)