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