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