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 : 0 : void initialize_chain()
45 : : {
46 [ # # # # ]: 0 : const auto params{CreateChainParams(ArgsManager{}, ChainType::REGTEST)};
47 [ # # # # : 0 : static const auto chain{CreateBlockChain(2 * COINBASE_MATURITY, *params)};
# # # # #
# # # ]
48 : 0 : g_chain = &chain;
49 [ # # # # : 0 : static const auto setup{
# # # # ]
50 [ # # # # ]: 0 : MakeNoLogFileContext<TestingSetup>(ChainType::REGTEST,
51 : 0 : TestOpts{
52 : : .setup_net = false,
53 : : .setup_validation_interface = false,
54 : : .min_validation_cache = true,
55 : : }),
56 : : };
57 : : if constexpr (INVALID) {
58 : 0 : auto& chainman{*setup->m_node.chainman};
59 [ # # ]: 0 : for (const auto& block : chain) {
60 : 0 : BlockValidationState dummy;
61 [ # # # # ]: 0 : bool processed{chainman.ProcessNewBlockHeaders({*block}, true, dummy)};
62 [ # # ]: 0 : Assert(processed);
63 [ # # # # : 0 : const auto* index{WITH_LOCK(::cs_main, return chainman.m_blockman.LookupBlockIndex(block->GetHash()))};
# # ]
64 [ # # ]: 0 : Assert(index);
65 : 0 : }
66 : 0 : }
67 : : g_setup = setup.get();
68 : 0 : }
69 : :
70 : : template <bool INVALID>
71 : 1431 : void utxo_snapshot_fuzz(FuzzBufferType buffer)
72 : : {
73 : 1431 : FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
74 : 1431 : auto& setup{*g_setup};
75 : 1431 : bool dirty_chainman{false}; // Re-use the global chainman, but reset it when it is dirty
76 : 1431 : auto& chainman{*setup.m_node.chainman};
77 : :
78 [ + - + - ]: 1431 : const auto snapshot_path = gArgs.GetDataDirNet() / "fuzzed_snapshot.dat";
79 : :
80 [ + - + - : 1431 : Assert(!chainman.SnapshotBlockhash());
+ - + - ]
81 : :
82 : : {
83 [ + - + - : 1431 : AutoFile outfile{fsbridge::fopen(snapshot_path, "wb")};
+ - + - ]
84 : : // Metadata
85 [ + - + + : 1431 : if (fuzzed_data_provider.ConsumeBool()) {
+ - + + ]
86 : 938 : std::vector<uint8_t> metadata{ConsumeRandomLengthByteVector(fuzzed_data_provider)};
87 [ + - + - : 938 : outfile << Span{metadata};
+ - + - ]
88 : 938 : } else {
89 [ + - + - ]: 493 : auto msg_start = chainman.GetParams().MessageStart();
90 [ + - ]: 493 : int base_blockheight{fuzzed_data_provider.ConsumeIntegralInRange<int>(1, 2 * COINBASE_MATURITY)};
91 [ + - + - : 493 : uint256 base_blockhash{g_chain->at(base_blockheight - 1)->GetHash()};
+ - + - ]
92 [ + - ]: 493 : uint64_t m_coins_count{fuzzed_data_provider.ConsumeIntegralInRange<uint64_t>(1, 3 * COINBASE_MATURITY)};
93 [ + - + - ]: 493 : SnapshotMetadata metadata{msg_start, base_blockhash, m_coins_count};
94 [ + - + - ]: 493 : outfile << metadata;
95 : 493 : }
96 : : // Coins
97 [ + - + + : 1431 : if (fuzzed_data_provider.ConsumeBool()) {
+ - + + ]
98 : 704 : std::vector<uint8_t> file_data{ConsumeRandomLengthByteVector(fuzzed_data_provider)};
99 [ + - + - : 704 : outfile << Span{file_data};
+ - + - ]
100 : 704 : } else {
101 : 727 : int height{0};
102 [ + + + + ]: 146127 : for (const auto& block : *g_chain) {
103 [ + - + - ]: 145400 : auto coinbase{block->vtx.at(0)};
104 [ + - + - : 145400 : outfile << coinbase->GetHash();
+ - ]
105 [ + - + - ]: 145400 : WriteCompactSize(outfile, 1); // number of coins for the hash
106 [ + - + - ]: 145400 : WriteCompactSize(outfile, 0); // index of coin
107 [ - + + - : 145400 : outfile << Coin(coinbase->vout[0], height, /*fCoinBaseIn=*/1);
- + + - ]
108 : 145400 : height++;
109 : 145400 : }
110 : 727 : }
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 : 117 : const auto& coinbase{g_chain->back()->vtx.back()};
116 [ + - ]: 117 : outfile << coinbase->GetHash();
117 [ + - ]: 117 : WriteCompactSize(outfile, 1); // number of coins for the hash
118 [ + - ]: 117 : WriteCompactSize(outfile, 999); // index of coin
119 [ + - + - ]: 117 : outfile << Coin{coinbase->vout[0], /*nHeightIn=*/999, /*fCoinBaseIn=*/0};
120 : 117 : }
121 : 1431 : }
122 : :
123 : 4293 : const auto ActivateFuzzedSnapshot{[&] {
124 [ + - + - ]: 2862 : AutoFile infile{fsbridge::fopen(snapshot_path, "rb")};
125 : 2862 : auto msg_start = chainman.GetParams().MessageStart();
126 [ + - + - ]: 2862 : SnapshotMetadata metadata{msg_start};
127 : : try {
128 [ + + + + ]: 2862 : infile >> metadata;
129 [ - + - + ]: 2862 : } catch (const std::ios_base::failure&) {
130 : 1876 : return false;
131 [ + - + - ]: 1876 : }
132 [ + - + - ]: 986 : return !!chainman.ActivateSnapshot(infile, metadata, /*in_memory=*/true);
133 : 4738 : }};
134 : :
135 [ + - + + : 1431 : if (fuzzed_data_provider.ConsumeBool()) {
+ - + + ]
136 : : // Consume the bool, but skip the code for the INVALID fuzz target
137 : : if constexpr (!INVALID) {
138 [ + + ]: 88641 : for (const auto& block : *g_chain) {
139 : 88200 : BlockValidationState dummy;
140 [ + - + - ]: 88200 : bool processed{chainman.ProcessNewBlockHeaders({*block}, true, dummy)};
141 [ - + ]: 88200 : Assert(processed);
142 [ + - + - : 176400 : const auto* index{WITH_LOCK(::cs_main, return chainman.m_blockman.LookupBlockIndex(block->GetHash()))};
+ - + - ]
143 [ + - ]: 88200 : Assert(index);
144 : 88200 : }
145 : 441 : dirty_chainman = true;
146 : : }
147 : 495 : }
148 : :
149 [ + - + + : 1431 : if (ActivateFuzzedSnapshot()) {
+ - - + ]
150 [ + - + - : 39 : LOCK(::cs_main);
# # ]
151 [ + - + - : 39 : Assert(!chainman.ActiveChainstate().m_from_snapshot_blockhash->IsNull());
+ - # # #
# # # ]
152 [ + - + - : 39 : Assert(*chainman.ActiveChainstate().m_from_snapshot_blockhash ==
+ - + - #
# # # # #
# # ]
153 : : *chainman.SnapshotBlockhash());
154 [ + - + - : 39 : const auto& coinscache{chainman.ActiveChainstate().CoinsTip()};
# # # # ]
155 [ + + # # ]: 7839 : for (const auto& block : *g_chain) {
156 [ + - + - : 7800 : Assert(coinscache.HaveCoin(COutPoint{block->vtx.at(0)->GetHash(), 0}));
+ - + - +
- # # # #
# # # # ]
157 [ + - + - : 7800 : const auto* index{chainman.m_blockman.LookupBlockIndex(block->GetHash())};
# # # # ]
158 [ - + # # ]: 7800 : Assert(index);
159 [ - + # # ]: 7800 : Assert(index->nTx == 0);
160 [ + - + - : 7800 : if (index->nHeight == chainman.GetSnapshotBaseHeight()) {
+ + # # #
# ]
161 [ + - + - : 39 : auto params{chainman.GetParams().AssumeutxoForHeight(index->nHeight)};
# # ]
162 [ - + # # ]: 39 : Assert(params.has_value());
163 [ + - + - : 39 : Assert(params.value().m_chain_tx_count == index->m_chain_tx_count);
# # # # ]
164 : 39 : } else {
165 [ + - # # ]: 7761 : Assert(index->m_chain_tx_count == 0);
166 : : }
167 : 7800 : }
168 [ + - + - : 39 : Assert(g_chain->size() == coinscache.GetCacheSize());
# # # # ]
169 : 39 : dirty_chainman = true;
170 : 39 : } else {
171 [ + - + - : 1392 : Assert(!chainman.SnapshotBlockhash());
+ - + - ]
172 [ + - + - : 1392 : Assert(!chainman.ActiveChainstate().m_from_snapshot_blockhash);
+ - + - ]
173 : : }
174 : : // Snapshot should refuse to load a second time regardless of validity
175 [ + - + - : 1431 : 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 [ + - ]: 117 : Assert(!dirty_chainman);
180 : : }
181 [ + + - + ]: 1431 : if (dirty_chainman) {
182 : 441 : setup.m_node.chainman.reset();
183 [ + - # # ]: 441 : setup.m_make_chainman();
184 [ + - # # ]: 441 : setup.LoadVerifyActivateChainstate();
185 : 441 : }
186 : 1431 : }
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 [ + - ]: 1316 : FUZZ_TARGET(utxo_snapshot /*valid*/, .init = initialize_chain<false>) { utxo_snapshot_fuzz<false>(buffer); }
198 [ + - ]: 119 : FUZZ_TARGET(utxo_snapshot_invalid, .init = initialize_chain<true>) { utxo_snapshot_fuzz<true>(buffer); }
199 : :
200 : : } // namespace
|