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 : : 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 : 33 : CreateAndActivateUTXOSnapshot(
34 : : TestingSetup* fixture,
35 : : F malleation = NoMalleation,
36 : : bool reset_chainstate = false,
37 : : bool in_memory_chainstate = false)
38 : : {
39 : 33 : node::NodeContext& node = fixture->m_node;
40 : 33 : fs::path root = fixture->m_path_root;
41 : :
42 : : // Write out a snapshot to the test's tempdir.
43 : : //
44 : : int height;
45 [ + - + - : 99 : WITH_LOCK(::cs_main, height = node.chainman->ActiveHeight());
+ - ]
[ + - + - ]
46 [ + - + - : 165 : fs::path snapshot_path = root / fs::u8path(tfm::format("test_snapshot.%d.dat", height));
+ - ]
47 [ + - ]: 33 : FILE* outfile{fsbridge::fopen(snapshot_path, "wb")};
48 [ + - ]: 33 : AutoFile auto_outfile{outfile};
49 : :
50 [ + - + - ]: 33 : UniValue result = CreateUTXOSnapshot(node,
51 : : node.chainman->ActiveChainstate(),
52 : : std::move(auto_outfile), // Will close auto_outfile.
53 : : snapshot_path,
54 : : snapshot_path);
55 [ + - + - : 99 : 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 [ + - ]: 33 : FILE* infile{fsbridge::fopen(snapshot_path, "rb")};
61 [ + - ]: 33 : AutoFile auto_infile{infile};
62 [ + - ]: 33 : node::SnapshotMetadata metadata{node.chainman->GetParams().MessageStart()};
63 [ - + ]: 33 : auto_infile >> metadata;
64 : :
65 [ + - ]: 20 : malleation(auto_infile, metadata);
66 : :
67 [ + + ]: 33 : 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 [ + - ]: 1 : LOCK(::cs_main);
77 [ + - + - ]: 1 : CBlockIndex *orig_tip = node.chainman->ActiveChainstate().m_chain.Tip();
78 [ + - + - ]: 2 : uint256 gen_hash = node.chainman->ActiveChainstate().m_chain[0]->GetBlockHash();
79 [ + - ]: 1 : node.chainman->ResetChainstates();
80 [ + - ]: 1 : node.chainman->InitializeChainstate(node.mempool.get());
81 [ + - ]: 1 : Chainstate& chain = node.chainman->ActiveChainstate();
82 [ + - + - ]: 1 : Assert(chain.LoadGenesisBlock());
83 : : // These cache values will be corrected shortly in `MaybeRebalanceCaches`.
84 [ + - ]: 1 : chain.InitCoinsDB(1 << 20, true, false, "");
85 [ + - ]: 1 : chain.InitCoinsCache(1 << 20);
86 [ + - + - ]: 1 : chain.CoinsTip().SetBestBlock(gen_hash);
87 [ + - + - ]: 1 : chain.setBlockIndexCandidates.insert(node.chainman->m_blockman.LookupBlockIndex(gen_hash));
88 [ + - ]: 1 : chain.LoadChainTip();
89 [ + - ]: 1 : 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 [ + - + - : 222 : 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 [ - + ]: 110 : assert(pindex->IsValid(BlockStatus::BLOCK_VALID_TREE));
103 : 110 : pindex->nStatus = BlockStatus::BLOCK_VALID_TREE;
104 : 110 : pindex->nTx = 0;
105 : 110 : pindex->m_chain_tx_count = 0;
106 : 110 : pindex->nSequenceId = 0;
107 : 110 : pindex = pindex->pprev;
108 : : }
109 [ + - ]: 1 : }
110 : 1 : BlockValidationState state;
111 [ + - + - : 1 : if (!node.chainman->ActiveChainstate().ActivateBestChain(state)) {
- + - + ]
112 [ # # # # : 0 : throw std::runtime_error(strprintf("ActivateBestChain failed. (%s)", state.ToString()));
# # ]
113 : : }
114 [ + - + - : 3 : Assert(
+ - + - ]
[ # # # # ]
115 : : 0 == WITH_LOCK(node.chainman->GetMutex(), return node.chainman->ActiveHeight()));
116 : 1 : }
117 : :
118 [ + - ]: 33 : auto& new_active = node.chainman->ActiveChainstate();
119 [ + - ]: 33 : 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 [ + + ]: 33 : if (tip->pprev) {
127 [ + - ]: 32 : new_active.m_chain.SetTip(*(tip->pprev));
128 : : }
129 : :
130 [ + - ]: 33 : auto res = node.chainman->ActivateSnapshot(auto_infile, metadata, in_memory_chainstate);
131 : :
132 : : // Restore the old tip.
133 [ + - ]: 33 : new_active.m_chain.SetTip(*tip);
134 : 33 : return !!res;
135 : 99 : }
136 : :
137 : :
138 : : #endif // BITCOIN_TEST_UTIL_CHAINSTATE_H
|