1- // Copyright (c) 2021-2022 The Bitcoin Core developers
1+ // Copyright (c) 2021-present The Bitcoin Core developers
22// Distributed under the MIT software license, see the accompanying
33// file COPYING or http://www.opensource.org/licenses/mit-license.php.
44
5+ #include < chain.h>
56#include < chainparams.h>
7+ #include < coins.h>
8+ #include < consensus/consensus.h>
69#include < consensus/validation.h>
10+ #include < node/blockstorage.h>
711#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>
818#include < test/fuzz/FuzzedDataProvider.h>
919#include < test/fuzz/fuzz.h>
1020#include < test/fuzz/util.h>
1121#include < test/util/mining.h>
1222#include < test/util/setup_common.h>
13- #include < util/chaintype.h>
23+ #include < uint256.h>
24+ #include < util/check.h>
1425#include < util/fs.h>
26+ #include < util/result.h>
1527#include < validation.h>
16- #include < validationinterface.h>
28+
29+ #include < cstdint>
30+ #include < functional>
31+ #include < ios>
32+ #include < memory>
33+ #include < optional>
34+ #include < vector>
1735
1836using node::SnapshotMetadata;
1937
2038namespace {
2139
2240const std::vector<std::shared_ptr<CBlock>>* g_chain;
41+ TestingSetup* g_setup;
2342
43+ template <bool INVALID>
2444void initialize_chain ()
2545{
2646 const auto params{CreateChainParams (ArgsManager{}, ChainType::REGTEST)};
2747 static const auto chain{CreateBlockChain (2 * COINBASE_MATURITY, *params)};
2848 g_chain = &chain;
49+ static const auto setup{
50+ MakeNoLogFileContext<TestingSetup>(ChainType::REGTEST,
51+ TestOpts{
52+ .setup_net = false ,
53+ .setup_validation_interface = false ,
54+ }),
55+ };
56+ if constexpr (INVALID) {
57+ auto & chainman{*setup->m_node .chainman };
58+ for (const auto & block : chain) {
59+ BlockValidationState dummy;
60+ bool processed{chainman.ProcessNewBlockHeaders ({*block}, true , dummy)};
61+ Assert (processed);
62+ const auto * index{WITH_LOCK (::cs_main, return chainman.m_blockman .LookupBlockIndex (block->GetHash ()))};
63+ Assert (index);
64+ }
65+ }
66+ g_setup = setup.get ();
2967}
3068
31- FUZZ_TARGET (utxo_snapshot, .init = initialize_chain)
69+ template <bool INVALID>
70+ void utxo_snapshot_fuzz (FuzzBufferType buffer)
3271{
3372 FuzzedDataProvider fuzzed_data_provider (buffer.data (), buffer.size ());
34- std::unique_ptr<const TestingSetup> setup{
35- MakeNoLogFileContext<const TestingSetup>(
36- ChainType::REGTEST,
37- TestOpts{
38- .setup_net = false ,
39- .setup_validation_interface = false ,
40- })};
41- const auto & node = setup->m_node ;
42- auto & chainman{*node.chainman };
73+ auto & setup{*g_setup};
74+ bool dirty_chainman{false }; // Re-use the global chainman, but reset it when it is dirty
75+ auto & chainman{*setup.m_node .chainman };
4376
4477 const auto snapshot_path = gArgs .GetDataDirNet () / " fuzzed_snapshot.dat" ;
4578
@@ -74,6 +107,16 @@ FUZZ_TARGET(utxo_snapshot, .init = initialize_chain)
74107 height++;
75108 }
76109 }
110+ if constexpr (INVALID) {
111+ // Append an invalid coin to ensure invalidity. This error will be
112+ // detected late in PopulateAndValidateSnapshot, and allows the
113+ // INVALID fuzz target to reach more potential code coverage.
114+ const auto & coinbase{g_chain->back ()->vtx .back ()};
115+ outfile << coinbase->GetHash ();
116+ WriteCompactSize (outfile, 1 ); // number of coins for the hash
117+ WriteCompactSize (outfile, 999 ); // index of coin
118+ outfile << Coin{coinbase->vout [0 ], /* nHeightIn=*/ 999 , /* fCoinBaseIn=*/ 0 };
119+ }
77120 }
78121
79122 const auto ActivateFuzzedSnapshot{[&] {
@@ -89,12 +132,16 @@ FUZZ_TARGET(utxo_snapshot, .init = initialize_chain)
89132 }};
90133
91134 if (fuzzed_data_provider.ConsumeBool ()) {
92- for (const auto & block : *g_chain) {
93- BlockValidationState dummy;
94- bool processed{chainman.ProcessNewBlockHeaders ({*block}, true , dummy)};
95- Assert (processed);
96- const auto * index{WITH_LOCK (::cs_main, return chainman.m_blockman .LookupBlockIndex (block->GetHash ()))};
97- Assert (index);
135+ // Consume the bool, but skip the code for the INVALID fuzz target
136+ if constexpr (!INVALID) {
137+ for (const auto & block : *g_chain) {
138+ BlockValidationState dummy;
139+ bool processed{chainman.ProcessNewBlockHeaders ({*block}, true , dummy)};
140+ Assert (processed);
141+ const auto * index{WITH_LOCK (::cs_main, return chainman.m_blockman .LookupBlockIndex (block->GetHash ()))};
142+ Assert (index);
143+ }
144+ dirty_chainman = true ;
98145 }
99146 }
100147
@@ -118,11 +165,35 @@ FUZZ_TARGET(utxo_snapshot, .init = initialize_chain)
118165 }
119166 }
120167 Assert (g_chain->size () == coinscache.GetCacheSize ());
168+ dirty_chainman = true ;
121169 } else {
122170 Assert (!chainman.SnapshotBlockhash ());
123171 Assert (!chainman.ActiveChainstate ().m_from_snapshot_blockhash );
124172 }
125173 // Snapshot should refuse to load a second time regardless of validity
126174 Assert (!ActivateFuzzedSnapshot ());
175+ if constexpr (INVALID) {
176+ // Activating the snapshot, or any other action that makes the chainman
177+ // "dirty" can and must not happen for the INVALID fuzz target
178+ Assert (!dirty_chainman);
179+ }
180+ if (dirty_chainman) {
181+ setup.m_node .chainman .reset ();
182+ setup.m_make_chainman ();
183+ setup.LoadVerifyActivateChainstate ();
184+ }
127185}
186+
187+ // There are two fuzz targets:
188+ //
189+ // The target 'utxo_snapshot', which allows valid snapshots, but is slow,
190+ // because it has to reset the chainstate manager on almost all fuzz inputs.
191+ // Otherwise, a dirty header tree or dirty chainstate could leak from one fuzz
192+ // input execution into the next, which makes execution non-deterministic.
193+ //
194+ // The target 'utxo_snapshot_invalid', which is fast and does not require any
195+ // expensive state to be reset.
196+ FUZZ_TARGET (utxo_snapshot /* valid*/ , .init = initialize_chain<false >) { utxo_snapshot_fuzz<false >(buffer); }
197+ FUZZ_TARGET (utxo_snapshot_invalid, .init = initialize_chain<true >) { utxo_snapshot_fuzz<true >(buffer); }
198+
128199} // namespace
0 commit comments