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(
51 : : node, node.chainman->ActiveChainstate(), auto_outfile, snapshot_path, snapshot_path);
52 [ + - + - : 99 : 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 [ + - ]: 33 : FILE* infile{fsbridge::fopen(snapshot_path, "rb")};
58 [ + - ]: 33 : AutoFile auto_infile{infile};
59 [ + - ]: 33 : node::SnapshotMetadata metadata{node.chainman->GetParams().MessageStart()};
60 [ - + ]: 33 : auto_infile >> metadata;
61 : :
62 [ + - ]: 20 : malleation(auto_infile, metadata);
63 : :
64 [ + + ]: 33 : 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 [ + - ]: 1 : LOCK(::cs_main);
74 [ + - + - ]: 1 : CBlockIndex *orig_tip = node.chainman->ActiveChainstate().m_chain.Tip();
75 [ + - + - ]: 2 : uint256 gen_hash = node.chainman->ActiveChainstate().m_chain[0]->GetBlockHash();
76 [ + - ]: 1 : node.chainman->ResetChainstates();
77 [ + - ]: 1 : node.chainman->InitializeChainstate(node.mempool.get());
78 [ + - ]: 1 : Chainstate& chain = node.chainman->ActiveChainstate();
79 [ + - + - ]: 1 : Assert(chain.LoadGenesisBlock());
80 : : // These cache values will be corrected shortly in `MaybeRebalanceCaches`.
81 [ + - ]: 1 : chain.InitCoinsDB(1 << 20, true, false, "");
82 [ + - ]: 1 : chain.InitCoinsCache(1 << 20);
83 [ + - + - ]: 1 : chain.CoinsTip().SetBestBlock(gen_hash);
84 [ + - + - ]: 1 : chain.setBlockIndexCandidates.insert(node.chainman->m_blockman.LookupBlockIndex(gen_hash));
85 [ + - ]: 1 : chain.LoadChainTip();
86 [ + - ]: 1 : 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 [ + - + - : 222 : 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 [ - + ]: 110 : assert(pindex->IsValid(BlockStatus::BLOCK_VALID_TREE));
100 : 110 : pindex->nStatus = BlockStatus::BLOCK_VALID_TREE;
101 : 110 : pindex->nTx = 0;
102 : 110 : pindex->m_chain_tx_count = 0;
103 : 110 : pindex->nSequenceId = 0;
104 : 110 : pindex = pindex->pprev;
105 : : }
106 [ + - ]: 1 : }
107 : 1 : BlockValidationState state;
108 [ + - + - : 1 : if (!node.chainman->ActiveChainstate().ActivateBestChain(state)) {
- + - + ]
109 [ # # # # : 0 : throw std::runtime_error(strprintf("ActivateBestChain failed. (%s)", state.ToString()));
# # ]
110 : : }
111 [ # # # # ]: 3 : Assert(
[ + - + -
+ - + - ]
112 : : 0 == WITH_LOCK(node.chainman->GetMutex(), return node.chainman->ActiveHeight()));
113 : 1 : }
114 : :
115 [ + - ]: 33 : auto& new_active = node.chainman->ActiveChainstate();
116 [ + - ]: 33 : 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 [ + + ]: 33 : if (tip->pprev) {
124 [ + - ]: 32 : new_active.m_chain.SetTip(*(tip->pprev));
125 : : }
126 : :
127 [ + - ]: 33 : auto res = node.chainman->ActivateSnapshot(auto_infile, metadata, in_memory_chainstate);
128 : :
129 : : // Restore the old tip.
130 [ + - ]: 33 : new_active.m_chain.SetTip(*tip);
131 : 33 : return !!res;
132 : 165 : }
133 : :
134 : :
135 : : #endif // BITCOIN_TEST_UTIL_CHAINSTATE_H
|