Skip to content

Commit 891e4bf

Browse files
committed
Merge bitcoin/bitcoin#28339: validation: improve performance of CheckBlockIndex
5bc2077 validation: allow to specify frequency for -checkblockindex (Martin Zumsande) d5a631b validation: improve performance of CheckBlockIndex (Martin Zumsande) 32c8041 bench: add benchmark for checkblockindex (Martin Zumsande) Pull request description: `CheckBlockIndex() ` are consistency checks that are currently enabled by default on regtest. The function is rather slow, which is annoying if you * attempt to run it on other networks, especially if not fully synced * want to generate a long chain on regtest and see block generation slow down because you forgot to disable `-checkblockindex` or don't know it existed. One reason why it's slow is that in order to be able to traverse the block tree depth-first from genesis, it inserts pointers to all block indices into a `std::multimap` - for which inserts and lookups become slow once there are hundred thousands of entries. However, typically the block index is mostly chain-like with just a few forks so a multimap isn't really needed for the most part. This PR suggests to store the block indices of the chain ending in the best header in a vector instead, and store only the rest of the indices in a multimap. 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. This adds a bit of complication to make sure each block is visited (note that there are asserts that check it), making sure that the two containers are traversed correctly, but it speeds up the function considerably: On master, a single invocation of `CheckBlockIndex` takes ~1.4s on mainnet for me (4.9s on testnet which has >2.4 million blocks). With this branch, the runtime goes down to ~0.27s (0.85s on testnet).This is a speedup by a factor ~5. ACKs for top commit: achow101: ACK 5bc2077 furszy: ACK 5bc2077 ryanofsky: Code review ACK 5bc2077. Just added suggested assert and simplification since last review Tree-SHA512: 6b9c3e3e5069d6152b45a09040f962380d114851ff0f9ff1771cf8cad7bb4fa0ba25cd787ceaa3dfa5241fb249748e2ee6987af0ccb24b786a5301b2836f8487
2 parents 1bcc91a + 5bc2077 commit 891e4bf

File tree

8 files changed

+73
-19
lines changed

8 files changed

+73
-19
lines changed

src/Makefile.bench.include

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ bench_bench_bitcoin_SOURCES = \
2323
bench/ccoins_caching.cpp \
2424
bench/chacha20.cpp \
2525
bench/checkblock.cpp \
26+
bench/checkblockindex.cpp \
2627
bench/checkqueue.cpp \
2728
bench/crypto_hash.cpp \
2829
bench/data.cpp \

src/bench/checkblockindex.cpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Copyright (c) 2023-present The Bitcoin Core developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
#include <bench/bench.h>
6+
#include <test/util/setup_common.h>
7+
#include <validation.h>
8+
9+
static void CheckBlockIndex(benchmark::Bench& bench)
10+
{
11+
auto testing_setup{MakeNoLogFileContext<TestChain100Setup>()};
12+
// Mine some more blocks
13+
testing_setup->mineBlocks(1000);
14+
bench.run([&] {
15+
testing_setup->m_node.chainman->CheckBlockIndex();
16+
});
17+
}
18+
19+
20+
BENCHMARK(CheckBlockIndex, benchmark::PriorityLevel::HIGH);

src/init.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -598,7 +598,7 @@ void SetupServerArgs(ArgsManager& argsman)
598598

599599
argsman.AddArg("-checkblocks=<n>", strprintf("How many blocks to check at startup (default: %u, 0 = all)", DEFAULT_CHECKBLOCKS), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST);
600600
argsman.AddArg("-checklevel=<n>", strprintf("How thorough the block verification of -checkblocks is: %s (0-4, default: %u)", Join(CHECKLEVEL_DOC, ", "), DEFAULT_CHECKLEVEL), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST);
601-
argsman.AddArg("-checkblockindex", strprintf("Do a consistency check for the block tree, chainstate, and other validation data structures occasionally. (default: %u, regtest: %u)", defaultChainParams->DefaultConsistencyChecks(), regtestChainParams->DefaultConsistencyChecks()), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST);
601+
argsman.AddArg("-checkblockindex", strprintf("Do a consistency check for the block tree, chainstate, and other validation data structures every <n> operations. Use 0 to disable. (default: %u, regtest: %u)", defaultChainParams->DefaultConsistencyChecks(), regtestChainParams->DefaultConsistencyChecks()), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST);
602602
argsman.AddArg("-checkaddrman=<n>", strprintf("Run addrman consistency checks every <n> operations. Use 0 to disable. (default: %u)", DEFAULT_ADDRMAN_CONSISTENCY_CHECKS), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST);
603603
argsman.AddArg("-checkmempool=<n>", strprintf("Run mempool consistency checks every <n> transactions. Use 0 to disable. (default: %u, regtest: %u)", defaultChainParams->DefaultConsistencyChecks(), regtestChainParams->DefaultConsistencyChecks()), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST);
604604
argsman.AddArg("-checkpoints", strprintf("Enable rejection of any forks from the known historical chain until block %s (default: %u)", defaultChainParams->Checkpoints().GetHeight(), DEFAULT_CHECKPOINTS_ENABLED), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST);

src/kernel/chainstatemanager_opts.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ namespace kernel {
3333
struct ChainstateManagerOpts {
3434
const CChainParams& chainparams;
3535
fs::path datadir;
36-
std::optional<bool> check_block_index{};
36+
std::optional<int32_t> check_block_index{};
3737
bool checkpoints_enabled{DEFAULT_CHECKPOINTS_ENABLED};
3838
//! If set, it will override the minimum work we will assume exists on some valid chain.
3939
std::optional<arith_uint256> minimum_chain_work{};

src/node/chainstatemanager_args.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,10 @@
2424
namespace node {
2525
util::Result<void> ApplyArgsManOptions(const ArgsManager& args, ChainstateManager::Options& opts)
2626
{
27-
if (auto value{args.GetBoolArg("-checkblockindex")}) opts.check_block_index = *value;
27+
if (auto value{args.GetIntArg("-checkblockindex")}) {
28+
// Interpret bare -checkblockindex argument as 1 instead of 0.
29+
opts.check_block_index = args.GetArg("-checkblockindex")->empty() ? 1 : *value;
30+
}
2831

2932
if (auto value{args.GetBoolArg("-checkpoints")}) opts.checkpoints_enabled = *value;
3033

src/test/util/setup_common.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ ChainTestingSetup::ChainTestingSetup(const ChainType chainType, const std::vecto
238238
const ChainstateManager::Options chainman_opts{
239239
.chainparams = chainparams,
240240
.datadir = m_args.GetDataDirNet(),
241-
.check_block_index = true,
241+
.check_block_index = 1,
242242
.notifications = *m_node.notifications,
243243
.signals = m_node.validation_signals.get(),
244244
.worker_threads_num = 2,

src/validation.cpp

Lines changed: 44 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5092,6 +5092,14 @@ void ChainstateManager::LoadExternalBlockFile(
50925092
LogPrintf("Loaded %i blocks from external file in %dms\n", nLoaded, Ticks<std::chrono::milliseconds>(SteadyClock::now() - start));
50935093
}
50945094

5095+
bool ChainstateManager::ShouldCheckBlockIndex() const
5096+
{
5097+
// Assert to verify Flatten() has been called.
5098+
if (!*Assert(m_options.check_block_index)) return false;
5099+
if (GetRand(*m_options.check_block_index) >= 1) return false;
5100+
return true;
5101+
}
5102+
50955103
void ChainstateManager::CheckBlockIndex()
50965104
{
50975105
if (!ShouldCheckBlockIndex()) {
@@ -5108,19 +5116,28 @@ void ChainstateManager::CheckBlockIndex()
51085116
return;
51095117
}
51105118

5111-
// Build forward-pointing map of the entire block tree.
5119+
// Build forward-pointing data structure for the entire block tree.
5120+
// For performance reasons, indexes of the best header chain are stored in a vector (within CChain).
5121+
// All remaining blocks are stored in a multimap.
5122+
// The best header chain can differ from the active chain: E.g. its entries may belong to blocks that
5123+
// are not yet validated.
5124+
CChain best_hdr_chain;
5125+
assert(m_best_header);
5126+
best_hdr_chain.SetTip(*m_best_header);
5127+
51125128
std::multimap<CBlockIndex*,CBlockIndex*> forward;
51135129
for (auto& [_, block_index] : m_blockman.m_block_index) {
5114-
forward.emplace(block_index.pprev, &block_index);
5130+
// Only save indexes in forward that are not part of the best header chain.
5131+
if (!best_hdr_chain.Contains(&block_index)) {
5132+
// Only genesis, which must be part of the best header chain, can have a nullptr parent.
5133+
assert(block_index.pprev);
5134+
forward.emplace(block_index.pprev, &block_index);
5135+
}
51155136
}
5137+
assert(forward.size() + best_hdr_chain.Height() + 1 == m_blockman.m_block_index.size());
51165138

5117-
assert(forward.size() == m_blockman.m_block_index.size());
5118-
5119-
std::pair<std::multimap<CBlockIndex*,CBlockIndex*>::iterator,std::multimap<CBlockIndex*,CBlockIndex*>::iterator> rangeGenesis = forward.equal_range(nullptr);
5120-
CBlockIndex *pindex = rangeGenesis.first->second;
5121-
rangeGenesis.first++;
5122-
assert(rangeGenesis.first == rangeGenesis.second); // There is only one index entry with parent nullptr.
5123-
5139+
CBlockIndex* pindex = best_hdr_chain[0];
5140+
assert(pindex);
51245141
// Iterate over the entire block tree, using depth-first search.
51255142
// Along the way, remember whether there are blocks on the path from genesis
51265143
// block being explored which are the first to have certain properties.
@@ -5332,14 +5349,21 @@ void ChainstateManager::CheckBlockIndex()
53325349
// assert(pindex->GetBlockHash() == pindex->GetBlockHeader().GetHash()); // Perhaps too slow
53335350
// End: actual consistency checks.
53345351

5335-
// Try descending into the first subnode.
5352+
5353+
// Try descending into the first subnode. Always process forks first and the best header chain after.
53365354
snap_update_firsts();
53375355
std::pair<std::multimap<CBlockIndex*,CBlockIndex*>::iterator,std::multimap<CBlockIndex*,CBlockIndex*>::iterator> range = forward.equal_range(pindex);
53385356
if (range.first != range.second) {
5339-
// A subnode was found.
5357+
// A subnode not part of the best header chain was found.
53405358
pindex = range.first->second;
53415359
nHeight++;
53425360
continue;
5361+
} else if (best_hdr_chain.Contains(pindex)) {
5362+
// Descend further into best header chain.
5363+
nHeight++;
5364+
pindex = best_hdr_chain[nHeight];
5365+
if (!pindex) break; // we are finished, since the best header chain is always processed last
5366+
continue;
53435367
}
53445368
// This is a leaf node.
53455369
// Move upwards until we reach a node of which we have not yet visited the last child.
@@ -5365,9 +5389,15 @@ void ChainstateManager::CheckBlockIndex()
53655389
// Proceed to the next one.
53665390
rangePar.first++;
53675391
if (rangePar.first != rangePar.second) {
5368-
// Move to the sibling.
5392+
// Move to a sibling not part of the best header chain.
53695393
pindex = rangePar.first->second;
53705394
break;
5395+
} else if (pindexPar == best_hdr_chain[nHeight - 1]) {
5396+
// Move to pindex's sibling on the best-chain, if it has one.
5397+
pindex = best_hdr_chain[nHeight];
5398+
// There will not be a next block if (and only if) parent block is the best header.
5399+
assert((pindex == nullptr) == (pindexPar == best_hdr_chain.Tip()));
5400+
break;
53715401
} else {
53725402
// Move up further.
53735403
pindex = pindexPar;
@@ -5377,8 +5407,8 @@ void ChainstateManager::CheckBlockIndex()
53775407
}
53785408
}
53795409

5380-
// Check that we actually traversed the entire map.
5381-
assert(nNodes == forward.size());
5410+
// Check that we actually traversed the entire block index.
5411+
assert(nNodes == forward.size() + best_hdr_chain.Height() + 1);
53825412
}
53835413

53845414
std::string Chainstate::ToString()

src/validation.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -938,7 +938,7 @@ class ChainstateManager
938938

939939
const CChainParams& GetParams() const { return m_options.chainparams; }
940940
const Consensus::Params& GetConsensus() const { return m_options.chainparams.GetConsensus(); }
941-
bool ShouldCheckBlockIndex() const { return *Assert(m_options.check_block_index); }
941+
bool ShouldCheckBlockIndex() const;
942942
const arith_uint256& MinimumChainWork() const { return *Assert(m_options.minimum_chain_work); }
943943
const uint256& AssumedValidBlock() const { return *Assert(m_options.assumed_valid_block); }
944944
kernel::Notifications& GetNotifications() const { return m_options.notifications; };

0 commit comments

Comments
 (0)