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