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