Skip to content

Commit 6082c84

Browse files
committed
refactor: Add Chainstate::m_target_blockhash member
Make Chainstate objects aware of what block they are targeting. This makes Chainstate objects more self contained, so it's possible for validation code to look at one Chainstate object and know what blocks to connect to it without needing to consider global validation state or look at other Chainstate objects. The motivation for this change is to make validation and networking code more readable, so understanding it just requires knowing about chains and blocks, not reasoning about assumeutxo download states. This change also enables simplifications to the ChainstateManager interface in subsequent commits, and could make it easier to implement new features like creating new Chainstate objects to generate UTXO snapshots or index UTXO data. Note that behavior of the MaybeCompleteSnapshotValidation function is not changing here but some checks that were previously impossible to trigger like the BASE_BLOCKHASH_MISMATCH case have been turned into asserts.
1 parent de00e87 commit 6082c84

File tree

6 files changed

+125
-76
lines changed

6 files changed

+125
-76
lines changed

src/init.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1880,7 +1880,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
18801880
}
18811881
} else {
18821882
// Prior to setting NODE_NETWORK, check if we can provide historical blocks.
1883-
if (!WITH_LOCK(chainman.GetMutex(), return chainman.BackgroundSyncInProgress())) {
1883+
if (!WITH_LOCK(chainman.GetMutex(), return chainman.HistoricalChainstate())) {
18841884
LogInfo("Setting NODE_NETWORK in non-prune mode");
18851885
g_local_services = ServiceFlags(g_local_services | NODE_NETWORK);
18861886
} else {

src/net_processing.cpp

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5921,18 +5921,19 @@ bool PeerManagerImpl::SendMessages(CNode* pto)
59215921
return std::max(0, MAX_BLOCKS_IN_TRANSIT_PER_PEER - static_cast<int>(state.vBlocksInFlight.size()));
59225922
};
59235923

5924-
// If a snapshot chainstate is in use, we want to find its next blocks
5925-
// before the background chainstate to prioritize getting to network tip.
5924+
// If there are multiple chainstates, download blocks for the
5925+
// current chainstate first, to prioritize getting to network tip
5926+
// before downloading historical blocks.
59265927
FindNextBlocksToDownload(*peer, get_inflight_budget(), vToDownload, staller);
5927-
if (m_chainman.BackgroundSyncInProgress() && !IsLimitedPeer(*peer)) {
5928-
// If the background tip is not an ancestor of the snapshot block,
5928+
auto historical_blocks{m_chainman.GetHistoricalBlockRange()};
5929+
if (historical_blocks && !IsLimitedPeer(*peer)) {
5930+
// If the first needed historical block is not an ancestor of the last,
59295931
// we need to start requesting blocks from their last common ancestor.
5930-
const CBlockIndex *from_tip = LastCommonAncestor(m_chainman.GetBackgroundSyncTip(), m_chainman.GetSnapshotBaseBlock());
5932+
const CBlockIndex* from_tip = LastCommonAncestor(historical_blocks->first, historical_blocks->second);
59315933
TryDownloadingHistoricalBlocks(
59325934
*peer,
59335935
get_inflight_budget(),
5934-
vToDownload, from_tip,
5935-
Assert(m_chainman.GetSnapshotBaseBlock()));
5936+
vToDownload, from_tip, historical_blocks->second);
59365937
}
59375938
for (const CBlockIndex *pindex : vToDownload) {
59385939
uint32_t nFetchFlags = GetFetchFlags(*peer);

src/node/chainstate.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,12 +166,14 @@ ChainstateLoadResult LoadChainstate(ChainstateManager& chainman, const CacheSize
166166
chainman.m_total_coinsdb_cache = cache_sizes.coins_db;
167167

168168
// Load the fully validated chainstate.
169-
chainman.InitializeChainstate(options.mempool);
169+
Chainstate& validated_cs{chainman.InitializeChainstate(options.mempool)};
170170

171171
// Load a chain created from a UTXO snapshot, if any exist.
172172
bool has_snapshot = chainman.DetectSnapshotChainstate();
173173

174174
if (has_snapshot && options.wipe_chainstate_db) {
175+
// Reset chainstate target to network tip instead of snapshot block.
176+
validated_cs.SetTargetBlock(nullptr);
175177
LogInfo("[snapshot] deleting snapshot chainstate due to reindexing");
176178
if (!chainman.DeleteSnapshotChainstate()) {
177179
return {ChainstateLoadStatus::FAILURE_FATAL, Untranslated("Couldn't remove snapshot chainstate.")};

src/test/validation_chainstate_tests.cpp

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -107,16 +107,7 @@ BOOST_FIXTURE_TEST_CASE(chainstate_update_tip, TestChain100Setup)
107107

108108
curr_tip = get_notify_tip();
109109

110-
BOOST_CHECK_EQUAL(chainman.GetAll().size(), 2);
111-
112-
Chainstate& background_cs{*Assert([&]() -> Chainstate* {
113-
for (Chainstate* cs : chainman.GetAll()) {
114-
if (cs != &chainman.ActiveChainstate()) {
115-
return cs;
116-
}
117-
}
118-
return nullptr;
119-
}())};
110+
Chainstate& background_cs{*Assert(WITH_LOCK(::cs_main, return chainman.HistoricalChainstate()))};
120111

121112
// Append the first block to the background chain.
122113
BlockValidationState state;

src/validation.cpp

Lines changed: 73 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1891,6 +1891,29 @@ const CBlockIndex* Chainstate::SnapshotBase() const
18911891
return m_cached_snapshot_base;
18921892
}
18931893

1894+
const CBlockIndex* Chainstate::TargetBlock() const
1895+
{
1896+
if (!m_target_blockhash) return nullptr;
1897+
if (!m_cached_target_block) m_cached_target_block = Assert(m_chainman.m_blockman.LookupBlockIndex(*m_target_blockhash));
1898+
return m_cached_target_block;
1899+
}
1900+
1901+
void Chainstate::SetTargetBlock(CBlockIndex* block)
1902+
{
1903+
if (block) {
1904+
m_target_blockhash = block->GetBlockHash();
1905+
} else {
1906+
m_target_blockhash.reset();
1907+
}
1908+
m_cached_target_block = block;
1909+
}
1910+
1911+
void Chainstate::SetTargetBlockHash(uint256 block_hash)
1912+
{
1913+
m_target_blockhash = block_hash;
1914+
m_cached_target_block = nullptr;
1915+
}
1916+
18941917
void Chainstate::InitCoinsDB(
18951918
size_t cache_size_bytes,
18961919
bool in_memory,
@@ -3144,8 +3167,6 @@ bool Chainstate::ConnectTip(
31443167
// If we are the background validation chainstate, check to see if we are done
31453168
// validating the snapshot (i.e. our tip has reached the snapshot's base block).
31463169
if (this != &m_chainman.ActiveChainstate()) {
3147-
// This call may set `m_disabled`, which is referenced immediately afterwards in
3148-
// ActivateBestChain, so that we stop connecting blocks past the snapshot base.
31493170
m_chainman.MaybeCompleteSnapshotValidation();
31503171
}
31513172

@@ -3439,12 +3460,8 @@ bool Chainstate::ActivateBestChain(BlockValidationState& state, std::shared_ptr<
34393460
}
34403461
}
34413462

3442-
// This will have been toggled in
3443-
// ActivateBestChainStep -> ConnectTip -> MaybeCompleteSnapshotValidation,
3444-
// if at all, so we should catch it here.
3445-
//
3446-
// Break this do-while to ensure we don't advance past the base snapshot.
3447-
if (m_disabled) {
3463+
// Break this do-while to ensure we don't advance past the target block.
3464+
if (ReachedTarget()) {
34483465
break;
34493466
}
34503467
} while (!m_chain.Tip() || (starting_tip && CBlockIndexWorkComparator()(m_chain.Tip(), starting_tip)));
@@ -3486,7 +3503,7 @@ bool Chainstate::ActivateBestChain(BlockValidationState& state, std::shared_ptr<
34863503
} // release cs_main
34873504
// When we reach this point, we switched to a new tip (stored in pindexNewTip).
34883505

3489-
bool disabled{false};
3506+
bool reached_target;
34903507
{
34913508
LOCK(m_chainman.GetMutex());
34923509
if (exited_ibd) {
@@ -3500,15 +3517,14 @@ bool Chainstate::ActivateBestChain(BlockValidationState& state, std::shared_ptr<
35003517
return false;
35013518
}
35023519

3503-
disabled = m_disabled;
3520+
reached_target = ReachedTarget();
35043521
}
35053522

3506-
if (disabled) {
3507-
// Background chainstate has reached the snapshot base block, so exit.
3508-
3509-
// Restart indexes to resume indexing for all blocks unique to the snapshot
3510-
// chain. This resumes indexing "in order" from where the indexing on the
3511-
// background validation chain left off.
3523+
if (reached_target) {
3524+
// Chainstate has reached the target block, so exit.
3525+
//
3526+
// Restart indexes so indexes can resync and index new blocks after
3527+
// the target block.
35123528
//
35133529
// This cannot be done while holding cs_main (within
35143530
// MaybeCompleteSnapshotValidation) or a cs_main deadlock will occur.
@@ -3789,17 +3805,15 @@ void Chainstate::TryAddBlockIndexCandidate(CBlockIndex* pindex)
37893805
return;
37903806
}
37913807

3792-
bool is_active_chainstate = this == &m_chainman.ActiveChainstate();
3793-
if (is_active_chainstate) {
3794-
// The active chainstate should always add entries that have more
3808+
const CBlockIndex* target_block{TargetBlock()};
3809+
if (!target_block) {
3810+
// If no specific target block, add all entries that have more
37953811
// work than the tip.
37963812
setBlockIndexCandidates.insert(pindex);
3797-
} else if (!m_disabled) {
3798-
// For the background chainstate, we only consider connecting blocks
3799-
// towards the snapshot base (which can't be nullptr or else we'll
3800-
// never make progress).
3801-
const CBlockIndex* snapshot_base{Assert(m_chainman.GetSnapshotBaseBlock())};
3802-
if (snapshot_base->GetAncestor(pindex->nHeight) == pindex) {
3813+
} else {
3814+
// If there is a target block, only consider connecting blocks
3815+
// towards the target block.
3816+
if (target_block->GetAncestor(pindex->nHeight) == pindex) {
38033817
setBlockIndexCandidates.insert(pindex);
38043818
}
38053819
}
@@ -4483,7 +4497,7 @@ bool ChainstateManager::ProcessNewBlock(const std::shared_ptr<const CBlock>& blo
44834497
return false;
44844498
}
44854499

4486-
Chainstate* bg_chain{WITH_LOCK(cs_main, return BackgroundSyncInProgress() ? m_ibd_chainstate.get() : nullptr)};
4500+
Chainstate* bg_chain{WITH_LOCK(cs_main, return HistoricalChainstate())};
44874501
BlockValidationState bg_state;
44884502
if (bg_chain && !bg_chain->ActivateBestChain(bg_state, block)) {
44894503
LogError("%s: [background] ActivateBestChain failed (%s)\n", __func__, bg_state.ToString());
@@ -5787,6 +5801,11 @@ util::Result<CBlockIndex*> ChainstateManager::ActivateSnapshot(
57875801
const bool chaintip_loaded = m_snapshot_chainstate->LoadChainTip();
57885802
assert(chaintip_loaded);
57895803

5804+
// Set snapshot block as the target block for the historical chainstate.
5805+
assert(m_ibd_chainstate.get());
5806+
assert(!m_ibd_chainstate->m_target_blockhash);
5807+
m_ibd_chainstate->SetTargetBlockHash(base_blockhash);
5808+
57905809
// Transfer possession of the mempool to the snapshot chainstate.
57915810
// Mempool is empty at this point because we're still in IBD.
57925811
Assert(m_active_chainstate->m_mempool->size() == 0);
@@ -6048,23 +6067,25 @@ util::Result<void> ChainstateManager::PopulateAndValidateSnapshot(
60486067
SnapshotCompletionResult ChainstateManager::MaybeCompleteSnapshotValidation()
60496068
{
60506069
AssertLockHeld(cs_main);
6070+
6071+
// If a snapshot does not need to be validated...
60516072
if (m_ibd_chainstate.get() == &this->ActiveChainstate() ||
6073+
// Or if either chainstate is unusable...
60526074
!this->IsUsable(m_snapshot_chainstate.get()) ||
60536075
!this->IsUsable(m_ibd_chainstate.get()) ||
6054-
!m_ibd_chainstate->m_chain.Tip()) {
6055-
// Nothing to do - this function only applies to the background
6056-
// validation chainstate.
6076+
!m_snapshot_chainstate->m_from_snapshot_blockhash ||
6077+
!m_ibd_chainstate->m_chain.Tip() ||
6078+
// Or the validated chainstate is not targeting the snapshot block...
6079+
!m_ibd_chainstate->m_target_blockhash ||
6080+
*m_ibd_chainstate->m_target_blockhash != *m_snapshot_chainstate->m_from_snapshot_blockhash ||
6081+
// Or the validated chainstate has not reached the snapshot block yet...
6082+
!m_ibd_chainstate->ReachedTarget()) {
6083+
// Then a snapshot cannot be validated and there is nothing to do.
60576084
return SnapshotCompletionResult::SKIPPED;
60586085
}
60596086
const int snapshot_tip_height = this->ActiveHeight();
60606087
const int snapshot_base_height = *Assert(this->GetSnapshotBaseHeight());
60616088
const CBlockIndex& index_new = *Assert(m_ibd_chainstate->m_chain.Tip());
6062-
6063-
if (index_new.nHeight < snapshot_base_height) {
6064-
// Background IBD not complete yet.
6065-
return SnapshotCompletionResult::SKIPPED;
6066-
}
6067-
60686089
assert(SnapshotBlockhash());
60696090
uint256 snapshot_blockhash = *Assert(SnapshotBlockhash());
60706091

@@ -6087,6 +6108,9 @@ SnapshotCompletionResult ChainstateManager::MaybeCompleteSnapshotValidation()
60876108
LogError("[snapshot] !!! %s\n", user_error.original);
60886109
LogError("[snapshot] deleting snapshot, reverting to validated chain, and stopping node\n");
60896110

6111+
// Reset chainstate target to network tip instead of snapshot block.
6112+
m_ibd_chainstate->SetTargetBlock(nullptr);
6113+
60906114
m_active_chainstate = m_ibd_chainstate.get();
60916115
m_snapshot_chainstate->m_disabled = true;
60926116
assert(!this->IsUsable(m_snapshot_chainstate.get()));
@@ -6100,14 +6124,7 @@ SnapshotCompletionResult ChainstateManager::MaybeCompleteSnapshotValidation()
61006124
GetNotifications().fatalError(user_error);
61016125
};
61026126

6103-
if (index_new.GetBlockHash() != snapshot_blockhash) {
6104-
LogWarning("[snapshot] supposed base block %s does not match the "
6105-
"snapshot base block %s (height %d). Snapshot is not valid.",
6106-
index_new.ToString(), snapshot_blockhash.ToString(), snapshot_base_height);
6107-
handle_invalid_snapshot();
6108-
return SnapshotCompletionResult::BASE_BLOCKHASH_MISMATCH;
6109-
}
6110-
6127+
assert(index_new.GetBlockHash() == snapshot_blockhash);
61116128
assert(index_new.nHeight == snapshot_base_height);
61126129

61136130
int curr_height = m_ibd_chainstate->m_chain.Height();
@@ -6286,6 +6303,12 @@ Chainstate& ChainstateManager::ActivateExistingSnapshot(uint256 base_blockhash)
62866303
std::make_unique<Chainstate>(nullptr, m_blockman, *this, base_blockhash);
62876304
LogInfo("[snapshot] switching active chainstate to %s", m_snapshot_chainstate->ToString());
62886305

6306+
// Set target block for historical chainstate to snapshot block.
6307+
assert(m_ibd_chainstate.get());
6308+
assert(!m_ibd_chainstate->m_target_blockhash);
6309+
m_ibd_chainstate->SetTargetBlockHash(base_blockhash);
6310+
6311+
// Transfer possession of the mempool to the chainstate.
62896312
// Mempool is empty at this point because we're still in IBD.
62906313
Assert(m_active_chainstate->m_mempool->size() == 0);
62916314
Assert(!m_snapshot_chainstate->m_mempool);
@@ -6522,3 +6545,10 @@ std::pair<int, int> ChainstateManager::GetPruneRange(const Chainstate& chainstat
65226545

65236546
return {prune_start, prune_end};
65246547
}
6548+
6549+
std::optional<std::pair<const CBlockIndex*, const CBlockIndex*>> ChainstateManager::GetHistoricalBlockRange() const
6550+
{
6551+
const Chainstate* chainstate{HistoricalChainstate()};
6552+
if (!chainstate) return {};
6553+
return std::make_pair(chainstate->m_chain.Tip(), chainstate->TargetBlock());
6554+
}

src/validation.h

Lines changed: 39 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -561,6 +561,9 @@ class Chainstate
561561
//! Cached result of LookupBlockIndex(*m_from_snapshot_blockhash)
562562
mutable const CBlockIndex* m_cached_snapshot_base GUARDED_BY(::cs_main){nullptr};
563563

564+
//! Cached result of LookupBlockIndex(*m_target_blockhash)
565+
mutable const CBlockIndex* m_cached_target_block GUARDED_BY(::cs_main){nullptr};
566+
564567
std::optional<const char*> m_last_script_check_reason_logged GUARDED_BY(::cs_main){};
565568

566569
public:
@@ -620,13 +623,39 @@ class Chainstate
620623
*/
621624
const std::optional<uint256> m_from_snapshot_blockhash;
622625

626+
//! Target block for this chainstate. If this is not set, chainstate will
627+
//! target the most-work, valid block. If this is set, ChainstateManager
628+
//! considers this a "historical" chainstate since it will only contain old
629+
//! blocks up to the target block, not newer blocks.
630+
std::optional<uint256> m_target_blockhash GUARDED_BY(::cs_main);
631+
623632
/**
624633
* The base of the snapshot this chainstate was created from.
625634
*
626635
* nullptr if this chainstate was not created from a snapshot.
627636
*/
628637
const CBlockIndex* SnapshotBase() const EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
629638

639+
//! Return target block which chainstate tip is expected to reach, if this
640+
//! is a historic chainstate being used to validate a snapshot, or null if
641+
//! chainstate targets the most-work block.
642+
const CBlockIndex* TargetBlock() const EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
643+
//! Set target block for this chainstate. If null, chainstate will target
644+
//! the most-work valid block. If non-null chainstate will be a historic
645+
//! chainstate and target the specified block.
646+
void SetTargetBlock(CBlockIndex* block) EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
647+
//! Set target block for this chainstate using just a block hash. Useful
648+
//! when the block database has not been loaded yet.
649+
void SetTargetBlockHash(uint256 block_hash) EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
650+
651+
//! Return true if chainstate reached target block.
652+
bool ReachedTarget() const EXCLUSIVE_LOCKS_REQUIRED(::cs_main)
653+
{
654+
const CBlockIndex* target_block{TargetBlock()};
655+
assert(!target_block || target_block->GetAncestor(m_chain.Height()) == m_chain.Tip());
656+
return target_block && target_block == m_chain.Tip();
657+
}
658+
630659
/**
631660
* The set of all CBlockIndex entries that have as much work as our current
632661
* tip or more, and transaction data needed to be validated (with
@@ -862,10 +891,6 @@ enum class SnapshotCompletionResult {
862891
// The UTXO set hash of the background validation chainstate does not match
863892
// the one expected by assumeutxo chainparams.
864893
HASH_MISMATCH,
865-
866-
// The blockhash of the current tip of the background validation chainstate does
867-
// not match the one expected by the snapshot chainstate.
868-
BASE_BLOCKHASH_MISMATCH,
869894
};
870895

871896
/**
@@ -1121,22 +1146,19 @@ class ChainstateManager
11211146
//! Returns nullptr if no snapshot has been loaded.
11221147
const CBlockIndex* GetSnapshotBaseBlock() const EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
11231148

1149+
//! Return historical chainstate targeting a specific block, if any.
1150+
Chainstate* HistoricalChainstate() const EXCLUSIVE_LOCKS_REQUIRED(GetMutex())
1151+
{
1152+
auto* cs{m_ibd_chainstate.get()};
1153+
return IsUsable(cs) && cs->m_target_blockhash ? cs : nullptr;
1154+
}
1155+
11241156
//! The most-work chain.
11251157
Chainstate& ActiveChainstate() const;
11261158
CChain& ActiveChain() const EXCLUSIVE_LOCKS_REQUIRED(GetMutex()) { return ActiveChainstate().m_chain; }
11271159
int ActiveHeight() const EXCLUSIVE_LOCKS_REQUIRED(GetMutex()) { return ActiveChain().Height(); }
11281160
CBlockIndex* ActiveTip() const EXCLUSIVE_LOCKS_REQUIRED(GetMutex()) { return ActiveChain().Tip(); }
11291161

1130-
//! The state of a background sync (for net processing)
1131-
bool BackgroundSyncInProgress() const EXCLUSIVE_LOCKS_REQUIRED(GetMutex()) {
1132-
return IsUsable(m_snapshot_chainstate.get()) && IsUsable(m_ibd_chainstate.get());
1133-
}
1134-
1135-
//! The tip of the background sync chain
1136-
const CBlockIndex* GetBackgroundSyncTip() const EXCLUSIVE_LOCKS_REQUIRED(GetMutex()) {
1137-
return BackgroundSyncInProgress() ? m_ibd_chainstate->m_chain.Tip() : nullptr;
1138-
}
1139-
11401162
node::BlockMap& BlockIndex() EXCLUSIVE_LOCKS_REQUIRED(::cs_main)
11411163
{
11421164
AssertLockHeld(::cs_main);
@@ -1337,6 +1359,9 @@ class ChainstateManager
13371359
//! nullopt.
13381360
std::optional<int> GetSnapshotBaseHeight() const EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
13391361

1362+
//! Get range of historical blocks to download.
1363+
std::optional<std::pair<const CBlockIndex*, const CBlockIndex*>> GetHistoricalBlockRange() const EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
1364+
13401365
//! If, due to invalidation / reconsideration of blocks, the previous
13411366
//! best header is no longer valid / guaranteed to be the most-work
13421367
//! header in our block-index not known to be invalid, recalculate it.

0 commit comments

Comments
 (0)