chainstate.h raw

   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 <node/context.h>
  10  #include <node/utxo_snapshot.h>
  11  #include <rpc/blockchain.h>
  12  #include <test/util/setup_common.h>
  13  #include <util/byte_units.h>
  14  #include <util/fs.h>
  15  #include <util/log.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(node.chainman->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
 140