Skip to content

Commit a063647

Browse files
author
MarcoFalke
committed
Merge bitcoin/bitcoin#23280: init: Coalesce Chainstate loading sequence between {,non-}unittest codepaths
7f15eff style-only: Remove redundant scope in *Chainstate (Carl Dong) 89bec82 Collapse the 2 cs_main locks in LoadChainstate (Carl Dong) 3b1584b Remove all #include // for * comments (Carl Dong) 9a5a5a3 test/setup: Use LoadChainstate (Carl Dong) c541da0 node/chainstate: Add options for in-memory DBs (Carl Dong) ceb9790 node/caches: Remove intermediate variables (Carl Dong) ac4bf13 node/caches: Extract cache calculation logic (Carl Dong) 15f2e33 validation: VerifyDB only needs Consensus::Params (Carl Dong) 4da9c07 node/chainstate: Decouple from ShutdownRequested (Carl Dong) 05441c2 node/chainstate: Decouple from GetTime (Carl Dong) 2414ebc init: Delay RPC block notif until warmup finished (Carl Dong) 8d466a8 Move -checkblocks LogPrintf to AppInitMain (Carl Dong) aad8d59 node/chainstate: Reduce coupling of LogPrintf (Carl Dong) b345979 node/chainstate: Decouple from concept of uiInterface (Carl Dong) ca7c0b9 Split off VerifyLoadedChainstate (Carl Dong) adf4912 node/chainstate: Remove do/while loop (Carl Dong) 975235c Move init logistics message for BAD_GENESIS_BLOCK to init.cpp (Carl Dong) 8715658 Move mempool nullptr Assert out of LoadChainstate (Carl Dong) 9162a4f node/chainstate: Decouple from concept of NodeContext (Carl Dong) c7a5c46 node/chainstate: Decouple from ArgsManager (Carl Dong) ae9121f node/chainstate: Decouple from stringy errors (Carl Dong) cbac28b node/chainstate: Decouple from GetTimeMillis (Carl Dong) cb64af9 node: Extract chainstate loading sequence (Carl Dong) Pull request description: This PR: 1. Coalesce the Chainstate loading sequence between `AppInitMain` and `*TestingSetup` (which makes it more tested) 2. Makes the Chainstate loading sequence reusable in preparation for future work extracting out our consensus engine. Code-wise, this PR: 1. Extracts `AppInitMain`'s Chainstate loading sequence into a `::LoadChainstateSequence` function 2. Makes this `::LoadChainstateSequence` function reusable by 1. Decoupling it from various concepts (`ArgsManager`, `uiInterface`, etc) 2. Making it report errors using an `enum` rather than by setting a `bilingual_str` 3. Makes `*TestingSetup` use this new `::LoadChainstateSequence` Reviewers: Aside from commentary, I've also included `git diff` flags of interest in the commit messages which I hope will aid review! ACKs for top commit: ryanofsky: Code review ACK 7f15eff. Thanks for updates! MarcoFalke: review ACK 7f15eff 💳 Tree-SHA512: fb9a6cbd1c511a52b477c62a5e68e53a8be5dec2fff0e44a279966afb91efbab44bf1fe7c6b1519f8464ecc25f42dd4bae8e1efbf55ee91fc90fa0b92e3a83e2
2 parents 65b49f6 + 7f15eff commit a063647

File tree

11 files changed

+432
-202
lines changed

11 files changed

+432
-202
lines changed

src/Makefile.am

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,8 @@ BITCOIN_CORE_H = \
174174
netbase.h \
175175
netmessagemaker.h \
176176
node/blockstorage.h \
177+
node/caches.h \
178+
node/chainstate.h \
177179
node/coin.h \
178180
node/coinstats.h \
179181
node/context.h \
@@ -339,6 +341,8 @@ libbitcoin_server_a_SOURCES = \
339341
net.cpp \
340342
net_processing.cpp \
341343
node/blockstorage.cpp \
344+
node/caches.cpp \
345+
node/chainstate.cpp \
342346
node/coin.cpp \
343347
node/coinstats.cpp \
344348
node/context.cpp \

src/init.cpp

Lines changed: 105 additions & 186 deletions
Large diffs are not rendered by default.

src/node/caches.cpp

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// Copyright (c) 2021 The Bitcoin Core developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
#include <node/caches.h>
6+
7+
#include <txdb.h>
8+
#include <util/system.h>
9+
#include <validation.h>
10+
11+
CacheSizes CalculateCacheSizes(const ArgsManager& args, size_t n_indexes)
12+
{
13+
int64_t nTotalCache = (args.GetIntArg("-dbcache", nDefaultDbCache) << 20);
14+
nTotalCache = std::max(nTotalCache, nMinDbCache << 20); // total cache cannot be less than nMinDbCache
15+
nTotalCache = std::min(nTotalCache, nMaxDbCache << 20); // total cache cannot be greater than nMaxDbcache
16+
CacheSizes sizes;
17+
sizes.block_tree_db = std::min(nTotalCache / 8, nMaxBlockDBCache << 20);
18+
nTotalCache -= sizes.block_tree_db;
19+
sizes.tx_index = std::min(nTotalCache / 8, args.GetBoolArg("-txindex", DEFAULT_TXINDEX) ? nMaxTxIndexCache << 20 : 0);
20+
nTotalCache -= sizes.tx_index;
21+
sizes.filter_index = 0;
22+
if (n_indexes > 0) {
23+
int64_t max_cache = std::min(nTotalCache / 8, max_filter_index_cache << 20);
24+
sizes.filter_index = max_cache / n_indexes;
25+
nTotalCache -= sizes.filter_index * n_indexes;
26+
}
27+
sizes.coins_db = std::min(nTotalCache / 2, (nTotalCache / 4) + (1 << 23)); // use 25%-50% of the remainder for disk cache
28+
sizes.coins_db = std::min(sizes.coins_db, nMaxCoinsDBCache << 20); // cap total coins db cache
29+
nTotalCache -= sizes.coins_db;
30+
sizes.coins = nTotalCache; // the rest goes to in-memory cache
31+
return sizes;
32+
}

src/node/caches.h

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Copyright (c) 2021 The Bitcoin Core developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
#ifndef BITCOIN_NODE_CACHES_H
6+
#define BITCOIN_NODE_CACHES_H
7+
8+
#include <cstddef>
9+
#include <cstdint>
10+
11+
class ArgsManager;
12+
13+
struct CacheSizes {
14+
int64_t block_tree_db;
15+
int64_t coins_db;
16+
int64_t coins;
17+
int64_t tx_index;
18+
int64_t filter_index;
19+
};
20+
CacheSizes CalculateCacheSizes(const ArgsManager& args, size_t n_indexes = 0);
21+
22+
#endif // BITCOIN_NODE_CACHES_H

src/node/chainstate.cpp

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
// Copyright (c) 2021 The Bitcoin Core developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
#include <node/chainstate.h>
6+
7+
#include <consensus/params.h>
8+
#include <node/blockstorage.h>
9+
#include <validation.h>
10+
11+
std::optional<ChainstateLoadingError> LoadChainstate(bool fReset,
12+
ChainstateManager& chainman,
13+
CTxMemPool* mempool,
14+
bool fPruneMode,
15+
const Consensus::Params& consensus_params,
16+
bool fReindexChainState,
17+
int64_t nBlockTreeDBCache,
18+
int64_t nCoinDBCache,
19+
int64_t nCoinCacheUsage,
20+
bool block_tree_db_in_memory,
21+
bool coins_db_in_memory,
22+
std::function<bool()> shutdown_requested,
23+
std::function<void()> coins_error_cb)
24+
{
25+
auto is_coinsview_empty = [&](CChainState* chainstate) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) {
26+
return fReset || fReindexChainState || chainstate->CoinsTip().GetBestBlock().IsNull();
27+
};
28+
29+
LOCK(cs_main);
30+
chainman.InitializeChainstate(mempool);
31+
chainman.m_total_coinstip_cache = nCoinCacheUsage;
32+
chainman.m_total_coinsdb_cache = nCoinDBCache;
33+
34+
UnloadBlockIndex(mempool, chainman);
35+
36+
auto& pblocktree{chainman.m_blockman.m_block_tree_db};
37+
// new CBlockTreeDB tries to delete the existing file, which
38+
// fails if it's still open from the previous loop. Close it first:
39+
pblocktree.reset();
40+
pblocktree.reset(new CBlockTreeDB(nBlockTreeDBCache, block_tree_db_in_memory, fReset));
41+
42+
if (fReset) {
43+
pblocktree->WriteReindexing(true);
44+
//If we're reindexing in prune mode, wipe away unusable block files and all undo data files
45+
if (fPruneMode)
46+
CleanupBlockRevFiles();
47+
}
48+
49+
if (shutdown_requested && shutdown_requested()) return ChainstateLoadingError::SHUTDOWN_PROBED;
50+
51+
// LoadBlockIndex will load fHavePruned if we've ever removed a
52+
// block file from disk.
53+
// Note that it also sets fReindex based on the disk flag!
54+
// From here on out fReindex and fReset mean something different!
55+
if (!chainman.LoadBlockIndex()) {
56+
if (shutdown_requested && shutdown_requested()) return ChainstateLoadingError::SHUTDOWN_PROBED;
57+
return ChainstateLoadingError::ERROR_LOADING_BLOCK_DB;
58+
}
59+
60+
if (!chainman.BlockIndex().empty() &&
61+
!chainman.m_blockman.LookupBlockIndex(consensus_params.hashGenesisBlock)) {
62+
return ChainstateLoadingError::ERROR_BAD_GENESIS_BLOCK;
63+
}
64+
65+
// Check for changed -prune state. What we are concerned about is a user who has pruned blocks
66+
// in the past, but is now trying to run unpruned.
67+
if (fHavePruned && !fPruneMode) {
68+
return ChainstateLoadingError::ERROR_PRUNED_NEEDS_REINDEX;
69+
}
70+
71+
// At this point blocktree args are consistent with what's on disk.
72+
// If we're not mid-reindex (based on disk + args), add a genesis block on disk
73+
// (otherwise we use the one already on disk).
74+
// This is called again in ThreadImport after the reindex completes.
75+
if (!fReindex && !chainman.ActiveChainstate().LoadGenesisBlock()) {
76+
return ChainstateLoadingError::ERROR_LOAD_GENESIS_BLOCK_FAILED;
77+
}
78+
79+
// At this point we're either in reindex or we've loaded a useful
80+
// block tree into BlockIndex()!
81+
82+
for (CChainState* chainstate : chainman.GetAll()) {
83+
chainstate->InitCoinsDB(
84+
/* cache_size_bytes */ nCoinDBCache,
85+
/* in_memory */ coins_db_in_memory,
86+
/* should_wipe */ fReset || fReindexChainState);
87+
88+
if (coins_error_cb) {
89+
chainstate->CoinsErrorCatcher().AddReadErrCallback(coins_error_cb);
90+
}
91+
92+
// If necessary, upgrade from older database format.
93+
// This is a no-op if we cleared the coinsviewdb with -reindex or -reindex-chainstate
94+
if (!chainstate->CoinsDB().Upgrade()) {
95+
return ChainstateLoadingError::ERROR_CHAINSTATE_UPGRADE_FAILED;
96+
}
97+
98+
// ReplayBlocks is a no-op if we cleared the coinsviewdb with -reindex or -reindex-chainstate
99+
if (!chainstate->ReplayBlocks()) {
100+
return ChainstateLoadingError::ERROR_REPLAYBLOCKS_FAILED;
101+
}
102+
103+
// The on-disk coinsdb is now in a good state, create the cache
104+
chainstate->InitCoinsCache(nCoinCacheUsage);
105+
assert(chainstate->CanFlushToDisk());
106+
107+
if (!is_coinsview_empty(chainstate)) {
108+
// LoadChainTip initializes the chain based on CoinsTip()'s best block
109+
if (!chainstate->LoadChainTip()) {
110+
return ChainstateLoadingError::ERROR_LOADCHAINTIP_FAILED;
111+
}
112+
assert(chainstate->m_chain.Tip() != nullptr);
113+
}
114+
}
115+
116+
if (!fReset) {
117+
auto chainstates{chainman.GetAll()};
118+
if (std::any_of(chainstates.begin(), chainstates.end(),
119+
[](const CChainState* cs) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { return cs->NeedsRedownload(); })) {
120+
return ChainstateLoadingError::ERROR_BLOCKS_WITNESS_INSUFFICIENTLY_VALIDATED;
121+
}
122+
}
123+
124+
return std::nullopt;
125+
}
126+
127+
std::optional<ChainstateLoadVerifyError> VerifyLoadedChainstate(ChainstateManager& chainman,
128+
bool fReset,
129+
bool fReindexChainState,
130+
const Consensus::Params& consensus_params,
131+
unsigned int check_blocks,
132+
unsigned int check_level,
133+
std::function<int64_t()> get_unix_time_seconds)
134+
{
135+
auto is_coinsview_empty = [&](CChainState* chainstate) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) {
136+
return fReset || fReindexChainState || chainstate->CoinsTip().GetBestBlock().IsNull();
137+
};
138+
139+
LOCK(cs_main);
140+
141+
for (CChainState* chainstate : chainman.GetAll()) {
142+
if (!is_coinsview_empty(chainstate)) {
143+
const CBlockIndex* tip = chainstate->m_chain.Tip();
144+
if (tip && tip->nTime > get_unix_time_seconds() + 2 * 60 * 60) {
145+
return ChainstateLoadVerifyError::ERROR_BLOCK_FROM_FUTURE;
146+
}
147+
148+
if (!CVerifyDB().VerifyDB(
149+
*chainstate, consensus_params, chainstate->CoinsDB(),
150+
check_level,
151+
check_blocks)) {
152+
return ChainstateLoadVerifyError::ERROR_CORRUPTED_BLOCK_DB;
153+
}
154+
}
155+
}
156+
157+
return std::nullopt;
158+
}

src/node/chainstate.h

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
// Copyright (c) 2021 The Bitcoin Core developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
#ifndef BITCOIN_NODE_CHAINSTATE_H
6+
#define BITCOIN_NODE_CHAINSTATE_H
7+
8+
#include <cstdint>
9+
#include <functional>
10+
#include <optional>
11+
12+
class ChainstateManager;
13+
namespace Consensus {
14+
struct Params;
15+
}
16+
class CTxMemPool;
17+
18+
enum class ChainstateLoadingError {
19+
ERROR_LOADING_BLOCK_DB,
20+
ERROR_BAD_GENESIS_BLOCK,
21+
ERROR_PRUNED_NEEDS_REINDEX,
22+
ERROR_LOAD_GENESIS_BLOCK_FAILED,
23+
ERROR_CHAINSTATE_UPGRADE_FAILED,
24+
ERROR_REPLAYBLOCKS_FAILED,
25+
ERROR_LOADCHAINTIP_FAILED,
26+
ERROR_GENERIC_BLOCKDB_OPEN_FAILED,
27+
ERROR_BLOCKS_WITNESS_INSUFFICIENTLY_VALIDATED,
28+
SHUTDOWN_PROBED,
29+
};
30+
31+
/** This sequence can have 4 types of outcomes:
32+
*
33+
* 1. Success
34+
* 2. Shutdown requested
35+
* - nothing failed but a shutdown was triggered in the middle of the
36+
* sequence
37+
* 3. Soft failure
38+
* - a failure that might be recovered from with a reindex
39+
* 4. Hard failure
40+
* - a failure that definitively cannot be recovered from with a reindex
41+
*
42+
* Currently, LoadChainstate returns a std::optional<ChainstateLoadingError>
43+
* which:
44+
*
45+
* - if has_value()
46+
* - Either "Soft failure", "Hard failure", or "Shutdown requested",
47+
* differentiable by the specific enumerator.
48+
*
49+
* Note that a return value of SHUTDOWN_PROBED means ONLY that "during
50+
* this sequence, when we explicitly checked shutdown_requested() at
51+
* arbitrary points, one of those calls returned true". Therefore, a
52+
* return value other than SHUTDOWN_PROBED does not guarantee that
53+
* shutdown_requested() hasn't been called indirectly.
54+
* - else
55+
* - Success!
56+
*/
57+
std::optional<ChainstateLoadingError> LoadChainstate(bool fReset,
58+
ChainstateManager& chainman,
59+
CTxMemPool* mempool,
60+
bool fPruneMode,
61+
const Consensus::Params& consensus_params,
62+
bool fReindexChainState,
63+
int64_t nBlockTreeDBCache,
64+
int64_t nCoinDBCache,
65+
int64_t nCoinCacheUsage,
66+
bool block_tree_db_in_memory,
67+
bool coins_db_in_memory,
68+
std::function<bool()> shutdown_requested = nullptr,
69+
std::function<void()> coins_error_cb = nullptr);
70+
71+
enum class ChainstateLoadVerifyError {
72+
ERROR_BLOCK_FROM_FUTURE,
73+
ERROR_CORRUPTED_BLOCK_DB,
74+
ERROR_GENERIC_FAILURE,
75+
};
76+
77+
std::optional<ChainstateLoadVerifyError> VerifyLoadedChainstate(ChainstateManager& chainman,
78+
bool fReset,
79+
bool fReindexChainState,
80+
const Consensus::Params& consensus_params,
81+
unsigned int check_blocks,
82+
unsigned int check_level,
83+
std::function<int64_t()> get_unix_time_seconds);
84+
85+
#endif // BITCOIN_NODE_CHAINSTATE_H

src/rpc/blockchain.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1387,7 +1387,7 @@ static RPCHelpMan verifychain()
13871387

13881388
CChainState& active_chainstate = chainman.ActiveChainstate();
13891389
return CVerifyDB().VerifyDB(
1390-
active_chainstate, Params(), active_chainstate.CoinsTip(), check_level, check_depth);
1390+
active_chainstate, Params().GetConsensus(), active_chainstate.CoinsTip(), check_level, check_depth);
13911391
},
13921392
};
13931393
}

src/test/util/setup_common.cpp

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,16 @@
1717
#include <net_processing.h>
1818
#include <node/miner.h>
1919
#include <noui.h>
20+
#include <node/blockstorage.h>
21+
#include <node/chainstate.h>
2022
#include <policy/fees.h>
2123
#include <pow.h>
2224
#include <rpc/blockchain.h>
2325
#include <rpc/register.h>
2426
#include <rpc/server.h>
2527
#include <scheduler.h>
2628
#include <script/sigcache.h>
29+
#include <shutdown.h>
2730
#include <streams.h>
2831
#include <txdb.h>
2932
#include <util/strencodings.h>
@@ -143,8 +146,10 @@ ChainTestingSetup::ChainTestingSetup(const std::string& chainName, const std::ve
143146
m_node.fee_estimator = std::make_unique<CBlockPolicyEstimator>();
144147
m_node.mempool = std::make_unique<CTxMemPool>(m_node.fee_estimator.get(), 1);
145148

149+
m_cache_sizes = CalculateCacheSizes(m_args);
150+
146151
m_node.chainman = std::make_unique<ChainstateManager>();
147-
m_node.chainman->m_blockman.m_block_tree_db = std::make_unique<CBlockTreeDB>(1 << 20, true);
152+
m_node.chainman->m_blockman.m_block_tree_db = std::make_unique<CBlockTreeDB>(m_cache_sizes.block_tree_db, true);
148153

149154
// Start script-checking threads. Set g_parallel_script_checks to true so they are used.
150155
constexpr int script_check_threads = 2;
@@ -177,15 +182,18 @@ TestingSetup::TestingSetup(const std::string& chainName, const std::vector<const
177182
// instead of unit tests, but for now we need these here.
178183
RegisterAllCoreRPCCommands(tableRPC);
179184

180-
m_node.chainman->InitializeChainstate(m_node.mempool.get());
181-
m_node.chainman->ActiveChainstate().InitCoinsDB(
182-
/*cache_size_bytes=*/1 << 23, /*in_memory=*/true, /*should_wipe=*/false);
183-
assert(!m_node.chainman->ActiveChainstate().CanFlushToDisk());
184-
m_node.chainman->ActiveChainstate().InitCoinsCache(1 << 23);
185-
assert(m_node.chainman->ActiveChainstate().CanFlushToDisk());
186-
if (!m_node.chainman->ActiveChainstate().LoadGenesisBlock()) {
187-
throw std::runtime_error("LoadGenesisBlock failed.");
188-
}
185+
auto rv = LoadChainstate(fReindex.load(),
186+
*Assert(m_node.chainman.get()),
187+
Assert(m_node.mempool.get()),
188+
fPruneMode,
189+
chainparams.GetConsensus(),
190+
m_args.GetBoolArg("-reindex-chainstate", false),
191+
m_cache_sizes.block_tree_db,
192+
m_cache_sizes.coins_db,
193+
m_cache_sizes.coins,
194+
true,
195+
true);
196+
assert(!rv.has_value());
189197

190198
BlockValidationState state;
191199
if (!m_node.chainman->ActiveChainstate().ActivateBestChain(state)) {

0 commit comments

Comments
 (0)