validation_chainstatemanager_tests.cpp raw

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