utxo_total_supply.cpp raw

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