diff --git a/src/init.cpp b/src/init.cpp --- a/src/init.cpp +++ b/src/init.cpp @@ -260,12 +260,12 @@ // 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(); + } } } @@ -281,9 +281,11 @@ { 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(); } @@ -2423,15 +2425,20 @@ bool fLoaded = false; while (!fLoaded && !ShutdownRequested()) { const bool fReset = fReindex; + auto is_coinsview_empty = + [&](CChainState *chainstate) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) { + return fReset || fReindexChainState || + chainstate->CoinsTip().GetBestBlock().IsNull(); + }; bilingual_str strLoadError; uiInterface.InitMessage(_("Loading block index...").translated); do { + bool failed_verification = false; const int64_t load_block_index_start_time = GetTimeMillis(); try { LOCK(cs_main); - // This statement makes ::ChainstateActive() usable. - g_chainstate = std::make_unique(); + g_chainman.InitializeChainstate(); UnloadBlockIndex(); // new CBlockTreeDB tries to delete the existing file, which @@ -2511,94 +2518,122 @@ // 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; + + 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); - ::ChainstateActive().CoinsErrorCatcher().AddReadErrCallback( - []() { + chainstate->CoinsErrorCatcher().AddReadErrCallback([]() { uiInterface.ThreadSafeMessageBox( _("Error reading from database, shutting down."), "", CClientUIInterface::MSG_ERROR); }); - // 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"); - break; - } - - // ReplayBlocks is a no-op if we cleared the coinsviewdb with - // -reindex or -reindex-chainstate - if (!::ChainstateActive().ReplayBlocks(params)) { - strLoadError = - _("Unable to replay blocks. You will need to rebuild " - "the database using -reindex-chainstate."); - break; - } - - // The on-disk coinsdb is now in a good state, create the cache - ::ChainstateActive().InitCoinsCache(); - assert(::ChainstateActive().CanFlushToDisk()); - - bool 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"); + // 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"); + failed_chainstate_init = true; break; } - assert(::ChainActive().Tip() != nullptr); - - 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(tip); - if (tip && tip->nTime > - GetAdjustedTime() + MAX_FUTURE_BLOCK_TIME) { - 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"); + // ReplayBlocks is a no-op if we cleared the coinsviewdb + // with -reindex or -reindex-chainstate + if (!chainstate->ReplayBlocks(params)) { + strLoadError = _( + "Unable to replay blocks. You will need to rebuild " + "the database using -reindex-chainstate."); + failed_chainstate_init = true; break; } - if (!CVerifyDB().VerifyDB( - config, &::ChainstateActive().CoinsDB(), - gArgs.GetArg("-checklevel", DEFAULT_CHECKLEVEL), - gArgs.GetArg("-checkblocks", - DEFAULT_CHECKBLOCKS))) { - strLoadError = _("Corrupted block database detected"); - 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"); + failed_chainstate_init = true; + // out of the per-chainstate loop + break; + } + assert(chainstate->m_chain.Tip() != nullptr); + } + } + + if (failed_chainstate_init) { + // out of the chainstate activation do-while + 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); + } + + const CBlockIndex *tip = chainstate->m_chain.Tip(); + RPCNotifyBlockChange(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"); + 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( + config, &chainstate->CoinsDB(), + gArgs.GetArg("-checklevel", DEFAULT_CHECKLEVEL), + gArgs.GetArg("-checkblocks", + DEFAULT_CHECKBLOCKS))) { + strLoadError = + _("Corrupted block database detected"); + failed_verification = true; + break; + } } } } catch (const std::exception &e) { LogPrintf("%s\n", e.what()); strLoadError = _("Error opening block database"); + 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()) { @@ -2672,8 +2707,11 @@ 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(); + } } } diff --git a/src/qt/test/apptests.cpp b/src/qt/test/apptests.cpp --- a/src/qt/test/apptests.cpp +++ b/src/qt/test/apptests.cpp @@ -104,6 +104,7 @@ // Reset global state to avoid interfering with later tests. AbortShutdown(); UnloadBlockIndex(); + g_chainman.Reset(); } //! Entry point for BitcoinGUI tests. diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -1628,7 +1628,7 @@ /** * Idea: the set of chain tips is ::ChainActive().tip, plus orphan blocks * which do not have another orphan building off of them. Algorithm: - * - Make one pass through g_blockman.m_block_index, picking out the orphan + * - Make one pass through BlockIndex(), picking out the orphan * blocks, and also storing a set of the orphan block's pprev pointers. * - Iterate through the orphan blocks. If the block isn't pointed to by * another orphan, it is a chain tip. diff --git a/src/test/util/setup_common.cpp b/src/test/util/setup_common.cpp --- a/src/test/util/setup_common.cpp +++ b/src/test/util/setup_common.cpp @@ -147,7 +147,7 @@ GetMainSignals().RegisterBackgroundSignalScheduler(*m_node.scheduler); pblocktree.reset(new CBlockTreeDB(1 << 20, true)); - g_chainstate = std::make_unique(); + g_chainman.InitializeChainstate(); ::ChainstateActive().InitCoinsDB( /* cache_size_bytes */ 1 << 23, /* in_memory */ true, /* should_wipe */ false); @@ -193,7 +193,7 @@ m_node.mempool = nullptr; m_node.scheduler.reset(); UnloadBlockIndex(); - g_chainstate.reset(); + g_chainman.Reset(); pblocktree.reset(); } diff --git a/src/validation.h b/src/validation.h --- a/src/validation.h +++ b/src/validation.h @@ -842,8 +842,8 @@ const CBlockIndex *m_finalizedBlockIndex GUARDED_BY(cs_main) = nullptr; public: - explicit CChainState(BlockManager &blockman) : m_blockman(blockman) {} - explicit CChainState(BlockHash from_snapshot_blockhash = BlockHash()); + explicit CChainState(BlockManager &blockman, + BlockHash from_snapshot_blockhash = BlockHash()); /** * Initialize the CoinsViews UTXO set database management data structures. @@ -1149,9 +1149,14 @@ bool m_snapshot_validated{false}; // For access to m_active_chainstate. + friend CChainState &ChainstateActive(); friend CChain &ChainActive(); public: + //! A single BlockManager instance is shared across each constructed + //! chainstate to avoid duplicating block metadata. + BlockManager m_blockman GUARDED_BY(::cs_main); + //! Instantiate a new chainstate and assign it based upon whether it is //! from a snapshot. //! @@ -1169,6 +1174,10 @@ int ActiveHeight() const { return ActiveChain().Height(); } CBlockIndex *ActiveTip() const { return ActiveChain().Tip(); } + BlockMap &BlockIndex() EXCLUSIVE_LOCKS_REQUIRED(::cs_main) { + return m_blockman.m_block_index; + } + bool IsSnapshotActive() const; std::optional SnapshotBlockhash() const; @@ -1196,6 +1205,8 @@ void Reset(); }; +extern ChainstateManager g_chainman; + /** @returns the most-work valid chainstate. */ CChainState &ChainstateActive(); @@ -1205,11 +1216,6 @@ /** @returns the global block index map. */ BlockMap &BlockIndex(); -// Most often ::ChainstateActive() should be used instead of this, but some code -// may not be able to assume that this has been initialized yet and so must use -// it directly, e.g. init.cpp. -extern std::unique_ptr g_chainstate; - /** * Global variable that points to the active block tree (protected by cs_main) */ diff --git a/src/validation.cpp b/src/validation.cpp --- a/src/validation.cpp +++ b/src/validation.cpp @@ -59,20 +59,15 @@ #define MICRO 0.000001 #define MILLI 0.001 -namespace { -BlockManager g_blockman; -} // namespace - -std::unique_ptr g_chainstate; +ChainstateManager g_chainman; CChainState &ChainstateActive() { - assert(g_chainstate); - return *g_chainstate; + assert(g_chainman.m_active_chainstate); + return *g_chainman.m_active_chainstate; } CChain &ChainActive() { - assert(g_chainstate); - return g_chainstate->m_chain; + return ::ChainstateActive().m_chain; } /** @@ -139,8 +134,8 @@ CBlockIndex *LookupBlockIndex(const BlockHash &hash) { AssertLockHeld(cs_main); - BlockMap::const_iterator it = g_blockman.m_block_index.find(hash); - return it == g_blockman.m_block_index.end() ? nullptr : it->second; + BlockMap::const_iterator it = g_chainman.BlockIndex().find(hash); + return it == g_chainman.BlockIndex().end() ? nullptr : it->second; } CBlockIndex *FindForkInGlobalIndex(const CChain &chain, @@ -740,11 +735,10 @@ m_cacheview = std::make_unique(&m_catcherview); } -// NOTE: for now m_blockman is set to a global, but this will be changed -// in a future commit. -CChainState::CChainState(BlockHash from_snapshot_blockhash) - : m_blockman(g_blockman), - m_from_snapshot_blockhash(from_snapshot_blockhash) {} +CChainState::CChainState(BlockManager &blockman, + BlockHash from_snapshot_blockhash) + : m_blockman(blockman), m_from_snapshot_blockhash(from_snapshot_blockhash) { +} void CChainState::InitCoinsDB(size_t cache_size_bytes, bool in_memory, bool should_wipe, std::string leveldb_name) { @@ -796,7 +790,7 @@ static CBlockIndex const *pindexBestForkBase = nullptr; BlockMap &BlockIndex() { - return g_blockman.m_block_index; + return g_chainman.m_blockman.m_block_index; } static void AlertNotify(const std::string &strMessage) { @@ -3710,7 +3704,7 @@ // Don't accept any forks from the main chain prior to last checkpoint. // GetLastCheckpoint finds the last checkpoint in MapCheckpoints that's - // in our g_blockman.m_block_index. + // in our BlockIndex(). CBlockIndex *pcheckpoint = Checkpoints::GetLastCheckpoint(checkpoints); if (pcheckpoint && nHeight < pcheckpoint->nHeight) { LogPrintf("ERROR: %s: forked chain older than last checkpoint " @@ -4000,8 +3994,8 @@ for (const CBlockHeader &header : headers) { // Use a temp pindex instead of ppindex to avoid a const_cast CBlockIndex *pindex = nullptr; - bool accepted = - g_blockman.AcceptBlockHeader(config, header, state, &pindex); + bool accepted = g_chainman.m_blockman.AcceptBlockHeader( + config, header, state, &pindex); ::ChainstateActive().CheckBlockIndex( config.GetChainParams().GetConsensus()); @@ -4336,7 +4330,7 @@ void PruneOneBlockFile(const int fileNumber) { LOCK(cs_LastBlockFile); - for (const auto &entry : g_blockman.m_block_index) { + for (const auto &entry : g_chainman.BlockIndex()) { CBlockIndex *pindex = entry.second; if (pindex->nFile == fileNumber) { pindex->nStatus = pindex->nStatus.withData(false).withUndo(false); @@ -4349,14 +4343,14 @@ // to be downloaded again in order to consider its chain, at which // point it would be considered as a candidate for // m_blocks_unlinked or setBlockIndexCandidates. - auto range = - g_blockman.m_blocks_unlinked.equal_range(pindex->pprev); + auto range = g_chainman.m_blockman.m_blocks_unlinked.equal_range( + pindex->pprev); while (range.first != range.second) { std::multimap::iterator _it = range.first; range.first++; if (_it->second == pindex) { - g_blockman.m_blocks_unlinked.erase(_it); + g_chainman.m_blockman.m_blocks_unlinked.erase(_it); } } } @@ -4614,7 +4608,7 @@ static bool LoadBlockIndexDB(const Consensus::Params ¶ms) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { - if (!g_blockman.LoadBlockIndex( + if (!g_chainman.m_blockman.LoadBlockIndex( params, *pblocktree, ::ChainstateActive().setBlockIndexCandidates)) { return false; @@ -4644,7 +4638,7 @@ LogPrintf("Checking all blk files are present...\n"); std::set setBlkDataFiles; for (const std::pair &item : - g_blockman.m_block_index) { + g_chainman.BlockIndex()) { CBlockIndex *pindex = item.second; if (pindex->nStatus.hasData()) { setBlkDataFiles.insert(pindex->nFile); @@ -5020,8 +5014,7 @@ // block index state void UnloadBlockIndex() { LOCK(cs_main); - ::ChainActive().SetTip(nullptr); - g_blockman.Unload(); + g_chainman.Unload(); pindexBestInvalid = nullptr; pindexBestParked = nullptr; pindexBestHeader = nullptr; @@ -5034,8 +5027,6 @@ setDirtyBlockIndex.clear(); setDirtyFileInfo.clear(); fHavePruned = false; - - ::ChainstateActive().UnloadBlockIndex(); } bool LoadBlockIndex(const Consensus::Params ¶ms) { @@ -5047,7 +5038,7 @@ return false; } - needs_init = g_blockman.m_block_index.empty(); + needs_init = g_chainman.m_blockman.m_block_index.empty(); } if (needs_init) { @@ -5805,10 +5796,10 @@ ~CMainCleanup() { // block headers for (const std::pair &it : - g_blockman.m_block_index) { + g_chainman.BlockIndex()) { delete it.second; } - g_blockman.m_block_index.clear(); + g_chainman.BlockIndex().clear(); } }; static CMainCleanup instance_of_cmaincleanup; @@ -5845,7 +5836,7 @@ throw std::logic_error("should not be overwriting a chainstate"); } - to_modify.reset(new CChainState(snapshot_blockhash)); + to_modify.reset(new CChainState(m_blockman, snapshot_blockhash)); // Snapshot chainstates and initial IBD chaintates always become active. if (is_snapshot || (!is_snapshot && !m_active_chainstate)) { @@ -5885,6 +5876,8 @@ chainstate->m_chain.SetTip(nullptr); chainstate->UnloadBlockIndex(); } + + m_blockman.Unload(); } void ChainstateManager::Reset() {