1
- // Copyright (c) 2021-2022 The Bitcoin Core developers
1
+ // Copyright (c) 2021-present The Bitcoin Core developers
2
2
// Distributed under the MIT software license, see the accompanying
3
3
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4
4
5
+ #include < chain.h>
5
6
#include < chainparams.h>
7
+ #include < coins.h>
8
+ #include < consensus/consensus.h>
6
9
#include < consensus/validation.h>
10
+ #include < node/blockstorage.h>
7
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>
8
18
#include < test/fuzz/FuzzedDataProvider.h>
9
19
#include < test/fuzz/fuzz.h>
10
20
#include < test/fuzz/util.h>
11
21
#include < test/util/mining.h>
12
22
#include < test/util/setup_common.h>
13
- #include < util/chaintype.h>
23
+ #include < uint256.h>
24
+ #include < util/check.h>
14
25
#include < util/fs.h>
26
+ #include < util/result.h>
15
27
#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>
17
35
18
36
using node::SnapshotMetadata;
19
37
20
38
namespace {
21
39
22
40
const std::vector<std::shared_ptr<CBlock>>* g_chain;
41
+ TestingSetup* g_setup;
23
42
43
+ template <bool INVALID>
24
44
void initialize_chain ()
25
45
{
26
46
const auto params{CreateChainParams (ArgsManager{}, ChainType::REGTEST)};
27
47
static const auto chain{CreateBlockChain (2 * COINBASE_MATURITY, *params)};
28
48
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 ();
29
67
}
30
68
31
- FUZZ_TARGET (utxo_snapshot, .init = initialize_chain)
69
+ template <bool INVALID>
70
+ void utxo_snapshot_fuzz (FuzzBufferType buffer)
32
71
{
33
72
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 };
43
76
44
77
const auto snapshot_path = gArgs .GetDataDirNet () / " fuzzed_snapshot.dat" ;
45
78
@@ -74,6 +107,16 @@ FUZZ_TARGET(utxo_snapshot, .init = initialize_chain)
74
107
height++;
75
108
}
76
109
}
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
+ }
77
120
}
78
121
79
122
const auto ActivateFuzzedSnapshot{[&] {
@@ -89,12 +132,16 @@ FUZZ_TARGET(utxo_snapshot, .init = initialize_chain)
89
132
}};
90
133
91
134
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 ;
98
145
}
99
146
}
100
147
@@ -118,11 +165,35 @@ FUZZ_TARGET(utxo_snapshot, .init = initialize_chain)
118
165
}
119
166
}
120
167
Assert (g_chain->size () == coinscache.GetCacheSize ());
168
+ dirty_chainman = true ;
121
169
} else {
122
170
Assert (!chainman.SnapshotBlockhash ());
123
171
Assert (!chainman.ActiveChainstate ().m_from_snapshot_blockhash );
124
172
}
125
173
// Snapshot should refuse to load a second time regardless of validity
126
174
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
+ }
127
185
}
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
+
128
199
} // namespace
0 commit comments