From d7419e42d6adb0e1b0c0de0b05147a98168edff5 Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Thu, 3 Oct 2024 08:58:02 +0000 Subject: [PATCH] node/chainstate: Decouple from stringy errors --- src/init.cpp | 70 +++++++++++++++++++++-- src/node/chainstate.cpp | 121 +++++++++++++--------------------------- src/node/chainstate.h | 70 ++++++++++++++++------- 3 files changed, 152 insertions(+), 109 deletions(-) diff --git a/src/init.cpp b/src/init.cpp index fefe7845d1..60aad46ee8 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -1864,11 +1864,8 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) bilingual_str strLoadError; uiInterface.InitMessage(_("Loading block index…").translated); - const auto load_block_index_start_time{SteadyClock::now()}; - bool rv = LoadChainstate(fLoaded, - strLoadError, - fReset, + auto rv = LoadChainstate(fReset, chainman, node, fPruneMode, @@ -1879,8 +1876,69 @@ 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_BAD_DEVNET_GENESIS_BLOCK: + return InitError(_("Incorrect or no devnet genesis block found. Wrong datadir for devnet specified?")); + case ChainstateLoadingError::ERROR_TXINDEX_DISABLED_WHEN_GOV_ENABLED: + return InitError(_("Transaction index can't be disabled with governance validation enabled. Either start with -disablegovernance command line switch or enable transaction index.")); + case ChainstateLoadingError::ERROR_ADDRIDX_NEEDS_REINDEX: + strLoadError = _("You need to rebuild the database using -reindex to enable -addressindex"); + break; + case ChainstateLoadingError::ERROR_SPENTIDX_NEEDS_REINDEX: + strLoadError = _("You need to rebuild the database using -reindex to enable -spentindex"); + break; + case ChainstateLoadingError::ERROR_TIMEIDX_NEEDS_REINDEX: + strLoadError = _("You need to rebuild the database using -reindex to enable -timestampindex"); + break; + 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_EVO_DB_SANITY_FAILED: + strLoadError = _("Error initializing block database"); + break; + case ChainstateLoadingError::ERROR_GENERIC_BLOCKDB_OPEN_FAILED: + strLoadError = _("Error opening block database"); + 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::ERROR_COMMITING_EVO_DB: + strLoadError = _("Failed to commit Evo database"); + break; + case ChainstateLoadingError::ERROR_UPGRADING_EVO_DB: + strLoadError = _("Error upgrading Evo database"); + break; + case ChainstateLoadingError::ERROR_UPGRADING_SIGNALS_DB: + strLoadError = _("Error upgrading evo database for EHF"); + break; + case ChainstateLoadingError::SHUTDOWN_PROBED: + break; + } + } else { + fLoaded = true; LogPrintf(" block index %15dms\n", Ticks(SteadyClock::now() - load_block_index_start_time)); } diff --git a/src/node/chainstate.cpp b/src/node/chainstate.cpp index e8dd229d42..b8f7c3ef5a 100644 --- a/src/node/chainstate.cpp +++ b/src/node/chainstate.cpp @@ -8,7 +8,6 @@ #include // for DeploymentActiveAfter #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 @@ -25,19 +24,18 @@ #include // for llmq::quorumInstantSendManager #include // for llmq::quorumSnapshotManager -bool LoadChainstate(bool& fLoaded, - bilingual_str& strLoadError, - bool fReset, - ChainstateManager& chainman, - NodeContext& node, - bool fPruneMode, - bool is_governance_enabled, - 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, + bool is_governance_enabled, + 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(); }; @@ -95,51 +93,49 @@ bool LoadChainstate(bool& fLoaded, CleanupBlockRevFiles(); } - if (ShutdownRequested()) break; + if (ShutdownRequested()) return ChainstateLoadingError::SHUTDOWN_PROBED; // LoadBlockIndex will load m_have_pruned 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 (is_governance_enabled && !args.GetBoolArg("-txindex", DEFAULT_TXINDEX) && chainparams.NetworkIDString() != CBaseChainParams::REGTEST) { // TODO remove this when pruning is fixed. See https://github.com/dashpay/dash/pull/1817 and https://github.com/dashpay/dash/pull/1743 - return InitError(_("Transaction index can't be disabled with governance validation enabled. Either start with -disablegovernance command line switch or enable transaction index.")); + // TODO: Remove this when pruning is fixed. + // See https://github.com/dashpay/dash/pull/1817 and https://github.com/dashpay/dash/pull/1743 + if (is_governance_enabled && !args.GetBoolArg("-txindex", DEFAULT_TXINDEX) && chainparams.NetworkIDString() != CBaseChainParams::REGTEST) { + return ChainstateLoadingError::ERROR_TXINDEX_DISABLED_WHEN_GOV_ENABLED; } // 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; } if (!chainparams.GetConsensus().hashDevnetGenesisBlock.IsNull() && !chainman.BlockIndex().empty() && !chainman.m_blockman.LookupBlockIndex(chainparams.GetConsensus().hashDevnetGenesisBlock)) { - return InitError(_("Incorrect or no devnet genesis block found. Wrong datadir for devnet specified?")); + return ChainstateLoadingError::ERROR_BAD_DEVNET_GENESIS_BLOCK; } if (!fReset && !fReindexChainState) { // Check for changed -addressindex state if (!fAddressIndex && fAddressIndex != args.GetBoolArg("-addressindex", DEFAULT_ADDRESSINDEX)) { - strLoadError = _("You need to rebuild the database using -reindex to enable -addressindex"); - break; + return ChainstateLoadingError::ERROR_ADDRIDX_NEEDS_REINDEX; } // Check for changed -timestampindex state if (!fTimestampIndex && fTimestampIndex != args.GetBoolArg("-timestampindex", DEFAULT_TIMESTAMPINDEX)) { - strLoadError = _("You need to rebuild the database using -reindex to enable -timestampindex"); - break; + return ChainstateLoadingError::ERROR_TIMEIDX_NEEDS_REINDEX; } // Check for changed -spentindex state if (!fSpentIndex && fSpentIndex != args.GetBoolArg("-spentindex", DEFAULT_SPENTINDEX)) { - strLoadError = _("You need to rebuild the database using -reindex to enable -spentindex"); - break; + return ChainstateLoadingError::ERROR_SPENTIDX_NEEDS_REINDEX; } } @@ -152,8 +148,7 @@ bool LoadChainstate(bool& fLoaded, // 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 (chainman.m_blockman.m_have_pruned && !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. @@ -161,15 +156,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, @@ -185,16 +177,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 @@ -206,46 +194,29 @@ bool LoadChainstate(bool& fLoaded, // (for multiple chainstates to actually work in parallel) // and not a global if (&chainman.ActiveChainstate() == chainstate && !node.evodb->CommitRootTransaction()) { - strLoadError = _("Failed to commit EvoDB"); - failed_chainstate_init = true; - break; + return ChainstateLoadingError::ERROR_COMMITING_EVO_DB; } 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 - } - - if (!node.dmnman->MigrateDBIfNeeded()) { - strLoadError = _("Error upgrading evo database"); - break; - } - if (!node.dmnman->MigrateDBIfNeeded2()) { - strLoadError = _("Error upgrading evo database"); - break; + if (!node.dmnman->MigrateDBIfNeeded() || !node.dmnman->MigrateDBIfNeeded2()) { + return ChainstateLoadingError::ERROR_UPGRADING_EVO_DB; } if (!node.mnhf_manager->ForceSignalDBUpdate()) { - strLoadError = _("Error upgrading evo database for EHF"); - break; + return ChainstateLoadingError::ERROR_UPGRADING_SIGNALS_DB; } } catch (const std::exception& e) { LogPrintf("%s\n", e.what()); - strLoadError = _("Error opening block database"); - break; + return ChainstateLoadingError::ERROR_GENERIC_BLOCKDB_OPEN_FAILED; } - bool failed_verification = false; - try { LOCK(cs_main); @@ -260,11 +231,7 @@ 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; } const bool v19active{DeploymentActiveAfter(tip, chainparams.GetConsensus(), Consensus::DEPLOYMENT_V19)}; if (v19active) { @@ -277,9 +244,7 @@ bool LoadChainstate(bool& fLoaded, *node.evodb, args.GetArg("-checklevel", DEFAULT_CHECKLEVEL), args.GetArg("-checkblocks", DEFAULT_CHECKBLOCKS))) { - strLoadError = _("Corrupted block database detected"); - failed_verification = true; - break; + return ChainstateLoadingError::ERROR_CORRUPTED_BLOCK_DB; } // VerifyDB() disconnects blocks which might result in us switching back to legacy. @@ -299,22 +264,14 @@ bool LoadChainstate(bool& fLoaded, // and not a global if (&chainman.ActiveChainstate() == chainstate && !node.evodb->IsEmpty()) { // EvoDB processed some blocks earlier but we have no blocks anymore, something is wrong - strLoadError = _("Error initializing block database"); - failed_verification = true; - break; + return ChainstateLoadingError::ERROR_EVO_DB_SANITY_FAILED; } } } } 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 15b7b216fc..0c8e547cac 100644 --- a/src/node/chainstate.h +++ b/src/node/chainstate.h @@ -6,13 +6,36 @@ #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_BAD_DEVNET_GENESIS_BLOCK, + ERROR_TXINDEX_DISABLED_WHEN_GOV_ENABLED, + ERROR_ADDRIDX_NEEDS_REINDEX, + ERROR_SPENTIDX_NEEDS_REINDEX, + ERROR_TIMEIDX_NEEDS_REINDEX, + ERROR_PRUNED_NEEDS_REINDEX, + ERROR_LOAD_GENESIS_BLOCK_FAILED, + ERROR_CHAINSTATE_UPGRADE_FAILED, + ERROR_REPLAYBLOCKS_FAILED, + ERROR_LOADCHAINTIP_FAILED, + ERROR_EVO_DB_SANITY_FAILED, + ERROR_GENERIC_BLOCKDB_OPEN_FAILED, + ERROR_BLOCK_FROM_FUTURE, + ERROR_CORRUPTED_BLOCK_DB, + ERROR_COMMITING_EVO_DB, + ERROR_UPGRADING_EVO_DB, + ERROR_UPGRADING_SIGNALS_DB, + SHUTDOWN_PROBED, +}; + /** This sequence can have 4 types of outcomes: * * 1. Success @@ -24,26 +47,31 @@ 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, - bool is_governance_enabled, - 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, + bool is_governance_enabled, + const CChainParams& chainparams, + const ArgsManager& args, + bool fReindexChainState, + int64_t nBlockTreeDBCache, + int64_t nCoinDBCache, + int64_t nCoinCacheUsage); #endif // BITCOIN_NODE_CHAINSTATE_H