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 <kernel/coinstats.h>
11 : : #include <node/block_template_manager.h>
12 : : #include <node/blockstorage.h>
13 : : #include <node/utxo_snapshot.h>
14 : : #include <primitives/block.h>
15 : : #include <primitives/transaction.h>
16 : : #include <serialize.h>
17 : : #include <span.h>
18 : : #include <streams.h>
19 : : #include <sync.h>
20 : : #include <test/fuzz/FuzzedDataProvider.h>
21 : : #include <test/fuzz/fuzz.h>
22 : : #include <test/fuzz/util.h>
23 : : #include <test/fuzz/util/reachability.h>
24 : : #include <test/util/mining.h>
25 : : #include <test/util/setup_common.h>
26 : : #include <test/util/time.h>
27 : : #include <uint256.h>
28 : : #include <util/check.h>
29 : : #include <util/fs.h>
30 : : #include <util/result.h>
31 : : #include <util/time.h>
32 : : #include <validation.h>
33 : :
34 : : #include <cstdint>
35 : : #include <functional>
36 : : #include <ios>
37 : : #include <memory>
38 : : #include <optional>
39 : : #include <vector>
40 : :
41 : : using node::SnapshotMetadata;
42 : :
43 : : namespace {
44 : :
45 : : const std::vector<std::shared_ptr<CBlock>>* g_chain;
46 : : TestingSetup* g_setup{nullptr};
47 : : constexpr ReachabilityGoal UTXO_SNAPSHOT_ACTIVATION_SUCCEEDS{"UTXO snapshot activation succeeds"};
48 : :
49 : : /** Sanity check the assumeutxo values hardcoded in chainparams for the fuzz target. */
50 : 2 : void sanity_check_snapshot()
51 : : {
52 [ + - - + ]: 2 : Assert(g_chain && g_setup == nullptr);
53 : :
54 : : // Create a temporary chainstate manager to connect the chain to.
55 [ + - ]: 2 : const auto tmp_setup{MakeNoLogFileContext<TestingSetup>(ChainType::REGTEST, TestOpts{.setup_net = false})};
56 : 2 : const auto& node{tmp_setup->m_node};
57 [ + + ]: 402 : for (auto& block: *g_chain) {
58 [ + - ]: 400 : ProcessBlock(node, block);
59 : : }
60 : :
61 : : // Connect the chain to the tmp chainman and sanity check the chainparams snapshot values.
62 [ + - ]: 2 : LOCK(cs_main);
63 [ + - ]: 2 : auto& cs{node.chainman->ActiveChainstate()};
64 [ + - ]: 2 : cs.ForceFlushStateToDisk(/*wipe_cache=*/false);
65 [ + - + - : 2 : const auto stats{*Assert(kernel::ComputeUTXOStats(kernel::CoinStatsHashType::HASH_SERIALIZED, cs.CoinsDB(), node.chainman->m_blockman))};
+ - ]
66 [ + - + - ]: 2 : const auto cp_au_data{*Assert(node.chainman->GetParams().AssumeutxoForHeight(2 * COINBASE_MATURITY))};
67 [ + - ]: 2 : Assert(stats.nHeight == cp_au_data.height);
68 [ + - ]: 2 : Assert(stats.nTransactions + 1 == cp_au_data.m_chain_tx_count); // +1 for the genesis tx.
69 [ - + + - ]: 2 : Assert(stats.hashBlock == cp_au_data.blockhash);
70 [ + - ]: 2 : Assert(AssumeutxoHash{stats.hashSerialized} == cp_au_data.hash_serialized);
71 : 2 : }
72 : :
73 : : template <bool INVALID>
74 : 2 : void initialize_chain()
75 : : {
76 : : if constexpr (!INVALID) {
77 : 1 : RegisterReachabilityGoal(UTXO_SNAPSHOT_ACTIVATION_SUCCEEDS);
78 : : }
79 [ + - ][ + - ]: 2 : const auto params{CreateChainParams(ArgsManager{}, ChainType::REGTEST)};
80 [ + - + - : 4 : static const auto chain{CreateBlockChain(2 * COINBASE_MATURITY, *params)};
+ - ][ + -
+ - + - ]
81 : 2 : g_chain = &chain;
82 [ + - ][ + - ]: 2 : FakeNodeClock node_clock{chain.back()->Time()};
83 : :
84 : : // Make sure we can generate a valid snapshot.
85 [ + - ][ + - ]: 2 : sanity_check_snapshot();
86 : :
87 [ + - + - : 4 : static const auto setup{
+ - ][ + -
+ - + - ]
88 : : MakeNoLogFileContext<TestingSetup>(ChainType::REGTEST,
89 : : TestOpts{
90 : : .setup_net = false,
91 : : .setup_validation_interface = false,
92 : : .min_validation_cache = true,
93 : : }),
94 : : };
95 : : if constexpr (INVALID) {
96 : 1 : auto& chainman{*setup->m_node.chainman};
97 [ + - + + ]: 201 : for (const auto& block : chain) {
98 [ + - ]: 200 : BlockValidationState dummy;
99 [ + - ]: 200 : bool processed{chainman.ProcessNewBlockHeaders({{*block}}, true, dummy)};
100 [ + - ]: 200 : Assert(processed);
101 [ + - ][ + - : 600 : const auto* index{WITH_LOCK(::cs_main, return chainman.m_blockman.LookupBlockIndex(block->GetHash()))};
+ - + - ]
102 [ + - ]: 200 : Assert(index);
103 : : }
104 : : }
105 : 2 : g_setup = setup.get();
106 : 2 : }
107 : :
108 : : template <bool INVALID>
109 : 1584 : void utxo_snapshot_fuzz(FuzzBufferType buffer)
110 : : {
111 : 1584 : SeedRandomStateForTest(SeedRand::ZEROS);
112 : 1584 : FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
113 : 1584 : FakeNodeClock node_clock{ConsumeTime(fuzzed_data_provider, /*min=*/1296688602)}; // regtest genesis block timestamp
114 : 1584 : auto& setup{*g_setup};
115 : 1584 : bool dirty_chainman{false}; // Reuse the global chainman, but reset it when it is dirty
116 [ + - ][ + - ]: 1584 : auto& chainman{*setup.m_node.chainman};
117 : :
118 [ + - ][ + - ]: 1584 : const auto snapshot_path = gArgs.GetDataDirNet() / "fuzzed_snapshot.dat";
119 : :
120 [ + - + - ]: 1584 : Assert(!chainman.ActiveChainstate().m_from_snapshot_blockhash);
[ + - + - ]
121 : :
122 : : {
123 [ + - + - ]: 3168 : AutoFile outfile{fsbridge::fopen(snapshot_path, "wb")};
[ + - + - ]
124 : : // Metadata
125 [ + + ]: 1584 : if (fuzzed_data_provider.ConsumeBool()) {
126 [ - + ]: 305 : std::vector<uint8_t> metadata{ConsumeRandomLengthByteVector(fuzzed_data_provider)};
127 [ + - ][ + - ]: 610 : outfile << std::span{metadata};
128 : 305 : } else {
129 : 1279 : auto msg_start = chainman.GetParams().MessageStart();
130 : 1279 : int base_blockheight{fuzzed_data_provider.ConsumeIntegralInRange<int>(1, 2 * COINBASE_MATURITY)};
131 [ + - + - ]: 1279 : uint256 base_blockhash{g_chain->at(base_blockheight - 1)->GetHash()};
132 : 1279 : uint64_t m_coins_count{fuzzed_data_provider.ConsumeIntegralInRange<uint64_t>(1, 3 * COINBASE_MATURITY)};
133 [ + - ]: 1279 : SnapshotMetadata metadata{msg_start, base_blockhash, m_coins_count};
134 : 1279 : outfile << metadata;
135 : 1279 : }
136 : : // Coins
137 [ + + ]: 1584 : if (fuzzed_data_provider.ConsumeBool()) {
138 [ - + ]: 1213 : std::vector<uint8_t> file_data{ConsumeRandomLengthByteVector(fuzzed_data_provider)};
139 [ + - ][ + - ]: 2426 : outfile << std::span{file_data};
140 : 1213 : } else {
141 : 371 : int height{1};
142 [ + - + + ]: 74571 : for (const auto& block : *g_chain) {
143 [ + - + - : 148400 : auto coinbase{block->vtx.at(0)};
+ - ][ + -
+ - + - ]
144 [ + - ][ + - ]: 74200 : outfile << coinbase->GetHash();
145 [ + - ][ + - ]: 74200 : WriteCompactSize(outfile, 1); // number of coins for the hash
146 [ + - ][ + - ]: 74200 : WriteCompactSize(outfile, 0); // index of coin
147 [ + - ][ + - ]: 148400 : outfile << Coin(coinbase->vout[0], height, /*fCoinBaseIn=*/true);
148 [ + - ]: 74200 : height++;
149 : : }
150 : : }
151 : : if constexpr (INVALID) {
152 : : // Append an invalid coin to ensure invalidity. This error will be
153 : : // detected late in PopulateAndValidateSnapshot, and allows the
154 : : // INVALID fuzz target to reach more potential code coverage.
155 [ + - ]: 917 : const auto& coinbase{g_chain->back()->vtx.back()};
156 [ + - ]: 917 : outfile << coinbase->GetHash();
157 [ + - ]: 917 : WriteCompactSize(outfile, 1); // number of coins for the hash
158 [ + - ]: 917 : WriteCompactSize(outfile, 999); // index of coin
159 [ + - + - ]: 1834 : outfile << Coin{coinbase->vout[0], /*nHeightIn=*/999, /*fCoinBaseIn=*/false};
160 : : }
161 [ - + ][ - + ]: 1584 : assert(outfile.fclose() == 0);
162 : 1584 : }
163 : :
164 : 4752 : const auto ActivateFuzzedSnapshot{[&] {
165 : 6336 : AutoFile infile{fsbridge::fopen(snapshot_path, "rb")};
166 [ + - + - ]: 3168 : auto msg_start = chainman.GetParams().MessageStart();
167 [ + - + - ]: 3168 : SnapshotMetadata metadata{msg_start};
168 : : try {
169 : 2916 : infile >> metadata;
170 [ - + - + ]: 252 : } catch (const std::ios_base::failure&) {
171 : : return false;
172 : : }
173 [ + - + - ]: 2916 : return !!chainman.ActivateSnapshot(infile, metadata, /*in_memory=*/true);
174 : 3168 : }};
175 : :
176 [ + + ]: 1584 : if (fuzzed_data_provider.ConsumeBool()) {
177 : : // Consume the bool, but skip the code for the INVALID fuzz target
178 : : if constexpr (!INVALID) {
179 [ + - + + ]: 126429 : for (const auto& block : *g_chain) {
180 [ + - ]: 125800 : BlockValidationState dummy;
181 [ + - ]: 125800 : bool processed{chainman.ProcessNewBlockHeaders({{*block}}, true, dummy)};
182 [ + - ]: 125800 : Assert(processed);
183 [ + - ][ + - : 377400 : const auto* index{WITH_LOCK(::cs_main, return chainman.m_blockman.LookupBlockIndex(block->GetHash()))};
+ - + - ]
184 [ + - ]: 125800 : Assert(index);
185 : : }
186 : : dirty_chainman = true;
187 : : }
188 : : }
189 : :
190 [ + - ][ + - ]: 1584 : const bool snapshot_activated{ActivateFuzzedSnapshot()};
191 [ - + ][ + + ]: 1584 : if (snapshot_activated) {
192 [ # # ][ + - ]: 52 : LOCK(::cs_main);
193 [ # # # # ]: 104 : Assert(!chainman.ActiveChainstate().m_from_snapshot_blockhash->IsNull());
[ + - + - ]
194 [ # # # # ]: 52 : const auto& coinscache{chainman.ActiveChainstate().CoinsTip()};
[ + - + - ]
195 [ # # ][ + + ]: 10452 : for (const auto& block : *g_chain) {
196 [ # # # # : 10400 : Assert(coinscache.HaveCoin(COutPoint{block->vtx.at(0)->GetHash(), 0}));
# # ][ + -
+ - + - ]
197 [ # # # # ]: 10400 : const auto* index{chainman.m_blockman.LookupBlockIndex(block->GetHash())};
[ + - + - ]
198 [ # # ][ + - ]: 10400 : Assert(index);
199 [ # # ][ + - ]: 10400 : Assert(index->nTx == 0);
200 [ # # # # : 10400 : if (index->nHeight == chainman.ActiveChainstate().SnapshotBase()->nHeight) {
# # ][ + -
+ - + + ]
201 : 52 : auto params{chainman.GetParams().AssumeutxoForHeight(index->nHeight)};
202 [ # # ][ + - ]: 52 : Assert(params.has_value());
203 [ # # # # ]: 52 : Assert(params.value().m_chain_tx_count == index->m_chain_tx_count);
[ + - + - ]
204 : : } else {
205 [ # # ][ + - ]: 10348 : Assert(index->m_chain_tx_count == 0);
206 : : }
207 : : }
208 [ # # # # : 52 : Assert(g_chain->size() == coinscache.GetCacheSize());
# # ][ - +
+ - + - ]
209 [ # # ][ + - ]: 52 : dirty_chainman = true;
210 : 52 : } else {
211 [ + - + - ]: 1532 : Assert(!chainman.ActiveChainstate().m_from_snapshot_blockhash);
[ + - + - ]
212 : : }
213 : : // Snapshot should refuse to load a second time regardless of validity
214 [ + - + - ]: 1584 : Assert(!ActivateFuzzedSnapshot());
[ + - + - ]
215 : : if constexpr (INVALID) {
216 : : // Activating the snapshot, or any other action that makes the chainman
217 : : // "dirty" can and must not happen for the INVALID fuzz target
218 [ + - ]: 917 : Assert(!dirty_chainman);
219 : : }
220 [ - + ][ + + ]: 1584 : if (dirty_chainman) {
221 [ # # ][ + - ]: 629 : setup.m_node.block_template_manager.reset();
222 [ # # ][ + - ]: 629 : setup.m_node.chainman.reset();
223 [ # # ][ + - ]: 629 : setup.m_make_chainman();
224 [ # # ][ + - ]: 629 : setup.LoadVerifyActivateChainstate();
225 [ # # ][ + - ]: 629 : setup.CreateBlockTemplateManager();
226 : : }
227 : : if constexpr (!INVALID) {
228 [ + - ]: 667 : ObserveReachabilityGoal(snapshot_activated, UTXO_SNAPSHOT_ACTIVATION_SUCCEEDS);
229 : : }
230 : 1584 : }
231 : :
232 : : // There are two fuzz targets:
233 : : //
234 : : // The target 'utxo_snapshot', which allows valid snapshots, but is slow,
235 : : // because it has to reset the chainstate manager on almost all fuzz inputs.
236 : : // Otherwise, a dirty header tree or dirty chainstate could leak from one fuzz
237 : : // input execution into the next, which makes execution non-deterministic.
238 : : //
239 : : // The target 'utxo_snapshot_invalid', which is fast and does not require any
240 : : // expensive state to be reset.
241 [ + - ]: 1143 : FUZZ_TARGET(utxo_snapshot /*valid*/, .init = initialize_chain<false>) { utxo_snapshot_fuzz<false>(buffer); }
242 [ + - ]: 1393 : FUZZ_TARGET(utxo_snapshot_invalid, .init = initialize_chain<true>) { utxo_snapshot_fuzz<true>(buffer); }
243 : :
244 : : } // namespace
|