Bitcoin Core  28.1.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>
8 #include <consensus/consensus.h>
9 #include <consensus/validation.h>
10 #include <node/blockstorage.h>
11 #include <node/utxo_snapshot.h>
12 #include <primitives/block.h>
13 #include <primitives/transaction.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>
22 #include <test/util/setup_common.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 
38 namespace {
39 
40 const std::vector<std::shared_ptr<CBlock>>* g_chain;
41 TestingSetup* g_setup;
42 
43 template <bool INVALID>
44 void initialize_chain()
45 {
46  const auto params{CreateChainParams(ArgsManager{}, ChainType::REGTEST)};
47  static const auto chain{CreateBlockChain(2 * COINBASE_MATURITY, *params)};
48  g_chain = &chain;
49  static const auto setup{
50  MakeNoLogFileContext<TestingSetup>(ChainType::REGTEST,
51  TestOpts{
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 
70 template <bool INVALID>
71 void 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) {
139  BlockValidationState dummy;
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()) {
150  LOCK(::cs_main);
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.
197 FUZZ_TARGET(utxo_snapshot /*valid*/, .init = initialize_chain<false>) { utxo_snapshot_fuzz<false>(buffer); }
198 FUZZ_TARGET(utxo_snapshot_invalid, .init = initialize_chain<true>) { utxo_snapshot_fuzz<true>(buffer); }
199 
200 } // namespace
bool setup_net
Definition: setup_common.h:56
FILE * fopen(const fs::path &p, const char *mode)
Definition: fs.cpp:26
A UTXO entry.
Definition: coins.h:32
static const int COINBASE_MATURITY
Coinbase transaction outputs can only be spent after this number of new blocks (network rule) ...
Definition: consensus.h:19
Non-refcounted RAII wrapper for FILE*.
Definition: streams.h:388
std::vector< B > ConsumeRandomLengthByteVector(FuzzedDataProvider &fuzzed_data_provider, const std::optional< size_t > &max_length=std::nullopt) noexcept
Definition: util.h:57
std::span< const uint8_t > FuzzBufferType
Definition: fuzz.h:25
fs::path GetDataDirNet() const
Get data directory path with appended network identifier.
Definition: args.h:232
#define LOCK(cs)
Definition: sync.h:257
void WriteCompactSize(SizeComputer &os, uint64_t nSize)
Definition: serialize.h:1095
An outpoint - a combination of a transaction hash and an index n into its vout.
Definition: transaction.h:28
#define WITH_LOCK(cs, code)
Run code while locking a mutex.
Definition: sync.h:301
ArgsManager gArgs
Definition: args.cpp:41
256-bit opaque blob.
Definition: uint256.h:178
std::unique_ptr< const CChainParams > CreateChainParams(const ArgsManager &args, const ChainType chain)
Creates and returns a std::unique_ptr<CChainParams> of the chosen chain.
A Span is an object that can refer to a contiguous sequence of objects.
Definition: solver.h:20
#define FUZZ_TARGET(...)
Definition: fuzz.h:35
std::vector< std::shared_ptr< CBlock > > CreateBlockChain(size_t total_height, const CChainParams &params)
Create a blockchain, starting from genesis.
Definition: mining.cpp:32
RecursiveMutex cs_main
Mutex to guard access to validation specific variables, such as reading or changing the chainstate...
Definition: cs_main.cpp:8
node::NodeContext m_node
Definition: setup_common.h:66
std::unique_ptr< ChainstateManager > chainman
Definition: context.h:69
Testing setup that configures a complete environment.
Definition: setup_common.h:96
#define Assert(val)
Identity function.
Definition: check.h:77
Metadata describing a serialized version of a UTXO set from which an assumeutxo Chainstate can be con...
Definition: utxo_snapshot.h:33