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+ .min_validation_cache = true ,
55+ }),
56+ };
57+ if constexpr (INVALID) {
58+ auto & chainman{*setup->m_node .chainman };
59+ for (const auto & block : chain) {
60+ BlockValidationState dummy;
61+ bool processed{chainman.ProcessNewBlockHeaders ({*block}, true , dummy)};
62+ Assert (processed);
63+ const auto * index{WITH_LOCK (::cs_main, return chainman.m_blockman .LookupBlockIndex (block->GetHash ()))};
64+ Assert (index);
65+ }
66+ }
67+ g_setup = setup.get ();
2968}
3069
31- FUZZ_TARGET (utxo_snapshot, .init = initialize_chain)
70+ template <bool INVALID>
71+ void utxo_snapshot_fuzz (FuzzBufferType buffer)
3272{
3373 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 };
74+ auto & setup{*g_setup};
75+ bool dirty_chainman{false }; // Re-use the global chainman, but reset it when it is dirty
76+ auto & chainman{*setup.m_node .chainman };
4377
4478 const auto snapshot_path = gArgs .GetDataDirNet () / " fuzzed_snapshot.dat" ;
4579
@@ -52,7 +86,6 @@ FUZZ_TARGET(utxo_snapshot, .init = initialize_chain)
5286 std::vector<uint8_t > metadata{ConsumeRandomLengthByteVector (fuzzed_data_provider)};
5387 outfile << Span{metadata};
5488 } else {
55- DataStream data_stream{};
5689 auto msg_start = chainman.GetParams ().MessageStart ();
5790 int base_blockheight{fuzzed_data_provider.ConsumeIntegralInRange <int >(1 , 2 * COINBASE_MATURITY)};
5891 uint256 base_blockhash{g_chain->at (base_blockheight - 1 )->GetHash ()};
@@ -75,6 +108,16 @@ FUZZ_TARGET(utxo_snapshot, .init = initialize_chain)
75108 height++;
76109 }
77110 }
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+ const auto & coinbase{g_chain->back ()->vtx .back ()};
116+ outfile << coinbase->GetHash ();
117+ WriteCompactSize (outfile, 1 ); // number of coins for the hash
118+ WriteCompactSize (outfile, 999 ); // index of coin
119+ outfile << Coin{coinbase->vout [0 ], /* nHeightIn=*/ 999 , /* fCoinBaseIn=*/ 0 };
120+ }
78121 }
79122
80123 const auto ActivateFuzzedSnapshot{[&] {
@@ -90,12 +133,16 @@ FUZZ_TARGET(utxo_snapshot, .init = initialize_chain)
90133 }};
91134
92135 if (fuzzed_data_provider.ConsumeBool ()) {
93- for (const auto & block : *g_chain) {
94- BlockValidationState dummy;
95- bool processed{chainman.ProcessNewBlockHeaders ({*block}, true , dummy)};
96- Assert (processed);
97- const auto * index{WITH_LOCK (::cs_main, return chainman.m_blockman .LookupBlockIndex (block->GetHash ()))};
98- Assert (index);
136+ // Consume the bool, but skip the code for the INVALID fuzz target
137+ if constexpr (!INVALID) {
138+ for (const auto & block : *g_chain) {
139+ BlockValidationState dummy;
140+ bool processed{chainman.ProcessNewBlockHeaders ({*block}, true , dummy)};
141+ Assert (processed);
142+ const auto * index{WITH_LOCK (::cs_main, return chainman.m_blockman .LookupBlockIndex (block->GetHash ()))};
143+ Assert (index);
144+ }
145+ dirty_chainman = true ;
99146 }
100147 }
101148
@@ -119,11 +166,35 @@ FUZZ_TARGET(utxo_snapshot, .init = initialize_chain)
119166 }
120167 }
121168 Assert (g_chain->size () == coinscache.GetCacheSize ());
169+ dirty_chainman = true ;
122170 } else {
123171 Assert (!chainman.SnapshotBlockhash ());
124172 Assert (!chainman.ActiveChainstate ().m_from_snapshot_blockhash );
125173 }
126174 // Snapshot should refuse to load a second time regardless of validity
127175 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+ Assert (!dirty_chainman);
180+ }
181+ if (dirty_chainman) {
182+ setup.m_node .chainman .reset ();
183+ setup.m_make_chainman ();
184+ setup.LoadVerifyActivateChainstate ();
185+ }
128186}
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+ FUZZ_TARGET (utxo_snapshot /* valid*/ , .init = initialize_chain<false >) { utxo_snapshot_fuzz<false >(buffer); }
198+ FUZZ_TARGET (utxo_snapshot_invalid, .init = initialize_chain<true >) { utxo_snapshot_fuzz<true >(buffer); }
199+
129200} // namespace
0 commit comments