utxo_total_supply.cpp raw

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