chainstate.h raw

   1  // Copyright (c) 2021-2022 The Limenka 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 LIMENKA_TEST_UTIL_CHAINSTATE_H
   6  #define LIMENKA_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(node,
  51                                           node.chainman->ActiveChainstate(),
  52                                           std::move(auto_outfile), // Will close auto_outfile.
  53                                           snapshot_path,
  54                                           snapshot_path);
  55      LogPrintf(
  56          "Wrote UTXO snapshot to %s: %s\n", fs::PathToString(snapshot_path.make_preferred()), result.write());
  57  
  58      // Read the written snapshot in and then activate it.
  59      //
  60      FILE* infile{fsbridge::fopen(snapshot_path, "rb")};
  61      AutoFile auto_infile{infile};
  62      node::SnapshotMetadata metadata{node.chainman->GetParams().MessageStart()};
  63      auto_infile >> metadata;
  64  
  65      malleation(auto_infile, metadata);
  66  
  67      if (reset_chainstate) {
  68          {
  69              // What follows is code to selectively reset chainstate data without
  70              // disturbing the existing BlockManager instance, which is needed to
  71              // recognize the headers chain previously generated by the chainstate we're
  72              // removing. Without those headers, we can't activate the snapshot below.
  73              //
  74              // This is a stripped-down version of node::LoadChainstate which
  75              // preserves the block index.
  76              LOCK(::cs_main);
  77              CBlockIndex *orig_tip = node.chainman->ActiveChainstate().m_chain.Tip();
  78              uint256 gen_hash = node.chainman->ActiveChainstate().m_chain[0]->GetBlockHash();
  79              node.chainman->ResetChainstates();
  80              node.chainman->InitializeChainstate(node.mempool.get());
  81              Chainstate& chain = node.chainman->ActiveChainstate();
  82              Assert(chain.LoadGenesisBlock());
  83              // These cache values will be corrected shortly in `MaybeRebalanceCaches`.
  84              chain.InitCoinsDB(1 << 20, true, false, "");
  85              chain.InitCoinsCache(1 << 20);
  86              chain.CoinsTip().SetBestBlock(gen_hash);
  87              chain.setBlockIndexCandidates.insert(node.chainman->m_blockman.LookupBlockIndex(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          }
 110          BlockValidationState state;
 111          if (!node.chainman->ActiveChainstate().ActivateBestChain(state)) {
 112              throw std::runtime_error(strprintf("ActivateBestChain failed. (%s)", state.ToString()));
 113          }
 114          Assert(
 115              0 == WITH_LOCK(node.chainman->GetMutex(), return node.chainman->ActiveHeight()));
 116      }
 117  
 118      auto& new_active = node.chainman->ActiveChainstate();
 119      auto* tip = new_active.m_chain.Tip();
 120  
 121      // Disconnect a block so that the snapshot chainstate will be ahead, otherwise
 122      // it will refuse to activate.
 123      //
 124      // TODO this is a unittest-specific hack, and we should probably rethink how to
 125      // better generate/activate snapshots in unittests.
 126      if (tip->pprev) {
 127          new_active.m_chain.SetTip(*(tip->pprev));
 128      }
 129  
 130      auto res = node.chainman->ActivateSnapshot(auto_infile, metadata, in_memory_chainstate);
 131  
 132      // Restore the old tip.
 133      new_active.m_chain.SetTip(*tip);
 134      return !!res;
 135  }
 136  
 137  
 138  #endif // LIMENKA_TEST_UTIL_CHAINSTATE_H
 139