From cb64af9635a9553e335f2dc0b1cca20c6bbd0933 Mon Sep 17 00:00:00 2001 From: Carl Dong Date: Mon, 20 Sep 2021 16:12:18 -0400 Subject: [PATCH 01/23] node: Extract chainstate loading sequence I strongly recommend reviewing with the following git-diff flags: --color-moved=dimmed_zebra --color-moved-ws=allow-indentation-change [META] This commit is intended to be as close to a move-only commit as possible, and lingering ugliness will be resolved in subsequent commits. A few variables that are passed in by value instead of by reference deserve explanation: - fReset and fReindexChainstate are both local variables in AppInitMain and are not modified in the sequence - fPruneMode, despite being a global, is only modified in AppInitParameterInteraction, long before LoadChainstate is called ---- [META] This semantic will change in a future commit named "node/chainstate: Decouple from stringy errors" --- src/Makefile.am | 2 + src/init.cpp | 187 +++--------------------------------- src/node/chainstate.cpp | 204 ++++++++++++++++++++++++++++++++++++++++ src/node/chainstate.h | 48 ++++++++++ 4 files changed, 268 insertions(+), 173 deletions(-) create mode 100644 src/node/chainstate.cpp create mode 100644 src/node/chainstate.h diff --git a/src/Makefile.am b/src/Makefile.am index 72f548c1921..880e6b5362c 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -174,6 +174,7 @@ BITCOIN_CORE_H = \ netbase.h \ netmessagemaker.h \ node/blockstorage.h \ + node/chainstate.h \ node/coin.h \ node/coinstats.h \ node/context.h \ @@ -339,6 +340,7 @@ libbitcoin_server_a_SOURCES = \ net.cpp \ net_processing.cpp \ node/blockstorage.cpp \ + node/chainstate.cpp \ node/coin.cpp \ node/coinstats.cpp \ node/context.cpp \ diff --git a/src/init.cpp b/src/init.cpp index 22c0d928b5a..869c3a8f7c0 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -34,6 +34,7 @@ #include #include #include +#include // for LoadChainstate #include #include #include @@ -1414,183 +1415,23 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) 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 { - const int64_t load_block_index_start_time = GetTimeMillis(); - try { - LOCK(cs_main); - chainman.InitializeChainstate(Assert(node.mempool.get())); - chainman.m_total_coinstip_cache = nCoinCacheUsage; - chainman.m_total_coinsdb_cache = nCoinDBCache; - - UnloadBlockIndex(node.mempool.get(), chainman); - - auto& pblocktree{chainman.m_blockman.m_block_tree_db}; - // new CBlockTreeDB tries to delete the existing file, which - // fails if it's still open from the previous loop. Close it first: - pblocktree.reset(); - pblocktree.reset(new CBlockTreeDB(nBlockTreeDBCache, false, fReset)); - - if (fReset) { - pblocktree->WriteReindexing(true); - //If we're reindexing in prune mode, wipe away unusable block files and all undo data files - if (fPruneMode) - CleanupBlockRevFiles(); - } - - if (ShutdownRequested()) break; - - // LoadBlockIndex will load fHavePruned if we've ever removed a - // block file from disk. - // Note that it also sets fReindex based on the disk flag! - // From here on out fReindex and fReset mean something different! - if (!chainman.LoadBlockIndex()) { - if (ShutdownRequested()) break; - strLoadError = _("Error loading block database"); - break; - } - - // If the loaded chain has a wrong genesis, bail out immediately - // (we're likely using a testnet datadir, or the other way around). - if (!chainman.BlockIndex().empty() && - !chainman.m_blockman.LookupBlockIndex(chainparams.GetConsensus().hashGenesisBlock)) { - return InitError(_("Incorrect or no genesis block found. Wrong datadir for network?")); - } - - // Check for changed -prune state. What we are concerned about is a user who has pruned blocks - // in the past, but is now trying to run unpruned. - if (fHavePruned && !fPruneMode) { - strLoadError = _("You need to rebuild the database using -reindex to go back to unpruned mode. This will redownload the entire blockchain"); - break; - } - - // At this point blocktree args are consistent with what's on disk. - // If we're not mid-reindex (based on disk + args), add a genesis block on disk - // (otherwise we use the one already on disk). - // This is called again in ThreadImport after the reindex completes. - if (!fReindex && !chainman.ActiveChainstate().LoadGenesisBlock()) { - strLoadError = _("Error initializing block database"); - break; - } - - // At this point we're either in reindex or we've loaded a useful - // block tree into BlockIndex()! - - bool failed_chainstate_init = false; - - for (CChainState* chainstate : chainman.GetAll()) { - chainstate->InitCoinsDB( - /* cache_size_bytes */ nCoinDBCache, - /* in_memory */ false, - /* should_wipe */ fReset || fReindexChainState); - - 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 (!chainstate->CoinsDB().Upgrade()) { - strLoadError = _("Error upgrading chainstate database"); - failed_chainstate_init = true; - break; - } - - // ReplayBlocks is a no-op if we cleared the coinsviewdb with -reindex or -reindex-chainstate - if (!chainstate->ReplayBlocks()) { - strLoadError = _("Unable to replay blocks. You will need to rebuild the database using -reindex-chainstate."); - failed_chainstate_init = true; - break; - } - - // The on-disk coinsdb is now in a good state, create the cache - chainstate->InitCoinsCache(nCoinCacheUsage); - assert(chainstate->CanFlushToDisk()); - - if (!is_coinsview_empty(chainstate)) { - // LoadChainTip initializes the chain based on CoinsTip()'s best block - if (!chainstate->LoadChainTip()) { - strLoadError = _("Error initializing block database"); - 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()); - strLoadError = _("Error opening block database"); - break; - } - - if (!fReset) { - LOCK(cs_main); - auto chainstates{chainman.GetAll()}; - if (std::any_of(chainstates.begin(), chainstates.end(), - [](const CChainState* cs) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { return cs->NeedsRedownload(); })) { - strLoadError = strprintf(_("Witness data for blocks after height %d requires validation. Please restart with -reindex."), - chainparams.GetConsensus().SegwitHeight); - break; - } - } - - bool failed_verification = false; - - try { - LOCK(cs_main); - - for (CChainState* chainstate : chainman.GetAll()) { - if (!is_coinsview_empty(chainstate)) { - uiInterface.InitMessage(_("Verifying blocks…").translated); - if (fHavePruned && args.GetIntArg("-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 > GetTime() + 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"); - failed_verification = true; - break; - } - - if (!CVerifyDB().VerifyDB( - *chainstate, chainparams, chainstate->CoinsDB(), - args.GetIntArg("-checklevel", DEFAULT_CHECKLEVEL), - args.GetIntArg("-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; - } - - if (!failed_verification) { - fLoaded = true; - LogPrintf(" block index %15dms\n", GetTimeMillis() - load_block_index_start_time); - } - } while(false); + bool rv = LoadChainstate(fLoaded, + strLoadError, + fReset, + chainman, + node, + fPruneMode, + chainparams, + args, + fReindexChainState, + nBlockTreeDBCache, + nCoinDBCache, + nCoinCacheUsage); + if (!rv) return false; if (!fLoaded && !ShutdownRequested()) { // first suggest a reindex diff --git a/src/node/chainstate.cpp b/src/node/chainstate.cpp new file mode 100644 index 00000000000..b7a92d412a5 --- /dev/null +++ b/src/node/chainstate.cpp @@ -0,0 +1,204 @@ +// Copyright (c) 2021 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include + +#include // for CChainParams +#include // for RPCNotifyBlockChange +#include // for GetTime, GetTimeMillis +#include // for bilingual_str +#include // for CleanupBlockRevFiles, fHavePruned, fReindex +#include // for NodeContext +#include // for InitError, uiInterface, and CClientUIInterface member access +#include // for ShutdownRequested +#include // for a lot of things + +bool LoadChainstate(bool& fLoaded, + bilingual_str& strLoadError, + bool fReset, + ChainstateManager& chainman, + NodeContext& node, + bool fPruneMode, + const CChainParams& chainparams, + const ArgsManager& args, + bool fReindexChainState, + int64_t nBlockTreeDBCache, + int64_t nCoinDBCache, + int64_t nCoinCacheUsage) { + auto is_coinsview_empty = [&](CChainState* chainstate) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) { + return fReset || fReindexChainState || chainstate->CoinsTip().GetBestBlock().IsNull(); + }; + + do { + const int64_t load_block_index_start_time = GetTimeMillis(); + try { + LOCK(cs_main); + chainman.InitializeChainstate(Assert(node.mempool.get())); + chainman.m_total_coinstip_cache = nCoinCacheUsage; + chainman.m_total_coinsdb_cache = nCoinDBCache; + + UnloadBlockIndex(node.mempool.get(), chainman); + + auto& pblocktree{chainman.m_blockman.m_block_tree_db}; + // new CBlockTreeDB tries to delete the existing file, which + // fails if it's still open from the previous loop. Close it first: + pblocktree.reset(); + pblocktree.reset(new CBlockTreeDB(nBlockTreeDBCache, false, fReset)); + + if (fReset) { + pblocktree->WriteReindexing(true); + //If we're reindexing in prune mode, wipe away unusable block files and all undo data files + if (fPruneMode) + CleanupBlockRevFiles(); + } + + if (ShutdownRequested()) break; + + // LoadBlockIndex will load fHavePruned if we've ever removed a + // block file from disk. + // Note that it also sets fReindex based on the disk flag! + // From here on out fReindex and fReset mean something different! + if (!chainman.LoadBlockIndex()) { + if (ShutdownRequested()) break; + strLoadError = _("Error loading block database"); + break; + } + + // If the loaded chain has a wrong genesis, bail out immediately + // (we're likely using a testnet datadir, or the other way around). + if (!chainman.BlockIndex().empty() && + !chainman.m_blockman.LookupBlockIndex(chainparams.GetConsensus().hashGenesisBlock)) { + return InitError(_("Incorrect or no genesis block found. Wrong datadir for network?")); + } + + // Check for changed -prune state. What we are concerned about is a user who has pruned blocks + // in the past, but is now trying to run unpruned. + if (fHavePruned && !fPruneMode) { + strLoadError = _("You need to rebuild the database using -reindex to go back to unpruned mode. This will redownload the entire blockchain"); + break; + } + + // At this point blocktree args are consistent with what's on disk. + // If we're not mid-reindex (based on disk + args), add a genesis block on disk + // (otherwise we use the one already on disk). + // This is called again in ThreadImport after the reindex completes. + if (!fReindex && !chainman.ActiveChainstate().LoadGenesisBlock()) { + strLoadError = _("Error initializing block database"); + break; + } + + // At this point we're either in reindex or we've loaded a useful + // block tree into BlockIndex()! + + bool failed_chainstate_init = false; + + for (CChainState* chainstate : chainman.GetAll()) { + chainstate->InitCoinsDB( + /* cache_size_bytes */ nCoinDBCache, + /* in_memory */ false, + /* should_wipe */ fReset || fReindexChainState); + + 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 (!chainstate->CoinsDB().Upgrade()) { + strLoadError = _("Error upgrading chainstate database"); + failed_chainstate_init = true; + break; + } + + // ReplayBlocks is a no-op if we cleared the coinsviewdb with -reindex or -reindex-chainstate + if (!chainstate->ReplayBlocks()) { + strLoadError = _("Unable to replay blocks. You will need to rebuild the database using -reindex-chainstate."); + failed_chainstate_init = true; + break; + } + + // The on-disk coinsdb is now in a good state, create the cache + chainstate->InitCoinsCache(nCoinCacheUsage); + assert(chainstate->CanFlushToDisk()); + + if (!is_coinsview_empty(chainstate)) { + // LoadChainTip initializes the chain based on CoinsTip()'s best block + if (!chainstate->LoadChainTip()) { + strLoadError = _("Error initializing block database"); + 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()); + strLoadError = _("Error opening block database"); + break; + } + + if (!fReset) { + LOCK(cs_main); + auto chainstates{chainman.GetAll()}; + if (std::any_of(chainstates.begin(), chainstates.end(), + [](const CChainState* cs) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { return cs->NeedsRedownload(); })) { + strLoadError = strprintf(_("Witness data for blocks after height %d requires validation. Please restart with -reindex."), + chainparams.GetConsensus().SegwitHeight); + break; + } + } + + bool failed_verification = false; + + try { + LOCK(cs_main); + + for (CChainState* chainstate : chainman.GetAll()) { + if (!is_coinsview_empty(chainstate)) { + uiInterface.InitMessage(_("Verifying blocks…").translated); + if (fHavePruned && args.GetIntArg("-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 > GetTime() + 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"); + failed_verification = true; + break; + } + + if (!CVerifyDB().VerifyDB( + *chainstate, chainparams, chainstate->CoinsDB(), + args.GetIntArg("-checklevel", DEFAULT_CHECKLEVEL), + args.GetIntArg("-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; + } + + if (!failed_verification) { + fLoaded = true; + LogPrintf(" block index %15dms\n", GetTimeMillis() - load_block_index_start_time); + } + } while(false); + return true; +} diff --git a/src/node/chainstate.h b/src/node/chainstate.h new file mode 100644 index 00000000000..6181e671bae --- /dev/null +++ b/src/node/chainstate.h @@ -0,0 +1,48 @@ +// Copyright (c) 2021 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_NODE_CHAINSTATE_H +#define BITCOIN_NODE_CHAINSTATE_H + +#include // for int64_t + +class ArgsManager; +struct bilingual_str; +class CChainParams; +class ChainstateManager; +struct NodeContext; + +/** This sequence can have 4 types of outcomes: + * + * 1. Success + * 2. Shutdown requested + * - nothing failed but a shutdown was triggered in the middle of the + * sequence + * 3. Soft failure + * - a failure that might be recovered from with a reindex + * 4. Hard failure + * - a failure that definitively cannot be recovered from with a reindex + * + * Currently, LoadChainstate returns a bool which: + * - if false + * - Definitely a "Hard failure" + * - if true + * - if fLoaded -> "Success" + * - if ShutdownRequested() -> "Shutdown requested" + * - else -> "Soft failure" + */ +bool LoadChainstate(bool& fLoaded, + bilingual_str& strLoadError, + bool fReset, + ChainstateManager& chainman, + NodeContext& node, + bool fPruneMode, + const CChainParams& chainparams, + const ArgsManager& args, + bool fReindexChainState, + int64_t nBlockTreeDBCache, + int64_t nCoinDBCache, + int64_t nCoinCacheUsage); + +#endif // BITCOIN_NODE_CHAINSTATE_H From cbac28b72f5b831f6f84b7628f73e85627af3d94 Mon Sep 17 00:00:00 2001 From: Carl Dong Date: Mon, 20 Sep 2021 17:08:18 -0400 Subject: [PATCH 02/23] node/chainstate: Decouple from GetTimeMillis ...instead just move it out --- src/init.cpp | 4 ++++ src/node/chainstate.cpp | 4 +--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/init.cpp b/src/init.cpp index 869c3a8f7c0..3dc73a058ca 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -1419,6 +1419,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) uiInterface.InitMessage(_("Loading block index…").translated); + const int64_t load_block_index_start_time = GetTimeMillis(); bool rv = LoadChainstate(fLoaded, strLoadError, fReset, @@ -1432,6 +1433,9 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) nCoinDBCache, nCoinCacheUsage); if (!rv) return false; + if (fLoaded) { + LogPrintf(" block index %15dms\n", GetTimeMillis() - load_block_index_start_time); + } if (!fLoaded && !ShutdownRequested()) { // first suggest a reindex diff --git a/src/node/chainstate.cpp b/src/node/chainstate.cpp index b7a92d412a5..3da61c95b88 100644 --- a/src/node/chainstate.cpp +++ b/src/node/chainstate.cpp @@ -6,7 +6,7 @@ #include // for CChainParams #include // for RPCNotifyBlockChange -#include // for GetTime, GetTimeMillis +#include // for GetTime #include // for bilingual_str #include // for CleanupBlockRevFiles, fHavePruned, fReindex #include // for NodeContext @@ -31,7 +31,6 @@ bool LoadChainstate(bool& fLoaded, }; do { - const int64_t load_block_index_start_time = GetTimeMillis(); try { LOCK(cs_main); chainman.InitializeChainstate(Assert(node.mempool.get())); @@ -197,7 +196,6 @@ bool LoadChainstate(bool& fLoaded, if (!failed_verification) { fLoaded = true; - LogPrintf(" block index %15dms\n", GetTimeMillis() - load_block_index_start_time); } } while(false); return true; From ae9121f958a4124ea6238cad0c3f2acb8b9eb4bb Mon Sep 17 00:00:00 2001 From: Carl Dong Date: Tue, 17 Aug 2021 18:07:14 -0400 Subject: [PATCH 03/23] node/chainstate: Decouple from stringy errors This allows us to separate the initialization code from translations and error reporting. This change changes the caller semantics of LoadChainstate quite drastically. To see that this change doesn't change behaviour, observe that: 1. Prior to this change, LoadChainstate returned false only in the "bad genesis block" failure case (by returning InitError()), indicating that the caller should immediately bail. After this change, the corresponding ERROR_BAD_GENESIS_BLOCK handler in src/init.cpp maintains behavioue by also bailing immediately. 2. The failed_* temporary booleans were only used to break out of the outer do/while(false) loop. They can therefore be safely removed. --- src/init.cpp | 49 ++++++++++++++++++++--- src/node/chainstate.cpp | 86 +++++++++++++---------------------------- src/node/chainstate.h | 60 ++++++++++++++++++---------- 3 files changed, 109 insertions(+), 86 deletions(-) diff --git a/src/init.cpp b/src/init.cpp index 3dc73a058ca..474a31c7588 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -1418,11 +1418,8 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) bilingual_str strLoadError; uiInterface.InitMessage(_("Loading block index…").translated); - const int64_t load_block_index_start_time = GetTimeMillis(); - bool rv = LoadChainstate(fLoaded, - strLoadError, - fReset, + auto rv = LoadChainstate(fReset, chainman, node, fPruneMode, @@ -1432,8 +1429,48 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) nBlockTreeDBCache, nCoinDBCache, nCoinCacheUsage); - if (!rv) return false; - if (fLoaded) { + if (rv.has_value()) { + switch (rv.value()) { + case ChainstateLoadingError::ERROR_LOADING_BLOCK_DB: + strLoadError = _("Error loading block database"); + break; + case ChainstateLoadingError::ERROR_BAD_GENESIS_BLOCK: + return InitError(_("Incorrect or no genesis block found. Wrong datadir for network?")); + case ChainstateLoadingError::ERROR_PRUNED_NEEDS_REINDEX: + strLoadError = _("You need to rebuild the database using -reindex to go back to unpruned mode. This will redownload the entire blockchain"); + break; + case ChainstateLoadingError::ERROR_LOAD_GENESIS_BLOCK_FAILED: + strLoadError = _("Error initializing block database"); + break; + case ChainstateLoadingError::ERROR_CHAINSTATE_UPGRADE_FAILED: + strLoadError = _("Error upgrading chainstate database"); + break; + case ChainstateLoadingError::ERROR_REPLAYBLOCKS_FAILED: + strLoadError = _("Unable to replay blocks. You will need to rebuild the database using -reindex-chainstate."); + break; + case ChainstateLoadingError::ERROR_LOADCHAINTIP_FAILED: + strLoadError = _("Error initializing block database"); + break; + case ChainstateLoadingError::ERROR_GENERIC_BLOCKDB_OPEN_FAILED: + strLoadError = _("Error opening block database"); + break; + case ChainstateLoadingError::ERROR_BLOCKS_WITNESS_INSUFFICIENTLY_VALIDATED: + strLoadError = strprintf(_("Witness data for blocks after height %d requires validation. Please restart with -reindex."), + chainparams.GetConsensus().SegwitHeight); + break; + case ChainstateLoadingError::ERROR_BLOCK_FROM_FUTURE: + 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"); + break; + case ChainstateLoadingError::ERROR_CORRUPTED_BLOCK_DB: + strLoadError = _("Corrupted block database detected"); + break; + case ChainstateLoadingError::SHUTDOWN_PROBED: + break; + } + } else { + fLoaded = true; LogPrintf(" block index %15dms\n", GetTimeMillis() - load_block_index_start_time); } diff --git a/src/node/chainstate.cpp b/src/node/chainstate.cpp index 3da61c95b88..fa39e2442da 100644 --- a/src/node/chainstate.cpp +++ b/src/node/chainstate.cpp @@ -7,25 +7,23 @@ #include // for CChainParams #include // for RPCNotifyBlockChange #include // for GetTime -#include // for bilingual_str #include // for CleanupBlockRevFiles, fHavePruned, fReindex #include // for NodeContext #include // for InitError, uiInterface, and CClientUIInterface member access #include // for ShutdownRequested #include // for a lot of things -bool LoadChainstate(bool& fLoaded, - bilingual_str& strLoadError, - bool fReset, - ChainstateManager& chainman, - NodeContext& node, - bool fPruneMode, - const CChainParams& chainparams, - const ArgsManager& args, - bool fReindexChainState, - int64_t nBlockTreeDBCache, - int64_t nCoinDBCache, - int64_t nCoinCacheUsage) { +std::optional LoadChainstate(bool fReset, + ChainstateManager& chainman, + NodeContext& node, + bool fPruneMode, + const CChainParams& chainparams, + const ArgsManager& args, + bool fReindexChainState, + int64_t nBlockTreeDBCache, + int64_t nCoinDBCache, + int64_t nCoinCacheUsage) +{ auto is_coinsview_empty = [&](CChainState* chainstate) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) { return fReset || fReindexChainState || chainstate->CoinsTip().GetBestBlock().IsNull(); }; @@ -52,30 +50,28 @@ bool LoadChainstate(bool& fLoaded, CleanupBlockRevFiles(); } - if (ShutdownRequested()) break; + if (ShutdownRequested()) return ChainstateLoadingError::SHUTDOWN_PROBED; // LoadBlockIndex will load fHavePruned if we've ever removed a // block file from disk. // Note that it also sets fReindex based on the disk flag! // From here on out fReindex and fReset mean something different! if (!chainman.LoadBlockIndex()) { - if (ShutdownRequested()) break; - strLoadError = _("Error loading block database"); - break; + if (ShutdownRequested()) return ChainstateLoadingError::SHUTDOWN_PROBED; + return ChainstateLoadingError::ERROR_LOADING_BLOCK_DB; } // If the loaded chain has a wrong genesis, bail out immediately // (we're likely using a testnet datadir, or the other way around). if (!chainman.BlockIndex().empty() && !chainman.m_blockman.LookupBlockIndex(chainparams.GetConsensus().hashGenesisBlock)) { - return InitError(_("Incorrect or no genesis block found. Wrong datadir for network?")); + return ChainstateLoadingError::ERROR_BAD_GENESIS_BLOCK; } // Check for changed -prune state. What we are concerned about is a user who has pruned blocks // in the past, but is now trying to run unpruned. if (fHavePruned && !fPruneMode) { - strLoadError = _("You need to rebuild the database using -reindex to go back to unpruned mode. This will redownload the entire blockchain"); - break; + return ChainstateLoadingError::ERROR_PRUNED_NEEDS_REINDEX; } // At this point blocktree args are consistent with what's on disk. @@ -83,15 +79,12 @@ bool LoadChainstate(bool& fLoaded, // (otherwise we use the one already on disk). // This is called again in ThreadImport after the reindex completes. if (!fReindex && !chainman.ActiveChainstate().LoadGenesisBlock()) { - strLoadError = _("Error initializing block database"); - break; + return ChainstateLoadingError::ERROR_LOAD_GENESIS_BLOCK_FAILED; } // At this point we're either in reindex or we've loaded a useful // block tree into BlockIndex()! - bool failed_chainstate_init = false; - for (CChainState* chainstate : chainman.GetAll()) { chainstate->InitCoinsDB( /* cache_size_bytes */ nCoinDBCache, @@ -107,16 +100,12 @@ bool LoadChainstate(bool& fLoaded, // 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; + return ChainstateLoadingError::ERROR_CHAINSTATE_UPGRADE_FAILED; } // ReplayBlocks is a no-op if we cleared the coinsviewdb with -reindex or -reindex-chainstate if (!chainstate->ReplayBlocks()) { - strLoadError = _("Unable to replay blocks. You will need to rebuild the database using -reindex-chainstate."); - failed_chainstate_init = true; - break; + return ChainstateLoadingError::ERROR_REPLAYBLOCKS_FAILED; } // The on-disk coinsdb is now in a good state, create the cache @@ -126,21 +115,14 @@ bool LoadChainstate(bool& fLoaded, if (!is_coinsview_empty(chainstate)) { // LoadChainTip initializes the chain based on CoinsTip()'s best block if (!chainstate->LoadChainTip()) { - strLoadError = _("Error initializing block database"); - failed_chainstate_init = true; - break; // out of the per-chainstate loop + return ChainstateLoadingError::ERROR_LOADCHAINTIP_FAILED; } 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()); - strLoadError = _("Error opening block database"); - break; + return ChainstateLoadingError::ERROR_GENERIC_BLOCKDB_OPEN_FAILED; } if (!fReset) { @@ -148,14 +130,10 @@ bool LoadChainstate(bool& fLoaded, auto chainstates{chainman.GetAll()}; if (std::any_of(chainstates.begin(), chainstates.end(), [](const CChainState* cs) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { return cs->NeedsRedownload(); })) { - strLoadError = strprintf(_("Witness data for blocks after height %d requires validation. Please restart with -reindex."), - chainparams.GetConsensus().SegwitHeight); - break; + return ChainstateLoadingError::ERROR_BLOCKS_WITNESS_INSUFFICIENTLY_VALIDATED; } } - bool failed_verification = false; - try { LOCK(cs_main); @@ -170,33 +148,21 @@ bool LoadChainstate(bool& fLoaded, const CBlockIndex* tip = chainstate->m_chain.Tip(); RPCNotifyBlockChange(tip); if (tip && tip->nTime > GetTime() + 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"); - failed_verification = true; - break; + return ChainstateLoadingError::ERROR_BLOCK_FROM_FUTURE; } if (!CVerifyDB().VerifyDB( *chainstate, chainparams, chainstate->CoinsDB(), args.GetIntArg("-checklevel", DEFAULT_CHECKLEVEL), args.GetIntArg("-checkblocks", DEFAULT_CHECKBLOCKS))) { - strLoadError = _("Corrupted block database detected"); - failed_verification = true; - break; + return ChainstateLoadingError::ERROR_CORRUPTED_BLOCK_DB; } } } } catch (const std::exception& e) { LogPrintf("%s\n", e.what()); - strLoadError = _("Error opening block database"); - failed_verification = true; - break; - } - - if (!failed_verification) { - fLoaded = true; + return ChainstateLoadingError::ERROR_GENERIC_BLOCKDB_OPEN_FAILED; } } while(false); - return true; + return std::nullopt; } diff --git a/src/node/chainstate.h b/src/node/chainstate.h index 6181e671bae..921b8d89e5f 100644 --- a/src/node/chainstate.h +++ b/src/node/chainstate.h @@ -6,13 +6,28 @@ #define BITCOIN_NODE_CHAINSTATE_H #include // for int64_t +#include // for std::optional class ArgsManager; -struct bilingual_str; class CChainParams; class ChainstateManager; struct NodeContext; +enum class ChainstateLoadingError { + ERROR_LOADING_BLOCK_DB, + ERROR_BAD_GENESIS_BLOCK, + ERROR_PRUNED_NEEDS_REINDEX, + ERROR_LOAD_GENESIS_BLOCK_FAILED, + ERROR_CHAINSTATE_UPGRADE_FAILED, + ERROR_REPLAYBLOCKS_FAILED, + ERROR_LOADCHAINTIP_FAILED, + ERROR_GENERIC_BLOCKDB_OPEN_FAILED, + ERROR_BLOCKS_WITNESS_INSUFFICIENTLY_VALIDATED, + ERROR_BLOCK_FROM_FUTURE, + ERROR_CORRUPTED_BLOCK_DB, + SHUTDOWN_PROBED, +}; + /** This sequence can have 4 types of outcomes: * * 1. Success @@ -24,25 +39,30 @@ struct NodeContext; * 4. Hard failure * - a failure that definitively cannot be recovered from with a reindex * - * Currently, LoadChainstate returns a bool which: - * - if false - * - Definitely a "Hard failure" - * - if true - * - if fLoaded -> "Success" - * - if ShutdownRequested() -> "Shutdown requested" - * - else -> "Soft failure" + * Currently, LoadChainstate returns a std::optional + * which: + * + * - if has_value() + * - Either "Soft failure", "Hard failure", or "Shutdown requested", + * differentiable by the specific enumerator. + * + * Note that a return value of SHUTDOWN_PROBED means ONLY that "during + * this sequence, when we explicitly checked ShutdownRequested() at + * arbitrary points, one of those calls returned true". Therefore, a + * return value other than SHUTDOWN_PROBED does not guarantee that + * ShutdownRequested() hasn't been called indirectly. + * - else + * - Success! */ -bool LoadChainstate(bool& fLoaded, - bilingual_str& strLoadError, - bool fReset, - ChainstateManager& chainman, - NodeContext& node, - bool fPruneMode, - const CChainParams& chainparams, - const ArgsManager& args, - bool fReindexChainState, - int64_t nBlockTreeDBCache, - int64_t nCoinDBCache, - int64_t nCoinCacheUsage); +std::optional LoadChainstate(bool fReset, + ChainstateManager& chainman, + NodeContext& node, + bool fPruneMode, + const CChainParams& chainparams, + const ArgsManager& args, + bool fReindexChainState, + int64_t nBlockTreeDBCache, + int64_t nCoinDBCache, + int64_t nCoinCacheUsage); #endif // BITCOIN_NODE_CHAINSTATE_H From c7a5c46e6fd6d6ff46ca7a65fc3f0fff3cbdb24e Mon Sep 17 00:00:00 2001 From: Carl Dong Date: Mon, 16 Aug 2021 17:03:33 -0400 Subject: [PATCH 04/23] node/chainstate: Decouple from ArgsManager ...instead pass in only the necessary information --- src/init.cpp | 5 +++-- src/node/chainstate.cpp | 11 ++++++----- src/node/chainstate.h | 6 +++--- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/init.cpp b/src/init.cpp index 474a31c7588..f44373728d5 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -1424,11 +1424,12 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) node, fPruneMode, chainparams, - args, fReindexChainState, nBlockTreeDBCache, nCoinDBCache, - nCoinCacheUsage); + nCoinCacheUsage, + args.GetIntArg("-checkblocks", DEFAULT_CHECKBLOCKS), + args.GetIntArg("-checklevel", DEFAULT_CHECKLEVEL)); if (rv.has_value()) { switch (rv.value()) { case ChainstateLoadingError::ERROR_LOADING_BLOCK_DB: diff --git a/src/node/chainstate.cpp b/src/node/chainstate.cpp index fa39e2442da..3d02c52edb0 100644 --- a/src/node/chainstate.cpp +++ b/src/node/chainstate.cpp @@ -18,11 +18,12 @@ std::optional LoadChainstate(bool fReset, NodeContext& node, bool fPruneMode, const CChainParams& chainparams, - const ArgsManager& args, bool fReindexChainState, int64_t nBlockTreeDBCache, int64_t nCoinDBCache, - int64_t nCoinCacheUsage) + int64_t nCoinCacheUsage, + unsigned int check_blocks, + unsigned int check_level) { auto is_coinsview_empty = [&](CChainState* chainstate) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) { return fReset || fReindexChainState || chainstate->CoinsTip().GetBestBlock().IsNull(); @@ -140,7 +141,7 @@ std::optional LoadChainstate(bool fReset, for (CChainState* chainstate : chainman.GetAll()) { if (!is_coinsview_empty(chainstate)) { uiInterface.InitMessage(_("Verifying blocks…").translated); - if (fHavePruned && args.GetIntArg("-checkblocks", DEFAULT_CHECKBLOCKS) > MIN_BLOCKS_TO_KEEP) { + if (fHavePruned && check_blocks > MIN_BLOCKS_TO_KEEP) { LogPrintf("Prune: pruned datadir may not have more than %d blocks; only checking available blocks\n", MIN_BLOCKS_TO_KEEP); } @@ -153,8 +154,8 @@ std::optional LoadChainstate(bool fReset, if (!CVerifyDB().VerifyDB( *chainstate, chainparams, chainstate->CoinsDB(), - args.GetIntArg("-checklevel", DEFAULT_CHECKLEVEL), - args.GetIntArg("-checkblocks", DEFAULT_CHECKBLOCKS))) { + check_level, + check_blocks)) { return ChainstateLoadingError::ERROR_CORRUPTED_BLOCK_DB; } } diff --git a/src/node/chainstate.h b/src/node/chainstate.h index 921b8d89e5f..87aad23e279 100644 --- a/src/node/chainstate.h +++ b/src/node/chainstate.h @@ -8,7 +8,6 @@ #include // for int64_t #include // for std::optional -class ArgsManager; class CChainParams; class ChainstateManager; struct NodeContext; @@ -59,10 +58,11 @@ std::optional LoadChainstate(bool fReset, NodeContext& node, bool fPruneMode, const CChainParams& chainparams, - const ArgsManager& args, bool fReindexChainState, int64_t nBlockTreeDBCache, int64_t nCoinDBCache, - int64_t nCoinCacheUsage); + int64_t nCoinCacheUsage, + unsigned int check_blocks, + unsigned int check_level); #endif // BITCOIN_NODE_CHAINSTATE_H From 9162a4f93ef5aeb57fe11a6e09f5881cf431f5e6 Mon Sep 17 00:00:00 2001 From: Carl Dong Date: Mon, 20 Sep 2021 16:44:21 -0400 Subject: [PATCH 05/23] node/chainstate: Decouple from concept of NodeContext ...instead pass in only the necessary information Also allow mempool to be a nullptr --- src/init.cpp | 2 +- src/node/chainstate.cpp | 7 +++---- src/node/chainstate.h | 4 ++-- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/init.cpp b/src/init.cpp index f44373728d5..eb23ae501c8 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -1421,7 +1421,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) const int64_t load_block_index_start_time = GetTimeMillis(); auto rv = LoadChainstate(fReset, chainman, - node, + node.mempool.get(), fPruneMode, chainparams, fReindexChainState, diff --git a/src/node/chainstate.cpp b/src/node/chainstate.cpp index 3d02c52edb0..4b0b71e00f6 100644 --- a/src/node/chainstate.cpp +++ b/src/node/chainstate.cpp @@ -8,14 +8,13 @@ #include // for RPCNotifyBlockChange #include // for GetTime #include // for CleanupBlockRevFiles, fHavePruned, fReindex -#include // for NodeContext #include // for InitError, uiInterface, and CClientUIInterface member access #include // for ShutdownRequested #include // for a lot of things std::optional LoadChainstate(bool fReset, ChainstateManager& chainman, - NodeContext& node, + CTxMemPool* mempool, bool fPruneMode, const CChainParams& chainparams, bool fReindexChainState, @@ -32,11 +31,11 @@ std::optional LoadChainstate(bool fReset, do { try { LOCK(cs_main); - chainman.InitializeChainstate(Assert(node.mempool.get())); + chainman.InitializeChainstate(Assert(mempool)); chainman.m_total_coinstip_cache = nCoinCacheUsage; chainman.m_total_coinsdb_cache = nCoinDBCache; - UnloadBlockIndex(node.mempool.get(), chainman); + UnloadBlockIndex(mempool, chainman); auto& pblocktree{chainman.m_blockman.m_block_tree_db}; // new CBlockTreeDB tries to delete the existing file, which diff --git a/src/node/chainstate.h b/src/node/chainstate.h index 87aad23e279..39621a31c5d 100644 --- a/src/node/chainstate.h +++ b/src/node/chainstate.h @@ -10,7 +10,7 @@ class CChainParams; class ChainstateManager; -struct NodeContext; +class CTxMemPool; enum class ChainstateLoadingError { ERROR_LOADING_BLOCK_DB, @@ -55,7 +55,7 @@ enum class ChainstateLoadingError { */ std::optional LoadChainstate(bool fReset, ChainstateManager& chainman, - NodeContext& node, + CTxMemPool* mempool, bool fPruneMode, const CChainParams& chainparams, bool fReindexChainState, From 8715658983a0a07c56513acd8ded8dfc59c5c169 Mon Sep 17 00:00:00 2001 From: Carl Dong Date: Thu, 11 Nov 2021 19:19:09 -0500 Subject: [PATCH 06/23] Move mempool nullptr Assert out of LoadChainstate --- src/init.cpp | 2 +- src/node/chainstate.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/init.cpp b/src/init.cpp index eb23ae501c8..6ed382a00ce 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -1421,7 +1421,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) const int64_t load_block_index_start_time = GetTimeMillis(); auto rv = LoadChainstate(fReset, chainman, - node.mempool.get(), + Assert(node.mempool.get()), fPruneMode, chainparams, fReindexChainState, diff --git a/src/node/chainstate.cpp b/src/node/chainstate.cpp index 4b0b71e00f6..b8914a0c657 100644 --- a/src/node/chainstate.cpp +++ b/src/node/chainstate.cpp @@ -31,7 +31,7 @@ std::optional LoadChainstate(bool fReset, do { try { LOCK(cs_main); - chainman.InitializeChainstate(Assert(mempool)); + chainman.InitializeChainstate(mempool); chainman.m_total_coinstip_cache = nCoinCacheUsage; chainman.m_total_coinsdb_cache = nCoinDBCache; From 975235ca0a8f5bcf9df880698b3b0d4bbde9f7fb Mon Sep 17 00:00:00 2001 From: Carl Dong Date: Thu, 11 Nov 2021 16:20:44 -0500 Subject: [PATCH 07/23] Move init logistics message for BAD_GENESIS_BLOCK to init.cpp --- src/init.cpp | 2 ++ src/node/chainstate.cpp | 2 -- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/init.cpp b/src/init.cpp index 6ed382a00ce..0db5464fbc8 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -1436,6 +1436,8 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) strLoadError = _("Error loading block database"); break; case ChainstateLoadingError::ERROR_BAD_GENESIS_BLOCK: + // If the loaded chain has a wrong genesis, bail out immediately + // (we're likely using a testnet datadir, or the other way around). return InitError(_("Incorrect or no genesis block found. Wrong datadir for network?")); case ChainstateLoadingError::ERROR_PRUNED_NEEDS_REINDEX: strLoadError = _("You need to rebuild the database using -reindex to go back to unpruned mode. This will redownload the entire blockchain"); diff --git a/src/node/chainstate.cpp b/src/node/chainstate.cpp index b8914a0c657..86de364ca72 100644 --- a/src/node/chainstate.cpp +++ b/src/node/chainstate.cpp @@ -61,8 +61,6 @@ std::optional LoadChainstate(bool fReset, return ChainstateLoadingError::ERROR_LOADING_BLOCK_DB; } - // If the loaded chain has a wrong genesis, bail out immediately - // (we're likely using a testnet datadir, or the other way around). if (!chainman.BlockIndex().empty() && !chainman.m_blockman.LookupBlockIndex(chainparams.GetConsensus().hashGenesisBlock)) { return ChainstateLoadingError::ERROR_BAD_GENESIS_BLOCK; From adf4912d77496b9a243476c5944528f95641f14d Mon Sep 17 00:00:00 2001 From: Carl Dong Date: Mon, 20 Sep 2021 17:46:06 -0400 Subject: [PATCH 08/23] node/chainstate: Remove do/while loop I strongly recommend reviewing with the following git-diff flags: --ignore-space-change --- src/node/chainstate.cpp | 237 ++++++++++++++++++++-------------------- 1 file changed, 118 insertions(+), 119 deletions(-) diff --git a/src/node/chainstate.cpp b/src/node/chainstate.cpp index 86de364ca72..8cc523fdd59 100644 --- a/src/node/chainstate.cpp +++ b/src/node/chainstate.cpp @@ -28,139 +28,138 @@ std::optional LoadChainstate(bool fReset, return fReset || fReindexChainState || chainstate->CoinsTip().GetBestBlock().IsNull(); }; - do { - try { - LOCK(cs_main); - chainman.InitializeChainstate(mempool); - chainman.m_total_coinstip_cache = nCoinCacheUsage; - chainman.m_total_coinsdb_cache = nCoinDBCache; + try { + LOCK(cs_main); + chainman.InitializeChainstate(mempool); + chainman.m_total_coinstip_cache = nCoinCacheUsage; + chainman.m_total_coinsdb_cache = nCoinDBCache; - UnloadBlockIndex(mempool, chainman); + UnloadBlockIndex(mempool, chainman); - auto& pblocktree{chainman.m_blockman.m_block_tree_db}; - // new CBlockTreeDB tries to delete the existing file, which - // fails if it's still open from the previous loop. Close it first: - pblocktree.reset(); - pblocktree.reset(new CBlockTreeDB(nBlockTreeDBCache, false, fReset)); + auto& pblocktree{chainman.m_blockman.m_block_tree_db}; + // new CBlockTreeDB tries to delete the existing file, which + // fails if it's still open from the previous loop. Close it first: + pblocktree.reset(); + pblocktree.reset(new CBlockTreeDB(nBlockTreeDBCache, false, fReset)); - if (fReset) { - pblocktree->WriteReindexing(true); - //If we're reindexing in prune mode, wipe away unusable block files and all undo data files - if (fPruneMode) - CleanupBlockRevFiles(); - } + if (fReset) { + pblocktree->WriteReindexing(true); + //If we're reindexing in prune mode, wipe away unusable block files and all undo data files + if (fPruneMode) + CleanupBlockRevFiles(); + } + if (ShutdownRequested()) return ChainstateLoadingError::SHUTDOWN_PROBED; + + // LoadBlockIndex will load fHavePruned if we've ever removed a + // block file from disk. + // Note that it also sets fReindex based on the disk flag! + // From here on out fReindex and fReset mean something different! + if (!chainman.LoadBlockIndex()) { if (ShutdownRequested()) return ChainstateLoadingError::SHUTDOWN_PROBED; - - // LoadBlockIndex will load fHavePruned if we've ever removed a - // block file from disk. - // Note that it also sets fReindex based on the disk flag! - // From here on out fReindex and fReset mean something different! - if (!chainman.LoadBlockIndex()) { - if (ShutdownRequested()) return ChainstateLoadingError::SHUTDOWN_PROBED; - return ChainstateLoadingError::ERROR_LOADING_BLOCK_DB; - } - - if (!chainman.BlockIndex().empty() && - !chainman.m_blockman.LookupBlockIndex(chainparams.GetConsensus().hashGenesisBlock)) { - return ChainstateLoadingError::ERROR_BAD_GENESIS_BLOCK; - } - - // Check for changed -prune state. What we are concerned about is a user who has pruned blocks - // in the past, but is now trying to run unpruned. - if (fHavePruned && !fPruneMode) { - return ChainstateLoadingError::ERROR_PRUNED_NEEDS_REINDEX; - } - - // At this point blocktree args are consistent with what's on disk. - // If we're not mid-reindex (based on disk + args), add a genesis block on disk - // (otherwise we use the one already on disk). - // This is called again in ThreadImport after the reindex completes. - if (!fReindex && !chainman.ActiveChainstate().LoadGenesisBlock()) { - return ChainstateLoadingError::ERROR_LOAD_GENESIS_BLOCK_FAILED; - } - - // At this point we're either in reindex or we've loaded a useful - // block tree into BlockIndex()! - - for (CChainState* chainstate : chainman.GetAll()) { - chainstate->InitCoinsDB( - /* cache_size_bytes */ nCoinDBCache, - /* in_memory */ false, - /* should_wipe */ fReset || fReindexChainState); - - 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 (!chainstate->CoinsDB().Upgrade()) { - return ChainstateLoadingError::ERROR_CHAINSTATE_UPGRADE_FAILED; - } - - // ReplayBlocks is a no-op if we cleared the coinsviewdb with -reindex or -reindex-chainstate - if (!chainstate->ReplayBlocks()) { - return ChainstateLoadingError::ERROR_REPLAYBLOCKS_FAILED; - } - - // The on-disk coinsdb is now in a good state, create the cache - chainstate->InitCoinsCache(nCoinCacheUsage); - assert(chainstate->CanFlushToDisk()); - - if (!is_coinsview_empty(chainstate)) { - // LoadChainTip initializes the chain based on CoinsTip()'s best block - if (!chainstate->LoadChainTip()) { - return ChainstateLoadingError::ERROR_LOADCHAINTIP_FAILED; - } - assert(chainstate->m_chain.Tip() != nullptr); - } - } - } catch (const std::exception& e) { - LogPrintf("%s\n", e.what()); - return ChainstateLoadingError::ERROR_GENERIC_BLOCKDB_OPEN_FAILED; + return ChainstateLoadingError::ERROR_LOADING_BLOCK_DB; } - if (!fReset) { - LOCK(cs_main); - auto chainstates{chainman.GetAll()}; - if (std::any_of(chainstates.begin(), chainstates.end(), - [](const CChainState* cs) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { return cs->NeedsRedownload(); })) { - return ChainstateLoadingError::ERROR_BLOCKS_WITNESS_INSUFFICIENTLY_VALIDATED; - } + if (!chainman.BlockIndex().empty() && + !chainman.m_blockman.LookupBlockIndex(chainparams.GetConsensus().hashGenesisBlock)) { + return ChainstateLoadingError::ERROR_BAD_GENESIS_BLOCK; } - try { - LOCK(cs_main); + // Check for changed -prune state. What we are concerned about is a user who has pruned blocks + // in the past, but is now trying to run unpruned. + if (fHavePruned && !fPruneMode) { + return ChainstateLoadingError::ERROR_PRUNED_NEEDS_REINDEX; + } - for (CChainState* chainstate : chainman.GetAll()) { - if (!is_coinsview_empty(chainstate)) { - uiInterface.InitMessage(_("Verifying blocks…").translated); - if (fHavePruned && check_blocks > MIN_BLOCKS_TO_KEEP) { - LogPrintf("Prune: pruned datadir may not have more than %d blocks; only checking available blocks\n", - MIN_BLOCKS_TO_KEEP); - } + // At this point blocktree args are consistent with what's on disk. + // If we're not mid-reindex (based on disk + args), add a genesis block on disk + // (otherwise we use the one already on disk). + // This is called again in ThreadImport after the reindex completes. + if (!fReindex && !chainman.ActiveChainstate().LoadGenesisBlock()) { + return ChainstateLoadingError::ERROR_LOAD_GENESIS_BLOCK_FAILED; + } - const CBlockIndex* tip = chainstate->m_chain.Tip(); - RPCNotifyBlockChange(tip); - if (tip && tip->nTime > GetTime() + MAX_FUTURE_BLOCK_TIME) { - return ChainstateLoadingError::ERROR_BLOCK_FROM_FUTURE; - } + // At this point we're either in reindex or we've loaded a useful + // block tree into BlockIndex()! - if (!CVerifyDB().VerifyDB( - *chainstate, chainparams, chainstate->CoinsDB(), - check_level, - check_blocks)) { - return ChainstateLoadingError::ERROR_CORRUPTED_BLOCK_DB; - } + for (CChainState* chainstate : chainman.GetAll()) { + chainstate->InitCoinsDB( + /* cache_size_bytes */ nCoinDBCache, + /* in_memory */ false, + /* should_wipe */ fReset || fReindexChainState); + + 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 (!chainstate->CoinsDB().Upgrade()) { + return ChainstateLoadingError::ERROR_CHAINSTATE_UPGRADE_FAILED; + } + + // ReplayBlocks is a no-op if we cleared the coinsviewdb with -reindex or -reindex-chainstate + if (!chainstate->ReplayBlocks()) { + return ChainstateLoadingError::ERROR_REPLAYBLOCKS_FAILED; + } + + // The on-disk coinsdb is now in a good state, create the cache + chainstate->InitCoinsCache(nCoinCacheUsage); + assert(chainstate->CanFlushToDisk()); + + if (!is_coinsview_empty(chainstate)) { + // LoadChainTip initializes the chain based on CoinsTip()'s best block + if (!chainstate->LoadChainTip()) { + return ChainstateLoadingError::ERROR_LOADCHAINTIP_FAILED; + } + assert(chainstate->m_chain.Tip() != nullptr); + } + } + } catch (const std::exception& e) { + LogPrintf("%s\n", e.what()); + return ChainstateLoadingError::ERROR_GENERIC_BLOCKDB_OPEN_FAILED; + } + + if (!fReset) { + LOCK(cs_main); + auto chainstates{chainman.GetAll()}; + if (std::any_of(chainstates.begin(), chainstates.end(), + [](const CChainState* cs) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { return cs->NeedsRedownload(); })) { + return ChainstateLoadingError::ERROR_BLOCKS_WITNESS_INSUFFICIENTLY_VALIDATED; + } + } + + try { + LOCK(cs_main); + + for (CChainState* chainstate : chainman.GetAll()) { + if (!is_coinsview_empty(chainstate)) { + uiInterface.InitMessage(_("Verifying blocks…").translated); + if (fHavePruned && check_blocks > 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 > GetTime() + MAX_FUTURE_BLOCK_TIME) { + return ChainstateLoadingError::ERROR_BLOCK_FROM_FUTURE; + } + + if (!CVerifyDB().VerifyDB( + *chainstate, chainparams, chainstate->CoinsDB(), + check_level, + check_blocks)) { + return ChainstateLoadingError::ERROR_CORRUPTED_BLOCK_DB; } } - } catch (const std::exception& e) { - LogPrintf("%s\n", e.what()); - return ChainstateLoadingError::ERROR_GENERIC_BLOCKDB_OPEN_FAILED; } - } while(false); + } catch (const std::exception& e) { + LogPrintf("%s\n", e.what()); + return ChainstateLoadingError::ERROR_GENERIC_BLOCKDB_OPEN_FAILED; + } + return std::nullopt; } From ca7c0b934db68acdc410e3a82f1ed898382da2e5 Mon Sep 17 00:00:00 2001 From: Carl Dong Date: Wed, 10 Nov 2021 15:57:14 -0500 Subject: [PATCH 09/23] Split off VerifyLoadedChainstate --- src/init.cpp | 38 +++++++++++++++++++++++++------------- src/node/chainstate.cpp | 24 ++++++++++++++++++------ src/node/chainstate.h | 19 ++++++++++++++----- 3 files changed, 57 insertions(+), 24 deletions(-) diff --git a/src/init.cpp b/src/init.cpp index 0db5464fbc8..e52ea57bdbc 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -1427,9 +1427,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) fReindexChainState, nBlockTreeDBCache, nCoinDBCache, - nCoinCacheUsage, - args.GetIntArg("-checkblocks", DEFAULT_CHECKBLOCKS), - args.GetIntArg("-checklevel", DEFAULT_CHECKLEVEL)); + nCoinCacheUsage); if (rv.has_value()) { switch (rv.value()) { case ChainstateLoadingError::ERROR_LOADING_BLOCK_DB: @@ -1461,20 +1459,34 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) strLoadError = strprintf(_("Witness data for blocks after height %d requires validation. Please restart with -reindex."), chainparams.GetConsensus().SegwitHeight); break; - case ChainstateLoadingError::ERROR_BLOCK_FROM_FUTURE: - 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"); - break; - case ChainstateLoadingError::ERROR_CORRUPTED_BLOCK_DB: - strLoadError = _("Corrupted block database detected"); - break; case ChainstateLoadingError::SHUTDOWN_PROBED: break; } } else { - fLoaded = true; - LogPrintf(" block index %15dms\n", GetTimeMillis() - load_block_index_start_time); + auto rv2 = VerifyLoadedChainstate(chainman, + fReset, + fReindexChainState, + chainparams, + args.GetIntArg("-checkblocks", DEFAULT_CHECKBLOCKS), + args.GetIntArg("-checklevel", DEFAULT_CHECKLEVEL)); + if (rv2.has_value()) { + switch (rv2.value()) { + case ChainstateLoadVerifyError::ERROR_BLOCK_FROM_FUTURE: + 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"); + break; + case ChainstateLoadVerifyError::ERROR_CORRUPTED_BLOCK_DB: + strLoadError = _("Corrupted block database detected"); + break; + case ChainstateLoadVerifyError::ERROR_GENERIC_FAILURE: + strLoadError = _("Error opening block database"); + break; + } + } else { + fLoaded = true; + LogPrintf(" block index %15dms\n", GetTimeMillis() - load_block_index_start_time); + } } if (!fLoaded && !ShutdownRequested()) { diff --git a/src/node/chainstate.cpp b/src/node/chainstate.cpp index 8cc523fdd59..7dbaedb4832 100644 --- a/src/node/chainstate.cpp +++ b/src/node/chainstate.cpp @@ -20,9 +20,7 @@ std::optional LoadChainstate(bool fReset, bool fReindexChainState, int64_t nBlockTreeDBCache, int64_t nCoinDBCache, - int64_t nCoinCacheUsage, - unsigned int check_blocks, - unsigned int check_level) + int64_t nCoinCacheUsage) { auto is_coinsview_empty = [&](CChainState* chainstate) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) { return fReset || fReindexChainState || chainstate->CoinsTip().GetBestBlock().IsNull(); @@ -131,6 +129,20 @@ std::optional LoadChainstate(bool fReset, } } + return std::nullopt; +} + +std::optional VerifyLoadedChainstate(ChainstateManager& chainman, + bool fReset, + bool fReindexChainState, + const CChainParams& chainparams, + unsigned int check_blocks, + unsigned int check_level) +{ + auto is_coinsview_empty = [&](CChainState* chainstate) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) { + return fReset || fReindexChainState || chainstate->CoinsTip().GetBestBlock().IsNull(); + }; + try { LOCK(cs_main); @@ -145,20 +157,20 @@ std::optional LoadChainstate(bool fReset, const CBlockIndex* tip = chainstate->m_chain.Tip(); RPCNotifyBlockChange(tip); if (tip && tip->nTime > GetTime() + MAX_FUTURE_BLOCK_TIME) { - return ChainstateLoadingError::ERROR_BLOCK_FROM_FUTURE; + return ChainstateLoadVerifyError::ERROR_BLOCK_FROM_FUTURE; } if (!CVerifyDB().VerifyDB( *chainstate, chainparams, chainstate->CoinsDB(), check_level, check_blocks)) { - return ChainstateLoadingError::ERROR_CORRUPTED_BLOCK_DB; + return ChainstateLoadVerifyError::ERROR_CORRUPTED_BLOCK_DB; } } } } catch (const std::exception& e) { LogPrintf("%s\n", e.what()); - return ChainstateLoadingError::ERROR_GENERIC_BLOCKDB_OPEN_FAILED; + return ChainstateLoadVerifyError::ERROR_GENERIC_FAILURE; } return std::nullopt; diff --git a/src/node/chainstate.h b/src/node/chainstate.h index 39621a31c5d..c122391a41d 100644 --- a/src/node/chainstate.h +++ b/src/node/chainstate.h @@ -22,8 +22,6 @@ enum class ChainstateLoadingError { ERROR_LOADCHAINTIP_FAILED, ERROR_GENERIC_BLOCKDB_OPEN_FAILED, ERROR_BLOCKS_WITNESS_INSUFFICIENTLY_VALIDATED, - ERROR_BLOCK_FROM_FUTURE, - ERROR_CORRUPTED_BLOCK_DB, SHUTDOWN_PROBED, }; @@ -61,8 +59,19 @@ std::optional LoadChainstate(bool fReset, bool fReindexChainState, int64_t nBlockTreeDBCache, int64_t nCoinDBCache, - int64_t nCoinCacheUsage, - unsigned int check_blocks, - unsigned int check_level); + int64_t nCoinCacheUsage); + +enum class ChainstateLoadVerifyError { + ERROR_BLOCK_FROM_FUTURE, + ERROR_CORRUPTED_BLOCK_DB, + ERROR_GENERIC_FAILURE, +}; + +std::optional VerifyLoadedChainstate(ChainstateManager& chainman, + bool fReset, + bool fReindexChainState, + const CChainParams& chainparams, + unsigned int check_blocks, + unsigned int check_level); #endif // BITCOIN_NODE_CHAINSTATE_H From b345979a2b03b671c0984edd7e48e0baec2e2f34 Mon Sep 17 00:00:00 2001 From: Carl Dong Date: Wed, 18 Aug 2021 13:39:34 -0400 Subject: [PATCH 10/23] node/chainstate: Decouple from concept of uiInterface ...instead allow the caller to optionally pass in callbacks which are triggered for certain events. Behaviour change: The string "Verifying blocks..." was previously printed for each chainstate in chainman which did not have an effectively empty coinsview, now it will be printed once unconditionally before we call VerifyLoadedChain. --- src/init.cpp | 8 +++++++- src/node/chainstate.cpp | 13 +++++-------- src/node/chainstate.h | 4 +++- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/src/init.cpp b/src/init.cpp index e52ea57bdbc..80f767a4317 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -1427,7 +1427,12 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) fReindexChainState, nBlockTreeDBCache, nCoinDBCache, - nCoinCacheUsage); + nCoinCacheUsage, + []() { + uiInterface.ThreadSafeMessageBox( + _("Error reading from database, shutting down."), + "", CClientUIInterface::MSG_ERROR); + }); if (rv.has_value()) { switch (rv.value()) { case ChainstateLoadingError::ERROR_LOADING_BLOCK_DB: @@ -1463,6 +1468,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) break; } } else { + uiInterface.InitMessage(_("Verifying blocks…").translated); auto rv2 = VerifyLoadedChainstate(chainman, fReset, fReindexChainState, diff --git a/src/node/chainstate.cpp b/src/node/chainstate.cpp index 7dbaedb4832..36d10e99d99 100644 --- a/src/node/chainstate.cpp +++ b/src/node/chainstate.cpp @@ -8,7 +8,6 @@ #include // for RPCNotifyBlockChange #include // for GetTime #include // for CleanupBlockRevFiles, fHavePruned, fReindex -#include // for InitError, uiInterface, and CClientUIInterface member access #include // for ShutdownRequested #include // for a lot of things @@ -20,7 +19,8 @@ std::optional LoadChainstate(bool fReset, bool fReindexChainState, int64_t nBlockTreeDBCache, int64_t nCoinDBCache, - int64_t nCoinCacheUsage) + int64_t nCoinCacheUsage, + std::function coins_error_cb) { auto is_coinsview_empty = [&](CChainState* chainstate) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) { return fReset || fReindexChainState || chainstate->CoinsTip().GetBestBlock().IsNull(); @@ -86,11 +86,9 @@ std::optional LoadChainstate(bool fReset, /* in_memory */ false, /* should_wipe */ fReset || fReindexChainState); - chainstate->CoinsErrorCatcher().AddReadErrCallback([]() { - uiInterface.ThreadSafeMessageBox( - _("Error reading from database, shutting down."), - "", CClientUIInterface::MSG_ERROR); - }); + if (coins_error_cb) { + chainstate->CoinsErrorCatcher().AddReadErrCallback(coins_error_cb); + } // If necessary, upgrade from older database format. // This is a no-op if we cleared the coinsviewdb with -reindex or -reindex-chainstate @@ -148,7 +146,6 @@ std::optional VerifyLoadedChainstate(ChainstateManage for (CChainState* chainstate : chainman.GetAll()) { if (!is_coinsview_empty(chainstate)) { - uiInterface.InitMessage(_("Verifying blocks…").translated); if (fHavePruned && check_blocks > MIN_BLOCKS_TO_KEEP) { LogPrintf("Prune: pruned datadir may not have more than %d blocks; only checking available blocks\n", MIN_BLOCKS_TO_KEEP); diff --git a/src/node/chainstate.h b/src/node/chainstate.h index c122391a41d..84a86a082b6 100644 --- a/src/node/chainstate.h +++ b/src/node/chainstate.h @@ -6,6 +6,7 @@ #define BITCOIN_NODE_CHAINSTATE_H #include // for int64_t +#include // for std::function #include // for std::optional class CChainParams; @@ -59,7 +60,8 @@ std::optional LoadChainstate(bool fReset, bool fReindexChainState, int64_t nBlockTreeDBCache, int64_t nCoinDBCache, - int64_t nCoinCacheUsage); + int64_t nCoinCacheUsage, + std::function coins_error_cb = nullptr); enum class ChainstateLoadVerifyError { ERROR_BLOCK_FROM_FUTURE, From aad8d597890c3707ae96fdb2b9fadc270ca574dd Mon Sep 17 00:00:00 2001 From: Carl Dong Date: Wed, 18 Aug 2021 13:54:08 -0400 Subject: [PATCH 11/23] node/chainstate: Reduce coupling of LogPrintf ...by moving the try/catch out of LoadChainstate I strongly recommend reviewing with the following git-diff flags: --color-moved=dimmed_zebra --color-moved-ws=allow-indentation-change --- src/init.cpp | 54 +++++++++++++++++++++++++---------------- src/node/chainstate.cpp | 10 ++------ 2 files changed, 35 insertions(+), 29 deletions(-) diff --git a/src/init.cpp b/src/init.cpp index 80f767a4317..411692d88b0 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -1419,20 +1419,26 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) uiInterface.InitMessage(_("Loading block index…").translated); const int64_t load_block_index_start_time = GetTimeMillis(); - auto rv = LoadChainstate(fReset, - chainman, - Assert(node.mempool.get()), - fPruneMode, - chainparams, - fReindexChainState, - nBlockTreeDBCache, - nCoinDBCache, - nCoinCacheUsage, - []() { - uiInterface.ThreadSafeMessageBox( - _("Error reading from database, shutting down."), - "", CClientUIInterface::MSG_ERROR); - }); + std::optional rv; + try { + rv = LoadChainstate(fReset, + chainman, + Assert(node.mempool.get()), + fPruneMode, + chainparams, + fReindexChainState, + nBlockTreeDBCache, + nCoinDBCache, + nCoinCacheUsage, + []() { + uiInterface.ThreadSafeMessageBox( + _("Error reading from database, shutting down."), + "", CClientUIInterface::MSG_ERROR); + }); + } catch (const std::exception& e) { + LogPrintf("%s\n", e.what()); + rv = ChainstateLoadingError::ERROR_GENERIC_BLOCKDB_OPEN_FAILED; + } if (rv.has_value()) { switch (rv.value()) { case ChainstateLoadingError::ERROR_LOADING_BLOCK_DB: @@ -1468,13 +1474,19 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) break; } } else { - uiInterface.InitMessage(_("Verifying blocks…").translated); - auto rv2 = VerifyLoadedChainstate(chainman, - fReset, - fReindexChainState, - chainparams, - args.GetIntArg("-checkblocks", DEFAULT_CHECKBLOCKS), - args.GetIntArg("-checklevel", DEFAULT_CHECKLEVEL)); + std::optional rv2; + try { + uiInterface.InitMessage(_("Verifying blocks…").translated); + rv2 = VerifyLoadedChainstate(chainman, + fReset, + fReindexChainState, + chainparams, + args.GetIntArg("-checkblocks", DEFAULT_CHECKBLOCKS), + args.GetIntArg("-checklevel", DEFAULT_CHECKLEVEL)); + } catch (const std::exception& e) { + LogPrintf("%s\n", e.what()); + rv2 = ChainstateLoadVerifyError::ERROR_GENERIC_FAILURE; + } if (rv2.has_value()) { switch (rv2.value()) { case ChainstateLoadVerifyError::ERROR_BLOCK_FROM_FUTURE: diff --git a/src/node/chainstate.cpp b/src/node/chainstate.cpp index 36d10e99d99..99bf3d8e94a 100644 --- a/src/node/chainstate.cpp +++ b/src/node/chainstate.cpp @@ -26,7 +26,7 @@ std::optional LoadChainstate(bool fReset, return fReset || fReindexChainState || chainstate->CoinsTip().GetBestBlock().IsNull(); }; - try { + { LOCK(cs_main); chainman.InitializeChainstate(mempool); chainman.m_total_coinstip_cache = nCoinCacheUsage; @@ -113,9 +113,6 @@ std::optional LoadChainstate(bool fReset, assert(chainstate->m_chain.Tip() != nullptr); } } - } catch (const std::exception& e) { - LogPrintf("%s\n", e.what()); - return ChainstateLoadingError::ERROR_GENERIC_BLOCKDB_OPEN_FAILED; } if (!fReset) { @@ -141,7 +138,7 @@ std::optional VerifyLoadedChainstate(ChainstateManage return fReset || fReindexChainState || chainstate->CoinsTip().GetBestBlock().IsNull(); }; - try { + { LOCK(cs_main); for (CChainState* chainstate : chainman.GetAll()) { @@ -165,9 +162,6 @@ std::optional VerifyLoadedChainstate(ChainstateManage } } } - } catch (const std::exception& e) { - LogPrintf("%s\n", e.what()); - return ChainstateLoadVerifyError::ERROR_GENERIC_FAILURE; } return std::nullopt; From 8d466a8504bfb81ce8699d650aa72ec9cc8b0a54 Mon Sep 17 00:00:00 2001 From: Carl Dong Date: Fri, 12 Nov 2021 18:15:47 -0500 Subject: [PATCH 12/23] Move -checkblocks LogPrintf to AppInitMain --- src/init.cpp | 7 ++++++- src/node/chainstate.cpp | 5 ----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/init.cpp b/src/init.cpp index 411692d88b0..0c5baeee3b9 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -1477,11 +1477,16 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) std::optional rv2; try { uiInterface.InitMessage(_("Verifying blocks…").translated); + auto check_blocks = args.GetIntArg("-checkblocks", DEFAULT_CHECKBLOCKS); + if (fHavePruned && check_blocks > MIN_BLOCKS_TO_KEEP) { + LogPrintf("Prune: pruned datadir may not have more than %d blocks; only checking available blocks\n", + MIN_BLOCKS_TO_KEEP); + } rv2 = VerifyLoadedChainstate(chainman, fReset, fReindexChainState, chainparams, - args.GetIntArg("-checkblocks", DEFAULT_CHECKBLOCKS), + check_blocks, args.GetIntArg("-checklevel", DEFAULT_CHECKLEVEL)); } catch (const std::exception& e) { LogPrintf("%s\n", e.what()); diff --git a/src/node/chainstate.cpp b/src/node/chainstate.cpp index 99bf3d8e94a..c795c740e45 100644 --- a/src/node/chainstate.cpp +++ b/src/node/chainstate.cpp @@ -143,11 +143,6 @@ std::optional VerifyLoadedChainstate(ChainstateManage for (CChainState* chainstate : chainman.GetAll()) { if (!is_coinsview_empty(chainstate)) { - if (fHavePruned && check_blocks > 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 > GetTime() + MAX_FUTURE_BLOCK_TIME) { From 2414ebc18b8bebf79c47e58a4293d0fc6420a811 Mon Sep 17 00:00:00 2001 From: Carl Dong Date: Wed, 18 Aug 2021 14:36:28 -0400 Subject: [PATCH 13/23] init: Delay RPC block notif until warmup finished See added code comment for more details. --- src/init.cpp | 10 ++++++++++ src/node/chainstate.cpp | 2 -- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/init.cpp b/src/init.cpp index 0c5baeee3b9..9c8963d10da 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -1777,7 +1777,17 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) // ********************************************************* Step 13: finished + // At this point, the RPC is "started", but still in warmup, which means it + // cannot yet be called. Before we make it callable, we need to make sure + // that the RPC's view of the best block is valid and consistent with + // ChainstateManager's ActiveTip. + // + // If we do not do this, RPC's view of the best block will be height=0 and + // hash=0x0. This will lead to erroroneous responses for things like + // waitforblockheight. + RPCNotifyBlockChange(chainman.ActiveTip()); SetRPCWarmupFinished(); + uiInterface.InitMessage(_("Done loading").translated); for (const auto& client : node.chain_clients) { diff --git a/src/node/chainstate.cpp b/src/node/chainstate.cpp index c795c740e45..56d1073bab8 100644 --- a/src/node/chainstate.cpp +++ b/src/node/chainstate.cpp @@ -5,7 +5,6 @@ #include #include // for CChainParams -#include // for RPCNotifyBlockChange #include // for GetTime #include // for CleanupBlockRevFiles, fHavePruned, fReindex #include // for ShutdownRequested @@ -144,7 +143,6 @@ std::optional VerifyLoadedChainstate(ChainstateManage for (CChainState* chainstate : chainman.GetAll()) { if (!is_coinsview_empty(chainstate)) { const CBlockIndex* tip = chainstate->m_chain.Tip(); - RPCNotifyBlockChange(tip); if (tip && tip->nTime > GetTime() + MAX_FUTURE_BLOCK_TIME) { return ChainstateLoadVerifyError::ERROR_BLOCK_FROM_FUTURE; } From 05441c2dc5f60e2025476d8ec94c9025032d118c Mon Sep 17 00:00:00 2001 From: Carl Dong Date: Wed, 22 Sep 2021 15:36:10 -0400 Subject: [PATCH 14/23] node/chainstate: Decouple from GetTime ...instead pass in a std::function Note that the static_cast is needed (apparently) for the compiler to know which overloaded GetTime to choose. --- src/init.cpp | 3 ++- src/node/chainstate.cpp | 6 +++--- src/node/chainstate.h | 3 ++- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/init.cpp b/src/init.cpp index 9c8963d10da..5df4d794f34 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -1487,7 +1487,8 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) fReindexChainState, chainparams, check_blocks, - args.GetIntArg("-checklevel", DEFAULT_CHECKLEVEL)); + args.GetIntArg("-checklevel", DEFAULT_CHECKLEVEL), + static_cast(GetTime)); } catch (const std::exception& e) { LogPrintf("%s\n", e.what()); rv2 = ChainstateLoadVerifyError::ERROR_GENERIC_FAILURE; diff --git a/src/node/chainstate.cpp b/src/node/chainstate.cpp index 56d1073bab8..242b202d2f1 100644 --- a/src/node/chainstate.cpp +++ b/src/node/chainstate.cpp @@ -5,7 +5,6 @@ #include #include // for CChainParams -#include // for GetTime #include // for CleanupBlockRevFiles, fHavePruned, fReindex #include // for ShutdownRequested #include // for a lot of things @@ -131,7 +130,8 @@ std::optional VerifyLoadedChainstate(ChainstateManage bool fReindexChainState, const CChainParams& chainparams, unsigned int check_blocks, - unsigned int check_level) + unsigned int check_level, + std::function get_unix_time_seconds) { auto is_coinsview_empty = [&](CChainState* chainstate) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) { return fReset || fReindexChainState || chainstate->CoinsTip().GetBestBlock().IsNull(); @@ -143,7 +143,7 @@ std::optional VerifyLoadedChainstate(ChainstateManage for (CChainState* chainstate : chainman.GetAll()) { if (!is_coinsview_empty(chainstate)) { const CBlockIndex* tip = chainstate->m_chain.Tip(); - if (tip && tip->nTime > GetTime() + MAX_FUTURE_BLOCK_TIME) { + if (tip && tip->nTime > get_unix_time_seconds() + 2 * 60 * 60) { return ChainstateLoadVerifyError::ERROR_BLOCK_FROM_FUTURE; } diff --git a/src/node/chainstate.h b/src/node/chainstate.h index 84a86a082b6..3c950409d39 100644 --- a/src/node/chainstate.h +++ b/src/node/chainstate.h @@ -74,6 +74,7 @@ std::optional VerifyLoadedChainstate(ChainstateManage bool fReindexChainState, const CChainParams& chainparams, unsigned int check_blocks, - unsigned int check_level); + unsigned int check_level, + std::function get_unix_time_seconds); #endif // BITCOIN_NODE_CHAINSTATE_H From 4da9c076d1cf12728730bb1f7e8906d4e9bfaba5 Mon Sep 17 00:00:00 2001 From: Carl Dong Date: Wed, 22 Sep 2021 15:36:24 -0400 Subject: [PATCH 15/23] node/chainstate: Decouple from ShutdownRequested ...instead allow optionally passing in a std::function --- src/init.cpp | 1 + src/node/chainstate.cpp | 6 +++--- src/node/chainstate.h | 5 +++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/init.cpp b/src/init.cpp index 5df4d794f34..67ae94eb2c0 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -1430,6 +1430,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) nBlockTreeDBCache, nCoinDBCache, nCoinCacheUsage, + ShutdownRequested, []() { uiInterface.ThreadSafeMessageBox( _("Error reading from database, shutting down."), diff --git a/src/node/chainstate.cpp b/src/node/chainstate.cpp index 242b202d2f1..c112bc949ae 100644 --- a/src/node/chainstate.cpp +++ b/src/node/chainstate.cpp @@ -6,7 +6,6 @@ #include // for CChainParams #include // for CleanupBlockRevFiles, fHavePruned, fReindex -#include // for ShutdownRequested #include // for a lot of things std::optional LoadChainstate(bool fReset, @@ -18,6 +17,7 @@ std::optional LoadChainstate(bool fReset, int64_t nBlockTreeDBCache, int64_t nCoinDBCache, int64_t nCoinCacheUsage, + std::function shutdown_requested, std::function coins_error_cb) { auto is_coinsview_empty = [&](CChainState* chainstate) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) { @@ -45,14 +45,14 @@ std::optional LoadChainstate(bool fReset, CleanupBlockRevFiles(); } - if (ShutdownRequested()) return ChainstateLoadingError::SHUTDOWN_PROBED; + if (shutdown_requested && shutdown_requested()) return ChainstateLoadingError::SHUTDOWN_PROBED; // LoadBlockIndex will load fHavePruned if we've ever removed a // block file from disk. // Note that it also sets fReindex based on the disk flag! // From here on out fReindex and fReset mean something different! if (!chainman.LoadBlockIndex()) { - if (ShutdownRequested()) return ChainstateLoadingError::SHUTDOWN_PROBED; + if (shutdown_requested && shutdown_requested()) return ChainstateLoadingError::SHUTDOWN_PROBED; return ChainstateLoadingError::ERROR_LOADING_BLOCK_DB; } diff --git a/src/node/chainstate.h b/src/node/chainstate.h index 3c950409d39..735906a540e 100644 --- a/src/node/chainstate.h +++ b/src/node/chainstate.h @@ -45,10 +45,10 @@ enum class ChainstateLoadingError { * differentiable by the specific enumerator. * * Note that a return value of SHUTDOWN_PROBED means ONLY that "during - * this sequence, when we explicitly checked ShutdownRequested() at + * this sequence, when we explicitly checked shutdown_requested() at * arbitrary points, one of those calls returned true". Therefore, a * return value other than SHUTDOWN_PROBED does not guarantee that - * ShutdownRequested() hasn't been called indirectly. + * shutdown_requested() hasn't been called indirectly. * - else * - Success! */ @@ -61,6 +61,7 @@ std::optional LoadChainstate(bool fReset, int64_t nBlockTreeDBCache, int64_t nCoinDBCache, int64_t nCoinCacheUsage, + std::function shutdown_requested = nullptr, std::function coins_error_cb = nullptr); enum class ChainstateLoadVerifyError { From 15f2e33bb3d1ad3bc997f6a84956337f46495091 Mon Sep 17 00:00:00 2001 From: Carl Dong Date: Mon, 20 Sep 2021 14:02:07 -0400 Subject: [PATCH 16/23] validation: VerifyDB only needs Consensus::Params Previously we were passing in CChainParams, when VerifyDB only needed the Consensus::Params subset. --- src/init.cpp | 4 ++-- src/node/chainstate.cpp | 10 +++++----- src/node/chainstate.h | 8 +++++--- src/rpc/blockchain.cpp | 2 +- src/validation.cpp | 8 ++++---- src/validation.h | 2 +- 6 files changed, 18 insertions(+), 16 deletions(-) diff --git a/src/init.cpp b/src/init.cpp index 67ae94eb2c0..e6f00717fbf 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -1425,7 +1425,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) chainman, Assert(node.mempool.get()), fPruneMode, - chainparams, + chainparams.GetConsensus(), fReindexChainState, nBlockTreeDBCache, nCoinDBCache, @@ -1486,7 +1486,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) rv2 = VerifyLoadedChainstate(chainman, fReset, fReindexChainState, - chainparams, + chainparams.GetConsensus(), check_blocks, args.GetIntArg("-checklevel", DEFAULT_CHECKLEVEL), static_cast(GetTime)); diff --git a/src/node/chainstate.cpp b/src/node/chainstate.cpp index c112bc949ae..b4264655d5c 100644 --- a/src/node/chainstate.cpp +++ b/src/node/chainstate.cpp @@ -4,7 +4,7 @@ #include -#include // for CChainParams +#include // for Consensus::Params #include // for CleanupBlockRevFiles, fHavePruned, fReindex #include // for a lot of things @@ -12,7 +12,7 @@ std::optional LoadChainstate(bool fReset, ChainstateManager& chainman, CTxMemPool* mempool, bool fPruneMode, - const CChainParams& chainparams, + const Consensus::Params& consensus_params, bool fReindexChainState, int64_t nBlockTreeDBCache, int64_t nCoinDBCache, @@ -57,7 +57,7 @@ std::optional LoadChainstate(bool fReset, } if (!chainman.BlockIndex().empty() && - !chainman.m_blockman.LookupBlockIndex(chainparams.GetConsensus().hashGenesisBlock)) { + !chainman.m_blockman.LookupBlockIndex(consensus_params.hashGenesisBlock)) { return ChainstateLoadingError::ERROR_BAD_GENESIS_BLOCK; } @@ -128,7 +128,7 @@ std::optional LoadChainstate(bool fReset, std::optional VerifyLoadedChainstate(ChainstateManager& chainman, bool fReset, bool fReindexChainState, - const CChainParams& chainparams, + const Consensus::Params& consensus_params, unsigned int check_blocks, unsigned int check_level, std::function get_unix_time_seconds) @@ -148,7 +148,7 @@ std::optional VerifyLoadedChainstate(ChainstateManage } if (!CVerifyDB().VerifyDB( - *chainstate, chainparams, chainstate->CoinsDB(), + *chainstate, consensus_params, chainstate->CoinsDB(), check_level, check_blocks)) { return ChainstateLoadVerifyError::ERROR_CORRUPTED_BLOCK_DB; diff --git a/src/node/chainstate.h b/src/node/chainstate.h index 735906a540e..e3369eb47cf 100644 --- a/src/node/chainstate.h +++ b/src/node/chainstate.h @@ -9,8 +9,10 @@ #include // for std::function #include // for std::optional -class CChainParams; class ChainstateManager; +namespace Consensus { + struct Params; +} class CTxMemPool; enum class ChainstateLoadingError { @@ -56,7 +58,7 @@ std::optional LoadChainstate(bool fReset, ChainstateManager& chainman, CTxMemPool* mempool, bool fPruneMode, - const CChainParams& chainparams, + const Consensus::Params& consensus_params, bool fReindexChainState, int64_t nBlockTreeDBCache, int64_t nCoinDBCache, @@ -73,7 +75,7 @@ enum class ChainstateLoadVerifyError { std::optional VerifyLoadedChainstate(ChainstateManager& chainman, bool fReset, bool fReindexChainState, - const CChainParams& chainparams, + const Consensus::Params& consensus_params, unsigned int check_blocks, unsigned int check_level, std::function get_unix_time_seconds); diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index bd11d768669..4af1f206a54 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -1364,7 +1364,7 @@ static RPCHelpMan verifychain() CChainState& active_chainstate = chainman.ActiveChainstate(); return CVerifyDB().VerifyDB( - active_chainstate, Params(), active_chainstate.CoinsTip(), check_level, check_depth); + active_chainstate, Params().GetConsensus(), active_chainstate.CoinsTip(), check_level, check_depth); }, }; } diff --git a/src/validation.cpp b/src/validation.cpp index 91ef961964b..d9f57d03de0 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -3860,7 +3860,7 @@ CVerifyDB::~CVerifyDB() bool CVerifyDB::VerifyDB( CChainState& chainstate, - const CChainParams& chainparams, + const Consensus::Params& consensus_params, CCoinsView& coinsview, int nCheckLevel, int nCheckDepth) { @@ -3902,10 +3902,10 @@ bool CVerifyDB::VerifyDB( } CBlock block; // check level 0: read from disk - if (!ReadBlockFromDisk(block, pindex, chainparams.GetConsensus())) + if (!ReadBlockFromDisk(block, pindex, consensus_params)) return error("VerifyDB(): *** ReadBlockFromDisk failed at %d, hash=%s", pindex->nHeight, pindex->GetBlockHash().ToString()); // check level 1: verify block validity - if (nCheckLevel >= 1 && !CheckBlock(block, state, chainparams.GetConsensus())) + if (nCheckLevel >= 1 && !CheckBlock(block, state, consensus_params)) return error("%s: *** found bad block at %d, hash=%s (%s)\n", __func__, pindex->nHeight, pindex->GetBlockHash().ToString(), state.ToString()); // check level 2: verify undo validity @@ -3953,7 +3953,7 @@ bool CVerifyDB::VerifyDB( uiInterface.ShowProgress(_("Verifying blocks…").translated, percentageDone, false); pindex = chainstate.m_chain.Next(pindex); CBlock block; - if (!ReadBlockFromDisk(block, pindex, chainparams.GetConsensus())) + if (!ReadBlockFromDisk(block, pindex, consensus_params)) return error("VerifyDB(): *** ReadBlockFromDisk failed at %d, hash=%s", pindex->nHeight, pindex->GetBlockHash().ToString()); if (!chainstate.ConnectBlock(block, state, pindex, coins)) { return error("VerifyDB(): *** found unconnectable block at %d, hash=%s (%s)", pindex->nHeight, pindex->GetBlockHash().ToString(), state.ToString()); diff --git a/src/validation.h b/src/validation.h index 2609538ecec..cc0cc0210fe 100644 --- a/src/validation.h +++ b/src/validation.h @@ -344,7 +344,7 @@ public: ~CVerifyDB(); bool VerifyDB( CChainState& chainstate, - const CChainParams& chainparams, + const Consensus::Params& consensus_params, CCoinsView& coinsview, int nCheckLevel, int nCheckDepth) EXCLUSIVE_LOCKS_REQUIRED(cs_main); From ac4bf138b849a8544798f3891d6623803040c265 Mon Sep 17 00:00:00 2001 From: Carl Dong Date: Tue, 21 Sep 2021 11:37:03 -0400 Subject: [PATCH 17/23] node/caches: Extract cache calculation logic I strongly recommend reviewing with the following git-diff flags: --color-moved=dimmed_zebra --color-moved-ws=allow-indentation-change [META] In a future commit, this function will be re-used in TestingSetup so that the behaviour matches across test and non-test init codepaths. --- src/Makefile.am | 2 ++ src/init.cpp | 41 +++++++++++++---------------------------- src/node/caches.cpp | 38 ++++++++++++++++++++++++++++++++++++++ src/node/caches.h | 22 ++++++++++++++++++++++ 4 files changed, 75 insertions(+), 28 deletions(-) create mode 100644 src/node/caches.cpp create mode 100644 src/node/caches.h diff --git a/src/Makefile.am b/src/Makefile.am index 880e6b5362c..a7911c49b2d 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -174,6 +174,7 @@ BITCOIN_CORE_H = \ netbase.h \ netmessagemaker.h \ node/blockstorage.h \ + node/caches.h \ node/chainstate.h \ node/coin.h \ node/coinstats.h \ @@ -340,6 +341,7 @@ libbitcoin_server_a_SOURCES = \ net.cpp \ net_processing.cpp \ node/blockstorage.cpp \ + node/caches.cpp \ node/chainstate.cpp \ node/coin.cpp \ node/coinstats.cpp \ diff --git a/src/init.cpp b/src/init.cpp index e6f00717fbf..3cac626e9db 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -34,6 +34,7 @@ #include #include #include +#include // for CalculateCacheSizes #include // for LoadChainstate #include #include @@ -1381,36 +1382,20 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) bool fReindexChainState = args.GetBoolArg("-reindex-chainstate", false); // cache size calculations - int64_t nTotalCache = (args.GetIntArg("-dbcache", nDefaultDbCache) << 20); - nTotalCache = std::max(nTotalCache, nMinDbCache << 20); // total cache cannot be less than nMinDbCache - nTotalCache = std::min(nTotalCache, nMaxDbCache << 20); // total cache cannot be greater than nMaxDbcache - int64_t nBlockTreeDBCache = std::min(nTotalCache / 8, nMaxBlockDBCache << 20); - nTotalCache -= nBlockTreeDBCache; - int64_t nTxIndexCache = std::min(nTotalCache / 8, args.GetBoolArg("-txindex", DEFAULT_TXINDEX) ? nMaxTxIndexCache << 20 : 0); - nTotalCache -= nTxIndexCache; - int64_t filter_index_cache = 0; - if (!g_enabled_filter_types.empty()) { - size_t n_indexes = g_enabled_filter_types.size(); - int64_t max_cache = std::min(nTotalCache / 8, max_filter_index_cache << 20); - filter_index_cache = max_cache / n_indexes; - nTotalCache -= filter_index_cache * n_indexes; - } - int64_t nCoinDBCache = std::min(nTotalCache / 2, (nTotalCache / 4) + (1 << 23)); // use 25%-50% of the remainder for disk cache - nCoinDBCache = std::min(nCoinDBCache, nMaxCoinsDBCache << 20); // cap total coins db cache - nTotalCache -= nCoinDBCache; - int64_t nCoinCacheUsage = nTotalCache; // the rest goes to in-memory cache + CacheSizes cache_sizes = CalculateCacheSizes(args, g_enabled_filter_types.size()); + int64_t nMempoolSizeMax = args.GetIntArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000; LogPrintf("Cache configuration:\n"); - LogPrintf("* Using %.1f MiB for block index database\n", nBlockTreeDBCache * (1.0 / 1024 / 1024)); + LogPrintf("* Using %.1f MiB for block index database\n", cache_sizes.block_tree_db * (1.0 / 1024 / 1024)); if (args.GetBoolArg("-txindex", DEFAULT_TXINDEX)) { - LogPrintf("* Using %.1f MiB for transaction index database\n", nTxIndexCache * (1.0 / 1024 / 1024)); + LogPrintf("* Using %.1f MiB for transaction index database\n", cache_sizes.tx_index * (1.0 / 1024 / 1024)); } for (BlockFilterType filter_type : g_enabled_filter_types) { LogPrintf("* Using %.1f MiB for %s block filter index database\n", - filter_index_cache * (1.0 / 1024 / 1024), BlockFilterTypeName(filter_type)); + cache_sizes.filter_index * (1.0 / 1024 / 1024), BlockFilterTypeName(filter_type)); } - LogPrintf("* Using %.1f MiB for chain state database\n", nCoinDBCache * (1.0 / 1024 / 1024)); - LogPrintf("* Using %.1f MiB for in-memory UTXO set (plus up to %.1f MiB of unused mempool space)\n", nCoinCacheUsage * (1.0 / 1024 / 1024), nMempoolSizeMax * (1.0 / 1024 / 1024)); + LogPrintf("* Using %.1f MiB for chain state database\n", cache_sizes.coins_db * (1.0 / 1024 / 1024)); + LogPrintf("* Using %.1f MiB for in-memory UTXO set (plus up to %.1f MiB of unused mempool space)\n", cache_sizes.coins * (1.0 / 1024 / 1024), nMempoolSizeMax * (1.0 / 1024 / 1024)); bool fLoaded = false; while (!fLoaded && !ShutdownRequested()) { @@ -1427,9 +1412,9 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) fPruneMode, chainparams.GetConsensus(), fReindexChainState, - nBlockTreeDBCache, - nCoinDBCache, - nCoinCacheUsage, + cache_sizes.block_tree_db, + cache_sizes.coins_db, + cache_sizes.coins, ShutdownRequested, []() { uiInterface.ThreadSafeMessageBox( @@ -1548,14 +1533,14 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) return InitError(*error); } - g_txindex = std::make_unique(nTxIndexCache, false, fReindex); + g_txindex = std::make_unique(cache_sizes.tx_index, false, fReindex); if (!g_txindex->Start(chainman.ActiveChainstate())) { return false; } } for (const auto& filter_type : g_enabled_filter_types) { - InitBlockFilterIndex(filter_type, filter_index_cache, false, fReindex); + InitBlockFilterIndex(filter_type, cache_sizes.filter_index, false, fReindex); if (!GetBlockFilterIndex(filter_type)->Start(chainman.ActiveChainstate())) { return false; } diff --git a/src/node/caches.cpp b/src/node/caches.cpp new file mode 100644 index 00000000000..be09c7e055f --- /dev/null +++ b/src/node/caches.cpp @@ -0,0 +1,38 @@ +// Copyright (c) 2021 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include + +#include +#include +#include + +CacheSizes CalculateCacheSizes(const ArgsManager& args, size_t n_indexes) +{ + int64_t nTotalCache = (args.GetIntArg("-dbcache", nDefaultDbCache) << 20); + nTotalCache = std::max(nTotalCache, nMinDbCache << 20); // total cache cannot be less than nMinDbCache + nTotalCache = std::min(nTotalCache, nMaxDbCache << 20); // total cache cannot be greater than nMaxDbcache + int64_t nBlockTreeDBCache = std::min(nTotalCache / 8, nMaxBlockDBCache << 20); + nTotalCache -= nBlockTreeDBCache; + int64_t nTxIndexCache = std::min(nTotalCache / 8, args.GetBoolArg("-txindex", DEFAULT_TXINDEX) ? nMaxTxIndexCache << 20 : 0); + nTotalCache -= nTxIndexCache; + int64_t filter_index_cache = 0; + if (n_indexes > 0) { + int64_t max_cache = std::min(nTotalCache / 8, max_filter_index_cache << 20); + filter_index_cache = max_cache / n_indexes; + nTotalCache -= filter_index_cache * n_indexes; + } + int64_t nCoinDBCache = std::min(nTotalCache / 2, (nTotalCache / 4) + (1 << 23)); // use 25%-50% of the remainder for disk cache + nCoinDBCache = std::min(nCoinDBCache, nMaxCoinsDBCache << 20); // cap total coins db cache + nTotalCache -= nCoinDBCache; + int64_t nCoinCacheUsage = nTotalCache; // the rest goes to in-memory cache + + return { + nBlockTreeDBCache, + nCoinDBCache, + nCoinCacheUsage, + nTxIndexCache, + filter_index_cache, + }; +} diff --git a/src/node/caches.h b/src/node/caches.h new file mode 100644 index 00000000000..437e7d10e57 --- /dev/null +++ b/src/node/caches.h @@ -0,0 +1,22 @@ +// Copyright (c) 2021 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_NODE_CACHES_H +#define BITCOIN_NODE_CACHES_H + +#include // for size_t +#include // for int64_t + +class ArgsManager; + +struct CacheSizes { + int64_t block_tree_db; + int64_t coins_db; + int64_t coins; + int64_t tx_index; + int64_t filter_index; +}; +CacheSizes CalculateCacheSizes(const ArgsManager& args, size_t n_indexes = 0); + +#endif // BITCOIN_NODE_CACHES_H From ceb979034184345a662be4e3b327a573fbb31c63 Mon Sep 17 00:00:00 2001 From: Carl Dong Date: Mon, 6 Dec 2021 16:52:18 -0500 Subject: [PATCH 18/23] node/caches: Remove intermediate variables --- src/node/caches.cpp | 32 +++++++++++++------------------- 1 file changed, 13 insertions(+), 19 deletions(-) diff --git a/src/node/caches.cpp b/src/node/caches.cpp index be09c7e055f..36254dc7141 100644 --- a/src/node/caches.cpp +++ b/src/node/caches.cpp @@ -13,26 +13,20 @@ CacheSizes CalculateCacheSizes(const ArgsManager& args, size_t n_indexes) int64_t nTotalCache = (args.GetIntArg("-dbcache", nDefaultDbCache) << 20); nTotalCache = std::max(nTotalCache, nMinDbCache << 20); // total cache cannot be less than nMinDbCache nTotalCache = std::min(nTotalCache, nMaxDbCache << 20); // total cache cannot be greater than nMaxDbcache - int64_t nBlockTreeDBCache = std::min(nTotalCache / 8, nMaxBlockDBCache << 20); - nTotalCache -= nBlockTreeDBCache; - int64_t nTxIndexCache = std::min(nTotalCache / 8, args.GetBoolArg("-txindex", DEFAULT_TXINDEX) ? nMaxTxIndexCache << 20 : 0); - nTotalCache -= nTxIndexCache; - int64_t filter_index_cache = 0; + CacheSizes sizes; + sizes.block_tree_db = std::min(nTotalCache / 8, nMaxBlockDBCache << 20); + nTotalCache -= sizes.block_tree_db; + sizes.tx_index = std::min(nTotalCache / 8, args.GetBoolArg("-txindex", DEFAULT_TXINDEX) ? nMaxTxIndexCache << 20 : 0); + nTotalCache -= sizes.tx_index; + sizes.filter_index = 0; if (n_indexes > 0) { int64_t max_cache = std::min(nTotalCache / 8, max_filter_index_cache << 20); - filter_index_cache = max_cache / n_indexes; - nTotalCache -= filter_index_cache * n_indexes; + sizes.filter_index = max_cache / n_indexes; + nTotalCache -= sizes.filter_index * n_indexes; } - int64_t nCoinDBCache = std::min(nTotalCache / 2, (nTotalCache / 4) + (1 << 23)); // use 25%-50% of the remainder for disk cache - nCoinDBCache = std::min(nCoinDBCache, nMaxCoinsDBCache << 20); // cap total coins db cache - nTotalCache -= nCoinDBCache; - int64_t nCoinCacheUsage = nTotalCache; // the rest goes to in-memory cache - - return { - nBlockTreeDBCache, - nCoinDBCache, - nCoinCacheUsage, - nTxIndexCache, - filter_index_cache, - }; + sizes.coins_db = std::min(nTotalCache / 2, (nTotalCache / 4) + (1 << 23)); // use 25%-50% of the remainder for disk cache + sizes.coins_db = std::min(sizes.coins_db, nMaxCoinsDBCache << 20); // cap total coins db cache + nTotalCache -= sizes.coins_db; + sizes.coins = nTotalCache; // the rest goes to in-memory cache + return sizes; } From c541da0d62eaf5e96eca00d7508899f98bdfc1bc Mon Sep 17 00:00:00 2001 From: Carl Dong Date: Tue, 21 Sep 2021 12:10:51 -0400 Subject: [PATCH 19/23] node/chainstate: Add options for in-memory DBs [META] In a future commit, these options will be used in TestingSetup to ensure that the DBs are in-memory. --- src/init.cpp | 2 ++ src/node/chainstate.cpp | 6 ++++-- src/node/chainstate.h | 2 ++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/init.cpp b/src/init.cpp index 3cac626e9db..56debdd2452 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -1415,6 +1415,8 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) cache_sizes.block_tree_db, cache_sizes.coins_db, cache_sizes.coins, + false, + false, ShutdownRequested, []() { uiInterface.ThreadSafeMessageBox( diff --git a/src/node/chainstate.cpp b/src/node/chainstate.cpp index b4264655d5c..35d4b4cc8c1 100644 --- a/src/node/chainstate.cpp +++ b/src/node/chainstate.cpp @@ -17,6 +17,8 @@ std::optional LoadChainstate(bool fReset, int64_t nBlockTreeDBCache, int64_t nCoinDBCache, int64_t nCoinCacheUsage, + bool block_tree_db_in_memory, + bool coins_db_in_memory, std::function shutdown_requested, std::function coins_error_cb) { @@ -36,7 +38,7 @@ std::optional LoadChainstate(bool fReset, // new CBlockTreeDB tries to delete the existing file, which // fails if it's still open from the previous loop. Close it first: pblocktree.reset(); - pblocktree.reset(new CBlockTreeDB(nBlockTreeDBCache, false, fReset)); + pblocktree.reset(new CBlockTreeDB(nBlockTreeDBCache, block_tree_db_in_memory, fReset)); if (fReset) { pblocktree->WriteReindexing(true); @@ -81,7 +83,7 @@ std::optional LoadChainstate(bool fReset, for (CChainState* chainstate : chainman.GetAll()) { chainstate->InitCoinsDB( /* cache_size_bytes */ nCoinDBCache, - /* in_memory */ false, + /* in_memory */ coins_db_in_memory, /* should_wipe */ fReset || fReindexChainState); if (coins_error_cb) { diff --git a/src/node/chainstate.h b/src/node/chainstate.h index e3369eb47cf..c3b8baf7fc7 100644 --- a/src/node/chainstate.h +++ b/src/node/chainstate.h @@ -63,6 +63,8 @@ std::optional LoadChainstate(bool fReset, int64_t nBlockTreeDBCache, int64_t nCoinDBCache, int64_t nCoinCacheUsage, + bool block_tree_db_in_memory, + bool coins_db_in_memory, std::function shutdown_requested = nullptr, std::function coins_error_cb = nullptr); From 9a5a5a3d08b2c130ab9147914739ff3583b0dc84 Mon Sep 17 00:00:00 2001 From: Carl Dong Date: Tue, 21 Sep 2021 14:09:23 -0400 Subject: [PATCH 20/23] test/setup: Use LoadChainstate This commit coalesces the chainstate loading sequence between our unit test and non-unit test init codepaths. --- src/test/util/setup_common.cpp | 28 ++++++++++++++++++---------- src/test/util/setup_common.h | 2 ++ 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/src/test/util/setup_common.cpp b/src/test/util/setup_common.cpp index f5cc88f4ced..628cedac190 100644 --- a/src/test/util/setup_common.cpp +++ b/src/test/util/setup_common.cpp @@ -17,6 +17,8 @@ #include #include #include +#include // for fReindex, fPruneMode +#include // for LoadChainstate #include #include #include @@ -24,6 +26,7 @@ #include #include #include