/ src / test / fuzz / utxo_total_supply.cpp
utxo_total_supply.cpp
  1  // Copyright (c) 2020-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 <chainparams.h>
  6  #include <consensus/consensus.h>
  7  #include <consensus/merkle.h>
  8  #include <kernel/coinstats.h>
  9  #include <node/miner.h>
 10  #include <script/interpreter.h>
 11  #include <streams.h>
 12  #include <test/fuzz/FuzzedDataProvider.h>
 13  #include <test/fuzz/fuzz.h>
 14  #include <test/fuzz/util.h>
 15  #include <test/util/mining.h>
 16  #include <test/util/setup_common.h>
 17  #include <test/util/time.h>
 18  #include <util/chaintype.h>
 19  #include <util/time.h>
 20  #include <validation.h>
 21  
 22  using node::BlockAssembler;
 23  
 24  FUZZ_TARGET(utxo_total_supply)
 25  {
 26      SeedRandomStateForTest(SeedRand::ZEROS);
 27      FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
 28      NodeClockContext clock_ctx{ConsumeTime(fuzzed_data_provider, /*min=*/1296688602)}; // regtest genesis block timestamp
 29      /** The testing setup that creates a chainman only (no chainstate) */
 30      ChainTestingSetup test_setup{
 31          ChainType::REGTEST,
 32          {
 33              .extra_args = {
 34                  "-testactivationheight=bip34@2",
 35              },
 36          },
 37      };
 38      // Create chainstate
 39      test_setup.LoadVerifyActivateChainstate();
 40      auto& node{test_setup.m_node};
 41      auto& chainman{*Assert(test_setup.m_node.chainman)};
 42  
 43      const auto ActiveHeight = [&]() {
 44          LOCK(chainman.GetMutex());
 45          return chainman.ActiveHeight();
 46      };
 47      BlockAssembler::Options options;
 48      options.coinbase_output_script = CScript() << OP_FALSE;
 49      options.include_dummy_extranonce = true;
 50      const auto PrepareNextBlock = [&]() {
 51          // Use OP_FALSE to avoid BIP30 check from hitting early
 52          auto block = PrepareBlock(node, options);
 53          // Replace OP_FALSE with OP_TRUE
 54          {
 55              CMutableTransaction tx{*block->vtx.back()};
 56              tx.nLockTime = 0; // Use the same nLockTime for all as we want to duplicate one of them.
 57              tx.vout.at(0).scriptPubKey = CScript{} << OP_TRUE;
 58              block->vtx.back() = MakeTransactionRef(tx);
 59          }
 60          return block;
 61      };
 62  
 63      /** The block template this fuzzer is working on */
 64      auto current_block = PrepareNextBlock();
 65      /** Append-only set of tx outpoints, entries are not removed when spent */
 66      std::vector<std::pair<COutPoint, CTxOut>> txos;
 67      /** The utxo stats at the chain tip */
 68      kernel::CCoinsStats utxo_stats;
 69      /** The total amount of coins in the utxo set */
 70      CAmount circulation{0};
 71  
 72  
 73      // Store the tx out in the txo map
 74      const auto StoreLastTxo = [&]() {
 75          // get last tx
 76          const CTransaction& tx = *current_block->vtx.back();
 77          // get last out
 78          const uint32_t i = tx.vout.size() - 1;
 79          // store it
 80          txos.emplace_back(COutPoint{tx.GetHash(), i}, tx.vout.at(i));
 81          if (current_block->vtx.size() == 1 && tx.vout.at(i).scriptPubKey[0] == OP_RETURN) {
 82              // also store coinbase
 83              const uint32_t i = tx.vout.size() - 2;
 84              txos.emplace_back(COutPoint{tx.GetHash(), i}, tx.vout.at(i));
 85          }
 86      };
 87      const auto AppendRandomTxo = [&](CMutableTransaction& tx) {
 88          const auto& txo = txos.at(fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, txos.size() - 1));
 89          tx.vin.emplace_back(txo.first);
 90          tx.vout.emplace_back(txo.second.nValue, txo.second.scriptPubKey); // "Forward" coin with no fee
 91      };
 92      const auto UpdateUtxoStats = [&](bool wipe_cache) {
 93          LOCK(chainman.GetMutex());
 94          chainman.ActiveChainstate().ForceFlushStateToDisk(wipe_cache);
 95          utxo_stats = std::move(
 96              *Assert(kernel::ComputeUTXOStats(kernel::CoinStatsHashType::NONE, &chainman.ActiveChainstate().CoinsDB(), chainman.m_blockman, {})));
 97          // Check that miner can't print more money than they are allowed to
 98          assert(circulation == utxo_stats.total_amount);
 99      };
100  
101  
102      // Update internal state to chain tip
103      StoreLastTxo();
104      UpdateUtxoStats(/*wipe_cache=*/fuzzed_data_provider.ConsumeBool());
105      assert(ActiveHeight() == 0);
106      // Get at which height we duplicate the coinbase
107      // Assuming that the fuzzer will mine relatively short chains (less than 200 blocks), we want the duplicate coinbase to be not too high.
108      // Up to 300 seems reasonable.
109      int64_t duplicate_coinbase_height = fuzzed_data_provider.ConsumeIntegralInRange(0, 300);
110      // Always pad with OP_0 at the end to avoid bad-cb-length error
111      const CScript duplicate_coinbase_script = CScript() << duplicate_coinbase_height << OP_0;
112      // Mine the first block with this duplicate
113      current_block = PrepareNextBlock();
114      StoreLastTxo();
115  
116      {
117          // Create duplicate (CScript should match exact format as in CreateNewBlock)
118          CMutableTransaction tx{*current_block->vtx.front()};
119          tx.vin.at(0).scriptSig = duplicate_coinbase_script;
120  
121          // Mine block and create next block template
122          current_block->vtx.front() = MakeTransactionRef(tx);
123      }
124      current_block->hashMerkleRoot = BlockMerkleRoot(*current_block);
125      assert(!MineBlock(node, current_block).IsNull());
126      circulation += GetBlockSubsidy(ActiveHeight(), Params().GetConsensus());
127  
128      assert(ActiveHeight() == 1);
129      UpdateUtxoStats(/*wipe_cache=*/fuzzed_data_provider.ConsumeBool());
130      current_block = PrepareNextBlock();
131      StoreLastTxo();
132  
133      // Limit to avoid timeout, but enough to cover duplicate_coinbase_height
134      // and CVE-2018-17144.
135      LIMITED_WHILE(fuzzed_data_provider.remaining_bytes(), 2'00)
136      {
137          CallOneOf(
138              fuzzed_data_provider,
139              [&] {
140                  // Append an input-output pair to the last tx in the current block
141                  CMutableTransaction tx{*current_block->vtx.back()};
142                  AppendRandomTxo(tx);
143                  current_block->vtx.back() = MakeTransactionRef(tx);
144                  StoreLastTxo();
145              },
146              [&] {
147                  // Append a tx to the list of txs in the current block
148                  CMutableTransaction tx{};
149                  AppendRandomTxo(tx);
150                  current_block->vtx.push_back(MakeTransactionRef(tx));
151                  StoreLastTxo();
152              },
153              [&] {
154                  // Append the current block to the active chain
155                  node::RegenerateCommitments(*current_block, chainman);
156                  const bool was_valid = !MineBlock(node, current_block).IsNull();
157  
158                  const uint256 prev_hash_serialized{utxo_stats.hashSerialized};
159                  if (was_valid) {
160                      if (duplicate_coinbase_height == ActiveHeight()) {
161                          // we mined the duplicate coinbase
162                          assert(current_block->vtx.at(0)->vin.at(0).scriptSig == duplicate_coinbase_script);
163                      }
164  
165                      circulation += GetBlockSubsidy(ActiveHeight(), Params().GetConsensus());
166                  }
167  
168                  UpdateUtxoStats(/*wipe_cache=*/fuzzed_data_provider.ConsumeBool());
169  
170                  if (!was_valid) {
171                      // utxo stats must not change
172                      assert(prev_hash_serialized == utxo_stats.hashSerialized);
173                  }
174  
175                  current_block = PrepareNextBlock();
176                  StoreLastTxo();
177              });
178      }
179  }