/ src / test / util / chainstate.h
chainstate.h
  1  // Copyright (c) 2021-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  #ifndef BITCOIN_TEST_UTIL_CHAINSTATE_H
  6  #define BITCOIN_TEST_UTIL_CHAINSTATE_H
  7  
  8  #include <clientversion.h>
  9  #include <logging.h>
 10  #include <node/context.h>
 11  #include <node/utxo_snapshot.h>
 12  #include <rpc/blockchain.h>
 13  #include <test/util/setup_common.h>
 14  #include <util/byte_units.h>
 15  #include <util/fs.h>
 16  #include <validation.h>
 17  
 18  #include <univalue.h>
 19  
 20  const auto NoMalleation = [](AutoFile& file, node::SnapshotMetadata& meta){};
 21  
 22  /**
 23   * Create and activate a UTXO snapshot, optionally providing a function to
 24   * malleate the snapshot.
 25   *
 26   * If `reset_chainstate` is true, reset the original chainstate back to the genesis
 27   * block. This allows us to simulate more realistic conditions in which a snapshot is
 28   * loaded into an otherwise mostly-uninitialized datadir. It also allows us to test
 29   * conditions that would otherwise cause shutdowns based on the IBD chainstate going
 30   * past the snapshot it generated.
 31   */
 32  template<typename F = decltype(NoMalleation)>
 33  static bool
 34  CreateAndActivateUTXOSnapshot(
 35      TestingSetup* fixture,
 36      F malleation = NoMalleation,
 37      bool reset_chainstate = false,
 38      bool in_memory_chainstate = false)
 39  {
 40      node::NodeContext& node = fixture->m_node;
 41      fs::path root = fixture->m_path_root;
 42  
 43      // Write out a snapshot to the test's tempdir.
 44      //
 45      int height;
 46      WITH_LOCK(::cs_main, height = node.chainman->ActiveHeight());
 47      fs::path snapshot_path = root / fs::u8path(tfm::format("test_snapshot.%d.dat", height));
 48      FILE* outfile{fsbridge::fopen(snapshot_path, "wb")};
 49      AutoFile auto_outfile{outfile};
 50  
 51      UniValue result = CreateUTXOSnapshot(node,
 52                                           node.chainman->ActiveChainstate(),
 53                                           std::move(auto_outfile), // Will close auto_outfile.
 54                                           snapshot_path,
 55                                           snapshot_path);
 56      LogInfo("Wrote UTXO snapshot to %s: %s",
 57              fs::PathToString(snapshot_path.make_preferred()), result.write());
 58  
 59      // Read the written snapshot in and then activate it.
 60      //
 61      FILE* infile{fsbridge::fopen(snapshot_path, "rb")};
 62      AutoFile auto_infile{infile};
 63      node::SnapshotMetadata metadata{node.chainman->GetParams().MessageStart()};
 64      auto_infile >> metadata;
 65  
 66      malleation(auto_infile, metadata);
 67  
 68      if (reset_chainstate) {
 69          {
 70              // What follows is code to selectively reset chainstate data without
 71              // disturbing the existing BlockManager instance, which is needed to
 72              // recognize the headers chain previously generated by the chainstate we're
 73              // removing. Without those headers, we can't activate the snapshot below.
 74              //
 75              // This is a stripped-down version of node::LoadChainstate which
 76              // preserves the block index.
 77              LOCK(::cs_main);
 78              CBlockIndex *orig_tip = node.chainman->ActiveChainstate().m_chain.Tip();
 79              uint256 gen_hash = node.chainman->ActiveChainstate().m_chain[0]->GetBlockHash();
 80              node.chainman->ResetChainstates();
 81              node.chainman->InitializeChainstate(node.mempool.get());
 82              Chainstate& chain = node.chainman->ActiveChainstate();
 83              Assert(chain.LoadGenesisBlock());
 84              // These cache values will be corrected shortly in `MaybeRebalanceCaches`.
 85              chain.InitCoinsDB(1_MiB, /*in_memory=*/true, /*should_wipe=*/false);
 86              chain.InitCoinsCache(1_MiB);
 87              chain.CoinsTip().SetBestBlock(gen_hash);
 88              chain.LoadChainTip();
 89              node.chainman->MaybeRebalanceCaches();
 90  
 91              // Reset the HAVE_DATA flags below the snapshot height, simulating
 92              // never-having-downloaded them in the first place.
 93              // TODO: perhaps we could improve this by using pruning to delete
 94              // these blocks instead
 95              CBlockIndex *pindex = orig_tip;
 96              while (pindex && pindex != chain.m_chain.Tip()) {
 97                  // Remove all data and validity flags by just setting
 98                  // BLOCK_VALID_TREE. Also reset transaction counts and sequence
 99                  // ids that are set when blocks are received, to make test setup
100                  // more realistic and satisfy consistency checks in
101                  // CheckBlockIndex().
102                  assert(pindex->IsValid(BlockStatus::BLOCK_VALID_TREE));
103                  pindex->nStatus = BlockStatus::BLOCK_VALID_TREE;
104                  pindex->nTx = 0;
105                  pindex->m_chain_tx_count = 0;
106                  pindex->nSequenceId = 0;
107                  pindex = pindex->pprev;
108              }
109              chain.PopulateBlockIndexCandidates();
110          }
111          BlockValidationState state;
112          if (!node.chainman->ActiveChainstate().ActivateBestChain(state)) {
113              throw std::runtime_error(strprintf("ActivateBestChain failed. (%s)", state.ToString()));
114          }
115          Assert(
116              0 == WITH_LOCK(node.chainman->GetMutex(), return node.chainman->ActiveHeight()));
117      }
118  
119      auto& new_active = node.chainman->ActiveChainstate();
120      auto* tip = new_active.m_chain.Tip();
121  
122      // Disconnect a block so that the snapshot chainstate will be ahead, otherwise
123      // it will refuse to activate.
124      //
125      // TODO this is a unittest-specific hack, and we should probably rethink how to
126      // better generate/activate snapshots in unittests.
127      if (tip->pprev) {
128          new_active.m_chain.SetTip(*(tip->pprev));
129      }
130  
131      auto res = node.chainman->ActivateSnapshot(auto_infile, metadata, in_memory_chainstate);
132  
133      // Restore the old tip.
134      new_active.m_chain.SetTip(*tip);
135      return !!res;
136  }
137  
138  
139  #endif // BITCOIN_TEST_UTIL_CHAINSTATE_H