validation_chainstatemanager_tests.cpp raw

   1  // Copyright (c) 2019-present The Bitcoin Core developers
   2  // Distributed under the MIT software license, see the accompanying
   3  // file COPYING or http://www.opensource.org/licenses/mit-license.php.
   4  //
   5  #include <chainparams.h>
   6  #include <consensus/validation.h>
   7  #include <kernel/disconnected_transactions.h>
   8  #include <node/chainstatemanager_args.h>
   9  #include <node/kernel_notifications.h>
  10  #include <node/utxo_snapshot.h>
  11  #include <random.h>
  12  #include <rpc/blockchain.h>
  13  #include <sync.h>
  14  #include <test/util/chainstate.h>
  15  #include <test/util/common.h>
  16  #include <test/util/logging.h>
  17  #include <test/util/random.h>
  18  #include <test/util/setup_common.h>
  19  #include <test/util/validation.h>
  20  #include <uint256.h>
  21  #include <util/byte_units.h>
  22  #include <util/result.h>
  23  #include <util/vector.h>
  24  #include <validation.h>
  25  #include <validationinterface.h>
  26  
  27  #include <tinyformat.h>
  28  
  29  #include <vector>
  30  
  31  #include <boost/test/unit_test.hpp>
  32  
  33  using node::BlockManager;
  34  using node::KernelNotifications;
  35  using node::SnapshotMetadata;
  36  
  37  BOOST_FIXTURE_TEST_SUITE(validation_chainstatemanager_tests, TestingSetup)
  38  
  39  //! Basic tests for ChainstateManager.
  40  //!
  41  //! First create a legacy (IBD) chainstate, then create a snapshot chainstate.
  42  BOOST_FIXTURE_TEST_CASE(chainstatemanager, TestChain100Setup)
  43  {
  44      ChainstateManager& manager = *m_node.chainman;
  45  
  46      BOOST_CHECK(WITH_LOCK(::cs_main, return !manager.CurrentChainstate().m_from_snapshot_blockhash));
  47  
  48      // Create a legacy (IBD) chainstate.
  49      //
  50      Chainstate& c1 = manager.ActiveChainstate();
  51  
  52      BOOST_CHECK(WITH_LOCK(::cs_main, return !manager.CurrentChainstate().m_from_snapshot_blockhash));
  53      {
  54          LOCK(manager.GetMutex());
  55          BOOST_CHECK_EQUAL(manager.m_chainstates.size(), 1);
  56          BOOST_CHECK_EQUAL(manager.m_chainstates[0].get(), &c1);
  57      }
  58  
  59      auto& active_chain = WITH_LOCK(manager.GetMutex(), return manager.ActiveChain());
  60      BOOST_CHECK_EQUAL(&active_chain, &c1.m_chain);
  61  
  62      // Get to a valid assumeutxo tip (per chainparams);
  63      mineBlocks(10);
  64      BOOST_CHECK_EQUAL(WITH_LOCK(manager.GetMutex(), return manager.ActiveHeight()), 110);
  65      auto active_tip = WITH_LOCK(manager.GetMutex(), return manager.ActiveTip());
  66      auto exp_tip = c1.m_chain.Tip();
  67      BOOST_CHECK_EQUAL(active_tip, exp_tip);
  68  
  69      BOOST_CHECK(WITH_LOCK(::cs_main, return !manager.CurrentChainstate().m_from_snapshot_blockhash));
  70  
  71      // Create a snapshot-based chainstate.
  72      //
  73      const uint256 snapshot_blockhash = active_tip->GetBlockHash();
  74      Chainstate& c2{WITH_LOCK(::cs_main, return manager.AddChainstate(std::make_unique<Chainstate>(nullptr, manager.m_blockman, manager, snapshot_blockhash)))};
  75      c2.InitCoinsDB(
  76          /*cache_size_bytes=*/8_MiB, /*in_memory=*/true, /*should_wipe=*/false);
  77      {
  78          LOCK(::cs_main);
  79          c2.InitCoinsCache(8_MiB);
  80          c2.CoinsTip().SetBestBlock(active_tip->GetBlockHash());
  81          for (const auto& cs : manager.m_chainstates) {
  82              cs->ClearBlockIndexCandidates();
  83          }
  84          c2.LoadChainTip();
  85          for (const auto& cs : manager.m_chainstates) {
  86              cs->PopulateBlockIndexCandidates();
  87          }
  88      }
  89      BlockValidationState _;
  90      BOOST_CHECK(c2.ActivateBestChain(_, nullptr));
  91  
  92      BOOST_CHECK_EQUAL(WITH_LOCK(::cs_main, return *manager.CurrentChainstate().m_from_snapshot_blockhash), snapshot_blockhash);
  93      BOOST_CHECK(WITH_LOCK(::cs_main, return manager.CurrentChainstate().m_assumeutxo == Assumeutxo::UNVALIDATED));
  94      BOOST_CHECK_EQUAL(&c2, &manager.ActiveChainstate());
  95      BOOST_CHECK(&c1 != &manager.ActiveChainstate());
  96      {
  97          LOCK(manager.GetMutex());
  98          BOOST_CHECK_EQUAL(manager.m_chainstates.size(), 2);
  99          BOOST_CHECK_EQUAL(manager.m_chainstates[0].get(), &c1);
 100          BOOST_CHECK_EQUAL(manager.m_chainstates[1].get(), &c2);
 101      }
 102  
 103      auto& active_chain2 = WITH_LOCK(manager.GetMutex(), return manager.ActiveChain());
 104      BOOST_CHECK_EQUAL(&active_chain2, &c2.m_chain);
 105  
 106      BOOST_CHECK_EQUAL(WITH_LOCK(manager.GetMutex(), return manager.ActiveHeight()), 110);
 107      mineBlocks(1);
 108      BOOST_CHECK_EQUAL(WITH_LOCK(manager.GetMutex(), return manager.ActiveHeight()), 111);
 109      BOOST_CHECK_EQUAL(WITH_LOCK(manager.GetMutex(), return c1.m_chain.Height()), 110);
 110  
 111      auto active_tip2 = WITH_LOCK(manager.GetMutex(), return manager.ActiveTip());
 112      BOOST_CHECK_EQUAL(active_tip, active_tip2->pprev);
 113      BOOST_CHECK_EQUAL(active_tip, c1.m_chain.Tip());
 114      BOOST_CHECK_EQUAL(active_tip2, c2.m_chain.Tip());
 115  
 116      // Let scheduler events finish running to avoid accessing memory that is going to be unloaded
 117      m_node.validation_signals->SyncWithValidationInterfaceQueue();
 118  }
 119  
 120  //! Test rebalancing the caches associated with each chainstate.
 121  BOOST_FIXTURE_TEST_CASE(chainstatemanager_rebalance_caches, TestChain100Setup)
 122  {
 123      ChainstateManager& manager = *m_node.chainman;
 124  
 125      size_t max_cache = 10000;
 126      manager.m_total_coinsdb_cache = max_cache;
 127      manager.m_total_coinstip_cache = max_cache;
 128  
 129      std::vector<Chainstate*> chainstates;
 130  
 131      // Create a legacy (IBD) chainstate.
 132      //
 133      Chainstate& c1 = manager.ActiveChainstate();
 134      chainstates.push_back(&c1);
 135      {
 136          LOCK(::cs_main);
 137          c1.InitCoinsCache(8_MiB);
 138          manager.MaybeRebalanceCaches();
 139      }
 140  
 141      BOOST_CHECK_EQUAL(c1.m_coinstip_cache_size_bytes, max_cache);
 142      BOOST_CHECK_EQUAL(c1.m_coinsdb_cache_size_bytes, max_cache);
 143  
 144      // Create a snapshot-based chainstate.
 145      //
 146      CBlockIndex* snapshot_base{WITH_LOCK(manager.GetMutex(), return manager.ActiveChain()[manager.ActiveChain().Height() / 2])};
 147      Chainstate& c2{WITH_LOCK(::cs_main, return manager.AddChainstate(std::make_unique<Chainstate>(nullptr, manager.m_blockman, manager, *snapshot_base->phashBlock)))};
 148      chainstates.push_back(&c2);
 149      c2.InitCoinsDB(
 150          /*cache_size_bytes=*/8_MiB, /*in_memory=*/true, /*should_wipe=*/false);
 151  
 152      // Reset IBD state so IsInitialBlockDownload() returns true and causes
 153      // MaybeRebalanceCaches() to prioritize the snapshot chainstate, giving it
 154      // more cache space than the snapshot chainstate. Calling ResetIbd() is
 155      // necessary because m_cached_is_ibd is already latched to false before
 156      // the test starts due to the test setup. After ResetIbd() is called,
 157      // IsInitialBlockDownload() will return true because at this point the active
 158      // chainstate has a null chain tip.
 159      static_cast<TestChainstateManager&>(manager).ResetIbd();
 160  
 161      {
 162          LOCK(::cs_main);
 163          c2.InitCoinsCache(8_MiB);
 164          manager.MaybeRebalanceCaches();
 165      }
 166  
 167      BOOST_CHECK_CLOSE(double(c1.m_coinstip_cache_size_bytes), max_cache * 0.05, 1);
 168      BOOST_CHECK_CLOSE(double(c1.m_coinsdb_cache_size_bytes), max_cache * 0.05, 1);
 169      BOOST_CHECK_CLOSE(double(c2.m_coinstip_cache_size_bytes), max_cache * 0.95, 1);
 170      BOOST_CHECK_CLOSE(double(c2.m_coinsdb_cache_size_bytes), max_cache * 0.95, 1);
 171  }
 172  
 173  BOOST_FIXTURE_TEST_CASE(chainstatemanager_ibd_exit_after_loading_blocks, ChainTestingSetup)
 174  {
 175      CBlockIndex tip;
 176      ChainstateManager& chainman{*Assert(m_node.chainman)};
 177      auto apply{[&](bool cached_is_ibd, bool loading_blocks, bool tip_exists, bool enough_work, bool tip_recent) {
 178          LOCK(::cs_main);
 179          chainman.ResetChainstates();
 180          chainman.InitializeChainstate(m_node.mempool.get());
 181  
 182          const auto recent_time{Now<NodeSeconds>() - chainman.m_options.max_tip_age};
 183  
 184          chainman.m_cached_is_ibd.store(cached_is_ibd, std::memory_order_relaxed);
 185          chainman.m_blockman.m_importing = loading_blocks;
 186          if (tip_exists) {
 187              tip.nChainWork = chainman.MinimumChainWork() - (enough_work ? 0 : 1);
 188              tip.nTime = (recent_time - (tip_recent ? 0h : 100h)).time_since_epoch().count();
 189              chainman.ActiveChain().SetTip(tip);
 190          } else {
 191              assert(!chainman.ActiveChain().Tip());
 192          }
 193          chainman.UpdateIBDStatus();
 194      }};
 195  
 196      for (const bool cached_is_ibd : {false, true}) {
 197          for (const bool loading_blocks : {false, true}) {
 198              for (const bool tip_exists : {false, true}) {
 199                  for (const bool enough_work : {false, true}) {
 200                      for (const bool tip_recent : {false, true}) {
 201                          apply(cached_is_ibd, loading_blocks, tip_exists, enough_work, tip_recent);
 202                          const bool expected_ibd = cached_is_ibd && (loading_blocks || !tip_exists || !enough_work || !tip_recent);
 203                          BOOST_CHECK_EQUAL(chainman.IsInitialBlockDownload(), expected_ibd);
 204                      }
 205                  }
 206              }
 207          }
 208      }
 209  }
 210  
 211  struct SnapshotTestSetup : TestChain100Setup {
 212      // Run with coinsdb on the filesystem to support, e.g., moving invalidated
 213      // chainstate dirs to "*_invalid".
 214      //
 215      // Note that this means the tests run considerably slower than in-memory DB
 216      // tests, but we can't otherwise test this functionality since it relies on
 217      // destructive filesystem operations.
 218      SnapshotTestSetup() : TestChain100Setup{
 219                                {},
 220                                {
 221                                    .coins_db_in_memory = false,
 222                                    .block_tree_db_in_memory = false,
 223                                },
 224                            }
 225      {
 226      }
 227  
 228      std::tuple<Chainstate*, Chainstate*> SetupSnapshot()
 229      {
 230          ChainstateManager& chainman = *Assert(m_node.chainman);
 231  
 232          {
 233              LOCK(::cs_main);
 234              BOOST_CHECK(!chainman.CurrentChainstate().m_from_snapshot_blockhash);
 235              BOOST_CHECK(!node::FindAssumeutxoChainstateDir(chainman.m_options.datadir));
 236          }
 237  
 238          size_t initial_size;
 239          size_t initial_total_coins{100};
 240  
 241          // Make some initial assertions about the contents of the chainstate.
 242          {
 243              LOCK(::cs_main);
 244              CCoinsViewCache& ibd_coinscache = chainman.ActiveChainstate().CoinsTip();
 245              initial_size = ibd_coinscache.GetCacheSize();
 246              size_t total_coins{0};
 247  
 248              for (CTransactionRef& txn : m_coinbase_txns) {
 249                  COutPoint op{txn->GetHash(), 0};
 250                  BOOST_CHECK(ibd_coinscache.HaveCoin(op));
 251                  total_coins++;
 252              }
 253  
 254              BOOST_CHECK_EQUAL(total_coins, initial_total_coins);
 255              BOOST_CHECK_EQUAL(initial_size, initial_total_coins);
 256          }
 257  
 258          Chainstate& validation_chainstate = chainman.ActiveChainstate();
 259  
 260          // Snapshot should refuse to load at this height.
 261          BOOST_REQUIRE(!CreateAndActivateUTXOSnapshot(this));
 262          BOOST_CHECK(!chainman.ActiveChainstate().m_from_snapshot_blockhash);
 263  
 264          // Mine 10 more blocks, putting at us height 110 where a valid assumeutxo value can
 265          // be found.
 266          constexpr int snapshot_height = 110;
 267          mineBlocks(10);
 268          initial_size += 10;
 269          initial_total_coins += 10;
 270  
 271          // Should not load malleated snapshots
 272          BOOST_REQUIRE(!CreateAndActivateUTXOSnapshot(
 273              this, [](AutoFile& auto_infile, SnapshotMetadata& metadata) {
 274                  // A UTXO is missing but count is correct
 275                  metadata.m_coins_count -= 1;
 276  
 277                  Txid txid;
 278                  auto_infile >> txid;
 279                  // coins size
 280                  (void)ReadCompactSize(auto_infile);
 281                  // vout index
 282                  (void)ReadCompactSize(auto_infile);
 283                  Coin coin;
 284                  auto_infile >> coin;
 285          }));
 286  
 287          BOOST_CHECK(!node::FindAssumeutxoChainstateDir(chainman.m_options.datadir));
 288  
 289          BOOST_REQUIRE(!CreateAndActivateUTXOSnapshot(
 290              this, [](AutoFile& auto_infile, SnapshotMetadata& metadata) {
 291                  // Coins count is larger than coins in file
 292                  metadata.m_coins_count += 1;
 293          }));
 294          BOOST_REQUIRE(!CreateAndActivateUTXOSnapshot(
 295              this, [](AutoFile& auto_infile, SnapshotMetadata& metadata) {
 296                  // Coins count is smaller than coins in file
 297                  metadata.m_coins_count -= 1;
 298          }));
 299          BOOST_REQUIRE(!CreateAndActivateUTXOSnapshot(
 300              this, [](AutoFile& auto_infile, SnapshotMetadata& metadata) {
 301                  // Wrong hash
 302                  metadata.m_base_blockhash = uint256::ZERO;
 303          }));
 304          BOOST_REQUIRE(!CreateAndActivateUTXOSnapshot(
 305              this, [](AutoFile& auto_infile, SnapshotMetadata& metadata) {
 306                  // Wrong hash
 307                  metadata.m_base_blockhash = uint256::ONE;
 308          }));
 309  
 310          BOOST_REQUIRE(CreateAndActivateUTXOSnapshot(this));
 311          BOOST_CHECK(fs::exists(*node::FindAssumeutxoChainstateDir(chainman.m_options.datadir)));
 312  
 313          // Ensure our active chain is the snapshot chainstate.
 314          BOOST_CHECK(!chainman.ActiveChainstate().m_from_snapshot_blockhash->IsNull());
 315  
 316          Chainstate& snapshot_chainstate = chainman.ActiveChainstate();
 317  
 318          {
 319              LOCK(::cs_main);
 320  
 321              fs::path found = *node::FindAssumeutxoChainstateDir(chainman.m_options.datadir);
 322  
 323              // Note: WriteSnapshotBaseBlockhash() is implicitly tested above.
 324              BOOST_CHECK_EQUAL(
 325                  *node::ReadSnapshotBaseBlockhash(found),
 326                  *Assert(chainman.CurrentChainstate().m_from_snapshot_blockhash));
 327          }
 328  
 329          const auto& au_data = ::Params().AssumeutxoForHeight(snapshot_height);
 330          const CBlockIndex* tip = WITH_LOCK(chainman.GetMutex(), return chainman.ActiveTip());
 331  
 332          BOOST_CHECK_EQUAL(tip->m_chain_tx_count, au_data->m_chain_tx_count);
 333  
 334          // To be checked against later when we try loading a subsequent snapshot.
 335          uint256 loaded_snapshot_blockhash{*Assert(WITH_LOCK(chainman.GetMutex(), return chainman.CurrentChainstate().m_from_snapshot_blockhash))};
 336  
 337          // Make some assertions about the both chainstates. These checks ensure the
 338          // legacy chainstate hasn't changed and that the newly created chainstate
 339          // reflects the expected content.
 340          {
 341              LOCK(::cs_main);
 342              int chains_tested{0};
 343  
 344              for (const auto& chainstate : chainman.m_chainstates) {
 345                  BOOST_TEST_MESSAGE("Checking coins in " << chainstate->ToString());
 346                  CCoinsViewCache& coinscache = chainstate->CoinsTip();
 347  
 348                  // Both caches will be empty initially.
 349                  BOOST_CHECK_EQUAL((unsigned int)0, coinscache.GetCacheSize());
 350  
 351                  size_t total_coins{0};
 352  
 353                  for (CTransactionRef& txn : m_coinbase_txns) {
 354                      COutPoint op{txn->GetHash(), 0};
 355                      BOOST_CHECK(coinscache.HaveCoin(op));
 356                      total_coins++;
 357                  }
 358  
 359                  BOOST_CHECK_EQUAL(initial_size , coinscache.GetCacheSize());
 360                  BOOST_CHECK_EQUAL(total_coins, initial_total_coins);
 361                  chains_tested++;
 362              }
 363  
 364              BOOST_CHECK_EQUAL(chains_tested, 2);
 365          }
 366  
 367          // Mine some new blocks on top of the activated snapshot chainstate.
 368          constexpr size_t new_coins{100};
 369          mineBlocks(new_coins);  // Defined in TestChain100Setup.
 370  
 371          {
 372              LOCK(::cs_main);
 373              size_t coins_in_active{0};
 374              size_t coins_in_background{0};
 375              size_t coins_missing_from_background{0};
 376  
 377              for (const auto& chainstate : chainman.m_chainstates) {
 378                  BOOST_TEST_MESSAGE("Checking coins in " << chainstate->ToString());
 379                  CCoinsViewCache& coinscache = chainstate->CoinsTip();
 380                  bool is_background = chainstate.get() != &chainman.ActiveChainstate();
 381  
 382                  for (CTransactionRef& txn : m_coinbase_txns) {
 383                      COutPoint op{txn->GetHash(), 0};
 384                      if (coinscache.HaveCoin(op)) {
 385                          (is_background ? coins_in_background : coins_in_active)++;
 386                      } else if (is_background) {
 387                          coins_missing_from_background++;
 388                      }
 389                  }
 390              }
 391  
 392              BOOST_CHECK_EQUAL(coins_in_active, initial_total_coins + new_coins);
 393              BOOST_CHECK_EQUAL(coins_in_background, initial_total_coins);
 394              BOOST_CHECK_EQUAL(coins_missing_from_background, new_coins);
 395          }
 396  
 397          // Snapshot should refuse to load after one has already loaded.
 398          BOOST_REQUIRE(!CreateAndActivateUTXOSnapshot(this));
 399  
 400          // Snapshot blockhash should be unchanged.
 401          BOOST_CHECK_EQUAL(
 402              *chainman.ActiveChainstate().m_from_snapshot_blockhash,
 403              loaded_snapshot_blockhash);
 404          return std::make_tuple(&validation_chainstate, &snapshot_chainstate);
 405      }
 406  
 407      // Simulate a restart of the node by flushing all state to disk, clearing the
 408      // existing ChainstateManager, and unloading the block index.
 409      //
 410      // @returns a reference to the "restarted" ChainstateManager
 411      ChainstateManager& SimulateNodeRestart()
 412      {
 413          ChainstateManager& chainman = *Assert(m_node.chainman);
 414  
 415          BOOST_TEST_MESSAGE("Simulating node restart");
 416          {
 417              LOCK(chainman.GetMutex());
 418              for (const auto& cs : chainman.m_chainstates) {
 419                  if (cs->CanFlushToDisk()) cs->ForceFlushStateToDisk();
 420              }
 421          }
 422          {
 423              // Process all callbacks referring to the old manager before wiping it.
 424              m_node.validation_signals->SyncWithValidationInterfaceQueue();
 425              LOCK(::cs_main);
 426              chainman.ResetChainstates();
 427              BOOST_CHECK_EQUAL(chainman.m_chainstates.size(), 0);
 428              m_node.notifications = std::make_unique<KernelNotifications>(Assert(m_node.shutdown_request), m_node.exit_status, *Assert(m_node.warnings));
 429              const ChainstateManager::Options chainman_opts{
 430                  .chainparams = ::Params(),
 431                  .datadir = chainman.m_options.datadir,
 432                  .notifications = *m_node.notifications,
 433                  .signals = m_node.validation_signals.get(),
 434              };
 435              const BlockManager::Options blockman_opts{
 436                  .chainparams = chainman_opts.chainparams,
 437                  .blocks_dir = m_args.GetBlocksDirPath(),
 438                  .notifications = chainman_opts.notifications,
 439                  .block_tree_db_params = DBParams{
 440                      .path = chainman.m_options.datadir / "blocks" / "index",
 441                      .cache_bytes = m_kernel_cache_sizes.block_tree_db,
 442                      .memory_only = m_block_tree_db_in_memory,
 443                  },
 444              };
 445              // For robustness, ensure the old manager is destroyed before creating a
 446              // new one.
 447              m_node.chainman.reset();
 448              m_node.chainman = std::make_unique<ChainstateManager>(*Assert(m_node.shutdown_signal), chainman_opts, blockman_opts);
 449          }
 450          return *Assert(m_node.chainman);
 451      }
 452  };
 453  
 454  //! Test basic snapshot activation.
 455  BOOST_FIXTURE_TEST_CASE(chainstatemanager_activate_snapshot, SnapshotTestSetup)
 456  {
 457      this->SetupSnapshot();
 458  }
 459  
 460  //! Test LoadBlockIndex behavior when multiple chainstates are in use.
 461  //!
 462  //! - First, verify that setBlockIndexCandidates is as expected when using a single,
 463  //!   fully-validating chainstate.
 464  //!
 465  //! - Then mark a region of the chain as missing data and introduce a second chainstate
 466  //!   that will tolerate assumed-valid blocks. Run LoadBlockIndex() and ensure that the first
 467  //!   chainstate only contains fully validated blocks and the other chainstate contains all blocks,
 468  //!   except those marked assume-valid, because those entries don't HAVE_DATA.
 469  //!
 470  BOOST_FIXTURE_TEST_CASE(chainstatemanager_loadblockindex, TestChain100Setup)
 471  {
 472      ChainstateManager& chainman = *Assert(m_node.chainman);
 473      Chainstate& cs1 = chainman.ActiveChainstate();
 474  
 475      int num_indexes{0};
 476      // Blocks in range [assumed_valid_start_idx, last_assumed_valid_idx) will be
 477      // marked as assumed-valid and not having data.
 478      const int expected_assumed_valid{20};
 479      const int last_assumed_valid_idx{111};
 480      const int assumed_valid_start_idx = last_assumed_valid_idx - expected_assumed_valid;
 481  
 482      // Mine to height 120, past the hardcoded regtest assumeutxo snapshot at
 483      // height 110
 484      mineBlocks(20);
 485  
 486      CBlockIndex* validated_tip{nullptr};
 487      CBlockIndex* assumed_base{nullptr};
 488      CBlockIndex* assumed_tip{WITH_LOCK(chainman.GetMutex(), return chainman.ActiveChain().Tip())};
 489      BOOST_CHECK_EQUAL(assumed_tip->nHeight, 120);
 490  
 491      auto reload_all_block_indexes = [&]() {
 492          LOCK(chainman.GetMutex());
 493          // For completeness, we also reset the block sequence counters to
 494          // ensure that no state which affects the ranking of tip-candidates is
 495          // retained (even though this isn't strictly necessary).
 496          chainman.ResetBlockSequenceCounters();
 497          for (const auto& cs : chainman.m_chainstates) {
 498              cs->ClearBlockIndexCandidates();
 499              BOOST_CHECK(cs->setBlockIndexCandidates.empty());
 500          }
 501          chainman.LoadBlockIndex();
 502          for (const auto& cs : chainman.m_chainstates) {
 503              cs->PopulateBlockIndexCandidates();
 504          }
 505      };
 506  
 507      // Ensure that without any assumed-valid BlockIndex entries, only the current tip is
 508      // considered as a candidate.
 509      reload_all_block_indexes();
 510      BOOST_CHECK_EQUAL(cs1.setBlockIndexCandidates.size(), 1);
 511  
 512      // Reset some region of the chain's nStatus, removing the HAVE_DATA flag.
 513      for (int i = 0; i <= cs1.m_chain.Height(); ++i) {
 514          LOCK(::cs_main);
 515          auto index = cs1.m_chain[i];
 516  
 517          // Blocks with heights in range [91, 110] are marked as missing data.
 518          if (i < last_assumed_valid_idx && i >= assumed_valid_start_idx) {
 519              index->nStatus = BlockStatus::BLOCK_VALID_TREE;
 520              index->nTx = 0;
 521              index->m_chain_tx_count = 0;
 522          }
 523  
 524          ++num_indexes;
 525  
 526          // Note the last fully-validated block as the expected validated tip.
 527          if (i == (assumed_valid_start_idx - 1)) {
 528              validated_tip = index;
 529          }
 530          // Note the last assumed valid block as the snapshot base
 531          if (i == last_assumed_valid_idx - 1) {
 532              assumed_base = index;
 533          }
 534      }
 535  
 536      // Note: cs2's tip is not set when ActivateExistingSnapshot is called.
 537      Chainstate& cs2{WITH_LOCK(::cs_main, return chainman.AddChainstate(std::make_unique<Chainstate>(nullptr, chainman.m_blockman, chainman, *assumed_base->phashBlock)))};
 538  
 539      // Set tip of the fully validated chain to be the validated tip
 540      cs1.m_chain.SetTip(*validated_tip);
 541  
 542      // Set tip of the assume-valid-based chain to the assume-valid block
 543      cs2.m_chain.SetTip(*assumed_base);
 544  
 545      // Sanity check test variables.
 546      BOOST_CHECK_EQUAL(num_indexes, 121); // 121 total blocks, including genesis
 547      BOOST_CHECK_EQUAL(assumed_tip->nHeight, 120);  // original chain has height 120
 548      BOOST_CHECK_EQUAL(validated_tip->nHeight, 90); // current cs1 chain has height 90
 549      BOOST_CHECK_EQUAL(assumed_base->nHeight, 110); // current cs2 chain has height 110
 550  
 551      // Regenerate cs1.setBlockIndexCandidates and cs2.setBlockIndexCandidate and
 552      // check contents below.
 553      reload_all_block_indexes();
 554  
 555      // The fully validated chain should only have the current validated tip
 556      // as a candidate (block 90). Specifically:
 557      //
 558      // - It does not have blocks 0-89 because they contain less work than the
 559      //   chain tip.
 560      //
 561      // - It has block 90 because it has data and equal work to the chain tip,
 562      //   (since it is the chain tip).
 563      //
 564      // - It does not have blocks 91-110 because they do not contain data.
 565      //
 566      // - It does not have any blocks after height 110 because cs1 is a background
 567      //   chainstate, and only blocks that are ancestors of the snapshot block
 568      //   are added as candidates for the background chainstate.
 569      BOOST_CHECK_EQUAL(cs1.setBlockIndexCandidates.size(), 1);
 570      BOOST_CHECK_EQUAL(cs1.setBlockIndexCandidates.count(validated_tip), 1);
 571  
 572      // The assumed-valid tolerant chain has the assumed valid base as a
 573      // candidate, but otherwise has none of the assumed-valid (which do not
 574      // HAVE_DATA) blocks as candidates.
 575      //
 576      // Specifically:
 577      // - All blocks below height 110 are not candidates, because cs2 chain tip
 578      //   has height 110 and they have less work than it does.
 579      //
 580      // - Block 110 is a candidate even though it does not have data, because it
 581      //   is the snapshot block, which is assumed valid.
 582      //
 583      // - Blocks 111-120 are added because they have data.
 584  
 585      // Check that block 90 is absent
 586      BOOST_CHECK_EQUAL(cs2.setBlockIndexCandidates.count(validated_tip), 0);
 587      // Check that block 109 is absent
 588      BOOST_CHECK_EQUAL(cs2.setBlockIndexCandidates.count(assumed_base->pprev), 0);
 589      // Check that block 110 is present
 590      BOOST_CHECK_EQUAL(cs2.setBlockIndexCandidates.count(assumed_base), 1);
 591      // Check that block 120 is present
 592      BOOST_CHECK_EQUAL(cs2.setBlockIndexCandidates.count(assumed_tip), 1);
 593      // Check that 11 blocks total are present.
 594      BOOST_CHECK_EQUAL(cs2.setBlockIndexCandidates.size(), num_indexes - last_assumed_valid_idx + 1);
 595  }
 596  
 597  BOOST_FIXTURE_TEST_CASE(loadblockindex_invalid_descendants, TestChain100Setup)
 598  {
 599      LOCK(Assert(m_node.chainman)->GetMutex());
 600      // consider the chain of blocks grand_parent <- parent <- child
 601      // intentionally mark:
 602      //   - grand_parent: BLOCK_FAILED_VALID
 603      //   - parent: BLOCK_FAILED_CHILD
 604      //   - child: not invalid
 605      // Test that when the block index is loaded, all blocks are marked as BLOCK_FAILED_VALID
 606      auto* child{m_node.chainman->ActiveChain().Tip()};
 607      auto* parent{child->pprev};
 608      auto* grand_parent{parent->pprev};
 609      grand_parent->nStatus = (grand_parent->nStatus | BLOCK_FAILED_VALID);
 610      parent->nStatus = (parent->nStatus & ~BLOCK_FAILED_VALID) | BLOCK_FAILED_CHILD;
 611      child->nStatus = (child->nStatus & ~BLOCK_FAILED_VALID);
 612  
 613      // Reload block index to recompute block status validity flags.
 614      m_node.chainman->LoadBlockIndex();
 615  
 616      // check grand_parent, parent, child is marked as BLOCK_FAILED_VALID after reloading the block index
 617      BOOST_CHECK(grand_parent->nStatus & BLOCK_FAILED_VALID);
 618      BOOST_CHECK(parent->nStatus & BLOCK_FAILED_VALID);
 619      BOOST_CHECK(child->nStatus & BLOCK_FAILED_VALID);
 620  }
 621  
 622  //! Verify that ReconsiderBlock clears failure flags for the target block, its ancestors, and descendants,
 623  //! but not for sibling forks that diverge from a shared ancestor.
 624  BOOST_FIXTURE_TEST_CASE(invalidate_block_and_reconsider_fork, TestChain100Setup)
 625  {
 626      ChainstateManager& chainman = *Assert(m_node.chainman);
 627      Chainstate& chainstate = chainman.ActiveChainstate();
 628  
 629      // we have a chain of 100 blocks: genesis(0) <- ... <- block98 <- block99 <- block100
 630      CBlockIndex* block98;
 631      CBlockIndex* block99;
 632      CBlockIndex* block100;
 633      {
 634          LOCK(chainman.GetMutex());
 635          block98 = chainman.ActiveChain()[98];
 636          block99 = chainman.ActiveChain()[99];
 637          block100 = chainman.ActiveChain()[100];
 638      }
 639  
 640      // create the following block constellation:
 641      // genesis(0) <- ... <- block98 <- block99  <- block100
 642      //                              <- block99' <- block100'
 643      // by temporarily invalidating block99. the chain tip now falls to block98,
 644      // mine 2 new blocks on top of block 98 (block99' and block100') and then restore block99 and block 100.
 645      BlockValidationState state;
 646      BOOST_REQUIRE(chainstate.InvalidateBlock(state, block99));
 647      BOOST_REQUIRE(WITH_LOCK(cs_main, return chainman.ActiveChain().Tip()) == block98);
 648      CScript coinbase_script = CScript() << ToByteVector(coinbaseKey.GetPubKey()) << OP_CHECKSIG;
 649      for (int i = 0; i < 2; ++i) {
 650          CreateAndProcessBlock({}, coinbase_script);
 651      }
 652      const CBlockIndex* fork_block99;
 653      const CBlockIndex* fork_block100;
 654      {
 655          LOCK(chainman.GetMutex());
 656          fork_block99 = chainman.ActiveChain()[99];
 657          BOOST_REQUIRE(fork_block99->pprev == block98);
 658          fork_block100 = chainman.ActiveChain()[100];
 659          BOOST_REQUIRE(fork_block100->pprev == fork_block99);
 660      }
 661      // Restore original block99 and block100
 662      {
 663          LOCK(chainman.GetMutex());
 664          chainstate.ResetBlockFailureFlags(block99);
 665          chainman.RecalculateBestHeader();
 666      }
 667      chainstate.ActivateBestChain(state);
 668      BOOST_REQUIRE(WITH_LOCK(cs_main, return chainman.ActiveChain().Tip()) == block100);
 669  
 670      {
 671          LOCK(chainman.GetMutex());
 672          BOOST_CHECK(!(block100->nStatus & BLOCK_FAILED_VALID));
 673          BOOST_CHECK(!(block99->nStatus & BLOCK_FAILED_VALID));
 674          BOOST_CHECK(!(fork_block100->nStatus & BLOCK_FAILED_VALID));
 675          BOOST_CHECK(!(fork_block99->nStatus & BLOCK_FAILED_VALID));
 676      }
 677  
 678      // Invalidate block98
 679      BOOST_REQUIRE(chainstate.InvalidateBlock(state, block98));
 680  
 681      {
 682          LOCK(chainman.GetMutex());
 683          // block98 and all descendants of block98 are marked BLOCK_FAILED_VALID
 684          BOOST_CHECK(block98->nStatus & BLOCK_FAILED_VALID);
 685          BOOST_CHECK(block99->nStatus & BLOCK_FAILED_VALID);
 686          BOOST_CHECK(block100->nStatus & BLOCK_FAILED_VALID);
 687          BOOST_CHECK(fork_block99->nStatus & BLOCK_FAILED_VALID);
 688          BOOST_CHECK(fork_block100->nStatus & BLOCK_FAILED_VALID);
 689      }
 690  
 691      // Reconsider block99. ResetBlockFailureFlags clears BLOCK_FAILED_VALID from
 692      // block99 and its ancestors (block98) and descendants (block100)
 693      // but NOT from block99' and block100' (not a direct ancestor/descendant)
 694      {
 695          LOCK(chainman.GetMutex());
 696          chainstate.ResetBlockFailureFlags(block99);
 697          chainman.RecalculateBestHeader();
 698      }
 699      chainstate.ActivateBestChain(state);
 700      {
 701          LOCK(chainman.GetMutex());
 702          BOOST_CHECK(!(block98->nStatus & BLOCK_FAILED_VALID));
 703          BOOST_CHECK(!(block99->nStatus & BLOCK_FAILED_VALID));
 704          BOOST_CHECK(!(block100->nStatus & BLOCK_FAILED_VALID));
 705          BOOST_CHECK(fork_block99->nStatus & BLOCK_FAILED_VALID);
 706          BOOST_CHECK(fork_block100->nStatus & BLOCK_FAILED_VALID);
 707      }
 708  }
 709  
 710  //! Ensure that snapshot chainstate can be loaded when found on disk after a
 711  //! restart, and that new blocks can be connected to both chainstates.
 712  BOOST_FIXTURE_TEST_CASE(chainstatemanager_snapshot_init, SnapshotTestSetup)
 713  {
 714      ChainstateManager& chainman = *Assert(m_node.chainman);
 715      Chainstate& bg_chainstate = chainman.ActiveChainstate();
 716  
 717      this->SetupSnapshot();
 718  
 719      fs::path snapshot_chainstate_dir = *node::FindAssumeutxoChainstateDir(chainman.m_options.datadir);
 720      BOOST_CHECK(fs::exists(snapshot_chainstate_dir));
 721      BOOST_CHECK_EQUAL(snapshot_chainstate_dir, gArgs.GetDataDirNet() / "chainstate_snapshot");
 722  
 723      BOOST_CHECK(WITH_LOCK(::cs_main, return chainman.CurrentChainstate().m_from_snapshot_blockhash));
 724      const uint256 snapshot_tip_hash = WITH_LOCK(chainman.GetMutex(),
 725          return chainman.ActiveTip()->GetBlockHash());
 726  
 727      BOOST_CHECK_EQUAL(WITH_LOCK(chainman.GetMutex(), return chainman.m_chainstates.size()), 2);
 728  
 729      // "Rewind" the background chainstate so that its tip is not at the
 730      // base block of the snapshot - this is so after simulating a node restart,
 731      // it will initialize instead of attempting to complete validation.
 732      //
 733      // Note that this is not a realistic use of DisconnectTip().
 734      DisconnectedBlockTransactions unused_pool{MAX_DISCONNECTED_TX_POOL_BYTES};
 735      BlockValidationState unused_state;
 736      {
 737          LOCK2(::cs_main, bg_chainstate.MempoolMutex());
 738          BOOST_CHECK(bg_chainstate.DisconnectTip(unused_state, &unused_pool));
 739          unused_pool.clear();  // to avoid queuedTx assertion errors on teardown
 740      }
 741      BOOST_CHECK_EQUAL(bg_chainstate.m_chain.Height(), 109);
 742  
 743      // Test that simulating a shutdown (resetting ChainstateManager) and then performing
 744      // chainstate reinitializing successfully reloads both chainstates.
 745      ChainstateManager& chainman_restarted = this->SimulateNodeRestart();
 746  
 747      BOOST_TEST_MESSAGE("Performing Load/Verify/Activate of chainstate");
 748  
 749      // This call reinitializes the chainstates.
 750      this->LoadVerifyActivateChainstate();
 751  
 752      {
 753          LOCK(chainman_restarted.GetMutex());
 754          BOOST_CHECK_EQUAL(chainman_restarted.m_chainstates.size(), 2);
 755          // Background chainstate has height of 109 not 110 here due to a quirk
 756          // of the LoadVerifyActivate only calling ActivateBestChain on one
 757          // chainstate. The height would be 110 after a real restart, but it's
 758          // fine for this test which is focused on the snapshot chainstate.
 759          BOOST_CHECK_EQUAL(chainman_restarted.m_chainstates[0]->m_chain.Height(), 109);
 760          BOOST_CHECK_EQUAL(chainman_restarted.m_chainstates[1]->m_chain.Height(), 210);
 761  
 762          BOOST_CHECK(chainman_restarted.CurrentChainstate().m_from_snapshot_blockhash);
 763          BOOST_CHECK(chainman_restarted.CurrentChainstate().m_assumeutxo == Assumeutxo::UNVALIDATED);
 764  
 765          BOOST_CHECK_EQUAL(chainman_restarted.ActiveTip()->GetBlockHash(), snapshot_tip_hash);
 766          BOOST_CHECK_EQUAL(chainman_restarted.ActiveHeight(), 210);
 767          BOOST_CHECK_EQUAL(chainman_restarted.HistoricalChainstate()->m_chain.Height(), 109);
 768      }
 769  
 770      BOOST_TEST_MESSAGE(
 771          "Ensure we can mine blocks on top of the initialized snapshot chainstate");
 772      mineBlocks(10);
 773      {
 774          LOCK(chainman_restarted.GetMutex());
 775          BOOST_CHECK_EQUAL(chainman_restarted.ActiveHeight(), 220);
 776  
 777          // Background chainstate should be unaware of new blocks on the snapshot
 778          // chainstate, but the block disconnected above is now reattached.
 779          BOOST_CHECK_EQUAL(chainman_restarted.m_chainstates.size(), 2);
 780          BOOST_CHECK_EQUAL(chainman_restarted.m_chainstates[0]->m_chain.Height(), 110);
 781          BOOST_CHECK_EQUAL(chainman_restarted.m_chainstates[1]->m_chain.Height(), 220);
 782          BOOST_CHECK_EQUAL(chainman_restarted.HistoricalChainstate(), nullptr);
 783      }
 784  }
 785  
 786  BOOST_FIXTURE_TEST_CASE(chainstatemanager_snapshot_completion, SnapshotTestSetup)
 787  {
 788      this->SetupSnapshot();
 789  
 790      ChainstateManager& chainman = *Assert(m_node.chainman);
 791      Chainstate& active_cs = chainman.ActiveChainstate();
 792      Chainstate& validated_cs{*Assert(WITH_LOCK(cs_main, return chainman.HistoricalChainstate()))};
 793      auto tip_cache_before_complete = active_cs.m_coinstip_cache_size_bytes;
 794      auto db_cache_before_complete = active_cs.m_coinsdb_cache_size_bytes;
 795  
 796      SnapshotCompletionResult res;
 797      m_node.notifications->m_shutdown_on_fatal_error = false;
 798  
 799      fs::path snapshot_chainstate_dir = *node::FindAssumeutxoChainstateDir(chainman.m_options.datadir);
 800      BOOST_CHECK(fs::exists(snapshot_chainstate_dir));
 801      BOOST_CHECK_EQUAL(snapshot_chainstate_dir, gArgs.GetDataDirNet() / "chainstate_snapshot");
 802  
 803      BOOST_CHECK(WITH_LOCK(::cs_main, return chainman.CurrentChainstate().m_from_snapshot_blockhash));
 804      const uint256 snapshot_tip_hash = WITH_LOCK(chainman.GetMutex(),
 805          return chainman.ActiveTip()->GetBlockHash());
 806  
 807      res = WITH_LOCK(::cs_main, return chainman.MaybeValidateSnapshot(validated_cs, active_cs));
 808      BOOST_CHECK_EQUAL(res, SnapshotCompletionResult::SUCCESS);
 809  
 810      BOOST_CHECK(WITH_LOCK(::cs_main, return chainman.CurrentChainstate().m_assumeutxo == Assumeutxo::VALIDATED));
 811      BOOST_CHECK(WITH_LOCK(::cs_main, return chainman.CurrentChainstate().m_from_snapshot_blockhash));
 812      BOOST_CHECK_EQUAL(WITH_LOCK(chainman.GetMutex(), return chainman.HistoricalChainstate()), nullptr);
 813  
 814      // Cache should have been rebalanced and reallocated to the "only" remaining
 815      // chainstate.
 816      BOOST_CHECK(active_cs.m_coinstip_cache_size_bytes > tip_cache_before_complete);
 817      BOOST_CHECK(active_cs.m_coinsdb_cache_size_bytes > db_cache_before_complete);
 818  
 819      // Trying completion again should return false.
 820      res = WITH_LOCK(::cs_main, return chainman.MaybeValidateSnapshot(validated_cs, active_cs));
 821      BOOST_CHECK_EQUAL(res, SnapshotCompletionResult::SKIPPED);
 822  
 823      // The invalid snapshot path should not have been used.
 824      fs::path snapshot_invalid_dir = gArgs.GetDataDirNet() / "chainstate_snapshot_INVALID";
 825      BOOST_CHECK(!fs::exists(snapshot_invalid_dir));
 826      // chainstate_snapshot should still exist.
 827      BOOST_CHECK(fs::exists(snapshot_chainstate_dir));
 828  
 829      // Test that simulating a shutdown (resetting ChainstateManager) and then performing
 830      // chainstate reinitializing successfully cleans up the background-validation
 831      // chainstate data, and we end up with a single chainstate that is at tip.
 832      ChainstateManager& chainman_restarted = this->SimulateNodeRestart();
 833  
 834      BOOST_TEST_MESSAGE("Performing Load/Verify/Activate of chainstate");
 835  
 836      // This call reinitializes the chainstates, and should clean up the now unnecessary
 837      // background-validation leveldb contents.
 838      this->LoadVerifyActivateChainstate();
 839  
 840      BOOST_CHECK(!fs::exists(snapshot_invalid_dir));
 841      // chainstate_snapshot should now *not* exist.
 842      BOOST_CHECK(!fs::exists(snapshot_chainstate_dir));
 843  
 844      const Chainstate& active_cs2 = chainman_restarted.ActiveChainstate();
 845  
 846      {
 847          LOCK(chainman_restarted.GetMutex());
 848          BOOST_CHECK_EQUAL(chainman_restarted.m_chainstates.size(), 1);
 849          BOOST_CHECK(!chainman_restarted.CurrentChainstate().m_from_snapshot_blockhash);
 850          BOOST_CHECK(active_cs2.m_coinstip_cache_size_bytes > tip_cache_before_complete);
 851          BOOST_CHECK(active_cs2.m_coinsdb_cache_size_bytes > db_cache_before_complete);
 852  
 853          BOOST_CHECK_EQUAL(chainman_restarted.ActiveTip()->GetBlockHash(), snapshot_tip_hash);
 854          BOOST_CHECK_EQUAL(chainman_restarted.ActiveHeight(), 210);
 855      }
 856  
 857      BOOST_TEST_MESSAGE(
 858          "Ensure we can mine blocks on top of the \"new\" IBD chainstate");
 859      mineBlocks(10);
 860      {
 861          LOCK(chainman_restarted.GetMutex());
 862          BOOST_CHECK_EQUAL(chainman_restarted.ActiveHeight(), 220);
 863      }
 864  }
 865  
 866  BOOST_FIXTURE_TEST_CASE(chainstatemanager_snapshot_completion_hash_mismatch, SnapshotTestSetup)
 867  {
 868      auto chainstates = this->SetupSnapshot();
 869      Chainstate& validation_chainstate = *std::get<0>(chainstates);
 870      Chainstate& unvalidated_cs = *std::get<1>(chainstates);
 871      ChainstateManager& chainman = *Assert(m_node.chainman);
 872      SnapshotCompletionResult res;
 873      m_node.notifications->m_shutdown_on_fatal_error = false;
 874  
 875      // Test tampering with the IBD UTXO set with an extra coin to ensure it causes
 876      // snapshot completion to fail.
 877      CCoinsViewCache& ibd_coins = WITH_LOCK(::cs_main,
 878          return validation_chainstate.CoinsTip());
 879      Coin badcoin;
 880      badcoin.out.nValue = m_rng.rand32();
 881      badcoin.nHeight = 1;
 882      badcoin.out.scriptPubKey.assign(m_rng.randbits(6), 0);
 883      Txid txid = Txid::FromUint256(m_rng.rand256());
 884      ibd_coins.AddCoin(COutPoint(txid, 0), std::move(badcoin), false);
 885  
 886      fs::path snapshot_chainstate_dir = gArgs.GetDataDirNet() / "chainstate_snapshot";
 887      BOOST_CHECK(fs::exists(snapshot_chainstate_dir));
 888  
 889      {
 890          ASSERT_DEBUG_LOG("failed to validate the -assumeutxo snapshot state");
 891          res = WITH_LOCK(::cs_main, return chainman.MaybeValidateSnapshot(validation_chainstate, unvalidated_cs));
 892          BOOST_CHECK_EQUAL(res, SnapshotCompletionResult::HASH_MISMATCH);
 893      }
 894  
 895      {
 896          LOCK(chainman.GetMutex());
 897          BOOST_CHECK_EQUAL(chainman.m_chainstates.size(), 2);
 898          BOOST_CHECK(chainman.m_chainstates[0]->m_assumeutxo == Assumeutxo::VALIDATED);
 899          BOOST_CHECK(!chainman.m_chainstates[0]->SnapshotBase());
 900          BOOST_CHECK(chainman.m_chainstates[1]->m_assumeutxo == Assumeutxo::INVALID);
 901          BOOST_CHECK(chainman.m_chainstates[1]->SnapshotBase());
 902      }
 903  
 904      fs::path snapshot_invalid_dir = gArgs.GetDataDirNet() / "chainstate_snapshot_INVALID";
 905      BOOST_CHECK(fs::exists(snapshot_invalid_dir));
 906  
 907      // Test that simulating a shutdown (resetting ChainstateManager) and then performing
 908      // chainstate reinitializing successfully loads only the fully-validated
 909      // chainstate data, and we end up with a single chainstate that is at tip.
 910      ChainstateManager& chainman_restarted = this->SimulateNodeRestart();
 911  
 912      BOOST_TEST_MESSAGE("Performing Load/Verify/Activate of chainstate");
 913  
 914      // This call reinitializes the chainstates, and should clean up the now unnecessary
 915      // background-validation leveldb contents.
 916      this->LoadVerifyActivateChainstate();
 917  
 918      BOOST_CHECK(fs::exists(snapshot_invalid_dir));
 919      BOOST_CHECK(!fs::exists(snapshot_chainstate_dir));
 920  
 921      {
 922          LOCK(::cs_main);
 923          BOOST_CHECK_EQUAL(chainman_restarted.m_chainstates.size(), 1);
 924          BOOST_CHECK(!chainman_restarted.CurrentChainstate().m_from_snapshot_blockhash);
 925          BOOST_CHECK_EQUAL(chainman_restarted.ActiveHeight(), 210);
 926      }
 927  
 928      BOOST_TEST_MESSAGE(
 929          "Ensure we can mine blocks on top of the \"new\" IBD chainstate");
 930      mineBlocks(10);
 931      {
 932          LOCK(::cs_main);
 933          BOOST_CHECK_EQUAL(chainman_restarted.ActiveHeight(), 220);
 934      }
 935  }
 936  
 937  /** Helper function to parse args into args_man and return the result of applying them to opts */
 938  template <typename Options>
 939  util::Result<Options> SetOptsFromArgs(ArgsManager& args_man, Options opts,
 940                                        const std::vector<const char*>& args)
 941  {
 942      const auto argv{Cat({"ignore"}, args)};
 943      std::string error{};
 944      if (!args_man.ParseParameters(argv.size(), argv.data(), error)) {
 945          return util::Error{Untranslated("ParseParameters failed with error: " + error)};
 946      }
 947      const auto result{node::ApplyArgsManOptions(args_man, opts)};
 948      if (!result) return util::Error{util::ErrorString(result)};
 949      return opts;
 950  }
 951  
 952  BOOST_FIXTURE_TEST_CASE(chainstatemanager_args, BasicTestingSetup)
 953  {
 954      //! Try to apply the provided args to a ChainstateManager::Options
 955      auto get_opts = [&](const std::vector<const char*>& args) {
 956          static kernel::Notifications notifications{};
 957          static const ChainstateManager::Options options{
 958              .chainparams = ::Params(),
 959              .datadir = {},
 960              .notifications = notifications};
 961          return SetOptsFromArgs(*this->m_node.args, options, args);
 962      };
 963      //! Like get_opts, but requires the provided args to be valid and unwraps the result
 964      auto get_valid_opts = [&](const std::vector<const char*>& args) {
 965          const auto result{get_opts(args)};
 966          BOOST_REQUIRE_MESSAGE(result, util::ErrorString(result).original);
 967          return *result;
 968      };
 969  
 970      // test -assumevalid
 971      BOOST_CHECK(!get_valid_opts({}).assumed_valid_block);
 972      BOOST_CHECK_EQUAL(get_valid_opts({"-assumevalid="}).assumed_valid_block, uint256::ZERO);
 973      BOOST_CHECK_EQUAL(get_valid_opts({"-assumevalid=0"}).assumed_valid_block, uint256::ZERO);
 974      BOOST_CHECK_EQUAL(get_valid_opts({"-noassumevalid"}).assumed_valid_block, uint256::ZERO);
 975      BOOST_CHECK_EQUAL(get_valid_opts({"-assumevalid=0x12"}).assumed_valid_block, uint256{0x12});
 976  
 977      std::string assume_valid{"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"};
 978      BOOST_CHECK_EQUAL(get_valid_opts({("-assumevalid=" + assume_valid).c_str()}).assumed_valid_block, uint256::FromHex(assume_valid));
 979  
 980      BOOST_CHECK(!get_opts({"-assumevalid=xyz"}));                                                               // invalid hex characters
 981      BOOST_CHECK(!get_opts({"-assumevalid=01234567890123456789012345678901234567890123456789012345678901234"})); // > 64 hex chars
 982  
 983      // test -minimumchainwork
 984      BOOST_CHECK(!get_valid_opts({}).minimum_chain_work);
 985      BOOST_CHECK_EQUAL(get_valid_opts({"-minimumchainwork=0"}).minimum_chain_work, arith_uint256());
 986      BOOST_CHECK_EQUAL(get_valid_opts({"-nominimumchainwork"}).minimum_chain_work, arith_uint256());
 987      BOOST_CHECK_EQUAL(get_valid_opts({"-minimumchainwork=0x1234"}).minimum_chain_work, arith_uint256{0x1234});
 988  
 989      std::string minimum_chainwork{"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"};
 990      BOOST_CHECK_EQUAL(get_valid_opts({("-minimumchainwork=" + minimum_chainwork).c_str()}).minimum_chain_work, UintToArith256(uint256::FromHex(minimum_chainwork).value()));
 991  
 992      BOOST_CHECK(!get_opts({"-minimumchainwork=xyz"}));                                                               // invalid hex characters
 993      BOOST_CHECK(!get_opts({"-minimumchainwork=01234567890123456789012345678901234567890123456789012345678901234"})); // > 64 hex chars
 994  
 995      BOOST_CHECK_EQUAL(get_valid_opts({}).prevoutfetch_threads_num, DEFAULT_PREVOUTFETCH_THREADS);
 996      BOOST_CHECK_EQUAL(get_valid_opts({"-prevoutfetchthreads=0"}).prevoutfetch_threads_num, 0);
 997      BOOST_CHECK_EQUAL(get_valid_opts({"-prevoutfetchthreads=3"}).prevoutfetch_threads_num, 3);
 998      BOOST_CHECK_EQUAL(get_valid_opts({"-prevoutfetchthreads=100"}).prevoutfetch_threads_num, MAX_PREVOUTFETCH_THREADS);
 999      BOOST_CHECK(!get_opts({"-prevoutfetchthreads=-1"}));
1000  }
1001  
1002  BOOST_AUTO_TEST_SUITE_END()
1003