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