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