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 }