Bitcoin Core 28.0.0
P2P Digital Currency
Loading...
Searching...
No Matches
utxo_snapshot.cpp
Go to the documentation of this file.
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>
10#include <node/blockstorage.h>
11#include <node/utxo_snapshot.h>
12#include <primitives/block.h>
14#include <serialize.h>
15#include <span.h>
16#include <streams.h>
17#include <sync.h>
19#include <test/fuzz/fuzz.h>
20#include <test/fuzz/util.h>
21#include <test/util/mining.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
37
38namespace {
39
40const std::vector<std::shared_ptr<CBlock>>* g_chain;
41TestingSetup* g_setup;
42
43template <bool INVALID>
44void initialize_chain()
45{
47 static const auto chain{CreateBlockChain(2 * COINBASE_MATURITY, *params)};
48 g_chain = &chain;
49 static const auto setup{
52 .setup_net = false,
53 .setup_validation_interface = false,
54 .min_validation_cache = true,
55 }),
56 };
57 if constexpr (INVALID) {
58 auto& chainman{*setup->m_node.chainman};
59 for (const auto& block : chain) {
61 bool processed{chainman.ProcessNewBlockHeaders({*block}, true, dummy)};
62 Assert(processed);
63 const auto* index{WITH_LOCK(::cs_main, return chainman.m_blockman.LookupBlockIndex(block->GetHash()))};
64 Assert(index);
65 }
66 }
67 g_setup = setup.get();
68}
69
70template <bool INVALID>
71void utxo_snapshot_fuzz(FuzzBufferType buffer)
72{
73 FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
74 auto& setup{*g_setup};
75 bool dirty_chainman{false}; // Re-use the global chainman, but reset it when it is dirty
76 auto& chainman{*setup.m_node.chainman};
77
78 const auto snapshot_path = gArgs.GetDataDirNet() / "fuzzed_snapshot.dat";
79
80 Assert(!chainman.SnapshotBlockhash());
81
82 {
83 AutoFile outfile{fsbridge::fopen(snapshot_path, "wb")};
84 // Metadata
85 if (fuzzed_data_provider.ConsumeBool()) {
86 std::vector<uint8_t> metadata{ConsumeRandomLengthByteVector(fuzzed_data_provider)};
87 outfile << Span{metadata};
88 } else {
89 auto msg_start = chainman.GetParams().MessageStart();
90 int base_blockheight{fuzzed_data_provider.ConsumeIntegralInRange<int>(1, 2 * COINBASE_MATURITY)};
91 uint256 base_blockhash{g_chain->at(base_blockheight - 1)->GetHash()};
92 uint64_t m_coins_count{fuzzed_data_provider.ConsumeIntegralInRange<uint64_t>(1, 3 * COINBASE_MATURITY)};
93 SnapshotMetadata metadata{msg_start, base_blockhash, m_coins_count};
94 outfile << metadata;
95 }
96 // Coins
97 if (fuzzed_data_provider.ConsumeBool()) {
98 std::vector<uint8_t> file_data{ConsumeRandomLengthByteVector(fuzzed_data_provider)};
99 outfile << Span{file_data};
100 } else {
101 int height{0};
102 for (const auto& block : *g_chain) {
103 auto coinbase{block->vtx.at(0)};
104 outfile << coinbase->GetHash();
105 WriteCompactSize(outfile, 1); // number of coins for the hash
106 WriteCompactSize(outfile, 0); // index of coin
107 outfile << Coin(coinbase->vout[0], height, /*fCoinBaseIn=*/1);
108 height++;
109 }
110 }
111 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 const auto& coinbase{g_chain->back()->vtx.back()};
116 outfile << coinbase->GetHash();
117 WriteCompactSize(outfile, 1); // number of coins for the hash
118 WriteCompactSize(outfile, 999); // index of coin
119 outfile << Coin{coinbase->vout[0], /*nHeightIn=*/999, /*fCoinBaseIn=*/0};
120 }
121 }
122
123 const auto ActivateFuzzedSnapshot{[&] {
124 AutoFile infile{fsbridge::fopen(snapshot_path, "rb")};
125 auto msg_start = chainman.GetParams().MessageStart();
126 SnapshotMetadata metadata{msg_start};
127 try {
128 infile >> metadata;
129 } catch (const std::ios_base::failure&) {
130 return false;
131 }
132 return !!chainman.ActivateSnapshot(infile, metadata, /*in_memory=*/true);
133 }};
134
135 if (fuzzed_data_provider.ConsumeBool()) {
136 // Consume the bool, but skip the code for the INVALID fuzz target
137 if constexpr (!INVALID) {
138 for (const auto& block : *g_chain) {
140 bool processed{chainman.ProcessNewBlockHeaders({*block}, true, dummy)};
141 Assert(processed);
142 const auto* index{WITH_LOCK(::cs_main, return chainman.m_blockman.LookupBlockIndex(block->GetHash()))};
143 Assert(index);
144 }
145 dirty_chainman = true;
146 }
147 }
148
149 if (ActivateFuzzedSnapshot()) {
151 Assert(!chainman.ActiveChainstate().m_from_snapshot_blockhash->IsNull());
152 Assert(*chainman.ActiveChainstate().m_from_snapshot_blockhash ==
153 *chainman.SnapshotBlockhash());
154 const auto& coinscache{chainman.ActiveChainstate().CoinsTip()};
155 for (const auto& block : *g_chain) {
156 Assert(coinscache.HaveCoin(COutPoint{block->vtx.at(0)->GetHash(), 0}));
157 const auto* index{chainman.m_blockman.LookupBlockIndex(block->GetHash())};
158 Assert(index);
159 Assert(index->nTx == 0);
160 if (index->nHeight == chainman.GetSnapshotBaseHeight()) {
161 auto params{chainman.GetParams().AssumeutxoForHeight(index->nHeight)};
162 Assert(params.has_value());
163 Assert(params.value().m_chain_tx_count == index->m_chain_tx_count);
164 } else {
165 Assert(index->m_chain_tx_count == 0);
166 }
167 }
168 Assert(g_chain->size() == coinscache.GetCacheSize());
169 dirty_chainman = true;
170 } else {
171 Assert(!chainman.SnapshotBlockhash());
172 Assert(!chainman.ActiveChainstate().m_from_snapshot_blockhash);
173 }
174 // Snapshot should refuse to load a second time regardless of validity
175 Assert(!ActivateFuzzedSnapshot());
176 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 Assert(!dirty_chainman);
180 }
181 if (dirty_chainman) {
182 setup.m_node.chainman.reset();
183 setup.m_make_chainman();
184 setup.LoadVerifyActivateChainstate();
185 }
186}
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.
197FUZZ_TARGET(utxo_snapshot /*valid*/, .init = initialize_chain<false>) { utxo_snapshot_fuzz<false>(buffer); }
198FUZZ_TARGET(utxo_snapshot_invalid, .init = initialize_chain<true>) { utxo_snapshot_fuzz<true>(buffer); }
199
200} // namespace
ArgsManager gArgs
Definition args.cpp:41
std::unique_ptr< const CChainParams > CreateChainParams(const ArgsManager &args, const ChainType chain)
Creates and returns a std::unique_ptr<CChainParams> of the chosen chain.
#define Assert(val)
Identity function.
Definition check.h:77
fs::path GetDataDirNet() const
Get data directory path with appended network identifier.
Definition args.h:232
Non-refcounted RAII wrapper for FILE*.
Definition streams.h:389
An outpoint - a combination of a transaction hash and an index n into its vout.
Definition transaction.h:29
A UTXO entry.
Definition coins.h:33
A Span is an object that can refer to a contiguous sequence of objects.
Definition span.h:98
Metadata describing a serialized version of a UTXO set from which an assumeutxo Chainstate can be con...
256-bit opaque blob.
Definition uint256.h:178
static const int COINBASE_MATURITY
Coinbase transaction outputs can only be spent after this number of new blocks (network rule)
Definition consensus.h:19
RecursiveMutex cs_main
Mutex to guard access to validation specific variables, such as reading or changing the chainstate.
Definition cs_main.cpp:8
#define FUZZ_TARGET(...)
Definition fuzz.h:35
std::span< const uint8_t > FuzzBufferType
Definition fuzz.h:25
FILE * fopen(const fs::path &p, const char *mode)
Definition fs.cpp:26
void WriteCompactSize(SizeComputer &os, uint64_t nSize)
Definition serialize.h:1095
std::unique_ptr< T > MakeNoLogFileContext(const ChainType chain_type=ChainType::REGTEST, TestOpts opts={})
Make a test setup that has disk access to the debug.log file disabled.
Testing setup that configures a complete environment.
#define LOCK(cs)
Definition sync.h:257
#define WITH_LOCK(cs, code)
Run code while locking a mutex.
Definition sync.h:301
std::vector< B > ConsumeRandomLengthByteVector(FuzzedDataProvider &fuzzed_data_provider, const std::optional< size_t > &max_length=std::nullopt) noexcept
Definition util.h:57
std::vector< std::shared_ptr< CBlock > > CreateBlockChain(size_t total_height, const CChainParams &params)
Create a blockchain, starting from genesis.
Definition mining.cpp:32