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