Bitcoin Core 28.99.0
P2P Digital Currency
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{
50 MakeNoLogFileContext<TestingSetup>(ChainType::REGTEST,
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->GetBlockHeader()}}, 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{
74 FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
75 auto& setup{*g_setup};
76 bool dirty_chainman{false}; // Re-use the global chainman, but reset it when it is dirty
77 auto& chainman{*setup.m_node.chainman};
78
79 const auto snapshot_path = gArgs.GetDataDirNet() / "fuzzed_snapshot.dat";
80
81 Assert(!chainman.SnapshotBlockhash());
82
83 {
84 AutoFile outfile{fsbridge::fopen(snapshot_path, "wb")};
85 // Metadata
86 if (fuzzed_data_provider.ConsumeBool()) {
87 std::vector<uint8_t> metadata{ConsumeRandomLengthByteVector(fuzzed_data_provider)};
88 outfile << Span{metadata};
89 } else {
90 auto msg_start = chainman.GetParams().MessageStart();
91 int base_blockheight{fuzzed_data_provider.ConsumeIntegralInRange<int>(1, 2 * COINBASE_MATURITY)};
92 uint256 base_blockhash{g_chain->at(base_blockheight - 1)->GetHash()};
93 uint64_t m_coins_count{fuzzed_data_provider.ConsumeIntegralInRange<uint64_t>(1, 3 * COINBASE_MATURITY)};
94 SnapshotMetadata metadata{msg_start, base_blockhash, m_coins_count};
95 outfile << metadata;
96 }
97 // Coins
98 if (fuzzed_data_provider.ConsumeBool()) {
99 std::vector<uint8_t> file_data{ConsumeRandomLengthByteVector(fuzzed_data_provider)};
100 outfile << Span{file_data};
101 } else {
102 int height{0};
103 for (const auto& block : *g_chain) {
104 auto coinbase{block->vtx.at(0)};
105 outfile << coinbase->GetHash();
106 WriteCompactSize(outfile, 1); // number of coins for the hash
107 WriteCompactSize(outfile, 0); // index of coin
108 outfile << Coin(coinbase->vout[0], height, /*fCoinBaseIn=*/1);
109 height++;
110 }
111 }
112 if constexpr (INVALID) {
113 // Append an invalid coin to ensure invalidity. This error will be
114 // detected late in PopulateAndValidateSnapshot, and allows the
115 // INVALID fuzz target to reach more potential code coverage.
116 const auto& coinbase{g_chain->back()->vtx.back()};
117 outfile << coinbase->GetHash();
118 WriteCompactSize(outfile, 1); // number of coins for the hash
119 WriteCompactSize(outfile, 999); // index of coin
120 outfile << Coin{coinbase->vout[0], /*nHeightIn=*/999, /*fCoinBaseIn=*/0};
121 }
122 }
123
124 const auto ActivateFuzzedSnapshot{[&] {
125 AutoFile infile{fsbridge::fopen(snapshot_path, "rb")};
126 auto msg_start = chainman.GetParams().MessageStart();
127 SnapshotMetadata metadata{msg_start};
128 try {
129 infile >> metadata;
130 } catch (const std::ios_base::failure&) {
131 return false;
132 }
133 return !!chainman.ActivateSnapshot(infile, metadata, /*in_memory=*/true);
134 }};
135
136 if (fuzzed_data_provider.ConsumeBool()) {
137 // Consume the bool, but skip the code for the INVALID fuzz target
138 if constexpr (!INVALID) {
139 for (const auto& block : *g_chain) {
141 bool processed{chainman.ProcessNewBlockHeaders({{block->GetBlockHeader()}}, true, dummy)};
142 Assert(processed);
143 const auto* index{WITH_LOCK(::cs_main, return chainman.m_blockman.LookupBlockIndex(block->GetHash()))};
144 Assert(index);
145 }
146 dirty_chainman = true;
147 }
148 }
149
150 if (ActivateFuzzedSnapshot()) {
152 Assert(!chainman.ActiveChainstate().m_from_snapshot_blockhash->IsNull());
153 Assert(*chainman.ActiveChainstate().m_from_snapshot_blockhash ==
154 *chainman.SnapshotBlockhash());
155 const auto& coinscache{chainman.ActiveChainstate().CoinsTip()};
156 for (const auto& block : *g_chain) {
157 Assert(coinscache.HaveCoin(COutPoint{block->vtx.at(0)->GetHash(), 0}));
158 const auto* index{chainman.m_blockman.LookupBlockIndex(block->GetHash())};
159 Assert(index);
160 Assert(index->nTx == 0);
161 if (index->nHeight == chainman.GetSnapshotBaseHeight()) {
162 auto params{chainman.GetParams().AssumeutxoForHeight(index->nHeight)};
163 Assert(params.has_value());
164 Assert(params.value().m_chain_tx_count == index->m_chain_tx_count);
165 } else {
166 Assert(index->m_chain_tx_count == 0);
167 }
168 }
169 Assert(g_chain->size() == coinscache.GetCacheSize());
170 dirty_chainman = true;
171 } else {
172 Assert(!chainman.SnapshotBlockhash());
173 Assert(!chainman.ActiveChainstate().m_from_snapshot_blockhash);
174 }
175 // Snapshot should refuse to load a second time regardless of validity
176 Assert(!ActivateFuzzedSnapshot());
177 if constexpr (INVALID) {
178 // Activating the snapshot, or any other action that makes the chainman
179 // "dirty" can and must not happen for the INVALID fuzz target
180 Assert(!dirty_chainman);
181 }
182 if (dirty_chainman) {
183 setup.m_node.chainman.reset();
184 setup.m_make_chainman();
185 setup.LoadVerifyActivateChainstate();
186 }
187}
188
189// There are two fuzz targets:
190//
191// The target 'utxo_snapshot', which allows valid snapshots, but is slow,
192// because it has to reset the chainstate manager on almost all fuzz inputs.
193// Otherwise, a dirty header tree or dirty chainstate could leak from one fuzz
194// input execution into the next, which makes execution non-deterministic.
195//
196// The target 'utxo_snapshot_invalid', which is fast and does not require any
197// expensive state to be reset.
198FUZZ_TARGET(utxo_snapshot /*valid*/, .init = initialize_chain<false>) { utxo_snapshot_fuzz<false>(buffer); }
199FUZZ_TARGET(utxo_snapshot_invalid, .init = initialize_chain<true>) { utxo_snapshot_fuzz<true>(buffer); }
200
201} // namespace
ArgsManager gArgs
Definition: args.cpp:42
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:85
fs::path GetDataDirNet() const
Get data directory path with appended network identifier.
Definition: args.h:234
Non-refcounted RAII wrapper for FILE*.
Definition: streams.h:392
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...
Definition: utxo_snapshot.h:34
256-bit opaque blob.
Definition: uint256.h:190
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
@ INVALID
Failed decoding.
FILE * fopen(const fs::path &p, const char *mode)
Definition: fs.cpp:26
void WriteCompactSize(SizeComputer &os, uint64_t nSize)
Definition: serialize.h:1095
node::NodeContext m_node
Definition: setup_common.h:65
bool setup_net
Definition: setup_common.h:55
Testing setup that configures a complete environment.
Definition: setup_common.h:120
std::unique_ptr< ChainstateManager > chainman
Definition: context.h:72
#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:33
void SeedRandomStateForTest(SeedRand seedtype)
Seed the global RNG state for testing and log the seed value.
Definition: random.cpp:19
@ ZEROS
Seed with a compile time constant of zeros.