Skip to content

Commit b67115d

Browse files
author
MarcoFalke
committed
Merge bitcoin/bitcoin#23174: validation: have LoadBlockIndex account for snapshot use
2283b9c test: add tests for LoadBlockIndex when using multiple chainstates (James O'Beirne) 0fd599a validation: have LoadBlockIndex account for snapshot use (James O'Beirne) d0c6e61 validation: don't modify genesis during snapshot load (James O'Beirne) Pull request description: This is part of the [assumeutxo project](https://github.com/bitcoin/bitcoin/projects/11) (parent PR: #15606) --- Currently, `BlockManager::LoadBlockIndex` adds all blocks that have downloaded transactions to the active chain state's `setBlockIndexCandidates` set, ignoring the background chain state. This PR changes ChainstateManager::LoadBlockIndex to update `setBlockIndexCandidates` in the background chain, not just the active chain. In the active chain, the same blocks are added as before. In the background chain, only blocks that have actually been validated, not blocks marked assumed-valid are added so the background chain will continue to download and validate assumed-valid blocks. ACKs for top commit: MarcoFalke: Concept ACK 2283b9c 🤽 Sjors: utACK 2283b9c Tree-SHA512: 7c9a80802df4722d85d12b78d2e7f628ac5f11cb8be66913d5c3230339bd1220c6723805509d4460826a17d1dc04b0ae172eb7d09ac0ea5dc5e41d77975cbd5e
2 parents 7006496 + 2283b9c commit b67115d

File tree

3 files changed

+157
-18
lines changed

3 files changed

+157
-18
lines changed

src/test/validation_chainstatemanager_tests.cpp

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,9 @@ BOOST_FIXTURE_TEST_CASE(chainstatemanager_activate_snapshot, TestChain100Setup)
232232
*chainman.ActiveChainstate().m_from_snapshot_blockhash,
233233
*chainman.SnapshotBlockhash());
234234

235+
// Ensure that the genesis block was not marked assumed-valid.
236+
BOOST_CHECK(!chainman.ActiveChain().Genesis()->IsAssumedValid());
237+
235238
const AssumeutxoData& au_data = *ExpectedAssumeutxo(snapshot_height, ::Params());
236239
const CBlockIndex* tip = chainman.ActiveTip();
237240

@@ -309,4 +312,81 @@ BOOST_FIXTURE_TEST_CASE(chainstatemanager_activate_snapshot, TestChain100Setup)
309312
loaded_snapshot_blockhash);
310313
}
311314

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

src/validation.cpp

Lines changed: 71 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
#include <validationinterface.h>
5656
#include <warnings.h>
5757

58+
#include <algorithm>
5859
#include <numeric>
5960
#include <optional>
6061
#include <string>
@@ -3694,7 +3695,7 @@ CBlockIndex * BlockManager::InsertBlockIndex(const uint256& hash)
36943695

36953696
bool BlockManager::LoadBlockIndex(
36963697
const Consensus::Params& consensus_params,
3697-
std::set<CBlockIndex*, CBlockIndexWorkComparator>& block_index_candidates)
3698+
ChainstateManager& chainman)
36983699
{
36993700
if (!m_block_tree_db->LoadBlockIndexGuts(consensus_params, [this](const uint256& hash) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { return this->InsertBlockIndex(hash); })) {
37003701
return false;
@@ -3709,17 +3710,41 @@ bool BlockManager::LoadBlockIndex(
37093710
vSortedByHeight.push_back(std::make_pair(pindex->nHeight, pindex));
37103711
}
37113712
sort(vSortedByHeight.begin(), vSortedByHeight.end());
3713+
3714+
// Find start of assumed-valid region.
3715+
int first_assumed_valid_height = std::numeric_limits<int>::max();
3716+
3717+
for (const auto& [height, block] : vSortedByHeight) {
3718+
if (block->IsAssumedValid()) {
3719+
auto chainstates = chainman.GetAll();
3720+
3721+
// If we encounter an assumed-valid block index entry, ensure that we have
3722+
// one chainstate that tolerates assumed-valid entries and another that does
3723+
// not (i.e. the background validation chainstate), since assumed-valid
3724+
// entries should always be pending validation by a fully-validated chainstate.
3725+
auto any_chain = [&](auto fnc) { return std::any_of(chainstates.cbegin(), chainstates.cend(), fnc); };
3726+
assert(any_chain([](auto chainstate) { return chainstate->reliesOnAssumedValid(); }));
3727+
assert(any_chain([](auto chainstate) { return !chainstate->reliesOnAssumedValid(); }));
3728+
3729+
first_assumed_valid_height = height;
3730+
break;
3731+
}
3732+
}
3733+
37123734
for (const std::pair<int, CBlockIndex*>& item : vSortedByHeight)
37133735
{
37143736
if (ShutdownRequested()) return false;
37153737
CBlockIndex* pindex = item.second;
37163738
pindex->nChainWork = (pindex->pprev ? pindex->pprev->nChainWork : 0) + GetBlockProof(*pindex);
37173739
pindex->nTimeMax = (pindex->pprev ? std::max(pindex->pprev->nTimeMax, pindex->nTime) : pindex->nTime);
3718-
// We can link the chain of blocks for which we've received transactions at some point.
3740+
3741+
// We can link the chain of blocks for which we've received transactions at some point, or
3742+
// blocks that are assumed-valid on the basis of snapshot load (see
3743+
// PopulateAndValidateSnapshot()).
37193744
// Pruned nodes may have deleted the block.
37203745
if (pindex->nTx > 0) {
37213746
if (pindex->pprev) {
3722-
if (pindex->pprev->HaveTxsDownloaded()) {
3747+
if (pindex->pprev->nChainTx > 0) {
37233748
pindex->nChainTx = pindex->pprev->nChainTx + pindex->nTx;
37243749
} else {
37253750
pindex->nChainTx = 0;
@@ -3736,7 +3761,36 @@ bool BlockManager::LoadBlockIndex(
37363761
if (pindex->IsAssumedValid() ||
37373762
(pindex->IsValid(BLOCK_VALID_TRANSACTIONS) &&
37383763
(pindex->HaveTxsDownloaded() || pindex->pprev == nullptr))) {
3739-
block_index_candidates.insert(pindex);
3764+
3765+
// Fill each chainstate's block candidate set. Only add assumed-valid
3766+
// blocks to the tip candidate set if the chainstate is allowed to rely on
3767+
// assumed-valid blocks.
3768+
//
3769+
// If all setBlockIndexCandidates contained the assumed-valid blocks, the
3770+
// background chainstate's ActivateBestChain() call would add assumed-valid
3771+
// blocks to the chain (based on how FindMostWorkChain() works). Obviously
3772+
// we don't want this since the purpose of the background validation chain
3773+
// is to validate assued-valid blocks.
3774+
//
3775+
// Note: This is considering all blocks whose height is greater or equal to
3776+
// the first assumed-valid block to be assumed-valid blocks, and excluding
3777+
// them from the background chainstate's setBlockIndexCandidates set. This
3778+
// does mean that some blocks which are not technically assumed-valid
3779+
// (later blocks on a fork beginning before the first assumed-valid block)
3780+
// might not get added to the the background chainstate, but this is ok,
3781+
// because they will still be attached to the active chainstate if they
3782+
// actually contain more work.
3783+
//
3784+
// Instad of this height-based approach, an earlier attempt was made at
3785+
// detecting "holistically" whether the block index under consideration
3786+
// relied on an assumed-valid ancestor, but this proved to be too slow to
3787+
// be practical.
3788+
for (CChainState* chainstate : chainman.GetAll()) {
3789+
if (chainstate->reliesOnAssumedValid() ||
3790+
pindex->nHeight < first_assumed_valid_height) {
3791+
chainstate->setBlockIndexCandidates.insert(pindex);
3792+
}
3793+
}
37403794
}
37413795
if (pindex->nStatus & BLOCK_FAILED_MASK && (!pindexBestInvalid || pindex->nChainWork > pindexBestInvalid->nChainWork))
37423796
pindexBestInvalid = pindex;
@@ -3760,11 +3814,9 @@ void BlockManager::Unload() {
37603814
m_block_index.clear();
37613815
}
37623816

3763-
bool BlockManager::LoadBlockIndexDB(std::set<CBlockIndex*, CBlockIndexWorkComparator>& setBlockIndexCandidates)
3817+
bool BlockManager::LoadBlockIndexDB(ChainstateManager& chainman)
37643818
{
3765-
if (!LoadBlockIndex(
3766-
::Params().GetConsensus(),
3767-
setBlockIndexCandidates)) {
3819+
if (!LoadBlockIndex(::Params().GetConsensus(), chainman)) {
37683820
return false;
37693821
}
37703822

@@ -4110,7 +4162,7 @@ bool ChainstateManager::LoadBlockIndex()
41104162
// Load block index from databases
41114163
bool needs_init = fReindex;
41124164
if (!fReindex) {
4113-
bool ret = m_blockman.LoadBlockIndexDB(ActiveChainstate().setBlockIndexCandidates);
4165+
bool ret = m_blockman.LoadBlockIndexDB(*this);
41144166
if (!ret) return false;
41154167
needs_init = m_blockman.m_block_index.empty();
41164168
}
@@ -4999,7 +5051,14 @@ bool ChainstateManager::PopulateAndValidateSnapshot(
49995051

50005052
// Fake various pieces of CBlockIndex state:
50015053
CBlockIndex* index = nullptr;
5002-
for (int i = 0; i <= snapshot_chainstate.m_chain.Height(); ++i) {
5054+
5055+
// Don't make any modifications to the genesis block.
5056+
// This is especially important because we don't want to erroneously
5057+
// apply BLOCK_ASSUMED_VALID to genesis, which would happen if we didn't skip
5058+
// it here (since it apparently isn't BLOCK_VALID_SCRIPTS).
5059+
constexpr int AFTER_GENESIS_START{1};
5060+
5061+
for (int i = AFTER_GENESIS_START; i <= snapshot_chainstate.m_chain.Height(); ++i) {
50035062
index = snapshot_chainstate.m_chain[i];
50045063

50055064
// Fake nTx so that LoadBlockIndex() loads assumed-valid CBlockIndex
@@ -5008,7 +5067,7 @@ bool ChainstateManager::PopulateAndValidateSnapshot(
50085067
index->nTx = 1;
50095068
}
50105069
// Fake nChainTx so that GuessVerificationProgress reports accurately
5011-
index->nChainTx = index->pprev ? index->pprev->nChainTx + index->nTx : 1;
5070+
index->nChainTx = index->pprev->nChainTx + index->nTx;
50125071

50135072
// Mark unvalidated block index entries beneath the snapshot base block as assumed-valid.
50145073
if (!index->IsValid(BLOCK_VALID_SCRIPTS)) {
@@ -5019,7 +5078,7 @@ bool ChainstateManager::PopulateAndValidateSnapshot(
50195078

50205079
// Fake BLOCK_OPT_WITNESS so that CChainState::NeedsRedownload()
50215080
// won't ask to rewind the entire assumed-valid chain on startup.
5022-
if (index->pprev && DeploymentActiveAt(*index, ::Params().GetConsensus(), Consensus::DEPLOYMENT_SEGWIT)) {
5081+
if (DeploymentActiveAt(*index, ::Params().GetConsensus(), Consensus::DEPLOYMENT_SEGWIT)) {
50235082
index->nStatus |= BLOCK_OPT_WITNESS;
50245083
}
50255084

src/validation.h

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -433,20 +433,16 @@ class BlockManager
433433

434434
std::unique_ptr<CBlockTreeDB> m_block_tree_db GUARDED_BY(::cs_main);
435435

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

438438
/**
439439
* Load the blocktree off disk and into memory. Populate certain metadata
440440
* per index entry (nStatus, nChainWork, nTimeMax, etc.) as well as peripheral
441441
* collections like setDirtyBlockIndex.
442-
*
443-
* @param[out] block_index_candidates Fill this set with any valid blocks for
444-
* which we've downloaded all transactions.
445442
*/
446443
bool LoadBlockIndex(
447444
const Consensus::Params& consensus_params,
448-
std::set<CBlockIndex*, CBlockIndexWorkComparator>& block_index_candidates)
449-
EXCLUSIVE_LOCKS_REQUIRED(cs_main);
445+
ChainstateManager& chainman) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
450446

451447
/** Clear all data members. */
452448
void Unload() EXCLUSIVE_LOCKS_REQUIRED(cs_main);
@@ -626,6 +622,10 @@ class CChainState
626622
*/
627623
const std::optional<uint256> m_from_snapshot_blockhash;
628624

625+
//! Return true if this chainstate relies on blocks that are assumed-valid. In
626+
//! practice this means it was created based on a UTXO snapshot.
627+
bool reliesOnAssumedValid() { return m_from_snapshot_blockhash.has_value(); }
628+
629629
/**
630630
* The set of all CBlockIndex entries with either BLOCK_VALID_TRANSACTIONS (for
631631
* itself and all ancestors) *or* BLOCK_ASSUMED_VALID (if using background

0 commit comments

Comments
 (0)