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