Branch data Line data Source code
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 : 2 : void sanity_check_snapshot()
47 : : {
48 [ + - - + ]: 2 : Assert(g_chain && g_setup == nullptr);
49 : :
50 : : // Create a temporary chainstate manager to connect the chain to.
51 [ + - ]: 2 : const auto tmp_setup{MakeNoLogFileContext<TestingSetup>(ChainType::REGTEST, TestOpts{.setup_net = false})};
52 : 2 : const auto& node{tmp_setup->m_node};
53 [ + + ]: 402 : for (auto& block: *g_chain) {
54 [ + - ]: 400 : ProcessBlock(node, block);
55 : : }
56 : :
57 : : // Connect the chain to the tmp chainman and sanity check the chainparams snapshot values.
58 [ + - ]: 2 : LOCK(cs_main);
59 [ + - ]: 2 : auto& cs{node.chainman->ActiveChainstate()};
60 [ + - ]: 2 : cs.ForceFlushStateToDisk();
61 [ + - + - : 2 : const auto stats{*Assert(kernel::ComputeUTXOStats(kernel::CoinStatsHashType::HASH_SERIALIZED, &cs.CoinsDB(), node.chainman->m_blockman))};
+ - ]
62 [ + - + - ]: 2 : const auto cp_au_data{*Assert(node.chainman->GetParams().AssumeutxoForHeight(2 * COINBASE_MATURITY))};
63 [ + - ]: 2 : Assert(stats.nHeight == cp_au_data.height);
64 [ + - ]: 2 : Assert(stats.nTransactions + 1 == cp_au_data.m_chain_tx_count); // +1 for the genesis tx.
65 [ + - ]: 2 : Assert(stats.hashBlock == cp_au_data.blockhash);
66 [ + - ]: 2 : Assert(AssumeutxoHash{stats.hashSerialized} == cp_au_data.hash_serialized);
67 : 2 : }
68 : :
69 : : template <bool INVALID>
70 : 2 : void initialize_chain()
71 : : {
72 [ + - ]: 2 : const auto params{CreateChainParams(ArgsManager{}, ChainType::REGTEST)};
73 [ + - + - : 4 : static const auto chain{CreateBlockChain(2 * COINBASE_MATURITY, *params)};
+ - ]
74 : 2 : g_chain = &chain;
75 [ + - ]: 2 : SetMockTime(chain.back()->Time());
76 : :
77 : : // Make sure we can generate a valid snapshot.
78 [ + - ]: 2 : sanity_check_snapshot();
79 : :
80 [ + - + - ]: 4 : static const auto setup{
81 : : MakeNoLogFileContext<TestingSetup>(ChainType::REGTEST,
82 : : TestOpts{
83 : : .setup_net = false,
84 : : .setup_validation_interface = false,
85 : : .min_validation_cache = true,
86 : : }),
87 : : };
88 : : if constexpr (INVALID) {
89 : 1 : auto& chainman{*setup->m_node.chainman};
90 [ + + ]: 201 : for (const auto& block : chain) {
91 : 200 : BlockValidationState dummy;
92 [ + - ]: 200 : bool processed{chainman.ProcessNewBlockHeaders({{block->GetBlockHeader()}}, true, dummy)};
93 [ + - ]: 200 : Assert(processed);
94 [ + - + - : 600 : const auto* index{WITH_LOCK(::cs_main, return chainman.m_blockman.LookupBlockIndex(block->GetHash()))};
+ - ]
95 [ + - ]: 200 : Assert(index);
96 : : }
97 : : }
98 : 2 : g_setup = setup.get();
99 [ + - ]: 4 : }
100 : :
101 : : template <bool INVALID>
102 : 1295 : void utxo_snapshot_fuzz(FuzzBufferType buffer)
103 : : {
104 : 1295 : SeedRandomStateForTest(SeedRand::ZEROS);
105 : 1295 : FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
106 : 1295 : SetMockTime(ConsumeTime(fuzzed_data_provider, /*min=*/1296688602)); // regtest genesis block timestamp
107 : 1295 : auto& setup{*g_setup};
108 : 1295 : bool dirty_chainman{false}; // Reuse the global chainman, but reset it when it is dirty
109 : 1295 : auto& chainman{*setup.m_node.chainman};
110 : :
111 [ + - ]: 2590 : const auto snapshot_path = gArgs.GetDataDirNet() / "fuzzed_snapshot.dat";
112 : :
113 [ + - + - ]: 1295 : Assert(!chainman.SnapshotBlockhash());
114 : :
115 : : {
116 [ + - + - ]: 2590 : AutoFile outfile{fsbridge::fopen(snapshot_path, "wb")};
117 : : // Metadata
118 [ + + ]: 1295 : if (fuzzed_data_provider.ConsumeBool()) {
119 [ + - ]: 312 : std::vector<uint8_t> metadata{ConsumeRandomLengthByteVector(fuzzed_data_provider)};
120 [ + - ]: 624 : outfile << std::span{metadata};
121 : 312 : } else {
122 : 983 : auto msg_start = chainman.GetParams().MessageStart();
123 : 983 : int base_blockheight{fuzzed_data_provider.ConsumeIntegralInRange<int>(1, 2 * COINBASE_MATURITY)};
124 [ + - + - ]: 983 : uint256 base_blockhash{g_chain->at(base_blockheight - 1)->GetHash()};
125 : 983 : uint64_t m_coins_count{fuzzed_data_provider.ConsumeIntegralInRange<uint64_t>(1, 3 * COINBASE_MATURITY)};
126 [ + - ]: 983 : SnapshotMetadata metadata{msg_start, base_blockhash, m_coins_count};
127 : 983 : outfile << metadata;
128 : 983 : }
129 : : // Coins
130 [ + + ]: 1295 : if (fuzzed_data_provider.ConsumeBool()) {
131 [ + - ]: 907 : std::vector<uint8_t> file_data{ConsumeRandomLengthByteVector(fuzzed_data_provider)};
132 [ + - ]: 1814 : outfile << std::span{file_data};
133 : 907 : } else {
134 : 388 : int height{1};
135 [ + - + + ]: 77988 : for (const auto& block : *g_chain) {
136 [ + - + - : 155200 : auto coinbase{block->vtx.at(0)};
+ - ]
137 [ + - ]: 77600 : outfile << coinbase->GetHash();
138 [ + - ]: 77600 : WriteCompactSize(outfile, 1); // number of coins for the hash
139 [ + - ]: 77600 : WriteCompactSize(outfile, 0); // index of coin
140 [ + - ]: 155200 : outfile << Coin(coinbase->vout[0], height, /*fCoinBaseIn=*/1);
141 [ + - ]: 77600 : height++;
142 : : }
143 : : }
144 : : 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 [ + - ]: 587 : const auto& coinbase{g_chain->back()->vtx.back()};
149 [ + - ]: 587 : outfile << coinbase->GetHash();
150 [ + - ]: 587 : WriteCompactSize(outfile, 1); // number of coins for the hash
151 [ + - ]: 587 : WriteCompactSize(outfile, 999); // index of coin
152 [ + - + - ]: 1174 : outfile << Coin{coinbase->vout[0], /*nHeightIn=*/999, /*fCoinBaseIn=*/0};
153 : : }
154 [ - + ]: 1295 : assert(outfile.fclose() == 0);
155 : 1295 : }
156 : :
157 : 3885 : const auto ActivateFuzzedSnapshot{[&] {
158 : 5180 : AutoFile infile{fsbridge::fopen(snapshot_path, "rb")};
159 [ + - + - ]: 2590 : auto msg_start = chainman.GetParams().MessageStart();
160 [ + - + - ]: 2590 : SnapshotMetadata metadata{msg_start};
161 : : try {
162 : 2208 : infile >> metadata;
163 [ - + - + ]: 382 : } catch (const std::ios_base::failure&) {
164 : : return false;
165 : : }
166 [ + - + - ]: 2208 : return !!chainman.ActivateSnapshot(infile, metadata, /*in_memory=*/true);
167 : 2590 : }};
168 : :
169 [ + + ]: 1295 : if (fuzzed_data_provider.ConsumeBool()) {
170 : : // Consume the bool, but skip the code for the INVALID fuzz target
171 : : if constexpr (!INVALID) {
172 [ + + ]: 130047 : for (const auto& block : *g_chain) {
173 : 129400 : BlockValidationState dummy;
174 [ + - ]: 129400 : bool processed{chainman.ProcessNewBlockHeaders({{block->GetBlockHeader()}}, true, dummy)};
175 [ + - ]: 129400 : Assert(processed);
176 [ + - + - : 388200 : const auto* index{WITH_LOCK(::cs_main, return chainman.m_blockman.LookupBlockIndex(block->GetHash()))};
+ - ]
177 [ + - ]: 129400 : Assert(index);
178 : : }
179 : : dirty_chainman = true;
180 : : }
181 : : }
182 : :
183 [ + - + + ]: 1295 : if (ActivateFuzzedSnapshot()) {
184 [ + - ]: 44 : LOCK(::cs_main);
185 [ + - + - ]: 44 : Assert(!chainman.ActiveChainstate().m_from_snapshot_blockhash->IsNull());
186 [ + - + - : 44 : Assert(*chainman.ActiveChainstate().m_from_snapshot_blockhash ==
+ - ]
187 : : *chainman.SnapshotBlockhash());
188 [ + - + - ]: 44 : const auto& coinscache{chainman.ActiveChainstate().CoinsTip()};
189 [ + + ]: 8844 : for (const auto& block : *g_chain) {
190 [ + - + - : 8800 : Assert(coinscache.HaveCoin(COutPoint{block->vtx.at(0)->GetHash(), 0}));
+ - ]
191 [ + - + - ]: 8800 : const auto* index{chainman.m_blockman.LookupBlockIndex(block->GetHash())};
192 [ + - ]: 8800 : Assert(index);
193 [ + - ]: 8800 : Assert(index->nTx == 0);
194 [ + - + - ]: 8844 : if (index->nHeight == chainman.GetSnapshotBaseHeight()) {
195 [ + - ]: 44 : auto params{chainman.GetParams().AssumeutxoForHeight(index->nHeight)};
196 [ + - ]: 44 : Assert(params.has_value());
197 [ + - ]: 44 : Assert(params.value().m_chain_tx_count == index->m_chain_tx_count);
198 : : } else {
199 [ + - ]: 8756 : Assert(index->m_chain_tx_count == 0);
200 : : }
201 : : }
202 [ + - + - ]: 44 : Assert(g_chain->size() == coinscache.GetCacheSize());
203 [ + - ]: 44 : dirty_chainman = true;
204 : 44 : } else {
205 [ + - + - ]: 1251 : Assert(!chainman.SnapshotBlockhash());
206 [ + - + - ]: 1251 : Assert(!chainman.ActiveChainstate().m_from_snapshot_blockhash);
207 : : }
208 : : // Snapshot should refuse to load a second time regardless of validity
209 [ + - + - ]: 1295 : Assert(!ActivateFuzzedSnapshot());
210 : : 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 [ + - ]: 587 : Assert(!dirty_chainman);
214 : : }
215 [ + + ]: 1295 : if (dirty_chainman) {
216 [ + - ]: 647 : setup.m_node.chainman.reset();
217 [ + - ]: 647 : setup.m_make_chainman();
218 [ + - ]: 647 : setup.LoadVerifyActivateChainstate();
219 : : }
220 : 1295 : }
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 [ + - ]: 1162 : FUZZ_TARGET(utxo_snapshot /*valid*/, .init = initialize_chain<false>) { utxo_snapshot_fuzz<false>(buffer); }
232 [ + - ]: 1041 : FUZZ_TARGET(utxo_snapshot_invalid, .init = initialize_chain<true>) { utxo_snapshot_fuzz<true>(buffer); }
233 : :
234 : : } // namespace
|