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