Bitcoin Core 32.99.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 <kernel/coinstats.h>
12#include <node/blockstorage.h>
13#include <node/utxo_snapshot.h>
14#include <primitives/block.h>
16#include <serialize.h>
17#include <span.h>
18#include <streams.h>
19#include <sync.h>
21#include <test/fuzz/fuzz.h>
22#include <test/fuzz/util.h>
24#include <test/util/mining.h>
26#include <test/util/time.h>
27#include <uint256.h>
28#include <util/check.h>
29#include <util/fs.h>
30#include <util/result.h>
31#include <util/time.h>
32#include <validation.h>
33
34#include <cstdint>
35#include <functional>
36#include <ios>
37#include <memory>
38#include <optional>
39#include <vector>
40
42
43namespace {
44
45const std::vector<std::shared_ptr<CBlock>>* g_chain;
46TestingSetup* g_setup{nullptr};
47constexpr ReachabilityGoal UTXO_SNAPSHOT_ACTIVATION_SUCCEEDS{"UTXO snapshot activation succeeds"};
48
51{
52 Assert(g_chain && g_setup == nullptr);
53
54 // Create a temporary chainstate manager to connect the chain to.
56 const auto& node{tmp_setup->m_node};
57 for (auto& block: *g_chain) {
58 ProcessBlock(node, block);
59 }
60
61 // Connect the chain to the tmp chainman and sanity check the chainparams snapshot values.
63 auto& cs{node.chainman->ActiveChainstate()};
64 cs.ForceFlushStateToDisk(/*wipe_cache=*/false);
65 const auto stats{*Assert(kernel::ComputeUTXOStats(kernel::CoinStatsHashType::HASH_SERIALIZED, cs.CoinsDB(), node.chainman->m_blockman))};
66 const auto cp_au_data{*Assert(node.chainman->GetParams().AssumeutxoForHeight(2 * COINBASE_MATURITY))};
67 Assert(stats.nHeight == cp_au_data.height);
68 Assert(stats.nTransactions + 1 == cp_au_data.m_chain_tx_count); // +1 for the genesis tx.
69 Assert(stats.hashBlock == cp_au_data.blockhash);
70 Assert(AssumeutxoHash{stats.hashSerialized} == cp_au_data.hash_serialized);
71}
72
73template <bool INVALID>
75{
76 if constexpr (!INVALID) {
78 }
80 static const auto chain{CreateBlockChain(2 * COINBASE_MATURITY, *params)};
81 g_chain = &chain;
82 FakeNodeClock node_clock{chain.back()->Time()};
83
84 // Make sure we can generate a valid snapshot.
86
87 static const auto setup{
90 .setup_net = false,
91 .setup_validation_interface = false,
92 .min_validation_cache = true,
93 }),
94 };
95 if constexpr (INVALID) {
96 auto& chainman{*setup->m_node.chainman};
97 for (const auto& block : chain) {
99 bool processed{chainman.ProcessNewBlockHeaders({{*block}}, true, dummy)};
101 const auto* index{WITH_LOCK(::cs_main, return chainman.m_blockman.LookupBlockIndex(block->GetHash()))};
102 Assert(index);
103 }
104 }
105 g_setup = setup.get();
106}
107
108template <bool INVALID>
110{
112 FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
113 FakeNodeClock node_clock{ConsumeTime(fuzzed_data_provider, /*min=*/1296688602)}; // regtest genesis block timestamp
114 auto& setup{*g_setup};
115 bool dirty_chainman{false}; // Reuse the global chainman, but reset it when it is dirty
116 auto& chainman{*setup.m_node.chainman};
117
118 const auto snapshot_path = gArgs.GetDataDirNet() / "fuzzed_snapshot.dat";
119
120 Assert(!chainman.ActiveChainstate().m_from_snapshot_blockhash);
121
122 {
124 // Metadata
127 outfile << std::span{metadata};
128 } else {
129 auto msg_start = chainman.GetParams().MessageStart();
131 uint256 base_blockhash{g_chain->at(base_blockheight - 1)->GetHash()};
134 outfile << metadata;
135 }
136 // Coins
139 outfile << std::span{file_data};
140 } else {
141 int height{1};
142 for (const auto& block : *g_chain) {
143 auto coinbase{block->vtx.at(0)};
144 outfile << coinbase->GetHash();
145 WriteCompactSize(outfile, 1); // number of coins for the hash
146 WriteCompactSize(outfile, 0); // index of coin
147 outfile << Coin(coinbase->vout[0], height, /*fCoinBaseIn=*/true);
148 height++;
149 }
150 }
151 if constexpr (INVALID) {
152 // Append an invalid coin to ensure invalidity. This error will be
153 // detected late in PopulateAndValidateSnapshot, and allows the
154 // INVALID fuzz target to reach more potential code coverage.
155 const auto& coinbase{g_chain->back()->vtx.back()};
156 outfile << coinbase->GetHash();
157 WriteCompactSize(outfile, 1); // number of coins for the hash
158 WriteCompactSize(outfile, 999); // index of coin
159 outfile << Coin{coinbase->vout[0], /*nHeightIn=*/999, /*fCoinBaseIn=*/false};
160 }
161 assert(outfile.fclose() == 0);
162 }
163
164 const auto ActivateFuzzedSnapshot{[&] {
166 auto msg_start = chainman.GetParams().MessageStart();
168 try {
169 infile >> metadata;
170 } catch (const std::ios_base::failure&) {
171 return false;
172 }
173 return !!chainman.ActivateSnapshot(infile, metadata, /*in_memory=*/true);
174 }};
175
177 // Consume the bool, but skip the code for the INVALID fuzz target
178 if constexpr (!INVALID) {
179 for (const auto& block : *g_chain) {
181 bool processed{chainman.ProcessNewBlockHeaders({{*block}}, true, dummy)};
183 const auto* index{WITH_LOCK(::cs_main, return chainman.m_blockman.LookupBlockIndex(block->GetHash()))};
184 Assert(index);
185 }
186 dirty_chainman = true;
187 }
188 }
189
191 if (snapshot_activated) {
193 Assert(!chainman.ActiveChainstate().m_from_snapshot_blockhash->IsNull());
194 const auto& coinscache{chainman.ActiveChainstate().CoinsTip()};
195 for (const auto& block : *g_chain) {
196 Assert(coinscache.HaveCoin(COutPoint{block->vtx.at(0)->GetHash(), 0}));
197 const auto* index{chainman.m_blockman.LookupBlockIndex(block->GetHash())};
198 Assert(index);
199 Assert(index->nTx == 0);
200 if (index->nHeight == chainman.ActiveChainstate().SnapshotBase()->nHeight) {
201 auto params{chainman.GetParams().AssumeutxoForHeight(index->nHeight)};
202 Assert(params.has_value());
203 Assert(params.value().m_chain_tx_count == index->m_chain_tx_count);
204 } else {
205 Assert(index->m_chain_tx_count == 0);
206 }
207 }
208 Assert(g_chain->size() == coinscache.GetCacheSize());
209 dirty_chainman = true;
210 } else {
211 Assert(!chainman.ActiveChainstate().m_from_snapshot_blockhash);
212 }
213 // Snapshot should refuse to load a second time regardless of validity
215 if constexpr (INVALID) {
216 // Activating the snapshot, or any other action that makes the chainman
217 // "dirty" can and must not happen for the INVALID fuzz target
219 }
220 if (dirty_chainman) {
221 setup.m_node.block_template_manager.reset();
222 setup.m_node.chainman.reset();
223 setup.m_make_chainman();
224 setup.LoadVerifyActivateChainstate();
225 setup.CreateBlockTemplateManager();
226 }
227 if constexpr (!INVALID) {
229 }
230}
231
232// There are two fuzz targets:
233//
234// The target 'utxo_snapshot', which allows valid snapshots, but is slow,
235// because it has to reset the chainstate manager on almost all fuzz inputs.
236// Otherwise, a dirty header tree or dirty chainstate could leak from one fuzz
237// input execution into the next, which makes execution non-deterministic.
238//
239// The target 'utxo_snapshot_invalid', which is fast and does not require any
240// expensive state to be reset.
243
244} // namespace
ArgsManager gArgs
Definition args.cpp:39
static void pool cs
const TestingSetup * g_setup
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:119
fs::path GetDataDirNet() const EXCLUSIVE_LOCKS_REQUIRED(!cs_args)
Get data directory path with appended network identifier.
Definition args.cpp:329
Non-refcounted RAII wrapper for FILE*.
Definition streams.h:395
An outpoint - a combination of a transaction hash and an index n into its vout.
Definition transaction.h:30
A UTXO entry.
Definition coins.h:46
Helper to initialize the global NodeClock, let a duration elapse, and reset it after use in a test.
Definition time.h:54
T ConsumeIntegralInRange(T min, T max)
Metadata describing a serialized version of a UTXO set from which an assumeutxo Chainstate can be con...
256-bit opaque blob.
Definition uint256.h:196
constexpr 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:23
Definition basic.cpp:11
static std::optional< CCoinsStats > ComputeUTXOStats(T hash_obj, const CCoinsViewDB &view, node::BlockManager &blockman, const std::function< void()> &interruption_point)
Calculate statistics about the unspent transaction output set.
void ObserveReachabilityGoal(bool reached, const ReachabilityGoal &goal)
Record whether a registered goal is satisfied by at least one fuzz input.
void RegisterReachabilityGoal(const ReachabilityGoal &goal)
Register a goal before executing any fuzz inputs.
void WriteCompactSize(SizeComputer &os, uint64_t nSize)
Definition serialize.h:1151
node::NodeContext m_node
A reachability condition explicitly registered by a fuzz target.
bool setup_net
Testing setup that configures a complete environment.
std::unique_ptr< ChainstateManager > chainman
Definition context.h:74
#define LOCK(cs)
Definition sync.h:268
#define WITH_LOCK(cs, code)
Run code while locking a mutex.
Definition sync.h:299
SeedRandomStateForTest(SeedRand::ZEROS)
NodeSeconds ConsumeTime(FuzzedDataProvider &fuzzed_data_provider, const std::optional< int64_t > &min, const std::optional< int64_t > &max) noexcept
Definition util.cpp:34
std::vector< B > ConsumeRandomLengthByteVector(FuzzedDataProvider &fuzzed_data_provider, const std::optional< size_t > &max_length=std::nullopt) noexcept
Definition util.h:63
COutPoint ProcessBlock(const NodeContext &node, const std::shared_ptr< CBlock > &block)
Returns the generated coin (or Null if the block was invalid).
Definition mining.cpp:141
std::vector< std::shared_ptr< CBlock > > CreateBlockChain(size_t total_height, const CChainParams &params)
Create a blockchain, starting from genesis.
Definition mining.cpp:45
@ ZEROS
Seed with a compile time constant of zeros.
static int setup(void)
Definition tests.c:8289
constexpr auto Ticks(Dur2 d)
Helper to count the seconds of a duration/time_point.
Definition time.h:82
assert(!tx.IsCoinBase())
FuzzedDataProvider & fuzzed_data_provider
Definition fees.cpp:46