Skip to content

Commit 373cf91

Browse files
committed
validation: indexing changes for assumeutxo
When using an assumedvalid chainstate, only process validationinterface callbacks from the background chainstate within indexes. This ensures that all indexes are built in-order. Later, we can possibly designate indexes which can be built out of order and continue their operation during snapshot use. Once the background sync has completed, restart the indexes so that they continue to index the now-validated snapshot chainstate.
1 parent 1fffdd7 commit 373cf91

File tree

5 files changed

+95
-11
lines changed

5 files changed

+95
-11
lines changed

src/index/base.cpp

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,9 +79,15 @@ BaseIndex::~BaseIndex()
7979

8080
bool BaseIndex::Init()
8181
{
82+
AssertLockNotHeld(cs_main);
83+
84+
// May need reset if index is being restarted.
85+
m_interrupt.reset();
86+
8287
// m_chainstate member gives indexing code access to node internals. It is
8388
// removed in followup https://github.com/bitcoin/bitcoin/pull/24230
84-
m_chainstate = &m_chain->context()->chainman->ActiveChainstate();
89+
m_chainstate = WITH_LOCK(::cs_main,
90+
return &m_chain->context()->chainman->GetChainstateForIndexing());
8591
// Register to validation interface before setting the 'm_synced' flag, so that
8692
// callbacks are not missed once m_synced is true.
8793
RegisterValidationInterface(this);
@@ -92,7 +98,8 @@ bool BaseIndex::Init()
9298
}
9399

94100
LOCK(cs_main);
95-
CChain& active_chain = m_chainstate->m_chain;
101+
CChain& index_chain = m_chainstate->m_chain;
102+
96103
if (locator.IsNull()) {
97104
SetBestBlockIndex(nullptr);
98105
} else {
@@ -114,7 +121,7 @@ bool BaseIndex::Init()
114121
// Note: this will latch to true immediately if the user starts up with an empty
115122
// datadir and an index enabled. If this is the case, indexation will happen solely
116123
// via `BlockConnected` signals until, possibly, the next restart.
117-
m_synced = start_block == active_chain.Tip();
124+
m_synced = start_block == index_chain.Tip();
118125
m_init = true;
119126
return true;
120127
}
@@ -143,6 +150,8 @@ void BaseIndex::ThreadSync()
143150
std::chrono::steady_clock::time_point last_locator_write_time{0s};
144151
while (true) {
145152
if (m_interrupt) {
153+
LogPrintf("%s: m_interrupt set; exiting ThreadSync\n", GetName());
154+
146155
SetBestBlockIndex(pindex);
147156
// No need to handle errors in Commit. If it fails, the error will be already be
148157
// logged. The best way to recover is to continue, as index cannot be corrupted by
@@ -252,6 +261,17 @@ bool BaseIndex::Rewind(const CBlockIndex* current_tip, const CBlockIndex* new_ti
252261

253262
void BaseIndex::BlockConnected(ChainstateRole role, const std::shared_ptr<const CBlock>& block, const CBlockIndex* pindex)
254263
{
264+
// Ignore events from the assumed-valid chain; we will process its blocks
265+
// (sequentially) after it is fully verified by the background chainstate. This
266+
// is to avoid any out-of-order indexing.
267+
//
268+
// TODO at some point we could parameterize whether a particular index can be
269+
// built out of order, but for now just do the conservative simple thing.
270+
if (role == ChainstateRole::ASSUMEDVALID) {
271+
return;
272+
}
273+
274+
// Ignore BlockConnected signals until we have fully indexed the chain.
255275
if (!m_synced) {
256276
return;
257277
}
@@ -298,6 +318,12 @@ void BaseIndex::BlockConnected(ChainstateRole role, const std::shared_ptr<const
298318

299319
void BaseIndex::ChainStateFlushed(ChainstateRole role, const CBlockLocator& locator)
300320
{
321+
// Ignore events from the assumed-valid chain; we will process its blocks
322+
// (sequentially) after it is fully verified by the background chainstate.
323+
if (role == ChainstateRole::ASSUMEDVALID) {
324+
return;
325+
}
326+
301327
if (!m_synced) {
302328
return;
303329
}

src/index/base.h

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
class CBlock;
1616
class CBlockIndex;
1717
class Chainstate;
18+
class ChainstateManager;
1819
namespace interfaces {
1920
class Chain;
2021
} // namespace interfaces
@@ -30,6 +31,11 @@ struct IndexSummary {
3031
* Base class for indices of blockchain data. This implements
3132
* CValidationInterface and ensures blocks are indexed sequentially according
3233
* to their position in the active chain.
34+
*
35+
* In the presence of multiple chainstates (i.e. if a UTXO snapshot is loaded),
36+
* only the background "IBD" chainstate will be indexed to avoid building the
37+
* index out of order. When the background chainstate completes validation, the
38+
* index will be reinitialized and indexing will continue.
3339
*/
3440
class BaseIndex : public CValidationInterface
3541
{
@@ -122,9 +128,6 @@ class BaseIndex : public CValidationInterface
122128

123129
virtual DB& GetDB() const = 0;
124130

125-
/// Get the name of the index for display in logs.
126-
const std::string& GetName() const LIFETIMEBOUND { return m_name; }
127-
128131
/// Update the internal best block index as well as the prune lock.
129132
void SetBestBlockIndex(const CBlockIndex* block);
130133

@@ -133,6 +136,9 @@ class BaseIndex : public CValidationInterface
133136
/// Destructor interrupts sync thread if running and blocks until it exits.
134137
virtual ~BaseIndex();
135138

139+
/// Get the name of the index for display in logs.
140+
const std::string& GetName() const LIFETIMEBOUND { return m_name; }
141+
136142
/// Blocks the current thread until the index is caught up to the current
137143
/// state of the block chain. This only blocks if the index has gotten in
138144
/// sync once and only needs to process blocks in the ValidationInterface

src/init.cpp

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1478,6 +1478,25 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
14781478
node.chainman = std::make_unique<ChainstateManager>(node.kernel->interrupt, chainman_opts, blockman_opts);
14791479
ChainstateManager& chainman = *node.chainman;
14801480

1481+
// This is defined and set here instead of inline in validation.h to avoid a hard
1482+
// dependency between validation and index/base, since the latter is not in
1483+
// libbitcoinkernel.
1484+
chainman.restart_indexes = [&node]() {
1485+
LogPrintf("[snapshot] restarting indexes\n");
1486+
1487+
// Drain the validation interface queue to ensure that the old indexes
1488+
// don't have any pending work.
1489+
SyncWithValidationInterfaceQueue();
1490+
1491+
for (auto* index : node.indexes) {
1492+
index->Interrupt();
1493+
index->Stop();
1494+
if (!(index->Init() && index->StartBackgroundSync())) {
1495+
LogPrintf("[snapshot] WARNING failed to restart index %s on snapshot chain\n", index->GetName());
1496+
}
1497+
}
1498+
};
1499+
14811500
node::ChainstateLoadOptions options;
14821501
options.mempool = Assert(node.mempool.get());
14831502
options.reindex = node::fReindex;
@@ -1906,18 +1925,19 @@ bool StartIndexBackgroundSync(NodeContext& node)
19061925
// indexes_start_block='nullptr' means "start from height 0".
19071926
std::optional<const CBlockIndex*> indexes_start_block;
19081927
std::string older_index_name;
1909-
19101928
ChainstateManager& chainman = *Assert(node.chainman);
1929+
const Chainstate& chainstate = WITH_LOCK(::cs_main, return chainman.GetChainstateForIndexing());
1930+
const CChain& index_chain = chainstate.m_chain;
1931+
19111932
for (auto index : node.indexes) {
19121933
const IndexSummary& summary = index->GetSummary();
19131934
if (summary.synced) continue;
19141935

19151936
// Get the last common block between the index best block and the active chain
19161937
LOCK(::cs_main);
1917-
const CChain& active_chain = chainman.ActiveChain();
19181938
const CBlockIndex* pindex = chainman.m_blockman.LookupBlockIndex(summary.best_block_hash);
1919-
if (!active_chain.Contains(pindex)) {
1920-
pindex = active_chain.FindFork(pindex);
1939+
if (!index_chain.Contains(pindex)) {
1940+
pindex = index_chain.FindFork(pindex);
19211941
}
19221942

19231943
if (!indexes_start_block || !pindex || pindex->nHeight < indexes_start_block.value()->nHeight) {
@@ -1932,7 +1952,7 @@ bool StartIndexBackgroundSync(NodeContext& node)
19321952
LOCK(::cs_main);
19331953
const CBlockIndex* start_block = *indexes_start_block;
19341954
if (!start_block) start_block = chainman.ActiveChain().Genesis();
1935-
if (!chainman.m_blockman.CheckBlockDataAvailability(*chainman.ActiveChain().Tip(), *Assert(start_block))) {
1955+
if (!chainman.m_blockman.CheckBlockDataAvailability(*index_chain.Tip(), *Assert(start_block))) {
19361956
return InitError(strprintf(Untranslated("%s best block of the index goes beyond pruned data. Please disable the index or reindex (which will download the whole blockchain again)"), older_index_name));
19371957
}
19381958
}

src/validation.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3289,6 +3289,16 @@ bool Chainstate::ActivateBestChain(BlockValidationState& state, std::shared_ptr<
32893289

32903290
if (WITH_LOCK(::cs_main, return m_disabled)) {
32913291
// Background chainstate has reached the snapshot base block, so exit.
3292+
3293+
// Restart indexes to resume indexing for all blocks unique to the snapshot
3294+
// chain. This resumes indexing "in order" from where the indexing on the
3295+
// background validation chain left off.
3296+
//
3297+
// This cannot be done while holding cs_main (within
3298+
// MaybeCompleteSnapshotValidation) or a cs_main deadlock will occur.
3299+
if (m_chainman.restart_indexes) {
3300+
m_chainman.restart_indexes();
3301+
}
32923302
break;
32933303
}
32943304

@@ -5921,3 +5931,11 @@ bool ChainstateManager::ValidatedSnapshotCleanup()
59215931
}
59225932
return true;
59235933
}
5934+
5935+
Chainstate& ChainstateManager::GetChainstateForIndexing()
5936+
{
5937+
// We can't always return `m_ibd_chainstate` because after background validation
5938+
// has completed, `m_snapshot_chainstate == m_active_chainstate`, but it can be
5939+
// indexed.
5940+
return (this->GetAll().size() > 1) ? *m_ibd_chainstate : *m_active_chainstate;
5941+
}

src/validation.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -905,6 +905,10 @@ class ChainstateManager
905905

906906
explicit ChainstateManager(const util::SignalInterrupt& interrupt, Options options, node::BlockManager::Options blockman_options);
907907

908+
//! Function to restart active indexes; set dynamically to avoid a circular
909+
//! dependency on `base/index.cpp`.
910+
std::function<void()> restart_indexes = std::function<void()>();
911+
908912
const CChainParams& GetParams() const { return m_options.chainparams; }
909913
const Consensus::Params& GetConsensus() const { return m_options.chainparams.GetConsensus(); }
910914
bool ShouldCheckBlockIndex() const { return *Assert(m_options.check_block_index); }
@@ -1227,6 +1231,16 @@ class ChainstateManager
12271231
//! @sa node/chainstate:LoadChainstate()
12281232
bool ValidatedSnapshotCleanup() EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
12291233

1234+
//! @returns the chainstate that indexes should consult when ensuring that an
1235+
//! index is synced with a chain where we can expect block index entries to have
1236+
//! BLOCK_HAVE_DATA beneath the tip.
1237+
//!
1238+
//! In other words, give us the chainstate for which we can reasonably expect
1239+
//! that all blocks beneath the tip have been indexed. In practice this means
1240+
//! when using an assumed-valid chainstate based upon a snapshot, return only the
1241+
//! fully validated chain.
1242+
Chainstate& GetChainstateForIndexing() EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
1243+
12301244
~ChainstateManager();
12311245
};
12321246

0 commit comments

Comments
 (0)