Skip to content

Commit 2ec89f1

Browse files
furszyryanofsky
andcommitted
refactor: simplify pruning violation check
By generalizing 'GetFirstStoredBlock' and implementing 'CheckBlockDataAvailability' we can dedup code and avoid repeating work when multiple indexes are enabled. E.g. get the oldest block across all indexes and perform the pruning violation check from that point up to the tip only once (this feature is being introduced in a follow-up commit). This commit shouldn't change behavior in any way. Co-authored-by: Ryan Ofsky <[email protected]>
1 parent c82ef91 commit 2ec89f1

File tree

4 files changed

+48
-35
lines changed

4 files changed

+48
-35
lines changed

src/index/base.cpp

Lines changed: 16 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -111,37 +111,23 @@ bool BaseIndex::Init()
111111
const CBlockIndex* start_block = m_best_block_index.load();
112112
bool synced = start_block == active_chain.Tip();
113113
if (!synced && g_indexes_ready_to_sync) {
114-
bool prune_violation = false;
115-
if (!start_block) {
116-
// index is not built yet
117-
// make sure we have all block data back to the genesis
118-
bool has_tip_data = active_chain.Tip()->nStatus & BLOCK_HAVE_DATA;
119-
prune_violation = !has_tip_data || m_chainstate->m_blockman.GetFirstStoredBlock(*active_chain.Tip()) != active_chain.Genesis();
114+
const CBlockIndex* block_to_test = start_block ? start_block : active_chain.Genesis();
115+
116+
// Assert block_to_test is not null here. It can't be null because the
117+
// genesis block can't be null here. The genesis block will be null
118+
// during this BaseIndex::Init() call if the node is being started for
119+
// the first time, or if -reindex is used. But in both of these cases
120+
// m_best_block_index is also null so this branch is not reached.
121+
assert(block_to_test);
122+
123+
if (!active_chain.Contains(block_to_test)) {
124+
// if the bestblock is not part of the mainchain, find the fork
125+
// so we can make sure we have all data down to the fork
126+
block_to_test = active_chain.FindFork(block_to_test);
120127
}
121-
// in case the index has a best block set and is not fully synced
122-
// check if we have the required blocks to continue building the index
123-
else {
124-
const CBlockIndex* block_to_test = start_block;
125-
if (!active_chain.Contains(block_to_test)) {
126-
// if the bestblock is not part of the mainchain, find the fork
127-
// and make sure we have all data down to the fork
128-
block_to_test = active_chain.FindFork(block_to_test);
129-
}
130-
const CBlockIndex* block = active_chain.Tip();
131-
prune_violation = true;
132-
// check backwards from the tip if we have all block data until we reach the indexes bestblock
133-
while (block_to_test && block && (block->nStatus & BLOCK_HAVE_DATA)) {
134-
if (block_to_test == block) {
135-
prune_violation = false;
136-
break;
137-
}
138-
// block->pprev must exist at this point, since block_to_test is part of the chain
139-
// and thus must be encountered when going backwards from the tip
140-
assert(block->pprev);
141-
block = block->pprev;
142-
}
143-
}
144-
if (prune_violation) {
128+
129+
// make sure we have all block data back to the start block
130+
if (!m_chainstate->m_blockman.CheckBlockDataAvailability(*active_chain.Tip(), *Assert(block_to_test))) {
145131
return InitError(strprintf(Untranslated("%s best block of the index goes beyond pruned data. Please disable the index or reindex (which will download the whole blockchain again)"), GetName()));
146132
}
147133
}

src/node/blockstorage.cpp

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -403,17 +403,31 @@ bool BlockManager::IsBlockPruned(const CBlockIndex* pblockindex)
403403
return (m_have_pruned && !(pblockindex->nStatus & BLOCK_HAVE_DATA) && pblockindex->nTx > 0);
404404
}
405405

406-
const CBlockIndex* BlockManager::GetFirstStoredBlock(const CBlockIndex& start_block)
406+
const CBlockIndex* BlockManager::GetFirstStoredBlock(const CBlockIndex& upper_block, const CBlockIndex* lower_block)
407407
{
408408
AssertLockHeld(::cs_main);
409-
const CBlockIndex* last_block = &start_block;
410-
assert(last_block->nStatus & BLOCK_HAVE_DATA);
409+
const CBlockIndex* last_block = &upper_block;
410+
assert(last_block->nStatus & BLOCK_HAVE_DATA); // 'upper_block' must have data
411411
while (last_block->pprev && (last_block->pprev->nStatus & BLOCK_HAVE_DATA)) {
412+
if (lower_block) {
413+
// Return if we reached the lower_block
414+
if (last_block == lower_block) return lower_block;
415+
// if range was surpassed, means that 'lower_block' is not part of the 'upper_block' chain
416+
// and so far this is not allowed.
417+
assert(last_block->nHeight >= lower_block->nHeight);
418+
}
412419
last_block = last_block->pprev;
413420
}
421+
assert(last_block != nullptr);
414422
return last_block;
415423
}
416424

425+
bool BlockManager::CheckBlockDataAvailability(const CBlockIndex& upper_block, const CBlockIndex& lower_block)
426+
{
427+
if (!(upper_block.nStatus & BLOCK_HAVE_DATA)) return false;
428+
return GetFirstStoredBlock(upper_block, &lower_block) == &lower_block;
429+
}
430+
417431
// If we're using -prune with -reindex, then delete block files that will be ignored by the
418432
// reindex. Since reindexing works by starting at block file 0 and looping until a blockfile
419433
// is missing, do the same here to delete any later block files after a gap. Also delete all

src/node/blockstorage.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,10 +222,15 @@ class BlockManager
222222
//! Returns last CBlockIndex* that is a checkpoint
223223
const CBlockIndex* GetLastCheckpoint(const CCheckpointData& data) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
224224

225+
//! Check if all blocks in the [upper_block, lower_block] range have data available.
226+
//! The caller is responsible for ensuring that lower_block is an ancestor of upper_block
227+
//! (part of the same chain).
228+
bool CheckBlockDataAvailability(const CBlockIndex& upper_block LIFETIMEBOUND, const CBlockIndex& lower_block LIFETIMEBOUND) EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
229+
225230
//! Find the first stored ancestor of start_block immediately after the last
226231
//! pruned ancestor. Return value will never be null. Caller is responsible
227232
//! for ensuring that start_block has data is not pruned.
228-
const CBlockIndex* GetFirstStoredBlock(const CBlockIndex& start_block LIFETIMEBOUND) EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
233+
const CBlockIndex* GetFirstStoredBlock(const CBlockIndex& start_block LIFETIMEBOUND, const CBlockIndex* lower_block=nullptr) EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
229234

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

src/test/blockmanager_tests.cpp

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ BOOST_FIXTURE_TEST_CASE(blockmanager_scan_unlink_already_pruned_files, TestChain
9292

9393
BOOST_FIXTURE_TEST_CASE(blockmanager_block_data_availability, TestChain100Setup)
9494
{
95+
// The goal of the function is to return the first not pruned block in the range [upper_block, lower_block].
9596
LOCK(::cs_main);
9697
auto& chainman = m_node.chainman;
9798
auto& blockman = chainman->m_blockman;
@@ -110,15 +111,22 @@ BOOST_FIXTURE_TEST_CASE(blockmanager_block_data_availability, TestChain100Setup)
110111

111112
// 1) Return genesis block when all blocks are available
112113
BOOST_CHECK_EQUAL(blockman.GetFirstStoredBlock(tip), chainman->ActiveChain()[0]);
114+
BOOST_CHECK(blockman.CheckBlockDataAvailability(tip, *chainman->ActiveChain()[0]));
115+
116+
// 2) Check lower_block when all blocks are available
117+
CBlockIndex* lower_block = chainman->ActiveChain()[tip.nHeight / 2];
118+
BOOST_CHECK(blockman.CheckBlockDataAvailability(tip, *lower_block));
113119

114120
// Prune half of the blocks
115121
int height_to_prune = tip.nHeight / 2;
116122
CBlockIndex* first_available_block = chainman->ActiveChain()[height_to_prune + 1];
117123
CBlockIndex* last_pruned_block = first_available_block->pprev;
118124
func_prune_blocks(last_pruned_block);
119125

120-
// 2) The last block not pruned is in-between upper-block and the genesis block
126+
// 3) The last block not pruned is in-between upper-block and the genesis block
121127
BOOST_CHECK_EQUAL(blockman.GetFirstStoredBlock(tip), first_available_block);
128+
BOOST_CHECK(blockman.CheckBlockDataAvailability(tip, *first_available_block));
129+
BOOST_CHECK(!blockman.CheckBlockDataAvailability(tip, *last_pruned_block));
122130
}
123131

124132
BOOST_AUTO_TEST_SUITE_END()

0 commit comments

Comments
 (0)