Skip to content

Commit 2bb3c9c

Browse files
committed
merge bitcoin#17737: Add ChainstateManager, remove BlockManager global
Co-authored-by: "UdjinM6 <UdjinM6@users.noreply.github.com>"
1 parent f1e1312 commit 2bb3c9c

File tree

8 files changed

+544
-135
lines changed

8 files changed

+544
-135
lines changed

src/Makefile.test.include

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,7 @@ BITCOIN_TESTS =\
229229
test/uint256_tests.cpp \
230230
test/util_tests.cpp \
231231
test/validation_block_tests.cpp \
232+
test/validation_chainstatemanager_tests.cpp \
232233
test/validation_flush_tests.cpp \
233234
test/versionbits_tests.cpp
234235

src/init.cpp

Lines changed: 125 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -293,13 +293,12 @@ void PrepareShutdown(NodeContext& node)
293293
}
294294

295295
// FlushStateToDisk generates a ChainStateFlushed callback, which we should avoid missing
296-
//
297-
// g_chainstate is referenced here directly (instead of ::ChainstateActive()) because it
298-
// may not have been initialized yet.
299296
{
300297
LOCK(cs_main);
301-
if (g_chainstate && g_chainstate->CanFlushToDisk()) {
302-
g_chainstate->ForceFlushStateToDisk();
298+
for (CChainState* chainstate : g_chainman.GetAll()) {
299+
if (chainstate->CanFlushToDisk()) {
300+
chainstate->ForceFlushStateToDisk();
301+
}
303302
}
304303
}
305304

@@ -323,9 +322,11 @@ void PrepareShutdown(NodeContext& node)
323322

324323
{
325324
LOCK(cs_main);
326-
if (g_chainstate && g_chainstate->CanFlushToDisk()) {
327-
g_chainstate->ForceFlushStateToDisk();
328-
g_chainstate->ResetCoinsViews();
325+
for (CChainState* chainstate : g_chainman.GetAll()) {
326+
if (chainstate->CanFlushToDisk()) {
327+
chainstate->ForceFlushStateToDisk();
328+
chainstate->ResetCoinsViews();
329+
}
329330
}
330331
pblocktree.reset();
331332
llmq::DestroyLLMQSystem();
@@ -931,11 +932,17 @@ static void ThreadImport(std::vector<fs::path> vImportFiles)
931932
}
932933

933934
// scan for better chains in the block chain database, that are not yet connected in the active best chain
934-
CValidationState state;
935-
if (!ActivateBestChain(state, chainparams)) {
936-
LogPrintf("Failed to connect best block (%s)\n", FormatStateMessage(state));
937-
StartShutdown();
938-
return;
935+
936+
// We can't hold cs_main during ActivateBestChain even though we're accessing
937+
// the g_chainman unique_ptrs since ABC requires us not to be holding cs_main, so retrieve
938+
// the relevant pointers before the ABC call.
939+
for (CChainState* chainstate : WITH_LOCK(::cs_main, return g_chainman.GetAll())) {
940+
CValidationState state;
941+
if (!chainstate->ActivateBestChain(state, chainparams, nullptr)) {
942+
LogPrintf("Failed to connect best block (%s)\n", FormatStateMessage(state));
943+
StartShutdown();
944+
return;
945+
}
939946
}
940947

941948
if (gArgs.GetBoolArg("-stopafterblockimport", DEFAULT_STOPAFTERBLOCKIMPORT)) {
@@ -1957,17 +1964,20 @@ bool AppInitMain(const util::Ref& context, NodeContext& node, interfaces::BlockA
19571964

19581965
while (!fLoaded && !ShutdownRequested()) {
19591966
bool fReset = fReindex;
1967+
auto is_coinsview_empty = [&](CChainState* chainstate) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) {
1968+
return fReset || fReindexChainState || chainstate->CoinsTip().GetBestBlock().IsNull();
1969+
};
19601970
bilingual_str strLoadError;
19611971

19621972
uiInterface.InitMessage(_("Loading block index...").translated);
19631973

19641974
do {
1975+
bool failed_verification = false;
19651976
const int64_t load_block_index_start_time = GetTimeMillis();
1966-
bool is_coinsview_empty;
1977+
19671978
try {
19681979
LOCK(cs_main);
1969-
// This statement makes ::ChainstateActive() usable.
1970-
g_chainstate = MakeUnique<CChainState>();
1980+
g_chainman.InitializeChainstate();
19711981
UnloadBlockIndex();
19721982

19731983
// new CBlockTreeDB tries to delete the existing file, which
@@ -2056,82 +2066,108 @@ bool AppInitMain(const util::Ref& context, NodeContext& node, interfaces::BlockA
20562066
// At this point we're either in reindex or we've loaded a useful
20572067
// block tree into BlockIndex()!
20582068

2059-
::ChainstateActive().InitCoinsDB(
2060-
/* cache_size_bytes */ nCoinDBCache,
2061-
/* in_memory */ false,
2062-
/* should_wipe */ fReset || fReindexChainState);
2063-
2064-
::ChainstateActive().CoinsErrorCatcher().AddReadErrCallback([]() {
2065-
uiInterface.ThreadSafeMessageBox(
2066-
_("Error reading from database, shutting down."),
2067-
"", CClientUIInterface::MSG_ERROR);
2068-
});
2069-
2070-
// If necessary, upgrade from older database format.
2071-
// This is a no-op if we cleared the coinsviewdb with -reindex or -reindex-chainstate
2072-
if (!::ChainstateActive().CoinsDB().Upgrade()) {
2073-
strLoadError = _("Error upgrading chainstate database");
2074-
break;
2075-
}
2076-
2077-
// ReplayBlocks is a no-op if we cleared the coinsviewdb with -reindex or -reindex-chainstate
2078-
if (!::ChainstateActive().ReplayBlocks(chainparams)) {
2079-
strLoadError = _("Unable to replay blocks. You will need to rebuild the database using -reindex-chainstate.");
2080-
break;
2081-
}
2082-
2083-
// The on-disk coinsdb is now in a good state, create the cache
2084-
::ChainstateActive().InitCoinsCache();
2085-
assert(::ChainstateActive().CanFlushToDisk());
2069+
bool failed_chainstate_init = false;
2070+
for (CChainState* chainstate : g_chainman.GetAll()) {
2071+
LogPrintf("Initializing chainstate %s\n", chainstate->ToString());
2072+
chainstate->InitCoinsDB(
2073+
/* cache_size_bytes */ nCoinDBCache,
2074+
/* in_memory */ false,
2075+
/* should_wipe */ fReset || fReindexChainState);
2076+
2077+
chainstate->CoinsErrorCatcher().AddReadErrCallback([]() {
2078+
uiInterface.ThreadSafeMessageBox(
2079+
_("Error reading from database, shutting down."),
2080+
"", CClientUIInterface::MSG_ERROR);
2081+
});
2082+
2083+
// If necessary, upgrade from older database format.
2084+
// This is a no-op if we cleared the coinsviewdb with -reindex or -reindex-chainstate
2085+
if (!chainstate->CoinsDB().Upgrade()) {
2086+
strLoadError = _("Error upgrading chainstate database");
2087+
failed_chainstate_init = true;
2088+
break;
2089+
}
20862090

2087-
// flush evodb
2088-
if (!evoDb->CommitRootTransaction()) {
2089-
strLoadError = _("Failed to commit EvoDB");
2090-
break;
2091-
}
2091+
// ReplayBlocks is a no-op if we cleared the coinsviewdb with -reindex or -reindex-chainstate
2092+
if (!chainstate->ReplayBlocks(chainparams)) {
2093+
strLoadError = _("Unable to replay blocks. You will need to rebuild the database using -reindex-chainstate.");
2094+
failed_chainstate_init = true;
2095+
break;
2096+
}
20922097

2093-
is_coinsview_empty = fReset || fReindexChainState ||
2094-
::ChainstateActive().CoinsTip().GetBestBlock().IsNull();
2095-
if (!is_coinsview_empty) {
2096-
// LoadChainTip initializes the chain based on CoinsTip()'s best block
2097-
if (!::ChainstateActive().LoadChainTip(chainparams)) {
2098-
strLoadError = _("Error initializing block database");
2098+
// The on-disk coinsdb is now in a good state, create the cache
2099+
chainstate->InitCoinsCache();
2100+
assert(chainstate->CanFlushToDisk());
2101+
2102+
// flush evodb
2103+
// TODO: CEvoDB instance should probably be a part of CChainState
2104+
// (for multiple chainstates to actually work in parallel)
2105+
// and not a global
2106+
if (&::ChainstateActive() == chainstate && !evoDb->CommitRootTransaction()) {
2107+
strLoadError = _("Failed to commit EvoDB");
2108+
failed_chainstate_init = true;
20992109
break;
21002110
}
2101-
assert(::ChainActive().Tip() != NULL);
2111+
2112+
if (!is_coinsview_empty(chainstate)) {
2113+
// LoadChainTip initializes the chain based on CoinsTip()'s best block
2114+
if (!chainstate->LoadChainTip(chainparams)) {
2115+
strLoadError = _("Error initializing block database");
2116+
failed_chainstate_init = true;
2117+
break; // out of the per-chainstate loop
2118+
}
2119+
assert(chainstate->m_chain.Tip() != nullptr);
2120+
}
21022121
}
21032122

2104-
if (is_coinsview_empty && !evoDb->IsEmpty()) {
2105-
// EvoDB processed some blocks earlier but we have no blocks anymore, something is wrong
2106-
strLoadError = _("Error initializing block database");
2107-
break;
2123+
if (failed_chainstate_init) {
2124+
break; // out of the chainstate activation do-while
21082125
}
21092126

21102127
if (!deterministicMNManager->UpgradeDBIfNeeded() || !llmq::quorumBlockProcessor->UpgradeDB()) {
21112128
strLoadError = _("Error upgrading evo database");
21122129
break;
21132130
}
21142131

2115-
if (!is_coinsview_empty) {
2116-
uiInterface.InitMessage(_("Verifying blocks...").translated);
2117-
if (fHavePruned && gArgs.GetArg("-checkblocks", DEFAULT_CHECKBLOCKS) > MIN_BLOCKS_TO_KEEP) {
2118-
LogPrintf("Prune: pruned datadir may not have more than %d blocks; only checking available blocks\n",
2119-
MIN_BLOCKS_TO_KEEP);
2120-
}
2121-
2122-
CBlockIndex* tip = ::ChainActive().Tip();
2123-
RPCNotifyBlockChange(true, tip);
2124-
if (tip && tip->nTime > GetAdjustedTime() + 2 * 60 * 60) {
2125-
strLoadError = _("The block database contains a block which appears to be from the future. "
2126-
"This may be due to your computer's date and time being set incorrectly. "
2127-
"Only rebuild the block database if you are sure that your computer's date and time are correct");
2128-
break;
2129-
}
2130-
2131-
if (!CVerifyDB().VerifyDB(chainparams, &::ChainstateActive().CoinsDB(), gArgs.GetArg("-checklevel", DEFAULT_CHECKLEVEL),
2132-
gArgs.GetArg("-checkblocks", DEFAULT_CHECKBLOCKS))) {
2133-
strLoadError = _("Corrupted block database detected");
2134-
break;
2132+
for (CChainState* chainstate : g_chainman.GetAll()) {
2133+
if (!is_coinsview_empty(chainstate)) {
2134+
uiInterface.InitMessage(_("Verifying blocks...").translated);
2135+
if (fHavePruned && gArgs.GetArg("-checkblocks", DEFAULT_CHECKBLOCKS) > MIN_BLOCKS_TO_KEEP) {
2136+
LogPrintf("Prune: pruned datadir may not have more than %d blocks; only checking available blocks\n",
2137+
MIN_BLOCKS_TO_KEEP);
2138+
}
2139+
2140+
CBlockIndex* tip = chainstate->m_chain.Tip();
2141+
RPCNotifyBlockChange(true, tip);
2142+
if (tip && tip->nTime > GetAdjustedTime() + 2 * 60 * 60) {
2143+
strLoadError = _("The block database contains a block which appears to be from the future. "
2144+
"This may be due to your computer's date and time being set incorrectly. "
2145+
"Only rebuild the block database if you are sure that your computer's date and time are correct");
2146+
failed_verification = true;
2147+
break;
2148+
}
2149+
2150+
// Only verify the DB of the active chainstate. This is fixed in later
2151+
// work when we allow VerifyDB to be parameterized by chainstate.
2152+
if (&::ChainstateActive() == chainstate &&
2153+
!CVerifyDB().VerifyDB(
2154+
chainparams, &chainstate->CoinsDB(),
2155+
gArgs.GetArg("-checklevel", DEFAULT_CHECKLEVEL),
2156+
gArgs.GetArg("-checkblocks", DEFAULT_CHECKBLOCKS))) {
2157+
strLoadError = _("Corrupted block database detected");
2158+
failed_verification = true;
2159+
break;
2160+
}
2161+
} else {
2162+
// TODO: CEvoDB instance should probably be a part of CChainState
2163+
// (for multiple chainstates to actually work in parallel)
2164+
// and not a global
2165+
if (&::ChainstateActive() == chainstate && !evoDb->IsEmpty()) {
2166+
// EvoDB processed some blocks earlier but we have no blocks anymore, something is wrong
2167+
strLoadError = _("Error initializing block database");
2168+
failed_verification = true;
2169+
break;
2170+
}
21352171
}
21362172

21372173
if (gArgs.GetArg("-checklevel", DEFAULT_CHECKLEVEL) >= 3) {
@@ -2141,11 +2177,14 @@ bool AppInitMain(const util::Ref& context, NodeContext& node, interfaces::BlockA
21412177
} catch (const std::exception& e) {
21422178
LogPrintf("%s\n", e.what());
21432179
strLoadError = _("Error opening block database");
2180+
failed_verification = true;
21442181
break;
21452182
}
21462183

2147-
fLoaded = true;
2148-
LogPrintf(" block index %15dms\n", GetTimeMillis() - load_block_index_start_time);
2184+
if (!failed_verification) {
2185+
fLoaded = true;
2186+
LogPrintf(" block index %15dms\n", GetTimeMillis() - load_block_index_start_time);
2187+
}
21492188
} while(false);
21502189

21512190
if (!fLoaded && !ShutdownRequested()) {
@@ -2217,8 +2256,11 @@ bool AppInitMain(const util::Ref& context, NodeContext& node, interfaces::BlockA
22172256
LogPrintf("Unsetting NODE_NETWORK on prune mode\n");
22182257
nLocalServices = ServiceFlags(nLocalServices & ~NODE_NETWORK);
22192258
if (!fReindex) {
2220-
uiInterface.InitMessage(_("Pruning blockstore...").translated);
2221-
::ChainstateActive().PruneAndFlush();
2259+
LOCK(cs_main);
2260+
for (CChainState* chainstate : g_chainman.GetAll()) {
2261+
uiInterface.InitMessage(_("Pruning blockstore...").translated);
2262+
chainstate->PruneAndFlush();
2263+
}
22222264
}
22232265
}
22242266

src/qt/test/apptests.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ void AppTests::appTests()
8888
LogInstance().DisconnectTestLogger();
8989
AbortShutdown();
9090
UnloadBlockIndex();
91+
WITH_LOCK(::cs_main, g_chainman.Reset());
9192
}
9293

9394
//! Entry point for BitcoinGUI tests.

src/rpc/blockchain.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1599,7 +1599,7 @@ static UniValue getchaintips(const JSONRPCRequest& request)
15991599
/*
16001600
* Idea: the set of chain tips is ::ChainActive().tip, plus orphan blocks which do not have another orphan building off of them.
16011601
* Algorithm:
1602-
* - Make one pass through g_blockman.m_block_index, picking out the orphan blocks, and also storing a set of the orphan block's pprev pointers.
1602+
* - Make one pass through BlockIndex(), picking out the orphan blocks, and also storing a set of the orphan block's pprev pointers.
16031603
* - Iterate through the orphan blocks. If the block isn't pointed to by another orphan, it is a chain tip.
16041604
* - add ::ChainActive().Tip()
16051605
*/

src/test/util/setup_common.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,8 @@ TestingSetup::TestingSetup(const std::string& chainName) : BasicTestingSetup(cha
134134
m_node.connman = MakeUnique<CConnman>(0x1337, 0x1337); // Deterministic randomness for tests.
135135
m_node.peer_logic = MakeUnique<PeerLogicValidation>(m_node.connman.get(), m_node.banman.get(), *m_node.scheduler, *m_node.mempool, false);
136136
pblocktree.reset(new CBlockTreeDB(1 << 20, true));
137-
g_chainstate = MakeUnique<CChainState>();
137+
138+
g_chainman.InitializeChainstate();
138139
::ChainstateActive().InitCoinsDB(
139140
/* cache_size_bytes */ 1 << 23, /* in_memory */ true, /* should_wipe */ false);
140141
assert(!::ChainstateActive().CanFlushToDisk());
@@ -178,8 +179,8 @@ TestingSetup::~TestingSetup()
178179
m_node.mempool = nullptr;
179180
m_node.scheduler.reset();
180181
UnloadBlockIndex();
181-
g_chainstate.reset();
182182
llmq::DestroyLLMQSystem();
183+
m_node.chainman->Reset();
183184
pblocktree.reset();
184185
}
185186

0 commit comments

Comments
 (0)