Skip to content

Commit 96b4fac

Browse files
fjahrstickies-v
andcommitted
refactor, blockstorage: Generalize GetFirstStoredBlock
GetFirstStoredBlock is generalized to check for any data status with a status mask that needs to be passed as a parameter. To reflect this the function is also renamed to GetFirstBlock. Co-authored-by: stickies-v <[email protected]>
1 parent 3aaf732 commit 96b4fac

File tree

6 files changed

+93
-14
lines changed

6 files changed

+93
-14
lines changed

src/node/blockstorage.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -595,12 +595,12 @@ bool BlockManager::IsBlockPruned(const CBlockIndex& block)
595595
return m_have_pruned && !(block.nStatus & BLOCK_HAVE_DATA) && (block.nTx > 0);
596596
}
597597

598-
const CBlockIndex* BlockManager::GetFirstStoredBlock(const CBlockIndex& upper_block, const CBlockIndex* lower_block)
598+
const CBlockIndex* BlockManager::GetFirstBlock(const CBlockIndex& upper_block, uint32_t status_mask, const CBlockIndex* lower_block) const
599599
{
600600
AssertLockHeld(::cs_main);
601601
const CBlockIndex* last_block = &upper_block;
602-
assert(last_block->nStatus & BLOCK_HAVE_DATA); // 'upper_block' must have data
603-
while (last_block->pprev && (last_block->pprev->nStatus & BLOCK_HAVE_DATA)) {
602+
assert((last_block->nStatus & status_mask) == status_mask); // 'upper_block' must satisfy the status mask
603+
while (last_block->pprev && ((last_block->pprev->nStatus & status_mask) == status_mask)) {
604604
if (lower_block) {
605605
// Return if we reached the lower_block
606606
if (last_block == lower_block) return lower_block;
@@ -617,7 +617,7 @@ const CBlockIndex* BlockManager::GetFirstStoredBlock(const CBlockIndex& upper_bl
617617
bool BlockManager::CheckBlockDataAvailability(const CBlockIndex& upper_block, const CBlockIndex& lower_block)
618618
{
619619
if (!(upper_block.nStatus & BLOCK_HAVE_DATA)) return false;
620-
return GetFirstStoredBlock(upper_block, &lower_block) == &lower_block;
620+
return GetFirstBlock(upper_block, BLOCK_HAVE_DATA, &lower_block) == &lower_block;
621621
}
622622

623623
// If we're using -prune with -reindex, then delete block files that will be ignored by the

src/node/blockstorage.h

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -335,10 +335,33 @@ class BlockManager
335335
//! (part of the same chain).
336336
bool CheckBlockDataAvailability(const CBlockIndex& upper_block LIFETIMEBOUND, const CBlockIndex& lower_block LIFETIMEBOUND) EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
337337

338-
//! Find the first stored ancestor of start_block immediately after the last
339-
//! pruned ancestor. Return value will never be null. Caller is responsible
340-
//! for ensuring that start_block has data is not pruned.
341-
const CBlockIndex* GetFirstStoredBlock(const CBlockIndex& start_block LIFETIMEBOUND, const CBlockIndex* lower_block=nullptr) EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
338+
/**
339+
* @brief Returns the earliest block with specified `status_mask` flags set after
340+
* the latest block _not_ having those flags.
341+
*
342+
* This function starts from `upper_block`, which must have all
343+
* `status_mask` flags set, and iterates backwards through its ancestors. It
344+
* continues as long as each block has all `status_mask` flags set, until
345+
* reaching the oldest ancestor or `lower_block`.
346+
*
347+
* @pre `upper_block` must have all `status_mask` flags set.
348+
* @pre `lower_block` must be null or an ancestor of `upper_block`
349+
*
350+
* @param upper_block The starting block for the search, which must have all
351+
* `status_mask` flags set.
352+
* @param status_mask Bitmask specifying required status flags.
353+
* @param lower_block The earliest possible block to return. If null, the
354+
* search can extend to the genesis block.
355+
*
356+
* @return A non-null pointer to the earliest block between `upper_block`
357+
* and `lower_block`, inclusive, such that every block between the
358+
* returned block and `upper_block` has `status_mask` flags set.
359+
*/
360+
const CBlockIndex* GetFirstBlock(
361+
const CBlockIndex& upper_block LIFETIMEBOUND,
362+
uint32_t status_mask,
363+
const CBlockIndex* lower_block = nullptr
364+
) const EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
342365

343366
/** True if any block files have ever been pruned. */
344367
bool m_have_pruned = false;

src/rpc/blockchain.cpp

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -782,6 +782,24 @@ static RPCHelpMan getblock()
782782
};
783783
}
784784

785+
//! Return height of highest block that has been pruned, or std::nullopt if no blocks have been pruned
786+
std::optional<int> GetPruneHeight(const BlockManager& blockman, const CChain& chain) {
787+
AssertLockHeld(::cs_main);
788+
789+
const CBlockIndex* chain_tip{chain.Tip()};
790+
if (!(chain_tip->nStatus & BLOCK_HAVE_DATA)) return chain_tip->nHeight;
791+
792+
// Get first block with data, after the last block without data.
793+
// This is the start of the unpruned range of blocks.
794+
const auto& first_unpruned{*Assert(blockman.GetFirstBlock(*chain_tip, /*status_mask=*/BLOCK_HAVE_DATA))};
795+
if (!first_unpruned.pprev) {
796+
// No block before the first unpruned block means nothing is pruned.
797+
return std::nullopt;
798+
}
799+
// Block before the first unpruned block is the last pruned block.
800+
return Assert(first_unpruned.pprev)->nHeight;
801+
}
802+
785803
static RPCHelpMan pruneblockchain()
786804
{
787805
return RPCHelpMan{"pruneblockchain", "",
@@ -834,8 +852,7 @@ static RPCHelpMan pruneblockchain()
834852
}
835853

836854
PruneBlockFilesManual(active_chainstate, height);
837-
const CBlockIndex& block{*CHECK_NONFATAL(active_chain.Tip())};
838-
return block.nStatus & BLOCK_HAVE_DATA ? active_chainstate.m_blockman.GetFirstStoredBlock(block)->nHeight - 1 : block.nHeight;
855+
return GetPruneHeight(chainman.m_blockman, active_chain).value_or(-1);
839856
},
840857
};
841858
}
@@ -1288,8 +1305,8 @@ RPCHelpMan getblockchaininfo()
12881305
obj.pushKV("size_on_disk", chainman.m_blockman.CalculateCurrentUsage());
12891306
obj.pushKV("pruned", chainman.m_blockman.IsPruneMode());
12901307
if (chainman.m_blockman.IsPruneMode()) {
1291-
bool has_tip_data = tip.nStatus & BLOCK_HAVE_DATA;
1292-
obj.pushKV("pruneheight", has_tip_data ? chainman.m_blockman.GetFirstStoredBlock(tip)->nHeight : tip.nHeight + 1);
1308+
const auto prune_height{GetPruneHeight(chainman.m_blockman, active_chainstate.m_chain)};
1309+
obj.pushKV("pruneheight", prune_height ? prune_height.value() + 1 : 0);
12931310

12941311
const bool automatic_pruning{chainman.m_blockman.GetPruneTarget() != BlockManager::PRUNE_TARGET_MANUAL};
12951312
obj.pushKV("automatic_pruning", automatic_pruning);

src/rpc/blockchain.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ class CBlockIndex;
2121
class Chainstate;
2222
class UniValue;
2323
namespace node {
24+
class BlockManager;
2425
struct NodeContext;
2526
} // namespace node
2627

@@ -57,4 +58,7 @@ UniValue CreateUTXOSnapshot(
5758
const fs::path& path,
5859
const fs::path& tmppath);
5960

61+
//! Return height of highest block that has been pruned, or std::nullopt if no blocks have been pruned
62+
std::optional<int> GetPruneHeight(const node::BlockManager& blockman, const CChain& chain) EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
63+
6064
#endif // BITCOIN_RPC_BLOCKCHAIN_H

src/test/blockchain_tests.cpp

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
#include <boost/test/unit_test.hpp>
66

77
#include <chain.h>
8+
#include <node/blockstorage.h>
89
#include <rpc/blockchain.h>
10+
#include <sync.h>
911
#include <test/util/setup_common.h>
1012
#include <util/string.h>
1113

@@ -74,4 +76,36 @@ BOOST_AUTO_TEST_CASE(get_difficulty_for_very_high_target)
7476
TestDifficulty(0x12345678, 5913134931067755359633408.0);
7577
}
7678

79+
//! Prune chain from height down to genesis block and check that
80+
//! GetPruneHeight returns the correct value
81+
static void CheckGetPruneHeight(node::BlockManager& blockman, CChain& chain, int height) EXCLUSIVE_LOCKS_REQUIRED(::cs_main)
82+
{
83+
AssertLockHeld(::cs_main);
84+
85+
// Emulate pruning all blocks from `height` down to the genesis block
86+
// by unsetting the `BLOCK_HAVE_DATA` flag from `nStatus`
87+
for (CBlockIndex* it{chain[height]}; it != nullptr && it->nHeight > 0; it = it->pprev) {
88+
it->nStatus &= ~BLOCK_HAVE_DATA;
89+
}
90+
91+
const auto prune_height{GetPruneHeight(blockman, chain)};
92+
BOOST_REQUIRE(prune_height.has_value());
93+
BOOST_CHECK_EQUAL(*prune_height, height);
94+
}
95+
96+
BOOST_FIXTURE_TEST_CASE(get_prune_height, TestChain100Setup)
97+
{
98+
LOCK(::cs_main);
99+
auto& chain = m_node.chainman->ActiveChain();
100+
auto& blockman = m_node.chainman->m_blockman;
101+
102+
// Fresh chain of 100 blocks without any pruned blocks, so std::nullopt should be returned
103+
BOOST_CHECK(!GetPruneHeight(blockman, chain).has_value());
104+
105+
// Start pruning
106+
CheckGetPruneHeight(blockman, chain, 1);
107+
CheckGetPruneHeight(blockman, chain, 99);
108+
CheckGetPruneHeight(blockman, chain, 100);
109+
}
110+
77111
BOOST_AUTO_TEST_SUITE_END()

src/test/blockmanager_tests.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Distributed under the MIT software license, see the accompanying
33
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
44

5+
#include <chain.h>
56
#include <chainparams.h>
67
#include <clientversion.h>
78
#include <node/blockstorage.h>
@@ -113,7 +114,7 @@ BOOST_FIXTURE_TEST_CASE(blockmanager_block_data_availability, TestChain100Setup)
113114
};
114115

115116
// 1) Return genesis block when all blocks are available
116-
BOOST_CHECK_EQUAL(blockman.GetFirstStoredBlock(tip), chainman->ActiveChain()[0]);
117+
BOOST_CHECK_EQUAL(blockman.GetFirstBlock(tip, BLOCK_HAVE_DATA), chainman->ActiveChain()[0]);
117118
BOOST_CHECK(blockman.CheckBlockDataAvailability(tip, *chainman->ActiveChain()[0]));
118119

119120
// 2) Check lower_block when all blocks are available
@@ -127,7 +128,7 @@ BOOST_FIXTURE_TEST_CASE(blockmanager_block_data_availability, TestChain100Setup)
127128
func_prune_blocks(last_pruned_block);
128129

129130
// 3) The last block not pruned is in-between upper-block and the genesis block
130-
BOOST_CHECK_EQUAL(blockman.GetFirstStoredBlock(tip), first_available_block);
131+
BOOST_CHECK_EQUAL(blockman.GetFirstBlock(tip, BLOCK_HAVE_DATA), first_available_block);
131132
BOOST_CHECK(blockman.CheckBlockDataAvailability(tip, *first_available_block));
132133
BOOST_CHECK(!blockman.CheckBlockDataAvailability(tip, *last_pruned_block));
133134
}

0 commit comments

Comments
 (0)