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 : : #include <chain.h>
6 : : #include <chainparams.h>
7 : : #include <coins.h>
8 : : #include <consensus/consensus.h>
9 : : #include <consensus/validation.h>
10 : : #include <node/blockstorage.h>
11 : : #include <node/utxo_snapshot.h>
12 : : #include <primitives/block.h>
13 : : #include <primitives/transaction.h>
14 : : #include <serialize.h>
15 : : #include <span.h>
16 : : #include <streams.h>
17 : : #include <sync.h>
18 : : #include <test/fuzz/FuzzedDataProvider.h>
19 : : #include <test/fuzz/fuzz.h>
20 : : #include <test/fuzz/util.h>
21 : : #include <test/util/mining.h>
22 : : #include <test/util/setup_common.h>
23 : : #include <uint256.h>
24 : : #include <util/check.h>
25 : : #include <util/fs.h>
26 : : #include <util/result.h>
27 : : #include <validation.h>
28 : :
29 : : #include <cstdint>
30 : : #include <functional>
31 : : #include <ios>
32 : : #include <memory>
33 : : #include <optional>
34 : : #include <vector>
35 : :
36 : : using node::SnapshotMetadata;
37 : :
38 : : namespace {
39 : :
40 : : const std::vector<std::shared_ptr<CBlock>>* g_chain;
41 : : TestingSetup* g_setup;
42 : :
43 : : template <bool INVALID>
44 : 2 : void initialize_chain()
45 : : {
46 [ + - ]: 2 : const auto params{CreateChainParams(ArgsManager{}, ChainType::REGTEST)};
47 [ + - + - : 4 : static const auto chain{CreateBlockChain(2 * COINBASE_MATURITY, *params)};
+ - ]
48 : 2 : g_chain = &chain;
49 [ + - + - ]: 4 : static const auto setup{
50 : : MakeNoLogFileContext<TestingSetup>(ChainType::REGTEST,
51 : : TestOpts{
52 : : .setup_net = false,
53 : : .setup_validation_interface = false,
54 : : .min_validation_cache = true,
55 : : }),
56 : : };
57 : : if constexpr (INVALID) {
58 : 1 : auto& chainman{*setup->m_node.chainman};
59 [ + + ]: 201 : for (const auto& block : chain) {
60 : 200 : BlockValidationState dummy;
61 [ + - ]: 200 : bool processed{chainman.ProcessNewBlockHeaders({{block->GetBlockHeader()}}, true, dummy)};
62 [ + - ]: 200 : Assert(processed);
63 [ + - + - : 600 : const auto* index{WITH_LOCK(::cs_main, return chainman.m_blockman.LookupBlockIndex(block->GetHash()))};
+ - ]
64 [ + - ]: 200 : Assert(index);
65 : : }
66 : : }
67 : 2 : g_setup = setup.get();
68 [ + - ]: 4 : }
69 : :
70 : : template <bool INVALID>
71 : 898 : void utxo_snapshot_fuzz(FuzzBufferType buffer)
72 : : {
73 : 898 : FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
74 : 898 : auto& setup{*g_setup};
75 : 898 : bool dirty_chainman{false}; // Re-use the global chainman, but reset it when it is dirty
76 : 898 : auto& chainman{*setup.m_node.chainman};
77 : :
78 [ + - ]: 1796 : const auto snapshot_path = gArgs.GetDataDirNet() / "fuzzed_snapshot.dat";
79 : :
80 [ + - + - ]: 898 : Assert(!chainman.SnapshotBlockhash());
81 : :
82 : : {
83 [ + - + - ]: 1796 : AutoFile outfile{fsbridge::fopen(snapshot_path, "wb")};
84 : : // Metadata
85 [ + + ]: 898 : if (fuzzed_data_provider.ConsumeBool()) {
86 [ + - ]: 197 : std::vector<uint8_t> metadata{ConsumeRandomLengthByteVector(fuzzed_data_provider)};
87 [ + - ]: 394 : outfile << Span{metadata};
88 : 197 : } else {
89 : 701 : auto msg_start = chainman.GetParams().MessageStart();
90 : 701 : int base_blockheight{fuzzed_data_provider.ConsumeIntegralInRange<int>(1, 2 * COINBASE_MATURITY)};
91 [ + - + - ]: 701 : uint256 base_blockhash{g_chain->at(base_blockheight - 1)->GetHash()};
92 : 701 : uint64_t m_coins_count{fuzzed_data_provider.ConsumeIntegralInRange<uint64_t>(1, 3 * COINBASE_MATURITY)};
93 [ + - ]: 701 : SnapshotMetadata metadata{msg_start, base_blockhash, m_coins_count};
94 : 701 : outfile << metadata;
95 : 701 : }
96 : : // Coins
97 [ + + ]: 898 : if (fuzzed_data_provider.ConsumeBool()) {
98 [ + - ]: 654 : std::vector<uint8_t> file_data{ConsumeRandomLengthByteVector(fuzzed_data_provider)};
99 [ + - ]: 1308 : outfile << Span{file_data};
100 : 654 : } else {
101 : 244 : int height{0};
102 [ + - + + ]: 49044 : for (const auto& block : *g_chain) {
103 [ + - + - : 97600 : auto coinbase{block->vtx.at(0)};
+ - ]
104 [ + - ]: 48800 : outfile << coinbase->GetHash();
105 [ + - ]: 48800 : WriteCompactSize(outfile, 1); // number of coins for the hash
106 [ + - ]: 48800 : WriteCompactSize(outfile, 0); // index of coin
107 [ + - ]: 97600 : outfile << Coin(coinbase->vout[0], height, /*fCoinBaseIn=*/1);
108 [ + - ]: 48800 : height++;
109 : : }
110 : : }
111 : : if constexpr (INVALID) {
112 : : // Append an invalid coin to ensure invalidity. This error will be
113 : : // detected late in PopulateAndValidateSnapshot, and allows the
114 : : // INVALID fuzz target to reach more potential code coverage.
115 [ + - ]: 342 : const auto& coinbase{g_chain->back()->vtx.back()};
116 [ + - ]: 342 : outfile << coinbase->GetHash();
117 [ + - ]: 342 : WriteCompactSize(outfile, 1); // number of coins for the hash
118 [ + - ]: 342 : WriteCompactSize(outfile, 999); // index of coin
119 [ + - ]: 684 : outfile << Coin{coinbase->vout[0], /*nHeightIn=*/999, /*fCoinBaseIn=*/0};
120 : : }
121 : 0 : }
122 : :
123 : 2694 : const auto ActivateFuzzedSnapshot{[&] {
124 [ + - + - ]: 1796 : AutoFile infile{fsbridge::fopen(snapshot_path, "rb")};
125 [ + - + - ]: 1796 : auto msg_start = chainman.GetParams().MessageStart();
126 [ + - + - ]: 1796 : SnapshotMetadata metadata{msg_start};
127 : : try {
128 : 1412 : infile >> metadata;
129 [ - + - + ]: 384 : } catch (const std::ios_base::failure&) {
130 : : return false;
131 : : }
132 [ + - + - ]: 1412 : return !!chainman.ActivateSnapshot(infile, metadata, /*in_memory=*/true);
133 : 3592 : }};
134 : :
135 [ + + ]: 898 : if (fuzzed_data_provider.ConsumeBool()) {
136 : : // Consume the bool, but skip the code for the INVALID fuzz target
137 : : if constexpr (!INVALID) {
138 [ + + ]: 105123 : for (const auto& block : *g_chain) {
139 : 104600 : BlockValidationState dummy;
140 [ + - ]: 104600 : bool processed{chainman.ProcessNewBlockHeaders({{block->GetBlockHeader()}}, true, dummy)};
141 [ + - ]: 104600 : Assert(processed);
142 [ + - + - : 313800 : const auto* index{WITH_LOCK(::cs_main, return chainman.m_blockman.LookupBlockIndex(block->GetHash()))};
+ - ]
143 [ + - ]: 104600 : Assert(index);
144 : : }
145 : : dirty_chainman = true;
146 : : }
147 : : }
148 : :
149 [ + - + + ]: 898 : if (ActivateFuzzedSnapshot()) {
150 [ + - ]: 53 : LOCK(::cs_main);
151 [ + - + - ]: 53 : Assert(!chainman.ActiveChainstate().m_from_snapshot_blockhash->IsNull());
152 [ + - + - : 53 : Assert(*chainman.ActiveChainstate().m_from_snapshot_blockhash ==
+ - ]
153 : : *chainman.SnapshotBlockhash());
154 [ + - + - ]: 53 : const auto& coinscache{chainman.ActiveChainstate().CoinsTip()};
155 [ + + ]: 10653 : for (const auto& block : *g_chain) {
156 [ + - + - : 10600 : Assert(coinscache.HaveCoin(COutPoint{block->vtx.at(0)->GetHash(), 0}));
+ - ]
157 [ + - + - ]: 10600 : const auto* index{chainman.m_blockman.LookupBlockIndex(block->GetHash())};
158 [ + - ]: 10600 : Assert(index);
159 [ + - ]: 10600 : Assert(index->nTx == 0);
160 [ + - + - ]: 10653 : if (index->nHeight == chainman.GetSnapshotBaseHeight()) {
161 [ + - ]: 53 : auto params{chainman.GetParams().AssumeutxoForHeight(index->nHeight)};
162 [ + - ]: 53 : Assert(params.has_value());
163 [ + - ]: 53 : Assert(params.value().m_chain_tx_count == index->m_chain_tx_count);
164 : : } else {
165 [ + - ]: 10547 : Assert(index->m_chain_tx_count == 0);
166 : : }
167 : : }
168 [ + - + - ]: 53 : Assert(g_chain->size() == coinscache.GetCacheSize());
169 [ + - ]: 53 : dirty_chainman = true;
170 : 53 : } else {
171 [ + - + - ]: 845 : Assert(!chainman.SnapshotBlockhash());
172 [ + - + - ]: 845 : Assert(!chainman.ActiveChainstate().m_from_snapshot_blockhash);
173 : : }
174 : : // Snapshot should refuse to load a second time regardless of validity
175 [ + - + - ]: 898 : Assert(!ActivateFuzzedSnapshot());
176 : : if constexpr (INVALID) {
177 : : // Activating the snapshot, or any other action that makes the chainman
178 : : // "dirty" can and must not happen for the INVALID fuzz target
179 [ + - ]: 342 : Assert(!dirty_chainman);
180 : : }
181 [ + + ]: 898 : if (dirty_chainman) {
182 [ + - ]: 523 : setup.m_node.chainman.reset();
183 [ + - ]: 523 : setup.m_make_chainman();
184 [ + - ]: 523 : setup.LoadVerifyActivateChainstate();
185 : : }
186 : 898 : }
187 : :
188 : : // There are two fuzz targets:
189 : : //
190 : : // The target 'utxo_snapshot', which allows valid snapshots, but is slow,
191 : : // because it has to reset the chainstate manager on almost all fuzz inputs.
192 : : // Otherwise, a dirty header tree or dirty chainstate could leak from one fuzz
193 : : // input execution into the next, which makes execution non-deterministic.
194 : : //
195 : : // The target 'utxo_snapshot_invalid', which is fast and does not require any
196 : : // expensive state to be reset.
197 [ + - ]: 968 : FUZZ_TARGET(utxo_snapshot /*valid*/, .init = initialize_chain<false>) { utxo_snapshot_fuzz<false>(buffer); }
198 [ + - ]: 754 : FUZZ_TARGET(utxo_snapshot_invalid, .init = initialize_chain<true>) { utxo_snapshot_fuzz<true>(buffer); }
199 : :
200 : : } // namespace
|