Skip to content

Commit d5a631b

Browse files
mzumsanderyanofsky
andcommitted
validation: improve performance of CheckBlockIndex
by not saving all indexes in a std::multimap, but only those that are not part of the best header chain. The indexes of the best header chain are stored in a vector, which, in the typical case of a mostly linear chain with a few forks, results in a much smaller multimap, and increases performance noticeably for long chains. This does not change the actual consistency checks that are being performed for each index, just the way the block index tree is stored and traversed. Co-authored-by: Ryan Ofsky <ryan@ofsky.org>
1 parent 32c8041 commit d5a631b

File tree

1 file changed

+36
-14
lines changed

1 file changed

+36
-14
lines changed

src/validation.cpp

Lines changed: 36 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5050,19 +5050,28 @@ void ChainstateManager::CheckBlockIndex()
50505050
return;
50515051
}
50525052

5053-
// Build forward-pointing map of the entire block tree.
5053+
// Build forward-pointing data structure for the entire block tree.
5054+
// For performance reasons, indexes of the best header chain are stored in a vector (within CChain).
5055+
// All remaining blocks are stored in a multimap.
5056+
// The best header chain can differ from the active chain: E.g. its entries may belong to blocks that
5057+
// are not yet validated.
5058+
CChain best_hdr_chain;
5059+
assert(m_best_header);
5060+
best_hdr_chain.SetTip(*m_best_header);
5061+
50545062
std::multimap<CBlockIndex*,CBlockIndex*> forward;
50555063
for (auto& [_, block_index] : m_blockman.m_block_index) {
5056-
forward.emplace(block_index.pprev, &block_index);
5064+
// Only save indexes in forward that are not part of the best header chain.
5065+
if (!best_hdr_chain.Contains(&block_index)) {
5066+
// Only genesis, which must be part of the best header chain, can have a nullptr parent.
5067+
assert(block_index.pprev);
5068+
forward.emplace(block_index.pprev, &block_index);
5069+
}
50575070
}
5071+
assert(forward.size() + best_hdr_chain.Height() + 1 == m_blockman.m_block_index.size());
50585072

5059-
assert(forward.size() == m_blockman.m_block_index.size());
5060-
5061-
std::pair<std::multimap<CBlockIndex*,CBlockIndex*>::iterator,std::multimap<CBlockIndex*,CBlockIndex*>::iterator> rangeGenesis = forward.equal_range(nullptr);
5062-
CBlockIndex *pindex = rangeGenesis.first->second;
5063-
rangeGenesis.first++;
5064-
assert(rangeGenesis.first == rangeGenesis.second); // There is only one index entry with parent nullptr.
5065-
5073+
CBlockIndex* pindex = best_hdr_chain[0];
5074+
assert(pindex);
50665075
// Iterate over the entire block tree, using depth-first search.
50675076
// Along the way, remember whether there are blocks on the path from genesis
50685077
// block being explored which are the first to have certain properties.
@@ -5274,14 +5283,21 @@ void ChainstateManager::CheckBlockIndex()
52745283
// assert(pindex->GetBlockHash() == pindex->GetBlockHeader().GetHash()); // Perhaps too slow
52755284
// End: actual consistency checks.
52765285

5277-
// Try descending into the first subnode.
5286+
5287+
// Try descending into the first subnode. Always process forks first and the best header chain after.
52785288
snap_update_firsts();
52795289
std::pair<std::multimap<CBlockIndex*,CBlockIndex*>::iterator,std::multimap<CBlockIndex*,CBlockIndex*>::iterator> range = forward.equal_range(pindex);
52805290
if (range.first != range.second) {
5281-
// A subnode was found.
5291+
// A subnode not part of the best header chain was found.
52825292
pindex = range.first->second;
52835293
nHeight++;
52845294
continue;
5295+
} else if (best_hdr_chain.Contains(pindex)) {
5296+
// Descend further into best header chain.
5297+
nHeight++;
5298+
pindex = best_hdr_chain[nHeight];
5299+
if (!pindex) break; // we are finished, since the best header chain is always processed last
5300+
continue;
52855301
}
52865302
// This is a leaf node.
52875303
// Move upwards until we reach a node of which we have not yet visited the last child.
@@ -5307,9 +5323,15 @@ void ChainstateManager::CheckBlockIndex()
53075323
// Proceed to the next one.
53085324
rangePar.first++;
53095325
if (rangePar.first != rangePar.second) {
5310-
// Move to the sibling.
5326+
// Move to a sibling not part of the best header chain.
53115327
pindex = rangePar.first->second;
53125328
break;
5329+
} else if (pindexPar == best_hdr_chain[nHeight - 1]) {
5330+
// Move to pindex's sibling on the best-chain, if it has one.
5331+
pindex = best_hdr_chain[nHeight];
5332+
// There will not be a next block if (and only if) parent block is the best header.
5333+
assert((pindex == nullptr) == (pindexPar == best_hdr_chain.Tip()));
5334+
break;
53135335
} else {
53145336
// Move up further.
53155337
pindex = pindexPar;
@@ -5319,8 +5341,8 @@ void ChainstateManager::CheckBlockIndex()
53195341
}
53205342
}
53215343

5322-
// Check that we actually traversed the entire map.
5323-
assert(nNodes == forward.size());
5344+
// Check that we actually traversed the entire block index.
5345+
assert(nNodes == forward.size() + best_hdr_chain.Height() + 1);
53245346
}
53255347

53265348
std::string Chainstate::ToString()

0 commit comments

Comments
 (0)