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