Branch data Line data Source code
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 : 0 : 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 : 0 : CreateAndActivateUTXOSnapshot( 34 : : TestingSetup* fixture, 35 : : F malleation = NoMalleation, 36 : : bool reset_chainstate = false, 37 : : bool in_memory_chainstate = false) 38 : : { 39 : 0 : node::NodeContext& node = fixture->m_node; 40 : 0 : fs::path root = fixture->m_path_root; 41 : : 42 : : // Write out a snapshot to the test's tempdir. 43 : : // 44 : : int height; 45 : 0 : WITH_LOCK(::cs_main, height = node.chainman->ActiveHeight()); 46 : 0 : fs::path snapshot_path = root / fs::u8path(tfm::format("test_snapshot.%d.dat", height)); 47 : 0 : FILE* outfile{fsbridge::fopen(snapshot_path, "wb")}; 48 : 0 : AutoFile auto_outfile{outfile}; 49 : : 50 : 0 : UniValue result = CreateUTXOSnapshot( 51 : 0 : node, node.chainman->ActiveChainstate(), auto_outfile, snapshot_path, snapshot_path); 52 : 0 : 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 : 0 : FILE* infile{fsbridge::fopen(snapshot_path, "rb")}; 58 : 0 : AutoFile auto_infile{infile}; 59 : 0 : node::SnapshotMetadata metadata; 60 : 0 : auto_infile >> metadata; 61 : : 62 : 0 : malleation(auto_infile, metadata); 63 : : 64 : 0 : 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 : 0 : LOCK(::cs_main); 74 : 0 : CBlockIndex *orig_tip = node.chainman->ActiveChainstate().m_chain.Tip(); 75 : 0 : uint256 gen_hash = node.chainman->ActiveChainstate().m_chain[0]->GetBlockHash(); 76 : 0 : node.chainman->ResetChainstates(); 77 : 0 : node.chainman->InitializeChainstate(node.mempool.get()); 78 : 0 : Chainstate& chain = node.chainman->ActiveChainstate(); 79 : 0 : Assert(chain.LoadGenesisBlock()); 80 : : // These cache values will be corrected shortly in `MaybeRebalanceCaches`. 81 : 0 : chain.InitCoinsDB(1 << 20, true, false, ""); 82 : 0 : chain.InitCoinsCache(1 << 20); 83 : 0 : chain.CoinsTip().SetBestBlock(gen_hash); 84 : 0 : chain.setBlockIndexCandidates.insert(node.chainman->m_blockman.LookupBlockIndex(gen_hash)); 85 : 0 : chain.LoadChainTip(); 86 : 0 : 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 : 0 : CBlockIndex *pindex = orig_tip; 93 : 0 : while (pindex && pindex != chain.m_chain.Tip()) { 94 : 0 : pindex->nStatus &= ~BLOCK_HAVE_DATA; 95 : 0 : pindex->nStatus &= ~BLOCK_HAVE_UNDO; 96 : : // We have to set the ASSUMED_VALID flag, because otherwise it 97 : : // would not be possible to have a block index entry without HAVE_DATA 98 : : // and with nTx > 0 (since we aren't setting the pruned flag); 99 : : // see CheckBlockIndex(). 100 : 0 : pindex->nStatus |= BLOCK_ASSUMED_VALID; 101 : 0 : pindex = pindex->pprev; 102 : : } 103 : 0 : } 104 : 0 : BlockValidationState state; 105 : 0 : if (!node.chainman->ActiveChainstate().ActivateBestChain(state)) { 106 : 0 : throw std::runtime_error(strprintf("ActivateBestChain failed. (%s)", state.ToString())); 107 : : } 108 : 0 : Assert( 109 : : 0 == WITH_LOCK(node.chainman->GetMutex(), return node.chainman->ActiveHeight())); 110 : 0 : } 111 : : 112 : 0 : auto& new_active = node.chainman->ActiveChainstate(); 113 : 0 : auto* tip = new_active.m_chain.Tip(); 114 : : 115 : : // Disconnect a block so that the snapshot chainstate will be ahead, otherwise 116 : : // it will refuse to activate. 117 : : // 118 : : // TODO this is a unittest-specific hack, and we should probably rethink how to 119 : : // better generate/activate snapshots in unittests. 120 : 0 : if (tip->pprev) { 121 : 0 : new_active.m_chain.SetTip(*(tip->pprev)); 122 : 0 : } 123 : : 124 : 0 : bool res = node.chainman->ActivateSnapshot(auto_infile, metadata, in_memory_chainstate); 125 : : 126 : : // Restore the old tip. 127 : 0 : new_active.m_chain.SetTip(*tip); 128 : 0 : return res; 129 : 0 : } 130 : : 131 : : 132 : : #endif // BITCOIN_TEST_UTIL_CHAINSTATE_H