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