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