Skip to content

Commit ef29d5d

Browse files
committed
Merge bitcoin/bitcoin#27607: index: make startup more efficient
ca91c24 index: verify blocks data existence only once (furszy) fcbdaee init: don't start indexes sync thread prematurely (furszy) 2ec89f1 refactor: simplify pruning violation check (furszy) c82ef91 make GetFirstStoredBlock assert that 'start_block' always has data (furszy) 430e702 refactor: index, decouple 'Init' from 'Start' (furszy) 225e213 refactor: init indexes, decouple 'Start()' from the creation step (furszy) 2ebc7e6 doc: describe 'init load' thread actions (Martin Zumsande) 0457510 scripted-diff: rename 'loadblk' thread name to 'initload' (furszy) ed4462c init: start indexes sync earlier (furszy) Pull request description: Simplifies index startup code, eliminating the `g_indexes_ready_to_sync` variable, deduplicating code and moving the prune violation check out of the `BaseIndex` class. Also makes startup more efficient by running the prune violation check once for all indexes instead of once for each index, and by delaying the prune violation check and moving it off of the main thread so the node can start up faster and perform the block data availability verification even when the '-reindex" or the "-reindex-chainstate" flags are enabled (which hasn't being possible so far). ACKs for top commit: ryanofsky: Code review ACK ca91c24. Just rebase and suggested changes since last review (Start return check, and code simplification) TheCharlatan: re-ACK ca91c24 Tree-SHA512: e9c98ce89aeb29e8d0f505f17b34aa54fe44efefbf017f4746e3b446ab4de25ade4f707254a0bbe4b99b69731b04a4067ce529eb7aa834ced196784b694cf7ce
2 parents c464e67 + ca91c24 commit ef29d5d

20 files changed

+189
-108
lines changed

doc/developer-notes.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -621,8 +621,9 @@ Threads
621621
: Started from `main()` in `bitcoind.cpp`. Responsible for starting up and
622622
shutting down the application.
623623

624-
- [ThreadImport (`b-loadblk`)](https://doxygen.bitcoincore.org/namespacenode.html#ab4305679079866f0f420f7dbf278381d)
625-
: Loads blocks from `blk*.dat` files or `-loadblock=<file>` on startup.
624+
- [Init load (`b-initload`)](https://doxygen.bitcoincore.org/namespacenode.html#ab4305679079866f0f420f7dbf278381d)
625+
: Performs various loading tasks that are part of init but shouldn't block the node from being started: external block import,
626+
reindex, reindex-chainstate, main chain activation, spawn indexes background sync threads and mempool load.
626627

627628
- [CCheckQueue::Loop (`b-scriptch.x`)](https://doxygen.bitcoincore.org/class_c_check_queue.html#a6e7fa51d3a25e7cb65446d4b50e6a987)
628629
: Parallel script validation threads for transactions in blocks.

src/bitcoin-chainstate.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,7 @@ int main(int argc, char* argv[])
287287
// Without this precise shutdown sequence, there will be a lot of nullptr
288288
// dereferencing and UB.
289289
scheduler.stop();
290-
if (chainman.m_load_block.joinable()) chainman.m_load_block.join();
290+
if (chainman.m_thread_load.joinable()) chainman.m_thread_load.join();
291291
StopScriptCheckWorkerThreads();
292292

293293
GetMainSignals().FlushBackgroundCallbacks();

src/index/base.cpp

Lines changed: 19 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,6 @@
2323
#include <string>
2424
#include <utility>
2525

26-
using node::g_indexes_ready_to_sync;
27-
2826
constexpr uint8_t DB_BEST_BLOCK{'B'};
2927

3028
constexpr auto SYNC_LOG_INTERVAL{30s};
@@ -81,6 +79,13 @@ BaseIndex::~BaseIndex()
8179

8280
bool BaseIndex::Init()
8381
{
82+
// m_chainstate member gives indexing code access to node internals. It is
83+
// removed in followup https://github.com/bitcoin/bitcoin/pull/24230
84+
m_chainstate = &m_chain->context()->chainman->ActiveChainstate();
85+
// Register to validation interface before setting the 'm_synced' flag, so that
86+
// callbacks are not missed once m_synced is true.
87+
RegisterValidationInterface(this);
88+
8489
CBlockLocator locator;
8590
if (!GetDB().ReadBestBlock(locator)) {
8691
locator.SetNull();
@@ -100,53 +105,17 @@ bool BaseIndex::Init()
100105
SetBestBlockIndex(locator_index);
101106
}
102107

103-
// Skip pruning check if indexes are not ready to sync (because reindex-chainstate has wiped the chain).
104-
const CBlockIndex* start_block = m_best_block_index.load();
105-
bool synced = start_block == active_chain.Tip();
106-
if (!synced && g_indexes_ready_to_sync) {
107-
bool prune_violation = false;
108-
if (!start_block) {
109-
// index is not built yet
110-
// make sure we have all block data back to the genesis
111-
prune_violation = m_chainstate->m_blockman.GetFirstStoredBlock(*active_chain.Tip()) != active_chain.Genesis();
112-
}
113-
// in case the index has a best block set and is not fully synced
114-
// check if we have the required blocks to continue building the index
115-
else {
116-
const CBlockIndex* block_to_test = start_block;
117-
if (!active_chain.Contains(block_to_test)) {
118-
// if the bestblock is not part of the mainchain, find the fork
119-
// and make sure we have all data down to the fork
120-
block_to_test = active_chain.FindFork(block_to_test);
121-
}
122-
const CBlockIndex* block = active_chain.Tip();
123-
prune_violation = true;
124-
// check backwards from the tip if we have all block data until we reach the indexes bestblock
125-
while (block_to_test && block && (block->nStatus & BLOCK_HAVE_DATA)) {
126-
if (block_to_test == block) {
127-
prune_violation = false;
128-
break;
129-
}
130-
// block->pprev must exist at this point, since block_to_test is part of the chain
131-
// and thus must be encountered when going backwards from the tip
132-
assert(block->pprev);
133-
block = block->pprev;
134-
}
135-
}
136-
if (prune_violation) {
137-
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)"), GetName()));
138-
}
139-
}
140-
141108
// Child init
109+
const CBlockIndex* start_block = m_best_block_index.load();
142110
if (!CustomInit(start_block ? std::make_optional(interfaces::BlockKey{start_block->GetBlockHash(), start_block->nHeight}) : std::nullopt)) {
143111
return false;
144112
}
145113

146114
// Note: this will latch to true immediately if the user starts up with an empty
147115
// datadir and an index enabled. If this is the case, indexation will happen solely
148116
// via `BlockConnected` signals until, possibly, the next restart.
149-
m_synced = synced;
117+
m_synced = start_block == active_chain.Tip();
118+
m_init = true;
150119
return true;
151120
}
152121

@@ -168,12 +137,6 @@ static const CBlockIndex* NextSyncBlock(const CBlockIndex* pindex_prev, CChain&
168137

169138
void BaseIndex::ThreadSync()
170139
{
171-
// Wait for a possible reindex-chainstate to finish until continuing
172-
// with the index sync
173-
while (!g_indexes_ready_to_sync) {
174-
if (!m_interrupt.sleep_for(std::chrono::milliseconds(500))) return;
175-
}
176-
177140
const CBlockIndex* pindex = m_best_block_index.load();
178141
if (!m_synced) {
179142
std::chrono::steady_clock::time_point last_log_time{0s};
@@ -401,15 +364,9 @@ void BaseIndex::Interrupt()
401364
m_interrupt();
402365
}
403366

404-
bool BaseIndex::Start()
367+
bool BaseIndex::StartBackgroundSync()
405368
{
406-
// m_chainstate member gives indexing code access to node internals. It is
407-
// removed in followup https://github.com/bitcoin/bitcoin/pull/24230
408-
m_chainstate = &m_chain->context()->chainman->ActiveChainstate();
409-
// Need to register this ValidationInterface before running Init(), so that
410-
// callbacks are not missed if Init sets m_synced to true.
411-
RegisterValidationInterface(this);
412-
if (!Init()) return false;
369+
if (!m_init) throw std::logic_error("Error: Cannot start a non-initialized index");
413370

414371
m_thread_sync = std::thread(&util::TraceThread, GetName(), [this] { ThreadSync(); });
415372
return true;
@@ -429,7 +386,13 @@ IndexSummary BaseIndex::GetSummary() const
429386
IndexSummary summary{};
430387
summary.name = GetName();
431388
summary.synced = m_synced;
432-
summary.best_block_height = m_best_block_index ? m_best_block_index.load()->nHeight : 0;
389+
if (const auto& pindex = m_best_block_index.load()) {
390+
summary.best_block_height = pindex->nHeight;
391+
summary.best_block_hash = pindex->GetBlockHash();
392+
} else {
393+
summary.best_block_height = 0;
394+
summary.best_block_hash = m_chain->getBlockHash(0);
395+
}
433396
return summary;
434397
}
435398

src/index/base.h

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ struct IndexSummary {
2323
std::string name;
2424
bool synced{false};
2525
int best_block_height{0};
26+
uint256 best_block_hash;
2627
};
2728

2829
/**
@@ -54,6 +55,8 @@ class BaseIndex : public CValidationInterface
5455
};
5556

5657
private:
58+
/// Whether the index has been initialized or not.
59+
std::atomic<bool> m_init{false};
5760
/// Whether the index is in sync with the main chain. The flag is flipped
5861
/// from false to true once, after which point this starts processing
5962
/// ValidationInterface notifications to stay in sync.
@@ -69,9 +72,6 @@ class BaseIndex : public CValidationInterface
6972
std::thread m_thread_sync;
7073
CThreadInterrupt m_interrupt;
7174

72-
/// Read best block locator and check that data needed to sync has not been pruned.
73-
bool Init();
74-
7575
/// Sync the index with the block index starting from the current best block.
7676
/// Intended to be run in its own thread, m_thread_sync, and can be
7777
/// interrupted with m_interrupt. Once the index gets in sync, the m_synced
@@ -142,9 +142,12 @@ class BaseIndex : public CValidationInterface
142142

143143
void Interrupt();
144144

145-
/// Start initializes the sync state and registers the instance as a
146-
/// ValidationInterface so that it stays in sync with blockchain updates.
147-
[[nodiscard]] bool Start();
145+
/// Initializes the sync state and registers the instance to the
146+
/// validation interface so that it stays in sync with blockchain updates.
147+
[[nodiscard]] bool Init();
148+
149+
/// Starts the initial sync process.
150+
[[nodiscard]] bool StartBackgroundSync();
148151

149152
/// Stops the instance from staying in sync with blockchain updates.
150153
void Stop();

src/init.cpp

Lines changed: 64 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -125,13 +125,12 @@ using node::CalculateCacheSizes;
125125
using node::DEFAULT_PERSIST_MEMPOOL;
126126
using node::DEFAULT_PRINTPRIORITY;
127127
using node::fReindex;
128-
using node::g_indexes_ready_to_sync;
129128
using node::KernelNotifications;
130129
using node::LoadChainstate;
131130
using node::MempoolPath;
132131
using node::NodeContext;
133132
using node::ShouldPersistMempool;
134-
using node::ThreadImport;
133+
using node::ImportBlocks;
135134
using node::VerifyLoadedChainstate;
136135

137136
static constexpr bool DEFAULT_PROXYRANDOMIZE{true};
@@ -268,7 +267,7 @@ void Shutdown(NodeContext& node)
268267
// After everything has been shut down, but before things get flushed, stop the
269268
// CScheduler/checkqueue, scheduler and load block thread.
270269
if (node.scheduler) node.scheduler->stop();
271-
if (node.chainman && node.chainman->m_load_block.joinable()) node.chainman->m_load_block.join();
270+
if (node.chainman && node.chainman->m_thread_load.joinable()) node.chainman->m_thread_load.join();
272271
StopScriptCheckWorkerThreads();
273272

274273
// After the threads that potentially access these pointers have been stopped,
@@ -1545,34 +1544,29 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
15451544

15461545
// ********************************************************* Step 8: start indexers
15471546

1548-
// If reindex-chainstate was specified, delay syncing indexes until ThreadImport has reindexed the chain
1549-
if (!fReindexChainState) g_indexes_ready_to_sync = true;
15501547
if (args.GetBoolArg("-txindex", DEFAULT_TXINDEX)) {
15511548
auto result{WITH_LOCK(cs_main, return CheckLegacyTxindex(*Assert(chainman.m_blockman.m_block_tree_db)))};
15521549
if (!result) {
15531550
return InitError(util::ErrorString(result));
15541551
}
15551552

15561553
g_txindex = std::make_unique<TxIndex>(interfaces::MakeChain(node), cache_sizes.tx_index, false, fReindex);
1557-
if (!g_txindex->Start()) {
1558-
return false;
1559-
}
1554+
node.indexes.emplace_back(g_txindex.get());
15601555
}
15611556

15621557
for (const auto& filter_type : g_enabled_filter_types) {
15631558
InitBlockFilterIndex([&]{ return interfaces::MakeChain(node); }, filter_type, cache_sizes.filter_index, false, fReindex);
1564-
if (!GetBlockFilterIndex(filter_type)->Start()) {
1565-
return false;
1566-
}
1559+
node.indexes.emplace_back(GetBlockFilterIndex(filter_type));
15671560
}
15681561

15691562
if (args.GetBoolArg("-coinstatsindex", DEFAULT_COINSTATSINDEX)) {
15701563
g_coin_stats_index = std::make_unique<CoinStatsIndex>(interfaces::MakeChain(node), /*cache_size=*/0, false, fReindex);
1571-
if (!g_coin_stats_index->Start()) {
1572-
return false;
1573-
}
1564+
node.indexes.emplace_back(g_coin_stats_index.get());
15741565
}
15751566

1567+
// Init indexes
1568+
for (auto index : node.indexes) if (!index->Init()) return false;
1569+
15761570
// ********************************************************* Step 9: load wallet
15771571
for (const auto& client : node.chain_clients) {
15781572
if (!client->load()) {
@@ -1656,15 +1650,24 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
16561650
vImportFiles.push_back(fs::PathFromString(strFile));
16571651
}
16581652

1659-
chainman.m_load_block = std::thread(&util::TraceThread, "loadblk", [=, &chainman, &args] {
1660-
ThreadImport(chainman, vImportFiles, ShouldPersistMempool(args) ? MempoolPath(args) : fs::path{});
1653+
chainman.m_thread_load = std::thread(&util::TraceThread, "initload", [=, &chainman, &args, &node] {
1654+
// Import blocks
1655+
ImportBlocks(chainman, vImportFiles);
1656+
// Start indexes initial sync
1657+
if (!StartIndexBackgroundSync(node)) {
1658+
bilingual_str err_str = _("Failed to start indexes, shutting down..");
1659+
chainman.GetNotifications().fatalError(err_str.original, err_str);
1660+
return;
1661+
}
1662+
// Load mempool from disk
1663+
chainman.ActiveChainstate().LoadMempool(ShouldPersistMempool(args) ? MempoolPath(args) : fs::path{});
16611664
});
16621665

16631666
// Wait for genesis block to be processed
16641667
{
16651668
WAIT_LOCK(g_genesis_wait_mutex, lock);
16661669
// We previously could hang here if StartShutdown() is called prior to
1667-
// ThreadImport getting started, so instead we just wait on a timer to
1670+
// ImportBlocks getting started, so instead we just wait on a timer to
16681671
// check ShutdownRequested() regularly.
16691672
while (!fHaveGenesis && !ShutdownRequested()) {
16701673
g_genesis_wait_cv.wait_for(lock, std::chrono::milliseconds(500));
@@ -1875,3 +1878,47 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
18751878

18761879
return true;
18771880
}
1881+
1882+
bool StartIndexBackgroundSync(NodeContext& node)
1883+
{
1884+
// Find the oldest block among all indexes.
1885+
// This block is used to verify that we have the required blocks' data stored on disk,
1886+
// starting from that point up to the current tip.
1887+
// indexes_start_block='nullptr' means "start from height 0".
1888+
std::optional<const CBlockIndex*> indexes_start_block;
1889+
std::string older_index_name;
1890+
1891+
ChainstateManager& chainman = *Assert(node.chainman);
1892+
for (auto index : node.indexes) {
1893+
const IndexSummary& summary = index->GetSummary();
1894+
if (summary.synced) continue;
1895+
1896+
// Get the last common block between the index best block and the active chain
1897+
LOCK(::cs_main);
1898+
const CChain& active_chain = chainman.ActiveChain();
1899+
const CBlockIndex* pindex = chainman.m_blockman.LookupBlockIndex(summary.best_block_hash);
1900+
if (!active_chain.Contains(pindex)) {
1901+
pindex = active_chain.FindFork(pindex);
1902+
}
1903+
1904+
if (!indexes_start_block || !pindex || pindex->nHeight < indexes_start_block.value()->nHeight) {
1905+
indexes_start_block = pindex;
1906+
older_index_name = summary.name;
1907+
if (!pindex) break; // Starting from genesis so no need to look for earlier block.
1908+
}
1909+
};
1910+
1911+
// Verify all blocks needed to sync to current tip are present.
1912+
if (indexes_start_block) {
1913+
LOCK(::cs_main);
1914+
const CBlockIndex* start_block = *indexes_start_block;
1915+
if (!start_block) start_block = chainman.ActiveChain().Genesis();
1916+
if (!chainman.m_blockman.CheckBlockDataAvailability(*chainman.ActiveChain().Tip(), *Assert(start_block))) {
1917+
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));
1918+
}
1919+
}
1920+
1921+
// Start threads
1922+
for (auto index : node.indexes) if (!index->StartBackgroundSync()) return false;
1923+
return true;
1924+
}

src/init.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,4 +73,7 @@ bool AppInitMain(node::NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip
7373
*/
7474
void SetupServerArgs(ArgsManager& argsman);
7575

76+
/** Validates requirements to run the indexes and spawns each index initial sync thread */
77+
bool StartIndexBackgroundSync(node::NodeContext& node);
78+
7679
#endif // BITCOIN_INIT_H

src/node/blockstorage.cpp

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626

2727
namespace node {
2828
std::atomic_bool fReindex(false);
29-
std::atomic_bool g_indexes_ready_to_sync{false};
3029

3130
bool CBlockIndexWorkComparator::operator()(const CBlockIndex* pa, const CBlockIndex* pb) const
3231
{
@@ -403,16 +402,31 @@ bool BlockManager::IsBlockPruned(const CBlockIndex* pblockindex)
403402
return (m_have_pruned && !(pblockindex->nStatus & BLOCK_HAVE_DATA) && pblockindex->nTx > 0);
404403
}
405404

406-
const CBlockIndex* BlockManager::GetFirstStoredBlock(const CBlockIndex& start_block)
405+
const CBlockIndex* BlockManager::GetFirstStoredBlock(const CBlockIndex& upper_block, const CBlockIndex* lower_block)
407406
{
408407
AssertLockHeld(::cs_main);
409-
const CBlockIndex* last_block = &start_block;
408+
const CBlockIndex* last_block = &upper_block;
409+
assert(last_block->nStatus & BLOCK_HAVE_DATA); // 'upper_block' must have data
410410
while (last_block->pprev && (last_block->pprev->nStatus & BLOCK_HAVE_DATA)) {
411+
if (lower_block) {
412+
// Return if we reached the lower_block
413+
if (last_block == lower_block) return lower_block;
414+
// if range was surpassed, means that 'lower_block' is not part of the 'upper_block' chain
415+
// and so far this is not allowed.
416+
assert(last_block->nHeight >= lower_block->nHeight);
417+
}
411418
last_block = last_block->pprev;
412419
}
420+
assert(last_block != nullptr);
413421
return last_block;
414422
}
415423

424+
bool BlockManager::CheckBlockDataAvailability(const CBlockIndex& upper_block, const CBlockIndex& lower_block)
425+
{
426+
if (!(upper_block.nStatus & BLOCK_HAVE_DATA)) return false;
427+
return GetFirstStoredBlock(upper_block, &lower_block) == &lower_block;
428+
}
429+
416430
// If we're using -prune with -reindex, then delete block files that will be ignored by the
417431
// reindex. Since reindexing works by starting at block file 0 and looping until a blockfile
418432
// is missing, do the same here to delete any later block files after a gap. Also delete all
@@ -868,7 +882,7 @@ class ImportingNow
868882
}
869883
};
870884

871-
void ThreadImport(ChainstateManager& chainman, std::vector<fs::path> vImportFiles, const fs::path& mempool_path)
885+
void ImportBlocks(ChainstateManager& chainman, std::vector<fs::path> vImportFiles)
872886
{
873887
ScheduleBatchPriority();
874888

@@ -939,7 +953,5 @@ void ThreadImport(ChainstateManager& chainman, std::vector<fs::path> vImportFile
939953
return;
940954
}
941955
} // End scope of ImportingNow
942-
chainman.ActiveChainstate().LoadMempool(mempool_path);
943-
g_indexes_ready_to_sync = true;
944956
}
945957
} // namespace node

0 commit comments

Comments
 (0)