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 }