mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-11-12 23:18:14 +01:00
Merge #17737: Add ChainstateManager, remove BlockManager global
c9017ce3bcprotect g_chainman with cs_main (James O'Beirne)2b081c4568test: add basic tests for ChainstateManager (James O'Beirne)4ae29f5f0cuse ChainstateManager to initialize chainstate (James O'Beirne)5b690f0aaerefactor: move RewindBlockIndex to CChainState (James O'Beirne)89cdf4d569validation: introduce unused ChainstateManager (James O'Beirne)8e2ecfe249validation: 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-ACKc9017ce3bc📙 fjahr: Code Review Re-ACKc9017ce3bcariard: Code Review ACKc9017ceryanofsky: Code review ACKc9017ce3bc. No changes since last review other than a straight rebase Tree-SHA512: 3f250d0dc95d4bfd70852ef1e39e081a4a9b71a4453f276e6d474c2ae06ad6ae6a32b4173084fe499e1e9af72dd9007f4a8a375c63ce9ac472ffeaada41ab508
This commit is contained in:
204
src/init.cpp
204
src/init.cpp
@@ -243,13 +243,12 @@ void Shutdown(NodeContext& node)
|
||||
}
|
||||
|
||||
// FlushStateToDisk generates a ChainStateFlushed callback, which we should avoid missing
|
||||
//
|
||||
// g_chainstate is referenced here directly (instead of ::ChainstateActive()) because it
|
||||
// may not have been initialized yet.
|
||||
{
|
||||
LOCK(cs_main);
|
||||
if (g_chainstate && g_chainstate->CanFlushToDisk()) {
|
||||
g_chainstate->ForceFlushStateToDisk();
|
||||
for (CChainState* chainstate : g_chainman.GetAll()) {
|
||||
if (chainstate->CanFlushToDisk()) {
|
||||
chainstate->ForceFlushStateToDisk();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -273,9 +272,11 @@ void Shutdown(NodeContext& node)
|
||||
|
||||
{
|
||||
LOCK(cs_main);
|
||||
if (g_chainstate && g_chainstate->CanFlushToDisk()) {
|
||||
g_chainstate->ForceFlushStateToDisk();
|
||||
g_chainstate->ResetCoinsViews();
|
||||
for (CChainState* chainstate : g_chainman.GetAll()) {
|
||||
if (chainstate->CanFlushToDisk()) {
|
||||
chainstate->ForceFlushStateToDisk();
|
||||
chainstate->ResetCoinsViews();
|
||||
}
|
||||
}
|
||||
pblocktree.reset();
|
||||
}
|
||||
@@ -719,11 +720,17 @@ static void ThreadImport(std::vector<fs::path> vImportFiles)
|
||||
}
|
||||
|
||||
// scan for better chains in the block chain database, that are not yet connected in the active best chain
|
||||
BlockValidationState state;
|
||||
if (!ActivateBestChain(state, chainparams)) {
|
||||
LogPrintf("Failed to connect best block (%s)\n", state.ToString());
|
||||
StartShutdown();
|
||||
return;
|
||||
|
||||
// We can't hold cs_main during ActivateBestChain even though we're accessing
|
||||
// the g_chainman unique_ptrs since ABC requires us not to be holding cs_main, so retrieve
|
||||
// the relevant pointers before the ABC call.
|
||||
for (CChainState* chainstate : WITH_LOCK(::cs_main, return g_chainman.GetAll())) {
|
||||
BlockValidationState state;
|
||||
if (!chainstate->ActivateBestChain(state, chainparams, nullptr)) {
|
||||
LogPrintf("Failed to connect best block (%s)\n", state.ToString());
|
||||
StartShutdown();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (gArgs.GetBoolArg("-stopafterblockimport", DEFAULT_STOPAFTERBLOCKIMPORT)) {
|
||||
@@ -1513,17 +1520,18 @@ bool AppInitMain(NodeContext& node)
|
||||
bool fLoaded = false;
|
||||
while (!fLoaded && !ShutdownRequested()) {
|
||||
bool fReset = fReindex;
|
||||
auto is_coinsview_empty = [&](CChainState* chainstate) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) {
|
||||
return fReset || fReindexChainState || chainstate->CoinsTip().GetBestBlock().IsNull();
|
||||
};
|
||||
std::string strLoadError;
|
||||
|
||||
uiInterface.InitMessage(_("Loading block index...").translated);
|
||||
|
||||
do {
|
||||
const int64_t load_block_index_start_time = GetTimeMillis();
|
||||
bool is_coinsview_empty;
|
||||
try {
|
||||
LOCK(cs_main);
|
||||
// This statement makes ::ChainstateActive() usable.
|
||||
g_chainstate = MakeUnique<CChainState>();
|
||||
g_chainman.InitializeChainstate();
|
||||
UnloadBlockIndex();
|
||||
|
||||
// new CBlockTreeDB tries to delete the existing file, which
|
||||
@@ -1576,43 +1584,53 @@ bool AppInitMain(NodeContext& node)
|
||||
// At this point we're either in reindex or we've loaded a useful
|
||||
// block tree into BlockIndex()!
|
||||
|
||||
::ChainstateActive().InitCoinsDB(
|
||||
/* cache_size_bytes */ nCoinDBCache,
|
||||
/* in_memory */ false,
|
||||
/* should_wipe */ fReset || fReindexChainState);
|
||||
bool failed_chainstate_init = false;
|
||||
|
||||
::ChainstateActive().CoinsErrorCatcher().AddReadErrCallback([]() {
|
||||
uiInterface.ThreadSafeMessageBox(
|
||||
_("Error reading from database, shutting down.").translated,
|
||||
"", CClientUIInterface::MSG_ERROR);
|
||||
});
|
||||
for (CChainState* chainstate : g_chainman.GetAll()) {
|
||||
LogPrintf("Initializing chainstate %s\n", chainstate->ToString());
|
||||
chainstate->InitCoinsDB(
|
||||
/* cache_size_bytes */ nCoinDBCache,
|
||||
/* in_memory */ false,
|
||||
/* should_wipe */ fReset || fReindexChainState);
|
||||
|
||||
// If necessary, upgrade from older database format.
|
||||
// This is a no-op if we cleared the coinsviewdb with -reindex or -reindex-chainstate
|
||||
if (!::ChainstateActive().CoinsDB().Upgrade()) {
|
||||
strLoadError = _("Error upgrading chainstate database").translated;
|
||||
break;
|
||||
}
|
||||
chainstate->CoinsErrorCatcher().AddReadErrCallback([]() {
|
||||
uiInterface.ThreadSafeMessageBox(
|
||||
_("Error reading from database, shutting down.").translated,
|
||||
"", CClientUIInterface::MSG_ERROR);
|
||||
});
|
||||
|
||||
// ReplayBlocks is a no-op if we cleared the coinsviewdb with -reindex or -reindex-chainstate
|
||||
if (!::ChainstateActive().ReplayBlocks(chainparams)) {
|
||||
strLoadError = _("Unable to replay blocks. You will need to rebuild the database using -reindex-chainstate.").translated;
|
||||
break;
|
||||
}
|
||||
|
||||
// The on-disk coinsdb is now in a good state, create the cache
|
||||
::ChainstateActive().InitCoinsCache();
|
||||
assert(::ChainstateActive().CanFlushToDisk());
|
||||
|
||||
is_coinsview_empty = fReset || fReindexChainState ||
|
||||
::ChainstateActive().CoinsTip().GetBestBlock().IsNull();
|
||||
if (!is_coinsview_empty) {
|
||||
// LoadChainTip initializes the chain based on CoinsTip()'s best block
|
||||
if (!::ChainstateActive().LoadChainTip(chainparams)) {
|
||||
strLoadError = _("Error initializing block database").translated;
|
||||
// If necessary, upgrade from older database format.
|
||||
// This is a no-op if we cleared the coinsviewdb with -reindex or -reindex-chainstate
|
||||
if (!chainstate->CoinsDB().Upgrade()) {
|
||||
strLoadError = _("Error upgrading chainstate database").translated;
|
||||
failed_chainstate_init = true;
|
||||
break;
|
||||
}
|
||||
assert(::ChainActive().Tip() != nullptr);
|
||||
|
||||
// ReplayBlocks is a no-op if we cleared the coinsviewdb with -reindex or -reindex-chainstate
|
||||
if (!chainstate->ReplayBlocks(chainparams)) {
|
||||
strLoadError = _("Unable to replay blocks. You will need to rebuild the database using -reindex-chainstate.").translated;
|
||||
failed_chainstate_init = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// The on-disk coinsdb is now in a good state, create the cache
|
||||
chainstate->InitCoinsCache();
|
||||
assert(chainstate->CanFlushToDisk());
|
||||
|
||||
if (!is_coinsview_empty(chainstate)) {
|
||||
// LoadChainTip initializes the chain based on CoinsTip()'s best block
|
||||
if (!chainstate->LoadChainTip(chainparams)) {
|
||||
strLoadError = _("Error initializing block database").translated;
|
||||
failed_chainstate_init = true;
|
||||
break; // out of the per-chainstate loop
|
||||
}
|
||||
assert(chainstate->m_chain.Tip() != nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
if (failed_chainstate_init) {
|
||||
break; // out of the chainstate activation do-while
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
LogPrintf("%s\n", e.what());
|
||||
@@ -1620,49 +1638,76 @@ bool AppInitMain(NodeContext& node)
|
||||
break;
|
||||
}
|
||||
|
||||
if (!fReset) {
|
||||
// Note that RewindBlockIndex MUST run even if we're about to -reindex-chainstate.
|
||||
// It both disconnects blocks based on ::ChainActive(), and drops block data in
|
||||
// BlockIndex() based on lack of available witness data.
|
||||
uiInterface.InitMessage(_("Rewinding blocks...").translated);
|
||||
if (!RewindBlockIndex(chainparams)) {
|
||||
strLoadError = _("Unable to rewind the database to a pre-fork state. You will need to redownload the blockchain").translated;
|
||||
break;
|
||||
bool failed_rewind{false};
|
||||
// Can't hold cs_main while calling RewindBlockIndex, so retrieve the relevant
|
||||
// chainstates beforehand.
|
||||
for (CChainState* chainstate : WITH_LOCK(::cs_main, return g_chainman.GetAll())) {
|
||||
if (!fReset) {
|
||||
// Note that RewindBlockIndex MUST run even if we're about to -reindex-chainstate.
|
||||
// It both disconnects blocks based on the chainstate, and drops block data in
|
||||
// BlockIndex() based on lack of available witness data.
|
||||
uiInterface.InitMessage(_("Rewinding blocks...").translated);
|
||||
if (!chainstate->RewindBlockIndex(chainparams)) {
|
||||
strLoadError = _(
|
||||
"Unable to rewind the database to a pre-fork state. "
|
||||
"You will need to redownload the blockchain").translated;
|
||||
failed_rewind = true;
|
||||
break; // out of the per-chainstate loop
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (failed_rewind) {
|
||||
break; // out of the chainstate activation do-while
|
||||
}
|
||||
|
||||
bool failed_verification = false;
|
||||
|
||||
try {
|
||||
LOCK(cs_main);
|
||||
if (!is_coinsview_empty) {
|
||||
uiInterface.InitMessage(_("Verifying blocks...").translated);
|
||||
if (fHavePruned && gArgs.GetArg("-checkblocks", DEFAULT_CHECKBLOCKS) > MIN_BLOCKS_TO_KEEP) {
|
||||
LogPrintf("Prune: pruned datadir may not have more than %d blocks; only checking available blocks\n",
|
||||
MIN_BLOCKS_TO_KEEP);
|
||||
}
|
||||
|
||||
CBlockIndex* tip = ::ChainActive().Tip();
|
||||
RPCNotifyBlockChange(true, tip);
|
||||
if (tip && tip->nTime > GetAdjustedTime() + 2 * 60 * 60) {
|
||||
strLoadError = _("The block database contains a block which appears to be from the future. "
|
||||
"This may be due to your computer's date and time being set incorrectly. "
|
||||
"Only rebuild the block database if you are sure that your computer's date and time are correct").translated;
|
||||
break;
|
||||
}
|
||||
for (CChainState* chainstate : g_chainman.GetAll()) {
|
||||
if (!is_coinsview_empty(chainstate)) {
|
||||
uiInterface.InitMessage(_("Verifying blocks...").translated);
|
||||
if (fHavePruned && gArgs.GetArg("-checkblocks", DEFAULT_CHECKBLOCKS) > MIN_BLOCKS_TO_KEEP) {
|
||||
LogPrintf("Prune: pruned datadir may not have more than %d blocks; only checking available blocks\n",
|
||||
MIN_BLOCKS_TO_KEEP);
|
||||
}
|
||||
|
||||
if (!CVerifyDB().VerifyDB(chainparams, &::ChainstateActive().CoinsDB(), gArgs.GetArg("-checklevel", DEFAULT_CHECKLEVEL),
|
||||
gArgs.GetArg("-checkblocks", DEFAULT_CHECKBLOCKS))) {
|
||||
strLoadError = _("Corrupted block database detected").translated;
|
||||
break;
|
||||
const CBlockIndex* tip = chainstate->m_chain.Tip();
|
||||
RPCNotifyBlockChange(true, tip);
|
||||
if (tip && tip->nTime > GetAdjustedTime() + 2 * 60 * 60) {
|
||||
strLoadError = _("The block database contains a block which appears to be from the future. "
|
||||
"This may be due to your computer's date and time being set incorrectly. "
|
||||
"Only rebuild the block database if you are sure that your computer's date and time are correct").translated;
|
||||
failed_verification = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// Only verify the DB of the active chainstate. This is fixed in later
|
||||
// work when we allow VerifyDB to be parameterized by chainstate.
|
||||
if (&::ChainstateActive() == chainstate &&
|
||||
!CVerifyDB().VerifyDB(
|
||||
chainparams, &chainstate->CoinsDB(),
|
||||
gArgs.GetArg("-checklevel", DEFAULT_CHECKLEVEL),
|
||||
gArgs.GetArg("-checkblocks", DEFAULT_CHECKBLOCKS))) {
|
||||
strLoadError = _("Corrupted block database detected").translated;
|
||||
failed_verification = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
LogPrintf("%s\n", e.what());
|
||||
strLoadError = _("Error opening block database").translated;
|
||||
failed_verification = true;
|
||||
break;
|
||||
}
|
||||
|
||||
fLoaded = true;
|
||||
LogPrintf(" block index %15dms\n", GetTimeMillis() - load_block_index_start_time);
|
||||
if (!failed_verification) {
|
||||
fLoaded = true;
|
||||
LogPrintf(" block index %15dms\n", GetTimeMillis() - load_block_index_start_time);
|
||||
}
|
||||
} while(false);
|
||||
|
||||
if (!fLoaded && !ShutdownRequested()) {
|
||||
@@ -1726,8 +1771,11 @@ bool AppInitMain(NodeContext& node)
|
||||
LogPrintf("Unsetting NODE_NETWORK on prune mode\n");
|
||||
nLocalServices = ServiceFlags(nLocalServices & ~NODE_NETWORK);
|
||||
if (!fReindex) {
|
||||
uiInterface.InitMessage(_("Pruning blockstore...").translated);
|
||||
::ChainstateActive().PruneAndFlush();
|
||||
LOCK(cs_main);
|
||||
for (CChainState* chainstate : g_chainman.GetAll()) {
|
||||
uiInterface.InitMessage(_("Pruning blockstore...").translated);
|
||||
chainstate->PruneAndFlush();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user