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