Skip to content

Commit b402fd5

Browse files
committed
merge bitcoin#23174: have LoadBlockIndex account for snapshot use
1 parent a08f2f4 commit b402fd5

File tree

3 files changed

+157
-17
lines changed

3 files changed

+157
-17
lines changed

src/test/validation_chainstatemanager_tests.cpp

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,9 @@ BOOST_FIXTURE_TEST_CASE(chainstatemanager_activate_snapshot, TestChain100Setup)
249249
*chainman.ActiveChainstate().m_from_snapshot_blockhash,
250250
*chainman.SnapshotBlockhash());
251251

252+
// Ensure that the genesis block was not marked assumed-valid.
253+
BOOST_CHECK(!chainman.ActiveChain().Genesis()->IsAssumedValid());
254+
252255
const AssumeutxoData& au_data = *ExpectedAssumeutxo(snapshot_height, ::Params());
253256
const CBlockIndex* tip = chainman.ActiveTip();
254257

@@ -326,4 +329,81 @@ BOOST_FIXTURE_TEST_CASE(chainstatemanager_activate_snapshot, TestChain100Setup)
326329
loaded_snapshot_blockhash);
327330
}
328331

332+
//! Test LoadBlockIndex behavior when multiple chainstates are in use.
333+
//!
334+
//! - First, verfiy that setBlockIndexCandidates is as expected when using a single,
335+
//! fully-validating chainstate.
336+
//!
337+
//! - Then mark a region of the chain BLOCK_ASSUMED_VALID and introduce a second chainstate
338+
//! that will tolerate assumed-valid blocks. Run LoadBlockIndex() and ensure that the first
339+
//! chainstate only contains fully validated blocks and the other chainstate contains all blocks,
340+
//! even those assumed-valid.
341+
//!
342+
BOOST_FIXTURE_TEST_CASE(chainstatemanager_loadblockindex, TestChain100Setup)
343+
{
344+
ChainstateManager& chainman = *Assert(m_node.chainman);
345+
CTxMemPool& mempool = *m_node.mempool;
346+
CChainState& cs1 = chainman.ActiveChainstate();
347+
348+
int num_indexes{0};
349+
int num_assumed_valid{0};
350+
const int expected_assumed_valid{20};
351+
const int last_assumed_valid_idx{40};
352+
const int assumed_valid_start_idx = last_assumed_valid_idx - expected_assumed_valid;
353+
354+
CBlockIndex* validated_tip{nullptr};
355+
CBlockIndex* assumed_tip{chainman.ActiveChain().Tip()};
356+
357+
auto reload_all_block_indexes = [&]() {
358+
for (CChainState* cs : chainman.GetAll()) {
359+
LOCK(::cs_main);
360+
cs->UnloadBlockIndex();
361+
BOOST_CHECK(cs->setBlockIndexCandidates.empty());
362+
}
363+
364+
WITH_LOCK(::cs_main, chainman.LoadBlockIndex());
365+
};
366+
367+
// Ensure that without any assumed-valid BlockIndex entries, all entries are considered
368+
// tip candidates.
369+
reload_all_block_indexes();
370+
BOOST_CHECK_EQUAL(cs1.setBlockIndexCandidates.size(), cs1.m_chain.Height() + 1);
371+
372+
// Mark some region of the chain assumed-valid.
373+
for (int i = 0; i <= cs1.m_chain.Height(); ++i) {
374+
auto index = cs1.m_chain[i];
375+
376+
if (i < last_assumed_valid_idx && i >= assumed_valid_start_idx) {
377+
index->nStatus = BlockStatus::BLOCK_VALID_TREE | BlockStatus::BLOCK_ASSUMED_VALID;
378+
}
379+
380+
++num_indexes;
381+
if (index->IsAssumedValid()) ++num_assumed_valid;
382+
383+
// Note the last fully-validated block as the expected validated tip.
384+
if (i == (assumed_valid_start_idx - 1)) {
385+
validated_tip = index;
386+
BOOST_CHECK(!index->IsAssumedValid());
387+
}
388+
}
389+
390+
BOOST_CHECK_EQUAL(expected_assumed_valid, num_assumed_valid);
391+
392+
CChainState& cs2 = WITH_LOCK(::cs_main,
393+
return chainman.InitializeChainstate(&mempool, *m_node.evodb, m_node.chain_helper, llmq::chainLocksHandler, llmq::quorumInstantSendManager, GetRandHash()));
394+
395+
reload_all_block_indexes();
396+
397+
// The fully validated chain only has candidates up to the start of the assumed-valid
398+
// blocks.
399+
BOOST_CHECK_EQUAL(cs1.setBlockIndexCandidates.count(validated_tip), 1);
400+
BOOST_CHECK_EQUAL(cs1.setBlockIndexCandidates.count(assumed_tip), 0);
401+
BOOST_CHECK_EQUAL(cs1.setBlockIndexCandidates.size(), assumed_valid_start_idx);
402+
403+
// The assumed-valid tolerant chain has all blocks as candidates.
404+
BOOST_CHECK_EQUAL(cs2.setBlockIndexCandidates.count(validated_tip), 1);
405+
BOOST_CHECK_EQUAL(cs2.setBlockIndexCandidates.count(assumed_tip), 1);
406+
BOOST_CHECK_EQUAL(cs2.setBlockIndexCandidates.size(), num_indexes);
407+
}
408+
329409
BOOST_AUTO_TEST_SUITE_END()

src/validation.cpp

Lines changed: 70 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@
6767

6868
#include <statsd_client.h>
6969

70+
#include <algorithm>
7071
#include <deque>
7172
#include <numeric>
7273
#include <optional>
@@ -4245,7 +4246,7 @@ CBlockIndex * BlockManager::InsertBlockIndex(const uint256& hash)
42454246

42464247
bool BlockManager::LoadBlockIndex(
42474248
const Consensus::Params& consensus_params,
4248-
std::set<CBlockIndex*, CBlockIndexWorkComparator>& block_index_candidates)
4249+
ChainstateManager& chainman)
42494250
{
42504251
if (!m_block_tree_db->LoadBlockIndexGuts(consensus_params, [this](const uint256& hash) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { return this->InsertBlockIndex(hash); })) {
42514252
return false;
@@ -4265,17 +4266,41 @@ bool BlockManager::LoadBlockIndex(
42654266
}
42664267
}
42674268
sort(vSortedByHeight.begin(), vSortedByHeight.end());
4269+
4270+
// Find start of assumed-valid region.
4271+
int first_assumed_valid_height = std::numeric_limits<int>::max();
4272+
4273+
for (const auto& [height, block] : vSortedByHeight) {
4274+
if (block->IsAssumedValid()) {
4275+
auto chainstates = chainman.GetAll();
4276+
4277+
// If we encounter an assumed-valid block index entry, ensure that we have
4278+
// one chainstate that tolerates assumed-valid entries and another that does
4279+
// not (i.e. the background validation chainstate), since assumed-valid
4280+
// entries should always be pending validation by a fully-validated chainstate.
4281+
auto any_chain = [&](auto fnc) { return std::any_of(chainstates.cbegin(), chainstates.cend(), fnc); };
4282+
assert(any_chain([](auto chainstate) { return chainstate->reliesOnAssumedValid(); }));
4283+
assert(any_chain([](auto chainstate) { return !chainstate->reliesOnAssumedValid(); }));
4284+
4285+
first_assumed_valid_height = height;
4286+
break;
4287+
}
4288+
}
4289+
42684290
for (const std::pair<int, CBlockIndex*>& item : vSortedByHeight)
42694291
{
42704292
if (ShutdownRequested()) return false;
42714293
CBlockIndex* pindex = item.second;
42724294
pindex->nChainWork = (pindex->pprev ? pindex->pprev->nChainWork : 0) + GetBlockProof(*pindex);
42734295
pindex->nTimeMax = (pindex->pprev ? std::max(pindex->pprev->nTimeMax, pindex->nTime) : pindex->nTime);
4274-
// We can link the chain of blocks for which we've received transactions at some point.
4296+
4297+
// We can link the chain of blocks for which we've received transactions at some point, or
4298+
// blocks that are assumed-valid on the basis of snapshot load (see
4299+
// PopulateAndValidateSnapshot()).
42754300
// Pruned nodes may have deleted the block.
42764301
if (pindex->nTx > 0) {
42774302
if (pindex->pprev) {
4278-
if (pindex->pprev->HaveTxsDownloaded()) {
4303+
if (pindex->pprev->nChainTx > 0) {
42794304
pindex->nChainTx = pindex->pprev->nChainTx + pindex->nTx;
42804305
} else {
42814306
pindex->nChainTx = 0;
@@ -4292,7 +4317,36 @@ bool BlockManager::LoadBlockIndex(
42924317
if (pindex->IsAssumedValid() ||
42934318
(pindex->IsValid(BLOCK_VALID_TRANSACTIONS) &&
42944319
(pindex->HaveTxsDownloaded() || pindex->pprev == nullptr))) {
4295-
block_index_candidates.insert(pindex);
4320+
4321+
// Fill each chainstate's block candidate set. Only add assumed-valid
4322+
// blocks to the tip candidate set if the chainstate is allowed to rely on
4323+
// assumed-valid blocks.
4324+
//
4325+
// If all setBlockIndexCandidates contained the assumed-valid blocks, the
4326+
// background chainstate's ActivateBestChain() call would add assumed-valid
4327+
// blocks to the chain (based on how FindMostWorkChain() works). Obviously
4328+
// we don't want this since the purpose of the background validation chain
4329+
// is to validate assued-valid blocks.
4330+
//
4331+
// Note: This is considering all blocks whose height is greater or equal to
4332+
// the first assumed-valid block to be assumed-valid blocks, and excluding
4333+
// them from the background chainstate's setBlockIndexCandidates set. This
4334+
// does mean that some blocks which are not technically assumed-valid
4335+
// (later blocks on a fork beginning before the first assumed-valid block)
4336+
// might not get added to the the background chainstate, but this is ok,
4337+
// because they will still be attached to the active chainstate if they
4338+
// actually contain more work.
4339+
//
4340+
// Instad of this height-based approach, an earlier attempt was made at
4341+
// detecting "holistically" whether the block index under consideration
4342+
// relied on an assumed-valid ancestor, but this proved to be too slow to
4343+
// be practical.
4344+
for (CChainState* chainstate : chainman.GetAll()) {
4345+
if (chainstate->reliesOnAssumedValid() ||
4346+
pindex->nHeight < first_assumed_valid_height) {
4347+
chainstate->setBlockIndexCandidates.insert(pindex);
4348+
}
4349+
}
42964350
}
42974351
if (pindex->nStatus & BLOCK_FAILED_MASK && (!pindexBestInvalid || pindex->nChainWork > pindexBestInvalid->nChainWork))
42984352
pindexBestInvalid = pindex;
@@ -4317,11 +4371,9 @@ void BlockManager::Unload() {
43174371
m_prev_block_index.clear();
43184372
}
43194373

4320-
bool BlockManager::LoadBlockIndexDB(std::set<CBlockIndex*, CBlockIndexWorkComparator>& setBlockIndexCandidates)
4374+
bool BlockManager::LoadBlockIndexDB(ChainstateManager& chainman)
43214375
{
4322-
if (!LoadBlockIndex(
4323-
::Params().GetConsensus(),
4324-
setBlockIndexCandidates)) {
4376+
if (!LoadBlockIndex(::Params().GetConsensus(), chainman)) {
43254377
return false;
43264378
}
43274379

@@ -4758,7 +4810,7 @@ bool ChainstateManager::LoadBlockIndex()
47584810
// Load block index from databases
47594811
bool needs_init = fReindex;
47604812
if (!fReindex) {
4761-
bool ret = m_blockman.LoadBlockIndexDB(ActiveChainstate().setBlockIndexCandidates);
4813+
bool ret = m_blockman.LoadBlockIndexDB(*this);
47624814
if (!ret) return false;
47634815
needs_init = m_blockman.m_block_index.empty();
47644816
}
@@ -5694,7 +5746,14 @@ bool ChainstateManager::PopulateAndValidateSnapshot(
56945746

56955747
// Fake various pieces of CBlockIndex state:
56965748
CBlockIndex* index = nullptr;
5697-
for (int i = 0; i <= snapshot_chainstate.m_chain.Height(); ++i) {
5749+
5750+
// Don't make any modifications to the genesis block.
5751+
// This is especially important because we don't want to erroneously
5752+
// apply BLOCK_ASSUMED_VALID to genesis, which would happen if we didn't skip
5753+
// it here (since it apparently isn't BLOCK_VALID_SCRIPTS).
5754+
constexpr int AFTER_GENESIS_START{1};
5755+
5756+
for (int i = AFTER_GENESIS_START; i <= snapshot_chainstate.m_chain.Height(); ++i) {
56985757
index = snapshot_chainstate.m_chain[i];
56995758

57005759
// Fake nTx so that LoadBlockIndex() loads assumed-valid CBlockIndex
@@ -5703,7 +5762,7 @@ bool ChainstateManager::PopulateAndValidateSnapshot(
57035762
index->nTx = 1;
57045763
}
57055764
// Fake nChainTx so that GuessVerificationProgress reports accurately
5706-
index->nChainTx = index->pprev ? index->pprev->nChainTx + index->nTx : 1;
5765+
index->nChainTx = index->pprev->nChainTx + index->nTx;
57075766

57085767
// Mark unvalidated block index entries beneath the snapshot base block as assumed-valid.
57095768
if (!index->IsValid(BLOCK_VALID_SCRIPTS)) {

src/validation.h

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -471,20 +471,17 @@ class BlockManager
471471

472472
std::unique_ptr<CBlockTreeDB> m_block_tree_db GUARDED_BY(::cs_main);
473473

474-
bool LoadBlockIndexDB(std::set<CBlockIndex*, CBlockIndexWorkComparator>& setBlockIndexCandidates) EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
474+
bool LoadBlockIndexDB(ChainstateManager& chainman) EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
475475

476476
/**
477477
* Load the blocktree off disk and into memory. Populate certain metadata
478478
* per index entry (nStatus, nChainWork, nTimeMax, etc.) as well as peripheral
479479
* collections like setDirtyBlockIndex.
480-
*
481-
* @param[out] block_index_candidates Fill this set with any valid blocks for
482-
* which we've downloaded all transactions.
483480
*/
484481
bool LoadBlockIndex(
485482
const Consensus::Params& consensus_params,
486-
std::set<CBlockIndex*, CBlockIndexWorkComparator>& block_index_candidates)
487-
EXCLUSIVE_LOCKS_REQUIRED(cs_main);
483+
ChainstateManager& chainman) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
484+
488485
/** Clear all data members. */
489486
void Unload() EXCLUSIVE_LOCKS_REQUIRED(cs_main);
490487

@@ -678,6 +675,10 @@ class CChainState
678675
*/
679676
const std::optional<uint256> m_from_snapshot_blockhash;
680677

678+
//! Return true if this chainstate relies on blocks that are assumed-valid. In
679+
//! practice this means it was created based on a UTXO snapshot.
680+
bool reliesOnAssumedValid() { return m_from_snapshot_blockhash.has_value(); }
681+
681682
/**
682683
* The set of all CBlockIndex entries with either BLOCK_VALID_TRANSACTIONS (for
683684
* itself and all ancestors) *or* BLOCK_ASSUMED_VALID (if using background

0 commit comments

Comments
 (0)