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