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