Bitcoin Core  31.0.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 <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>
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 <uint256.h>
25 #include <util/check.h>
26 #include <util/fs.h>
27 #include <util/result.h>
28 #include <util/time.h>
29 #include <validation.h>
30 
31 #include <cstdint>
32 #include <functional>
33 #include <ios>
34 #include <memory>
35 #include <optional>
36 #include <vector>
37 
39 
40 namespace {
41 
42 const std::vector<std::shared_ptr<CBlock>>* g_chain;
43 TestingSetup* g_setup{nullptr};
44 
46 void sanity_check_snapshot()
47 {
48  Assert(g_chain && g_setup == nullptr);
49 
50  // Create a temporary chainstate manager to connect the chain to.
51  const auto tmp_setup{MakeNoLogFileContext<TestingSetup>(ChainType::REGTEST, TestOpts{.setup_net = false})};
52  const auto& node{tmp_setup->m_node};
53  for (auto& block: *g_chain) {
54  ProcessBlock(node, block);
55  }
56 
57  // Connect the chain to the tmp chainman and sanity check the chainparams snapshot values.
58  LOCK(cs_main);
59  auto& cs{node.chainman->ActiveChainstate()};
60  cs.ForceFlushStateToDisk(/*wipe_cache=*/false);
61  const auto stats{*Assert(kernel::ComputeUTXOStats(kernel::CoinStatsHashType::HASH_SERIALIZED, &cs.CoinsDB(), node.chainman->m_blockman))};
62  const auto cp_au_data{*Assert(node.chainman->GetParams().AssumeutxoForHeight(2 * COINBASE_MATURITY))};
63  Assert(stats.nHeight == cp_au_data.height);
64  Assert(stats.nTransactions + 1 == cp_au_data.m_chain_tx_count); // +1 for the genesis tx.
65  Assert(stats.hashBlock == cp_au_data.blockhash);
66  Assert(AssumeutxoHash{stats.hashSerialized} == cp_au_data.hash_serialized);
67 }
68 
69 template <bool INVALID>
70 void initialize_chain()
71 {
72  const auto params{CreateChainParams(ArgsManager{}, ChainType::REGTEST)};
73  static const auto chain{CreateBlockChain(2 * COINBASE_MATURITY, *params)};
74  g_chain = &chain;
75  SetMockTime(chain.back()->Time());
76 
77  // Make sure we can generate a valid snapshot.
78  sanity_check_snapshot();
79 
80  static const auto setup{
81  MakeNoLogFileContext<TestingSetup>(ChainType::REGTEST,
82  TestOpts{
83  .setup_net = false,
84  .setup_validation_interface = false,
85  .min_validation_cache = true,
86  }),
87  };
88  if constexpr (INVALID) {
89  auto& chainman{*setup->m_node.chainman};
90  for (const auto& block : chain) {
92  bool processed{chainman.ProcessNewBlockHeaders({{*block}}, true, dummy)};
93  Assert(processed);
94  const auto* index{WITH_LOCK(::cs_main, return chainman.m_blockman.LookupBlockIndex(block->GetHash()))};
95  Assert(index);
96  }
97  }
98  g_setup = setup.get();
99 }
100 
101 template <bool INVALID>
102 void utxo_snapshot_fuzz(FuzzBufferType buffer)
103 {
105  FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
106  SetMockTime(ConsumeTime(fuzzed_data_provider, /*min=*/1296688602)); // regtest genesis block timestamp
107  auto& setup{*g_setup};
108  bool dirty_chainman{false}; // Reuse the global chainman, but reset it when it is dirty
109  auto& chainman{*setup.m_node.chainman};
110 
111  const auto snapshot_path = gArgs.GetDataDirNet() / "fuzzed_snapshot.dat";
112 
113  Assert(!chainman.ActiveChainstate().m_from_snapshot_blockhash);
114 
115  {
116  AutoFile outfile{fsbridge::fopen(snapshot_path, "wb")};
117  // Metadata
119  std::vector<uint8_t> metadata{ConsumeRandomLengthByteVector(fuzzed_data_provider)};
120  outfile << std::span{metadata};
121  } else {
122  auto msg_start = chainman.GetParams().MessageStart();
123  int base_blockheight{fuzzed_data_provider.ConsumeIntegralInRange<int>(1, 2 * COINBASE_MATURITY)};
124  uint256 base_blockhash{g_chain->at(base_blockheight - 1)->GetHash()};
125  uint64_t m_coins_count{fuzzed_data_provider.ConsumeIntegralInRange<uint64_t>(1, 3 * COINBASE_MATURITY)};
126  SnapshotMetadata metadata{msg_start, base_blockhash, m_coins_count};
127  outfile << metadata;
128  }
129  // Coins
131  std::vector<uint8_t> file_data{ConsumeRandomLengthByteVector(fuzzed_data_provider)};
132  outfile << std::span{file_data};
133  } else {
134  int height{1};
135  for (const auto& block : *g_chain) {
136  auto coinbase{block->vtx.at(0)};
137  outfile << coinbase->GetHash();
138  WriteCompactSize(outfile, 1); // number of coins for the hash
139  WriteCompactSize(outfile, 0); // index of coin
140  outfile << Coin(coinbase->vout[0], height, /*fCoinBaseIn=*/1);
141  height++;
142  }
143  }
144  if constexpr (INVALID) {
145  // Append an invalid coin to ensure invalidity. This error will be
146  // detected late in PopulateAndValidateSnapshot, and allows the
147  // INVALID fuzz target to reach more potential code coverage.
148  const auto& coinbase{g_chain->back()->vtx.back()};
149  outfile << coinbase->GetHash();
150  WriteCompactSize(outfile, 1); // number of coins for the hash
151  WriteCompactSize(outfile, 999); // index of coin
152  outfile << Coin{coinbase->vout[0], /*nHeightIn=*/999, /*fCoinBaseIn=*/0};
153  }
154  assert(outfile.fclose() == 0);
155  }
156 
157  const auto ActivateFuzzedSnapshot{[&] {
158  AutoFile infile{fsbridge::fopen(snapshot_path, "rb")};
159  auto msg_start = chainman.GetParams().MessageStart();
160  SnapshotMetadata metadata{msg_start};
161  try {
162  infile >> metadata;
163  } catch (const std::ios_base::failure&) {
164  return false;
165  }
166  return !!chainman.ActivateSnapshot(infile, metadata, /*in_memory=*/true);
167  }};
168 
170  // Consume the bool, but skip the code for the INVALID fuzz target
171  if constexpr (!INVALID) {
172  for (const auto& block : *g_chain) {
173  BlockValidationState dummy;
174  bool processed{chainman.ProcessNewBlockHeaders({{*block}}, true, dummy)};
175  Assert(processed);
176  const auto* index{WITH_LOCK(::cs_main, return chainman.m_blockman.LookupBlockIndex(block->GetHash()))};
177  Assert(index);
178  }
179  dirty_chainman = true;
180  }
181  }
182 
183  if (ActivateFuzzedSnapshot()) {
184  LOCK(::cs_main);
185  Assert(!chainman.ActiveChainstate().m_from_snapshot_blockhash->IsNull());
186  const auto& coinscache{chainman.ActiveChainstate().CoinsTip()};
187  for (const auto& block : *g_chain) {
188  Assert(coinscache.HaveCoin(COutPoint{block->vtx.at(0)->GetHash(), 0}));
189  const auto* index{chainman.m_blockman.LookupBlockIndex(block->GetHash())};
190  Assert(index);
191  Assert(index->nTx == 0);
192  if (index->nHeight == chainman.ActiveChainstate().SnapshotBase()->nHeight) {
193  auto params{chainman.GetParams().AssumeutxoForHeight(index->nHeight)};
194  Assert(params.has_value());
195  Assert(params.value().m_chain_tx_count == index->m_chain_tx_count);
196  } else {
197  Assert(index->m_chain_tx_count == 0);
198  }
199  }
200  Assert(g_chain->size() == coinscache.GetCacheSize());
201  dirty_chainman = true;
202  } else {
203  Assert(!chainman.ActiveChainstate().m_from_snapshot_blockhash);
204  }
205  // Snapshot should refuse to load a second time regardless of validity
206  Assert(!ActivateFuzzedSnapshot());
207  if constexpr (INVALID) {
208  // Activating the snapshot, or any other action that makes the chainman
209  // "dirty" can and must not happen for the INVALID fuzz target
210  Assert(!dirty_chainman);
211  }
212  if (dirty_chainman) {
213  setup.m_node.chainman.reset();
214  setup.m_make_chainman();
215  setup.LoadVerifyActivateChainstate();
216  }
217 }
218 
219 // There are two fuzz targets:
220 //
221 // The target 'utxo_snapshot', which allows valid snapshots, but is slow,
222 // because it has to reset the chainstate manager on almost all fuzz inputs.
223 // Otherwise, a dirty header tree or dirty chainstate could leak from one fuzz
224 // input execution into the next, which makes execution non-deterministic.
225 //
226 // The target 'utxo_snapshot_invalid', which is fast and does not require any
227 // expensive state to be reset.
228 FUZZ_TARGET(utxo_snapshot /*valid*/, .init = initialize_chain<false>) { utxo_snapshot_fuzz<false>(buffer); }
229 FUZZ_TARGET(utxo_snapshot_invalid, .init = initialize_chain<true>) { utxo_snapshot_fuzz<true>(buffer); }
230 
231 } // namespace
bool setup_net
Definition: setup_common.h:56
assert(!tx.IsCoinBase())
FILE * fopen(const fs::path &p, const char *mode)
Definition: fs.cpp:25
A UTXO entry.
Definition: coins.h:34
static const int COINBASE_MATURITY
Coinbase transaction outputs can only be spent after this number of new blocks (network rule) ...
Definition: consensus.h:19
const TestingSetup * g_setup
Non-refcounted RAII wrapper for FILE*.
Definition: streams.h:372
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:239
#define LOCK(cs)
Definition: sync.h:258
void WriteCompactSize(SizeComputer &os, uint64_t nSize)
Definition: serialize.h:1089
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:289
NodeSeconds ConsumeTime(FuzzedDataProvider &fuzzed_data_provider, const std::optional< int64_t > &min, const std::optional< int64_t > &max) noexcept
Definition: util.cpp:34
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:106
Definition: messages.h:21
ArgsManager gArgs
Definition: args.cpp:40
void SeedRandomStateForTest(SeedRand seedtype)
Seed the global RNG state for testing and log the seed value.
Definition: random.cpp:19
256-bit opaque blob.
Definition: uint256.h:195
static bool ComputeUTXOStats(CCoinsView *view, CCoinsStats &stats, T hash_obj, const std::function< void()> &interruption_point, std::unique_ptr< CCoinsViewCursor > pcursor)
Calculate statistics about the unspent transaction output set.
Definition: coinstats.cpp:112
FuzzedDataProvider & fuzzed_data_provider
Definition: fees.cpp:38
static int setup(void)
Definition: tests.c:7808
std::unique_ptr< const CChainParams > CreateChainParams(const ArgsManager &args, const ChainType chain)
Creates and returns a std::unique_ptr<CChainParams> of the chosen chain.
static void pool cs
void SetMockTime(int64_t nMockTimeIn)
DEPRECATED Use SetMockTime with chrono type.
Definition: time.cpp:44
T ConsumeIntegralInRange(T min, T max)
#define FUZZ_TARGET(...)
Definition: fuzz.h:35
Seed with a compile time constant of zeros.
std::vector< std::shared_ptr< CBlock > > CreateBlockChain(size_t total_height, const CChainParams &params)
Create a blockchain, starting from genesis.
Definition: mining.cpp:37
RecursiveMutex cs_main
Mutex to guard access to validation specific variables, such as reading or changing the chainstate...
Definition: cs_main.cpp:8
Testing setup that configures a complete environment.
Definition: setup_common.h:121
static const auto INVALID
A stack representing the lack of any (dis)satisfactions.
Definition: miniscript.h:351
#define Assert(val)
Identity function.
Definition: check.h:113
Metadata describing a serialized version of a UTXO set from which an assumeutxo Chainstate can be con...
Definition: utxo_snapshot.h:37