Skip to content

Commit 10358a3

Browse files
author
MarcoFalke
committed
Merge #17737: Add ChainstateManager, remove BlockManager global
c9017ce protect g_chainman with cs_main (James O'Beirne) 2b081c4 test: add basic tests for ChainstateManager (James O'Beirne) 4ae29f5 use ChainstateManager to initialize chainstate (James O'Beirne) 5b690f0 refactor: move RewindBlockIndex to CChainState (James O'Beirne) 89cdf4d validation: introduce unused ChainstateManager (James O'Beirne) 8e2ecfe validation: add CChainState.m_from_snapshot_blockhash (James O'Beirne) Pull request description: This is part of the [assumeutxo project](https://github.com/bitcoin/bitcoin/projects/11): Parent PR: #15606 Issue: #15605 Specification: https://github.com/jamesob/assumeutxo-docs/tree/master/proposal --- This changeset introduces `ChainstateManager`, which is responsible for creating and managing access to multiple chainstates. Until we allow chainstate creation from UTXO snapshots (next assumeutxo PR?) it's basically unnecessary, but it is a prerequisite for background IBD support. Changes are also made to the initialization process to make use of `g_chainman` and thus clear the way for multiple chainstates being loaded on startup. One immediate benefit of this change is that we no longer have the `g_blockman` global, but instead have the ChainstateManager inject a reference of its shared BlockManager into any chainstate it creates. Another immediate benefit is that uses of `ChainActive()` and `ChainstateActive()` are now covered by lock annotations. Because use of `g_chainman` is annotated to require cs_main, these two functions subsequently follow. Because of whitespace changes, this diff looks bigger than it is. E.g., 4813167d98 is most easily reviewed with ```sh git show --color-moved=dimmed_zebra -w 4813167d98 ``` ACKs for top commit: MarcoFalke: re-ACK c9017ce 📙 fjahr: Code Review Re-ACK c9017ce ariard: Code Review ACK c9017ce ryanofsky: Code review ACK c9017ce. No changes since last review other than a straight rebase Tree-SHA512: 3f250d0dc95d4bfd70852ef1e39e081a4a9b71a4453f276e6d474c2ae06ad6ae6a32b4173084fe499e1e9af72dd9007f4a8a375c63ce9ac472ffeaada41ab508
2 parents a9213bb + c9017ce commit 10358a3

File tree

9 files changed

+544
-149
lines changed

9 files changed

+544
-149
lines changed

src/Makefile.test.include

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,7 @@ BITCOIN_TESTS =\
245245
test/uint256_tests.cpp \
246246
test/util_tests.cpp \
247247
test/validation_block_tests.cpp \
248+
test/validation_chainstatemanager_tests.cpp \
248249
test/validation_flush_tests.cpp \
249250
test/validationinterface_tests.cpp \
250251
test/versionbits_tests.cpp

src/init.cpp

Lines changed: 129 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -243,13 +243,12 @@ void Shutdown(NodeContext& node)
243243
}
244244

245245
// FlushStateToDisk generates a ChainStateFlushed callback, which we should avoid missing
246-
//
247-
// g_chainstate is referenced here directly (instead of ::ChainstateActive()) because it
248-
// may not have been initialized yet.
249246
{
250247
LOCK(cs_main);
251-
if (g_chainstate && g_chainstate->CanFlushToDisk()) {
252-
g_chainstate->ForceFlushStateToDisk();
248+
for (CChainState* chainstate : g_chainman.GetAll()) {
249+
if (chainstate->CanFlushToDisk()) {
250+
chainstate->ForceFlushStateToDisk();
251+
}
253252
}
254253
}
255254

@@ -273,9 +272,11 @@ void Shutdown(NodeContext& node)
273272

274273
{
275274
LOCK(cs_main);
276-
if (g_chainstate && g_chainstate->CanFlushToDisk()) {
277-
g_chainstate->ForceFlushStateToDisk();
278-
g_chainstate->ResetCoinsViews();
275+
for (CChainState* chainstate : g_chainman.GetAll()) {
276+
if (chainstate->CanFlushToDisk()) {
277+
chainstate->ForceFlushStateToDisk();
278+
chainstate->ResetCoinsViews();
279+
}
279280
}
280281
pblocktree.reset();
281282
}
@@ -719,11 +720,17 @@ static void ThreadImport(std::vector<fs::path> vImportFiles)
719720
}
720721

721722
// scan for better chains in the block chain database, that are not yet connected in the active best chain
722-
BlockValidationState state;
723-
if (!ActivateBestChain(state, chainparams)) {
724-
LogPrintf("Failed to connect best block (%s)\n", state.ToString());
725-
StartShutdown();
726-
return;
723+
724+
// We can't hold cs_main during ActivateBestChain even though we're accessing
725+
// the g_chainman unique_ptrs since ABC requires us not to be holding cs_main, so retrieve
726+
// the relevant pointers before the ABC call.
727+
for (CChainState* chainstate : WITH_LOCK(::cs_main, return g_chainman.GetAll())) {
728+
BlockValidationState state;
729+
if (!chainstate->ActivateBestChain(state, chainparams, nullptr)) {
730+
LogPrintf("Failed to connect best block (%s)\n", state.ToString());
731+
StartShutdown();
732+
return;
733+
}
727734
}
728735

729736
if (gArgs.GetBoolArg("-stopafterblockimport", DEFAULT_STOPAFTERBLOCKIMPORT)) {
@@ -1513,17 +1520,18 @@ bool AppInitMain(NodeContext& node)
15131520
bool fLoaded = false;
15141521
while (!fLoaded && !ShutdownRequested()) {
15151522
bool fReset = fReindex;
1523+
auto is_coinsview_empty = [&](CChainState* chainstate) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) {
1524+
return fReset || fReindexChainState || chainstate->CoinsTip().GetBestBlock().IsNull();
1525+
};
15161526
std::string strLoadError;
15171527

15181528
uiInterface.InitMessage(_("Loading block index...").translated);
15191529

15201530
do {
15211531
const int64_t load_block_index_start_time = GetTimeMillis();
1522-
bool is_coinsview_empty;
15231532
try {
15241533
LOCK(cs_main);
1525-
// This statement makes ::ChainstateActive() usable.
1526-
g_chainstate = MakeUnique<CChainState>();
1534+
g_chainman.InitializeChainstate();
15271535
UnloadBlockIndex();
15281536

15291537
// new CBlockTreeDB tries to delete the existing file, which
@@ -1576,93 +1584,130 @@ bool AppInitMain(NodeContext& node)
15761584
// At this point we're either in reindex or we've loaded a useful
15771585
// block tree into BlockIndex()!
15781586

1579-
::ChainstateActive().InitCoinsDB(
1580-
/* cache_size_bytes */ nCoinDBCache,
1581-
/* in_memory */ false,
1582-
/* should_wipe */ fReset || fReindexChainState);
1583-
1584-
::ChainstateActive().CoinsErrorCatcher().AddReadErrCallback([]() {
1585-
uiInterface.ThreadSafeMessageBox(
1586-
_("Error reading from database, shutting down.").translated,
1587-
"", CClientUIInterface::MSG_ERROR);
1588-
});
1589-
1590-
// If necessary, upgrade from older database format.
1591-
// This is a no-op if we cleared the coinsviewdb with -reindex or -reindex-chainstate
1592-
if (!::ChainstateActive().CoinsDB().Upgrade()) {
1593-
strLoadError = _("Error upgrading chainstate database").translated;
1594-
break;
1595-
}
1596-
1597-
// ReplayBlocks is a no-op if we cleared the coinsviewdb with -reindex or -reindex-chainstate
1598-
if (!::ChainstateActive().ReplayBlocks(chainparams)) {
1599-
strLoadError = _("Unable to replay blocks. You will need to rebuild the database using -reindex-chainstate.").translated;
1600-
break;
1601-
}
1602-
1603-
// The on-disk coinsdb is now in a good state, create the cache
1604-
::ChainstateActive().InitCoinsCache();
1605-
assert(::ChainstateActive().CanFlushToDisk());
1587+
bool failed_chainstate_init = false;
1588+
1589+
for (CChainState* chainstate : g_chainman.GetAll()) {
1590+
LogPrintf("Initializing chainstate %s\n", chainstate->ToString());
1591+
chainstate->InitCoinsDB(
1592+
/* cache_size_bytes */ nCoinDBCache,
1593+
/* in_memory */ false,
1594+
/* should_wipe */ fReset || fReindexChainState);
1595+
1596+
chainstate->CoinsErrorCatcher().AddReadErrCallback([]() {
1597+
uiInterface.ThreadSafeMessageBox(
1598+
_("Error reading from database, shutting down.").translated,
1599+
"", CClientUIInterface::MSG_ERROR);
1600+
});
1601+
1602+
// If necessary, upgrade from older database format.
1603+
// This is a no-op if we cleared the coinsviewdb with -reindex or -reindex-chainstate
1604+
if (!chainstate->CoinsDB().Upgrade()) {
1605+
strLoadError = _("Error upgrading chainstate database").translated;
1606+
failed_chainstate_init = true;
1607+
break;
1608+
}
16061609

1607-
is_coinsview_empty = fReset || fReindexChainState ||
1608-
::ChainstateActive().CoinsTip().GetBestBlock().IsNull();
1609-
if (!is_coinsview_empty) {
1610-
// LoadChainTip initializes the chain based on CoinsTip()'s best block
1611-
if (!::ChainstateActive().LoadChainTip(chainparams)) {
1612-
strLoadError = _("Error initializing block database").translated;
1610+
// ReplayBlocks is a no-op if we cleared the coinsviewdb with -reindex or -reindex-chainstate
1611+
if (!chainstate->ReplayBlocks(chainparams)) {
1612+
strLoadError = _("Unable to replay blocks. You will need to rebuild the database using -reindex-chainstate.").translated;
1613+
failed_chainstate_init = true;
16131614
break;
16141615
}
1615-
assert(::ChainActive().Tip() != nullptr);
1616+
1617+
// The on-disk coinsdb is now in a good state, create the cache
1618+
chainstate->InitCoinsCache();
1619+
assert(chainstate->CanFlushToDisk());
1620+
1621+
if (!is_coinsview_empty(chainstate)) {
1622+
// LoadChainTip initializes the chain based on CoinsTip()'s best block
1623+
if (!chainstate->LoadChainTip(chainparams)) {
1624+
strLoadError = _("Error initializing block database").translated;
1625+
failed_chainstate_init = true;
1626+
break; // out of the per-chainstate loop
1627+
}
1628+
assert(chainstate->m_chain.Tip() != nullptr);
1629+
}
1630+
}
1631+
1632+
if (failed_chainstate_init) {
1633+
break; // out of the chainstate activation do-while
16161634
}
16171635
} catch (const std::exception& e) {
16181636
LogPrintf("%s\n", e.what());
16191637
strLoadError = _("Error opening block database").translated;
16201638
break;
16211639
}
16221640

1623-
if (!fReset) {
1624-
// Note that RewindBlockIndex MUST run even if we're about to -reindex-chainstate.
1625-
// It both disconnects blocks based on ::ChainActive(), and drops block data in
1626-
// BlockIndex() based on lack of available witness data.
1627-
uiInterface.InitMessage(_("Rewinding blocks...").translated);
1628-
if (!RewindBlockIndex(chainparams)) {
1629-
strLoadError = _("Unable to rewind the database to a pre-fork state. You will need to redownload the blockchain").translated;
1630-
break;
1641+
bool failed_rewind{false};
1642+
// Can't hold cs_main while calling RewindBlockIndex, so retrieve the relevant
1643+
// chainstates beforehand.
1644+
for (CChainState* chainstate : WITH_LOCK(::cs_main, return g_chainman.GetAll())) {
1645+
if (!fReset) {
1646+
// Note that RewindBlockIndex MUST run even if we're about to -reindex-chainstate.
1647+
// It both disconnects blocks based on the chainstate, and drops block data in
1648+
// BlockIndex() based on lack of available witness data.
1649+
uiInterface.InitMessage(_("Rewinding blocks...").translated);
1650+
if (!chainstate->RewindBlockIndex(chainparams)) {
1651+
strLoadError = _(
1652+
"Unable to rewind the database to a pre-fork state. "
1653+
"You will need to redownload the blockchain").translated;
1654+
failed_rewind = true;
1655+
break; // out of the per-chainstate loop
1656+
}
16311657
}
16321658
}
16331659

1660+
if (failed_rewind) {
1661+
break; // out of the chainstate activation do-while
1662+
}
1663+
1664+
bool failed_verification = false;
1665+
16341666
try {
16351667
LOCK(cs_main);
1636-
if (!is_coinsview_empty) {
1637-
uiInterface.InitMessage(_("Verifying blocks...").translated);
1638-
if (fHavePruned && gArgs.GetArg("-checkblocks", DEFAULT_CHECKBLOCKS) > MIN_BLOCKS_TO_KEEP) {
1639-
LogPrintf("Prune: pruned datadir may not have more than %d blocks; only checking available blocks\n",
1640-
MIN_BLOCKS_TO_KEEP);
1641-
}
1642-
1643-
CBlockIndex* tip = ::ChainActive().Tip();
1644-
RPCNotifyBlockChange(true, tip);
1645-
if (tip && tip->nTime > GetAdjustedTime() + 2 * 60 * 60) {
1646-
strLoadError = _("The block database contains a block which appears to be from the future. "
1647-
"This may be due to your computer's date and time being set incorrectly. "
1648-
"Only rebuild the block database if you are sure that your computer's date and time are correct").translated;
1649-
break;
1650-
}
16511668

1652-
if (!CVerifyDB().VerifyDB(chainparams, &::ChainstateActive().CoinsDB(), gArgs.GetArg("-checklevel", DEFAULT_CHECKLEVEL),
1653-
gArgs.GetArg("-checkblocks", DEFAULT_CHECKBLOCKS))) {
1654-
strLoadError = _("Corrupted block database detected").translated;
1655-
break;
1669+
for (CChainState* chainstate : g_chainman.GetAll()) {
1670+
if (!is_coinsview_empty(chainstate)) {
1671+
uiInterface.InitMessage(_("Verifying blocks...").translated);
1672+
if (fHavePruned && gArgs.GetArg("-checkblocks", DEFAULT_CHECKBLOCKS) > MIN_BLOCKS_TO_KEEP) {
1673+
LogPrintf("Prune: pruned datadir may not have more than %d blocks; only checking available blocks\n",
1674+
MIN_BLOCKS_TO_KEEP);
1675+
}
1676+
1677+
const CBlockIndex* tip = chainstate->m_chain.Tip();
1678+
RPCNotifyBlockChange(true, tip);
1679+
if (tip && tip->nTime > GetAdjustedTime() + 2 * 60 * 60) {
1680+
strLoadError = _("The block database contains a block which appears to be from the future. "
1681+
"This may be due to your computer's date and time being set incorrectly. "
1682+
"Only rebuild the block database if you are sure that your computer's date and time are correct").translated;
1683+
failed_verification = true;
1684+
break;
1685+
}
1686+
1687+
// Only verify the DB of the active chainstate. This is fixed in later
1688+
// work when we allow VerifyDB to be parameterized by chainstate.
1689+
if (&::ChainstateActive() == chainstate &&
1690+
!CVerifyDB().VerifyDB(
1691+
chainparams, &chainstate->CoinsDB(),
1692+
gArgs.GetArg("-checklevel", DEFAULT_CHECKLEVEL),
1693+
gArgs.GetArg("-checkblocks", DEFAULT_CHECKBLOCKS))) {
1694+
strLoadError = _("Corrupted block database detected").translated;
1695+
failed_verification = true;
1696+
break;
1697+
}
16561698
}
16571699
}
16581700
} catch (const std::exception& e) {
16591701
LogPrintf("%s\n", e.what());
16601702
strLoadError = _("Error opening block database").translated;
1703+
failed_verification = true;
16611704
break;
16621705
}
16631706

1664-
fLoaded = true;
1665-
LogPrintf(" block index %15dms\n", GetTimeMillis() - load_block_index_start_time);
1707+
if (!failed_verification) {
1708+
fLoaded = true;
1709+
LogPrintf(" block index %15dms\n", GetTimeMillis() - load_block_index_start_time);
1710+
}
16661711
} while(false);
16671712

16681713
if (!fLoaded && !ShutdownRequested()) {
@@ -1726,8 +1771,11 @@ bool AppInitMain(NodeContext& node)
17261771
LogPrintf("Unsetting NODE_NETWORK on prune mode\n");
17271772
nLocalServices = ServiceFlags(nLocalServices & ~NODE_NETWORK);
17281773
if (!fReindex) {
1729-
uiInterface.InitMessage(_("Pruning blockstore...").translated);
1730-
::ChainstateActive().PruneAndFlush();
1774+
LOCK(cs_main);
1775+
for (CChainState* chainstate : g_chainman.GetAll()) {
1776+
uiInterface.InitMessage(_("Pruning blockstore...").translated);
1777+
chainstate->PruneAndFlush();
1778+
}
17311779
}
17321780
}
17331781

src/qt/test/apptests.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ void AppTests::appTests()
8282
// Reset global state to avoid interfering with later tests.
8383
AbortShutdown();
8484
UnloadBlockIndex();
85+
WITH_LOCK(::cs_main, g_chainman.Reset());
8586
}
8687

8788
//! Entry point for BitcoinGUI tests.

src/rpc/blockchain.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1314,7 +1314,7 @@ static UniValue getchaintips(const JSONRPCRequest& request)
13141314
/*
13151315
* Idea: the set of chain tips is ::ChainActive().tip, plus orphan blocks which do not have another orphan building off of them.
13161316
* Algorithm:
1317-
* - 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.
1317+
* - Make one pass through BlockIndex(), picking out the orphan blocks, and also storing a set of the orphan block's pprev pointers.
13181318
* - Iterate through the orphan blocks. If the block isn't pointed to by another orphan, it is a chain tip.
13191319
* - add ::ChainActive().Tip()
13201320
*/

src/test/util/setup_common.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,8 @@ TestingSetup::TestingSetup(const std::string& chainName) : BasicTestingSetup(cha
114114
GetMainSignals().RegisterBackgroundSignalScheduler(*g_rpc_node->scheduler);
115115

116116
pblocktree.reset(new CBlockTreeDB(1 << 20, true));
117-
g_chainstate = MakeUnique<CChainState>();
117+
118+
g_chainman.InitializeChainstate();
118119
::ChainstateActive().InitCoinsDB(
119120
/* cache_size_bytes */ 1 << 23, /* in_memory */ true, /* should_wipe */ false);
120121
assert(!::ChainstateActive().CanFlushToDisk());
@@ -161,7 +162,7 @@ TestingSetup::~TestingSetup()
161162
m_node.mempool = nullptr;
162163
m_node.scheduler.reset();
163164
UnloadBlockIndex();
164-
g_chainstate.reset();
165+
g_chainman.Reset();
165166
pblocktree.reset();
166167
}
167168

0 commit comments

Comments
 (0)