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