mirror of https://github.com/bitcoin/bitcoin.git
kernel: Add block hash type and block tree utility functions to C header
Introduce btck_BlockHash as a type-safe identifier for a block. Adds functions to retrieve block tree entries by hash or height, get block hashes and heights from entries. access the genesis block, and check if blocks are in the active chain.
This commit is contained in:
parent
d9d040fd16
commit
00000cb55a
|
@ -497,6 +497,7 @@ struct btck_Chain : Handle<btck_Chain, CChain> {};
|
|||
struct btck_BlockSpentOutputs : Handle<btck_BlockSpentOutputs, std::shared_ptr<CBlockUndo>> {};
|
||||
struct btck_TransactionSpentOutputs : Handle<btck_TransactionSpentOutputs, CTxUndo> {};
|
||||
struct btck_Coin : Handle<btck_Coin, Coin> {};
|
||||
struct btck_BlockHash : Handle<btck_BlockHash, uint256> {};
|
||||
|
||||
btck_Transaction* btck_transaction_create(const void* raw_transaction, size_t raw_transaction_len)
|
||||
{
|
||||
|
@ -912,6 +913,17 @@ btck_ChainstateManager* btck_chainstate_manager_create(
|
|||
return btck_ChainstateManager::create(std::move(chainman), opts.m_context);
|
||||
}
|
||||
|
||||
const btck_BlockTreeEntry* btck_chainstate_manager_get_block_tree_entry_by_hash(const btck_ChainstateManager* chainman, const btck_BlockHash* block_hash)
|
||||
{
|
||||
auto block_index = WITH_LOCK(btck_ChainstateManager::get(chainman).m_chainman->GetMutex(),
|
||||
return btck_ChainstateManager::get(chainman).m_chainman->m_blockman.LookupBlockIndex(btck_BlockHash::get(block_hash)));
|
||||
if (!block_index) {
|
||||
LogDebug(BCLog::KERNEL, "A block with the given hash is not indexed.");
|
||||
return nullptr;
|
||||
}
|
||||
return btck_BlockTreeEntry::ref(block_index);
|
||||
}
|
||||
|
||||
void btck_chainstate_manager_destroy(btck_ChainstateManager* chainman)
|
||||
{
|
||||
{
|
||||
|
@ -989,6 +1001,11 @@ int btck_block_to_bytes(const btck_Block* block, btck_WriteBytes writer, void* u
|
|||
}
|
||||
}
|
||||
|
||||
btck_BlockHash* btck_block_get_hash(const btck_Block* block)
|
||||
{
|
||||
return btck_BlockHash::create(btck_Block::get(block)->GetHash());
|
||||
}
|
||||
|
||||
void btck_block_destroy(btck_Block* block)
|
||||
{
|
||||
delete block;
|
||||
|
@ -1004,6 +1021,44 @@ btck_Block* btck_block_read(const btck_ChainstateManager* chainman, const btck_B
|
|||
return btck_Block::create(block);
|
||||
}
|
||||
|
||||
int32_t btck_block_tree_entry_get_height(const btck_BlockTreeEntry* entry)
|
||||
{
|
||||
return btck_BlockTreeEntry::get(entry).nHeight;
|
||||
}
|
||||
|
||||
btck_BlockHash* btck_block_tree_entry_get_block_hash(const btck_BlockTreeEntry* entry)
|
||||
{
|
||||
if (btck_BlockTreeEntry::get(entry).phashBlock == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
return btck_BlockHash::create(btck_BlockTreeEntry::get(entry).GetBlockHash());
|
||||
}
|
||||
|
||||
btck_BlockHash* btck_block_hash_create(const unsigned char block_hash[32])
|
||||
{
|
||||
return btck_BlockHash::create(std::span<const unsigned char>{block_hash, 32});
|
||||
}
|
||||
|
||||
btck_BlockHash* btck_block_hash_copy(const btck_BlockHash* block_hash)
|
||||
{
|
||||
return btck_BlockHash::copy(block_hash);
|
||||
}
|
||||
|
||||
void btck_block_hash_to_bytes(const btck_BlockHash* block_hash, unsigned char output[32])
|
||||
{
|
||||
std::memcpy(output, btck_BlockHash::get(block_hash).begin(), 32);
|
||||
}
|
||||
|
||||
int btck_block_hash_equals(const btck_BlockHash* hash1, const btck_BlockHash* hash2)
|
||||
{
|
||||
return btck_BlockHash::get(hash1) == btck_BlockHash::get(hash2);
|
||||
}
|
||||
|
||||
void btck_block_hash_destroy(btck_BlockHash* hash)
|
||||
{
|
||||
delete hash;
|
||||
}
|
||||
|
||||
btck_BlockSpentOutputs* btck_block_spent_outputs_read(const btck_ChainstateManager* chainman, const btck_BlockTreeEntry* entry)
|
||||
{
|
||||
auto block_undo{std::make_shared<CBlockUndo>()};
|
||||
|
@ -1114,3 +1169,20 @@ int btck_chain_get_height(const btck_Chain* chain)
|
|||
{
|
||||
return btck_Chain::get(chain).Height();
|
||||
}
|
||||
|
||||
const btck_BlockTreeEntry* btck_chain_get_genesis(const btck_Chain* chain)
|
||||
{
|
||||
return btck_BlockTreeEntry::ref(btck_Chain::get(chain).Genesis());
|
||||
}
|
||||
|
||||
const btck_BlockTreeEntry* btck_chain_get_by_height(const btck_Chain* chain, int height)
|
||||
{
|
||||
LOCK(::cs_main);
|
||||
return btck_BlockTreeEntry::ref(btck_Chain::get(chain)[height]);
|
||||
}
|
||||
|
||||
int btck_chain_contains(const btck_Chain* chain, const btck_BlockTreeEntry* entry)
|
||||
{
|
||||
LOCK(::cs_main);
|
||||
return btck_Chain::get(chain).Contains(&btck_BlockTreeEntry::get(entry)) ? 1 : 0;
|
||||
}
|
||||
|
|
|
@ -241,6 +241,13 @@ typedef struct btck_TransactionSpentOutputs btck_TransactionSpentOutputs;
|
|||
*/
|
||||
typedef struct btck_Coin btck_Coin;
|
||||
|
||||
/**
|
||||
* Opaque data structure for holding a block hash.
|
||||
*
|
||||
* This is a type-safe identifier for a block.
|
||||
*/
|
||||
typedef struct btck_BlockHash btck_BlockHash;
|
||||
|
||||
/** Current sync state passed to tip changed callbacks. */
|
||||
typedef uint8_t btck_SynchronizationState;
|
||||
#define btck_SynchronizationState_INIT_REINDEX ((btck_SynchronizationState)(0))
|
||||
|
@ -836,6 +843,24 @@ BITCOINKERNEL_API void btck_context_destroy(btck_Context* context);
|
|||
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);
|
||||
|
||||
/**
|
||||
* @brief Return the height of a certain block tree entry.
|
||||
*
|
||||
* @param[in] block_tree_entry Non-null.
|
||||
* @return The block height.
|
||||
*/
|
||||
BITCOINKERNEL_API int32_t BITCOINKERNEL_WARN_UNUSED_RESULT btck_block_tree_entry_get_height(
|
||||
const btck_BlockTreeEntry* block_tree_entry) BITCOINKERNEL_ARG_NONNULL(1);
|
||||
|
||||
/**
|
||||
* @brief Return the block hash associated with a block tree entry.
|
||||
*
|
||||
* @param[in] block_tree_entry Non-null.
|
||||
* @return The block hash.
|
||||
*/
|
||||
BITCOINKERNEL_API btck_BlockHash* BITCOINKERNEL_WARN_UNUSED_RESULT btck_block_tree_entry_get_block_hash(
|
||||
const btck_BlockTreeEntry* block_tree_entry) BITCOINKERNEL_ARG_NONNULL(1);
|
||||
|
||||
///@}
|
||||
|
||||
/** @name ChainstateManagerOptions
|
||||
|
@ -980,6 +1005,18 @@ BITCOINKERNEL_API int BITCOINKERNEL_WARN_UNUSED_RESULT btck_chainstate_manager_p
|
|||
BITCOINKERNEL_API const btck_Chain* BITCOINKERNEL_WARN_UNUSED_RESULT btck_chainstate_manager_get_active_chain(
|
||||
const btck_ChainstateManager* chainstate_manager) BITCOINKERNEL_ARG_NONNULL(1);
|
||||
|
||||
/**
|
||||
* @brief Retrieve a block tree entry by its block hash.
|
||||
*
|
||||
* @param[in] chainstate_manager Non-null.
|
||||
* @param[in] block_hash Non-null.
|
||||
* @return The block tree entry of the block with the passed in hash, or null if
|
||||
* the block hash is not found.
|
||||
*/
|
||||
BITCOINKERNEL_API const btck_BlockTreeEntry* BITCOINKERNEL_WARN_UNUSED_RESULT btck_chainstate_manager_get_block_tree_entry_by_hash(
|
||||
const btck_ChainstateManager* chainstate_manager,
|
||||
const btck_BlockHash* block_hash) BITCOINKERNEL_ARG_NONNULL(1, 2);
|
||||
|
||||
/**
|
||||
* Destroy the chainstate manager.
|
||||
*/
|
||||
|
@ -1044,6 +1081,15 @@ BITCOINKERNEL_API size_t BITCOINKERNEL_WARN_UNUSED_RESULT btck_block_count_trans
|
|||
BITCOINKERNEL_API const btck_Transaction* BITCOINKERNEL_WARN_UNUSED_RESULT btck_block_get_transaction_at(
|
||||
const btck_Block* block, size_t transaction_index) BITCOINKERNEL_ARG_NONNULL(1);
|
||||
|
||||
/*
|
||||
* @brief Calculate and return the hash of a block.
|
||||
*
|
||||
* @param[in] block Non-null.
|
||||
* @return The block hash.
|
||||
*/
|
||||
BITCOINKERNEL_API btck_BlockHash* BITCOINKERNEL_WARN_UNUSED_RESULT btck_block_get_hash(
|
||||
const btck_Block* block) BITCOINKERNEL_ARG_NONNULL(1);
|
||||
|
||||
/*
|
||||
* @brief Serializes the block through the passed in callback to bytes.
|
||||
* This is consensus serialization that is also used for the p2p network.
|
||||
|
@ -1109,6 +1155,40 @@ BITCOINKERNEL_API const btck_BlockTreeEntry* BITCOINKERNEL_WARN_UNUSED_RESULT bt
|
|||
BITCOINKERNEL_API int BITCOINKERNEL_WARN_UNUSED_RESULT btck_chain_get_height(
|
||||
const btck_Chain* chain) BITCOINKERNEL_ARG_NONNULL(1);
|
||||
|
||||
/*
|
||||
* @brief Get the block tree entry of the genesis block.
|
||||
*
|
||||
* @param[in] chain Non-null.
|
||||
* @return The block tree entry of the genesis block, or null if the chain is empty.
|
||||
*/
|
||||
BITCOINKERNEL_API const btck_BlockTreeEntry* BITCOINKERNEL_WARN_UNUSED_RESULT btck_chain_get_genesis(
|
||||
const btck_Chain* chain) BITCOINKERNEL_ARG_NONNULL(1);
|
||||
|
||||
/**
|
||||
* @brief Retrieve a block tree entry by its height in the currently active chain.
|
||||
* Once retrieved there is no guarantee that it remains in the active chain.
|
||||
*
|
||||
* @param[in] chain Non-null.
|
||||
* @param[in] block_height Height in the chain of the to be retrieved block tree entry.
|
||||
* @return The block tree entry at a certain height in the currently active chain, or null
|
||||
* if the height is out of bounds.
|
||||
*/
|
||||
BITCOINKERNEL_API const btck_BlockTreeEntry* BITCOINKERNEL_WARN_UNUSED_RESULT btck_chain_get_by_height(
|
||||
const btck_Chain* chain,
|
||||
int block_height) BITCOINKERNEL_ARG_NONNULL(1);
|
||||
|
||||
/**
|
||||
* @brief Return true if the passed in chain contains the block tree entry.
|
||||
*
|
||||
* @param[in] chain Non-null.
|
||||
* @param[in] block_tree_entry Non-null.
|
||||
* @return 1 if the block_tree_entry is in the chain, 0 otherwise.
|
||||
*
|
||||
*/
|
||||
BITCOINKERNEL_API int btck_chain_contains(
|
||||
const btck_Chain* chain,
|
||||
const btck_BlockTreeEntry* block_tree_entry) BITCOINKERNEL_ARG_NONNULL(1, 2);
|
||||
|
||||
///@}
|
||||
|
||||
/** @name BlockSpentOutputs
|
||||
|
@ -1261,6 +1341,52 @@ BITCOINKERNEL_API void btck_coin_destroy(btck_Coin* coin);
|
|||
|
||||
///@}
|
||||
|
||||
/** @name BlockHash
|
||||
* Functions for working with block hashes.
|
||||
*/
|
||||
///@{
|
||||
|
||||
/**
|
||||
* @brief Create a block hash from its raw data.
|
||||
*/
|
||||
BITCOINKERNEL_API btck_BlockHash* BITCOINKERNEL_WARN_UNUSED_RESULT btck_block_hash_create(
|
||||
const unsigned char block_hash[32]) BITCOINKERNEL_ARG_NONNULL(1);
|
||||
|
||||
/**
|
||||
* @brief Check if two block hashes are equal.
|
||||
*
|
||||
* @param[in] hash1 Non-null.
|
||||
* @param[in] hash2 Non-null.
|
||||
* @return 0 if the block hashes are not equal.
|
||||
*/
|
||||
BITCOINKERNEL_API int btck_block_hash_equals(
|
||||
const btck_BlockHash* hash1, const btck_BlockHash* hash2) BITCOINKERNEL_ARG_NONNULL(1, 2);
|
||||
|
||||
/**
|
||||
* @brief Copy a block hash.
|
||||
*
|
||||
* @param[in] block_hash Non-null.
|
||||
* @return The copied block hash.
|
||||
*/
|
||||
BITCOINKERNEL_API btck_BlockHash* BITCOINKERNEL_WARN_UNUSED_RESULT btck_block_hash_copy(
|
||||
const btck_BlockHash* block_hash) BITCOINKERNEL_ARG_NONNULL(1);
|
||||
|
||||
/**
|
||||
* @brief Serializes the block hash to bytes.
|
||||
*
|
||||
* @param[in] block_hash Non-null.
|
||||
* @param[in] output The serialized block hash.
|
||||
*/
|
||||
BITCOINKERNEL_API void btck_block_hash_to_bytes(
|
||||
const btck_BlockHash* block_hash, unsigned char output[32]) BITCOINKERNEL_ARG_NONNULL(1, 2);
|
||||
|
||||
/**
|
||||
* Destroy the block hash.
|
||||
*/
|
||||
BITCOINKERNEL_API void btck_block_hash_destroy(btck_BlockHash* block_hash);
|
||||
|
||||
///@}
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif // __cplusplus
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
#include <kernel/bitcoinkernel.h>
|
||||
|
||||
#include <array>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
@ -527,6 +528,33 @@ bool ScriptPubkeyApi<Derived>::Verify(int64_t amount,
|
|||
return result == 1;
|
||||
}
|
||||
|
||||
class BlockHash : public Handle<btck_BlockHash, btck_block_hash_copy, btck_block_hash_destroy>
|
||||
{
|
||||
public:
|
||||
explicit BlockHash(const std::array<std::byte, 32>& hash)
|
||||
: Handle{btck_block_hash_create(reinterpret_cast<const unsigned char*>(hash.data()))} {}
|
||||
|
||||
explicit BlockHash(btck_BlockHash* hash)
|
||||
: Handle{hash} {}
|
||||
|
||||
bool operator==(const BlockHash& other) const
|
||||
{
|
||||
return btck_block_hash_equals(get(), other.get()) != 0;
|
||||
}
|
||||
|
||||
bool operator!=(const BlockHash& other) const
|
||||
{
|
||||
return btck_block_hash_equals(get(), other.get()) == 0;
|
||||
}
|
||||
|
||||
std::array<std::byte, 32> ToBytes() const
|
||||
{
|
||||
std::array<std::byte, 32> hash;
|
||||
btck_block_hash_to_bytes(get(), reinterpret_cast<unsigned char*>(hash.data()));
|
||||
return hash;
|
||||
}
|
||||
};
|
||||
|
||||
class Block : public Handle<btck_Block, btck_block_copy, btck_block_destroy>
|
||||
{
|
||||
public:
|
||||
|
@ -552,6 +580,11 @@ public:
|
|||
return Range<Block, &Block::CountTransactions, &Block::GetTransaction>{*this};
|
||||
}
|
||||
|
||||
BlockHash GetHash() const
|
||||
{
|
||||
return BlockHash{btck_block_get_hash(get())};
|
||||
}
|
||||
|
||||
std::vector<std::byte> ToBytes() const
|
||||
{
|
||||
return write_bytes(get(), btck_block_to_bytes);
|
||||
|
@ -599,7 +632,7 @@ public:
|
|||
}
|
||||
};
|
||||
|
||||
class BlockTreeEntry : View<btck_BlockTreeEntry>
|
||||
class BlockTreeEntry : public View<btck_BlockTreeEntry>
|
||||
{
|
||||
public:
|
||||
BlockTreeEntry(const btck_BlockTreeEntry* entry)
|
||||
|
@ -614,7 +647,18 @@ public:
|
|||
return entry;
|
||||
}
|
||||
|
||||
int32_t GetHeight() const
|
||||
{
|
||||
return btck_block_tree_entry_get_height(get());
|
||||
}
|
||||
|
||||
BlockHash GetHash() const
|
||||
{
|
||||
return BlockHash{btck_block_tree_entry_get_block_hash(get())};
|
||||
}
|
||||
|
||||
friend class ChainMan;
|
||||
friend class Chain;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
|
@ -800,6 +844,28 @@ public:
|
|||
{
|
||||
return btck_chain_get_height(get());
|
||||
}
|
||||
|
||||
BlockTreeEntry Genesis() const
|
||||
{
|
||||
return btck_chain_get_genesis(get());
|
||||
}
|
||||
|
||||
BlockTreeEntry GetByHeight(int height) const
|
||||
{
|
||||
auto index{btck_chain_get_by_height(get(), height)};
|
||||
if (!index) throw std::runtime_error("No entry in the chain at the provided height");
|
||||
return index;
|
||||
}
|
||||
|
||||
bool Contains(BlockTreeEntry& entry) const
|
||||
{
|
||||
return btck_chain_contains(get(), entry.get());
|
||||
}
|
||||
|
||||
auto Entries() const
|
||||
{
|
||||
return Range<ChainView, &ChainView::Height, &ChainView::GetByHeight>{*this};
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Derived>
|
||||
|
@ -942,6 +1008,11 @@ public:
|
|||
return ChainView{btck_chainstate_manager_get_active_chain(get())};
|
||||
}
|
||||
|
||||
BlockTreeEntry GetBlockTreeEntry(const BlockHash& block_hash) const
|
||||
{
|
||||
return btck_chainstate_manager_get_block_tree_entry_by_hash(get(), block_hash.get());
|
||||
}
|
||||
|
||||
std::optional<Block> ReadBlock(const BlockTreeEntry& entry) const
|
||||
{
|
||||
auto block{btck_block_read(get(), entry.get())};
|
||||
|
|
|
@ -664,6 +664,38 @@ void chainman_reindex_test(TestDirectory& test_directory)
|
|||
|
||||
std::vector<std::string> import_files;
|
||||
BOOST_CHECK(chainman->ImportBlocks(import_files));
|
||||
|
||||
// Sanity check some block retrievals
|
||||
auto chain{chainman->GetChain()};
|
||||
BOOST_CHECK_THROW(chain.GetByHeight(1000), std::runtime_error);
|
||||
auto genesis_index{chain.Genesis()};
|
||||
BOOST_CHECK(!genesis_index.GetPrevious());
|
||||
auto genesis_block_raw{chainman->ReadBlock(genesis_index).value().ToBytes()};
|
||||
auto first_index{chain.GetByHeight(0)};
|
||||
auto first_block_raw{chainman->ReadBlock(genesis_index).value().ToBytes()};
|
||||
check_equal(genesis_block_raw, first_block_raw);
|
||||
auto height{first_index.GetHeight()};
|
||||
BOOST_CHECK_EQUAL(height, 0);
|
||||
|
||||
auto next_index{chain.GetByHeight(first_index.GetHeight() + 1)};
|
||||
BOOST_CHECK(chain.Contains(next_index));
|
||||
auto next_block_data{chainman->ReadBlock(next_index).value().ToBytes()};
|
||||
auto tip_index{chain.Tip()};
|
||||
auto tip_block_data{chainman->ReadBlock(tip_index).value().ToBytes()};
|
||||
auto second_index{chain.GetByHeight(1)};
|
||||
auto second_block{chainman->ReadBlock(second_index).value()};
|
||||
auto second_block_data{second_block.ToBytes()};
|
||||
auto second_height{second_index.GetHeight()};
|
||||
BOOST_CHECK_EQUAL(second_height, 1);
|
||||
check_equal(next_block_data, tip_block_data);
|
||||
check_equal(next_block_data, second_block_data);
|
||||
|
||||
auto second_hash{second_index.GetHash()};
|
||||
auto another_second_index{chainman->GetBlockTreeEntry(second_hash)};
|
||||
auto another_second_height{another_second_index.GetHeight()};
|
||||
auto second_block_hash{second_block.GetHash()};
|
||||
check_equal(second_block_hash.ToBytes(), second_hash.ToBytes());
|
||||
BOOST_CHECK_EQUAL(second_height, another_second_height);
|
||||
}
|
||||
|
||||
void chainman_reindex_chainstate_test(TestDirectory& test_directory)
|
||||
|
@ -761,6 +793,21 @@ BOOST_AUTO_TEST_CASE(btck_chainman_mainnet_tests)
|
|||
chainman_reindex_chainstate_test(test_directory);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(btck_block_hash_tests)
|
||||
{
|
||||
std::array<std::byte, 32> test_hash;
|
||||
std::array<std::byte, 32> test_hash_2;
|
||||
for (int i = 0; i < 32; ++i) {
|
||||
test_hash[i] = static_cast<std::byte>(i);
|
||||
test_hash_2[i] = static_cast<std::byte>(i + 1);
|
||||
}
|
||||
BlockHash block_hash{test_hash};
|
||||
BlockHash block_hash_2{test_hash_2};
|
||||
BOOST_CHECK(block_hash != block_hash_2);
|
||||
BOOST_CHECK(block_hash == block_hash);
|
||||
CheckHandle(block_hash, block_hash_2);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(btck_chainman_in_memory_tests)
|
||||
{
|
||||
auto in_memory_test_directory{TestDirectory{"in-memory_test_bitcoin_kernel"}};
|
||||
|
@ -853,6 +900,31 @@ BOOST_AUTO_TEST_CASE(btck_chainman_regtest_tests)
|
|||
}
|
||||
}
|
||||
|
||||
CheckRange(chain.Entries(), chain.Height());
|
||||
|
||||
for (const BlockTreeEntry entry : chain.Entries()) {
|
||||
std::optional<Block> block{chainman->ReadBlock(entry)};
|
||||
if (block) {
|
||||
for (const TransactionView transaction : block->Transactions()) {
|
||||
for (const TransactionOutputView output : transaction.Outputs()) {
|
||||
// skip data carrier outputs
|
||||
if ((unsigned char)output.GetScriptPubkey().ToBytes()[0] == 0x6a) {
|
||||
continue;
|
||||
}
|
||||
BOOST_CHECK_GT(output.Amount(), 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int32_t count{0};
|
||||
for (const auto entry : chain.Entries()) {
|
||||
BOOST_CHECK_EQUAL(entry.GetHeight(), count);
|
||||
++count;
|
||||
}
|
||||
BOOST_CHECK_EQUAL(count, chain.Height());
|
||||
|
||||
|
||||
std::filesystem::remove_all(test_directory.m_directory / "blocks" / "blk00000.dat");
|
||||
BOOST_CHECK(!chainman->ReadBlock(tip_2).has_value());
|
||||
std::filesystem::remove_all(test_directory.m_directory / "blocks" / "rev00000.dat");
|
||||
|
|
Loading…
Reference in New Issue