diff --git a/src/bitcoin-chainstate.cpp b/src/bitcoin-chainstate.cpp index fc83a4ad3a37d..f61a83c1fb62e 100644 --- a/src/bitcoin-chainstate.cpp +++ b/src/bitcoin-chainstate.cpp @@ -133,13 +133,13 @@ int main(int argc, char* argv[]) cache_sizes.coins = (450 << 20) - (2 << 20) - (2 << 22); node::ChainstateLoadOptions options; options.check_interrupt = [] { return false; }; - auto [status, error] = node::LoadChainstate(chainman, cache_sizes, options); - if (status != node::ChainstateLoadStatus::SUCCESS) { + auto result = node::LoadChainstate(chainman, cache_sizes, options); + if (!result) { std::cerr << "Failed to load Chain state from your datadir." << std::endl; goto epilogue; } else { - std::tie(status, error) = node::VerifyLoadedChainstate(chainman, options); - if (status != node::ChainstateLoadStatus::SUCCESS) { + result = node::VerifyLoadedChainstate(chainman, options); + if (!result) { std::cerr << "Failed to verify loaded Chain state from your datadir." << std::endl; goto epilogue; } diff --git a/src/init.cpp b/src/init.cpp index 00b30f6cfab65..8c2101ab6a8a9 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -1529,33 +1529,36 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) uiInterface.InitMessage(_("Loading block index…").translated); const auto load_block_index_start_time{SteadyClock::now()}; - auto catch_exceptions = [](auto&& f) { + auto catch_exceptions = [](auto&& f) -> util::Result { try { return f(); } catch (const std::exception& e) { LogPrintf("%s\n", e.what()); - return std::make_tuple(node::ChainstateLoadStatus::FAILURE, _("Error opening block database")); + return {util::Error{_("Error opening block database")}, node::ChainstateLoadError::FAILURE}; } }; - auto [status, error] = catch_exceptions([&]{ return LoadChainstate(chainman, cache_sizes, options); }); - if (status == node::ChainstateLoadStatus::SUCCESS) { + auto result = catch_exceptions([&]{ return LoadChainstate(chainman, cache_sizes, options); }); + if (result) { uiInterface.InitMessage(_("Verifying blocks…").translated); if (chainman.m_blockman.m_have_pruned && options.check_blocks > MIN_BLOCKS_TO_KEEP) { LogPrintfCategory(BCLog::PRUNE, "pruned datadir may not have more than %d blocks; only checking available blocks\n", MIN_BLOCKS_TO_KEEP); } - std::tie(status, error) = catch_exceptions([&]{ return VerifyLoadedChainstate(chainman, options);}); - if (status == node::ChainstateLoadStatus::SUCCESS) { + result = catch_exceptions([&]{ return VerifyLoadedChainstate(chainman, options);}); + if (result) { fLoaded = true; LogPrintf(" block index %15dms\n", Ticks(SteadyClock::now() - load_block_index_start_time)); } } - if (status == node::ChainstateLoadStatus::FAILURE_FATAL || status == node::ChainstateLoadStatus::FAILURE_INCOMPATIBLE_DB || status == node::ChainstateLoadStatus::FAILURE_INSUFFICIENT_DBCACHE) { - return InitError(error); + if (!result && (result.GetFailure() == node::ChainstateLoadError::FAILURE_FATAL || + result.GetFailure() == node::ChainstateLoadError::FAILURE_INCOMPATIBLE_DB || + result.GetFailure() == node::ChainstateLoadError::FAILURE_INSUFFICIENT_DBCACHE)) { + return InitError(util::ErrorString(result)); } if (!fLoaded && !ShutdownRequested()) { + bilingual_str error = util::ErrorString(result); // first suggest a reindex if (!options.reindex) { bool fRet = uiInterface.ThreadSafeQuestion( diff --git a/src/node/chainstate.cpp b/src/node/chainstate.cpp index eb1994177a109..d9b57bd5200f3 100644 --- a/src/node/chainstate.cpp +++ b/src/node/chainstate.cpp @@ -31,7 +31,7 @@ namespace node { // Complete initialization of chainstates after the initial call has been made // to ChainstateManager::InitializeChainstate(). -static ChainstateLoadResult CompleteChainstateInitialization( +static util::Result CompleteChainstateInitialization( ChainstateManager& chainman, const CacheSizes& cache_sizes, const ChainstateLoadOptions& options) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) @@ -55,28 +55,28 @@ static ChainstateLoadResult CompleteChainstateInitialization( } } - if (options.check_interrupt && options.check_interrupt()) return {ChainstateLoadStatus::INTERRUPTED, {}}; + if (options.check_interrupt && options.check_interrupt()) return {util::Error{}, ChainstateLoadError::INTERRUPTED}; // LoadBlockIndex will load m_have_pruned if we've ever removed a // block file from disk. // Note that it also sets fReindex global based on the disk flag! // From here on, fReindex and options.reindex values may be different! if (!chainman.LoadBlockIndex()) { - if (options.check_interrupt && options.check_interrupt()) return {ChainstateLoadStatus::INTERRUPTED, {}}; - return {ChainstateLoadStatus::FAILURE, _("Error loading block database")}; + if (options.check_interrupt && options.check_interrupt()) return {util::Error{}, ChainstateLoadError::INTERRUPTED}; + return {util::Error{_("Error loading block database")}, ChainstateLoadError::FAILURE}; } if (!chainman.BlockIndex().empty() && !chainman.m_blockman.LookupBlockIndex(chainman.GetConsensus().hashGenesisBlock)) { // If the loaded chain has a wrong genesis, bail out immediately // (we're likely using a testnet datadir, or the other way around). - return {ChainstateLoadStatus::FAILURE_INCOMPATIBLE_DB, _("Incorrect or no genesis block found. Wrong datadir for network?")}; + return {util::Error{_("Incorrect or no genesis block found. Wrong datadir for network?")}, ChainstateLoadError::FAILURE_INCOMPATIBLE_DB}; } // 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 && !options.prune) { - return {ChainstateLoadStatus::FAILURE, _("You need to rebuild the database using -reindex to go back to unpruned mode. This will redownload the entire blockchain")}; + return {util::Error{_("You need to rebuild the database using -reindex to go back to unpruned mode. This will redownload the entire blockchain")}, ChainstateLoadError::FAILURE}; } // At this point blocktree args are consistent with what's on disk. @@ -84,7 +84,7 @@ static ChainstateLoadResult CompleteChainstateInitialization( // (otherwise we use the one already on disk). // This is called again in ImportBlocks after the reindex completes. if (!fReindex && !chainman.ActiveChainstate().LoadGenesisBlock()) { - return {ChainstateLoadStatus::FAILURE, _("Error initializing block database")}; + return {util::Error{_("Error initializing block database")}, ChainstateLoadError::FAILURE}; } auto is_coinsview_empty = [&](Chainstate* chainstate) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) { @@ -118,14 +118,15 @@ static ChainstateLoadResult CompleteChainstateInitialization( // Refuse to load unsupported database format. // This is a no-op if we cleared the coinsviewdb with -reindex or -reindex-chainstate if (chainstate->CoinsDB().NeedsUpgrade()) { - return {ChainstateLoadStatus::FAILURE_INCOMPATIBLE_DB, _("Unsupported chainstate database format found. " - "Please restart with -reindex-chainstate. This will " - "rebuild the chainstate database.")}; + return {util::Error{ _("Unsupported chainstate database format found. " + "Please restart with -reindex-chainstate. This will " + "rebuild the chainstate database.")}, + ChainstateLoadError::FAILURE_INCOMPATIBLE_DB}; } // ReplayBlocks is a no-op if we cleared the coinsviewdb with -reindex or -reindex-chainstate if (!chainstate->ReplayBlocks()) { - return {ChainstateLoadStatus::FAILURE, _("Unable to replay blocks. You will need to rebuild the database using -reindex-chainstate.")}; + return {util::Error{_("Unable to replay blocks. You will need to rebuild the database using -reindex-chainstate.")}, ChainstateLoadError::FAILURE}; } // The on-disk coinsdb is now in a good state, create the cache @@ -135,7 +136,7 @@ static ChainstateLoadResult CompleteChainstateInitialization( if (!is_coinsview_empty(chainstate)) { // LoadChainTip initializes the chain based on CoinsTip()'s best block if (!chainstate->LoadChainTip()) { - return {ChainstateLoadStatus::FAILURE, _("Error initializing block database")}; + return {util::Error{_("Error initializing block database")}, ChainstateLoadError::FAILURE}; } assert(chainstate->m_chain.Tip() != nullptr); } @@ -145,8 +146,8 @@ static ChainstateLoadResult CompleteChainstateInitialization( auto chainstates{chainman.GetAll()}; if (std::any_of(chainstates.begin(), chainstates.end(), [](const Chainstate* cs) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { return cs->NeedsRedownload(); })) { - return {ChainstateLoadStatus::FAILURE, strprintf(_("Witness data for blocks after height %d requires validation. Please restart with -reindex."), - chainman.GetConsensus().SegwitHeight)}; + return {util::Error{strprintf(_("Witness data for blocks after height %d requires validation. Please restart with -reindex."), + chainman.GetConsensus().SegwitHeight)}, ChainstateLoadError::FAILURE}; }; } @@ -155,11 +156,11 @@ static ChainstateLoadResult CompleteChainstateInitialization( // on the condition of each chainstate. chainman.MaybeRebalanceCaches(); - return {ChainstateLoadStatus::SUCCESS, {}}; + return {}; } -ChainstateLoadResult LoadChainstate(ChainstateManager& chainman, const CacheSizes& cache_sizes, - const ChainstateLoadOptions& options) +util::Result LoadChainstate(ChainstateManager& chainman, const CacheSizes& cache_sizes, + const ChainstateLoadOptions& options) { if (!chainman.AssumedValidBlock().IsNull()) { LogPrintf("Assuming ancestors of block %s have valid signatures.\n", chainman.AssumedValidBlock().GetHex()); @@ -190,13 +191,13 @@ ChainstateLoadResult LoadChainstate(ChainstateManager& chainman, const CacheSize if (has_snapshot && (options.reindex || options.reindex_chainstate)) { LogPrintf("[snapshot] deleting snapshot chainstate due to reindexing\n"); if (!chainman.DeleteSnapshotChainstate()) { - return {ChainstateLoadStatus::FAILURE_FATAL, Untranslated("Couldn't remove snapshot chainstate.")}; + return {util::Error{Untranslated("Couldn't remove snapshot chainstate.")}, ChainstateLoadError::FAILURE_FATAL}; } } - auto [init_status, init_error] = CompleteChainstateInitialization(chainman, cache_sizes, options); - if (init_status != ChainstateLoadStatus::SUCCESS) { - return {init_status, init_error}; + auto result{CompleteChainstateInitialization(chainman, cache_sizes, options)}; + if (!result) { + return result; } // If a snapshot chainstate was fully validated by a background chainstate during @@ -214,7 +215,7 @@ ChainstateLoadResult LoadChainstate(ChainstateManager& chainman, const CacheSize } else if (snapshot_completion == SnapshotCompletionResult::SUCCESS) { LogPrintf("[snapshot] cleaning up unneeded background chainstate, then reinitializing\n"); if (!chainman.ValidatedSnapshotCleanup()) { - return {ChainstateLoadStatus::FAILURE_FATAL, Untranslated("Background chainstate cleanup failed unexpectedly.")}; + return {util::Error{Untranslated("Background chainstate cleanup failed unexpectedly.")}, ChainstateLoadError::FAILURE_FATAL}; } // Because ValidatedSnapshotCleanup() has torn down chainstates with @@ -230,20 +231,20 @@ ChainstateLoadResult LoadChainstate(ChainstateManager& chainman, const CacheSize // for the fully validated chainstate. chainman.ActiveChainstate().ClearBlockIndexCandidates(); - auto [init_status, init_error] = CompleteChainstateInitialization(chainman, cache_sizes, options); - if (init_status != ChainstateLoadStatus::SUCCESS) { - return {init_status, init_error}; + auto result{CompleteChainstateInitialization(chainman, cache_sizes, options)}; + if (!result) { + return result; } } else { - return {ChainstateLoadStatus::FAILURE, _( - "UTXO snapshot failed to validate. " + return util::Error{ + _("UTXO snapshot failed to validate. " "Restart to resume normal initial block download, or try loading a different snapshot.")}; } - return {ChainstateLoadStatus::SUCCESS, {}}; + return {}; } -ChainstateLoadResult VerifyLoadedChainstate(ChainstateManager& chainman, const ChainstateLoadOptions& options) +util::Result VerifyLoadedChainstate(ChainstateManager& chainman, const ChainstateLoadOptions& options) { auto is_coinsview_empty = [&](Chainstate* chainstate) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) { return options.reindex || options.reindex_chainstate || chainstate->CoinsTip().GetBestBlock().IsNull(); @@ -255,9 +256,10 @@ ChainstateLoadResult VerifyLoadedChainstate(ChainstateManager& chainman, const C if (!is_coinsview_empty(chainstate)) { const CBlockIndex* tip = chainstate->m_chain.Tip(); if (tip && tip->nTime > GetTime() + MAX_FUTURE_BLOCK_TIME) { - return {ChainstateLoadStatus::FAILURE, _("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")}; + return {util::Error{_("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")}, + ChainstateLoadError::FAILURE}; } VerifyDBResult result = CVerifyDB(chainman.GetNotifications()).VerifyDB( @@ -269,18 +271,18 @@ ChainstateLoadResult VerifyLoadedChainstate(ChainstateManager& chainman, const C case VerifyDBResult::SKIPPED_MISSING_BLOCKS: break; case VerifyDBResult::INTERRUPTED: - return {ChainstateLoadStatus::INTERRUPTED, _("Block verification was interrupted")}; + return {util::Error{_("Block verification was interrupted")}, ChainstateLoadError::INTERRUPTED}; case VerifyDBResult::CORRUPTED_BLOCK_DB: - return {ChainstateLoadStatus::FAILURE, _("Corrupted block database detected")}; + return {util::Error{_("Corrupted block database detected")}, ChainstateLoadError::FAILURE}; case VerifyDBResult::SKIPPED_L3_CHECKS: if (options.require_full_verification) { - return {ChainstateLoadStatus::FAILURE_INSUFFICIENT_DBCACHE, _("Insufficient dbcache for block verification")}; + return {util::Error{_("Insufficient dbcache for block verification")}, ChainstateLoadError::FAILURE_INSUFFICIENT_DBCACHE}; } break; } // no default case, so the compiler can warn about missing cases } } - return {ChainstateLoadStatus::SUCCESS, {}}; + return {}; } } // namespace node diff --git a/src/node/chainstate.h b/src/node/chainstate.h index 2e35035c283ec..ad64feed034bc 100644 --- a/src/node/chainstate.h +++ b/src/node/chainstate.h @@ -5,6 +5,7 @@ #ifndef BITCOIN_NODE_CHAINSTATE_H #define BITCOIN_NODE_CHAINSTATE_H +#include #include #include @@ -36,12 +37,11 @@ struct ChainstateLoadOptions { std::function coins_error_cb; }; -//! Chainstate load status. Simple applications can just check for the success -//! case, and treat other cases as errors. More complex applications may want to -//! try reindexing in the generic failure case, and pass an interrupt callback -//! and exit cleanly in the interrupted case. -enum class ChainstateLoadStatus { - SUCCESS, +//! Chainstate load errors. Simple applications can just treat all errors as +//! failures. More complex applications may want to try reindexing in the +//! generic error case, and pass an interrupt callback and exit cleanly in the +//! interrupted case. +enum class ChainstateLoadError { FAILURE, //!< Generic failure which reindexing may fix FAILURE_FATAL, //!< Fatal error which should not prompt to reindex FAILURE_INCOMPATIBLE_DB, @@ -49,25 +49,9 @@ enum class ChainstateLoadStatus { INTERRUPTED, }; -//! Chainstate load status code and optional error string. -using ChainstateLoadResult = std::tuple; - -/** 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 - * - * LoadChainstate returns a (status code, error string) tuple. - */ -ChainstateLoadResult LoadChainstate(ChainstateManager& chainman, const CacheSizes& cache_sizes, - const ChainstateLoadOptions& options); -ChainstateLoadResult VerifyLoadedChainstate(ChainstateManager& chainman, const ChainstateLoadOptions& options); +util::Result LoadChainstate(ChainstateManager& chainman, const CacheSizes& cache_sizes, + const ChainstateLoadOptions& options); +util::Result VerifyLoadedChainstate(ChainstateManager& chainman, const ChainstateLoadOptions& options); } // namespace node #endif // BITCOIN_NODE_CHAINSTATE_H diff --git a/src/test/util/setup_common.cpp b/src/test/util/setup_common.cpp index e70c105c8af29..00df760410814 100644 --- a/src/test/util/setup_common.cpp +++ b/src/test/util/setup_common.cpp @@ -221,11 +221,11 @@ void ChainTestingSetup::LoadVerifyActivateChainstate() options.check_blocks = m_args.GetIntArg("-checkblocks", DEFAULT_CHECKBLOCKS); options.check_level = m_args.GetIntArg("-checklevel", DEFAULT_CHECKLEVEL); options.require_full_verification = m_args.IsArgSet("-checkblocks") || m_args.IsArgSet("-checklevel"); - auto [status, error] = LoadChainstate(chainman, m_cache_sizes, options); - assert(status == node::ChainstateLoadStatus::SUCCESS); + auto result = LoadChainstate(chainman, m_cache_sizes, options); + assert(result); - std::tie(status, error) = VerifyLoadedChainstate(chainman, options); - assert(status == node::ChainstateLoadStatus::SUCCESS); + result = VerifyLoadedChainstate(chainman, options); + assert(result); BlockValidationState state; if (!chainman.ActiveChainstate().ActivateBestChain(state)) {