diff --git a/src/common/args.cpp b/src/common/args.cpp index 71dcd1ac10c..94eb9230fd2 100644 --- a/src/common/args.cpp +++ b/src/common/args.cpp @@ -709,6 +709,7 @@ std::string HelpMessageOpt(const std::string &option, const std::string &message const std::vector TEST_OPTIONS_DOC{ "addrman (use deterministic addrman)", + "reindex_after_failure_noninteractive_yes (When asked for a reindex after failure interactively, simulate as-if answered with 'yes')", "bip94 (enforce BIP94 consensus rules)", }; diff --git a/src/init.cpp b/src/init.cpp index cc9f8160a3b..08817463b11 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -1223,13 +1223,24 @@ static ChainstateLoadResult InitAndLoadChainstate( const kernel::CacheSizes& cache_sizes, const ArgsManager& args) { + // This function may be called twice, so any dirty state must be reset. + node.notifications.reset(); // Drop state, such as a cached tip block + node.mempool.reset(); + node.chainman.reset(); // Drop state, such as an initialized m_block_tree_db + const CChainParams& chainparams = Params(); + + Assert(!node.notifications); // Was reset above + node.notifications = std::make_unique(Assert(node.shutdown_request), node.exit_status, *Assert(node.warnings)); + ReadNotificationArgs(args, *node.notifications); + CTxMemPool::Options mempool_opts{ .check_ratio = chainparams.DefaultConsistencyChecks() ? 1 : 0, .signals = node.validation_signals.get(), }; Assert(ApplyArgsManOptions(args, chainparams, mempool_opts)); // no error can happen, already checked in AppInitParameterInteraction bilingual_str mempool_error; + Assert(!node.mempool); // Was reset above node.mempool = std::make_unique(mempool_opts, mempool_error); if (!mempool_error.empty()) { return {ChainstateLoadStatus::FAILURE_FATAL, mempool_error}; @@ -1260,6 +1271,7 @@ static ChainstateLoadResult InitAndLoadChainstate( // Creating the chainstate manager internally creates a BlockManager, opens // the blocks tree db, and wipes existing block files in case of a reindex. // The coinsdb is opened at a later point on LoadChainstate. + Assert(!node.chainman); // Was reset above try { node.chainman = std::make_unique(*Assert(node.shutdown_signal), chainman_opts, blockman_opts); } catch (dbwrapper_error& e) { @@ -1697,10 +1709,6 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) // ********************************************************* Step 7: load block chain - node.notifications = std::make_unique(Assert(node.shutdown_request), node.exit_status, *Assert(node.warnings)); - auto& kernel_notifications{*node.notifications}; - ReadNotificationArgs(args, kernel_notifications); - // cache size calculations const auto [index_cache_sizes, kernel_cache_sizes] = CalculateCacheSizes(args, g_enabled_filter_types.size()); @@ -1730,10 +1738,11 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) args); if (status == ChainstateLoadStatus::FAILURE && !do_reindex && !ShutdownRequested(node)) { // suggest a reindex - bool do_retry = uiInterface.ThreadSafeQuestion( + bool do_retry{HasTestOption(args, "reindex_after_failure_noninteractive_yes") || + uiInterface.ThreadSafeQuestion( error + Untranslated(".\n\n") + _("Do you want to rebuild the databases now?"), error.original + ".\nPlease restart with -reindex or -reindex-chainstate to recover.", - "", CClientUIInterface::MSG_ERROR | CClientUIInterface::BTN_ABORT); + "", CClientUIInterface::MSG_ERROR | CClientUIInterface::BTN_ABORT)}; if (!do_retry) { return false; } @@ -1760,6 +1769,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) } ChainstateManager& chainman = *Assert(node.chainman); + auto& kernel_notifications{*Assert(node.notifications)}; assert(!node.peerman); node.peerman = PeerManager::make(*node.connman, *node.addrman, diff --git a/test/functional/feature_reindex_init.py b/test/functional/feature_reindex_init.py new file mode 100755 index 00000000000..d031355e439 --- /dev/null +++ b/test/functional/feature_reindex_init.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python3 +# Copyright (c) The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +"""Test reindex works on init after a db load failure""" + +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import assert_equal +import os +import shutil + + +class ReindexInitTest(BitcoinTestFramework): + def set_test_params(self): + self.num_nodes = 1 + + def run_test(self): + node = self.nodes[0] + self.stop_nodes() + + self.log.info("Removing the block index leads to init error") + shutil.rmtree(node.blocks_path / "index") + node.assert_start_raises_init_error( + expected_msg=f": Error initializing block database.{os.linesep}" + "Please restart with -reindex or -reindex-chainstate to recover.", + ) + + self.log.info("Allowing the reindex should work fine") + self.start_node(0, extra_args=["-test=reindex_after_failure_noninteractive_yes"]) + assert_equal(node.getblockcount(), 200) + + +if __name__ == "__main__": + ReindexInitTest(__file__).main() diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index 44c2885bdf8..7c8c15f391d 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -289,6 +289,7 @@ BASE_SCRIPTS = [ 'p2p_leak.py', 'wallet_encryption.py', 'feature_dersig.py', + 'feature_reindex_init.py', 'feature_cltv.py', 'rpc_uptime.py', 'feature_discover.py',