/root/bitcoin/src/test/fuzz/utxo_total_supply.cpp
Line | Count | Source (jump to first uncovered line) |
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 <util/time.h> |
19 | | #include <validation.h> |
20 | | |
21 | | using node::BlockAssembler; |
22 | | |
23 | | FUZZ_TARGET(utxo_total_supply) |
24 | 0 | { |
25 | 0 | SeedRandomStateForTest(SeedRand::ZEROS); |
26 | 0 | FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size()); |
27 | 0 | 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 | 0 | ChainTestingSetup test_setup{ |
30 | 0 | ChainType::REGTEST, |
31 | 0 | { |
32 | 0 | .extra_args = { |
33 | 0 | "-testactivationheight=bip34@2", |
34 | 0 | strprintf("-mocktime=%d", mock_time).c_str() |
35 | 0 | }, |
36 | 0 | }, |
37 | 0 | }; |
38 | | // Create chainstate |
39 | 0 | test_setup.LoadVerifyActivateChainstate(); |
40 | 0 | auto& node{test_setup.m_node}; |
41 | 0 | auto& chainman{*Assert(test_setup.m_node.chainman)}; |
42 | |
|
43 | 0 | const auto ActiveHeight = [&]() { |
44 | 0 | LOCK(chainman.GetMutex()); |
45 | 0 | return chainman.ActiveHeight(); |
46 | 0 | }; |
47 | 0 | BlockAssembler::Options options; |
48 | 0 | options.coinbase_output_script = CScript() << OP_FALSE; |
49 | 0 | const auto PrepareNextBlock = [&]() { |
50 | | // Use OP_FALSE to avoid BIP30 check from hitting early |
51 | 0 | auto block = PrepareBlock(node, options); |
52 | | // Replace OP_FALSE with OP_TRUE |
53 | 0 | { |
54 | 0 | CMutableTransaction tx{*block->vtx.back()}; |
55 | 0 | tx.vout.at(0).scriptPubKey = CScript{} << OP_TRUE; |
56 | 0 | block->vtx.back() = MakeTransactionRef(tx); |
57 | 0 | } |
58 | 0 | return block; |
59 | 0 | }; |
60 | | |
61 | | /** The block template this fuzzer is working on */ |
62 | 0 | auto current_block = PrepareNextBlock(); |
63 | | /** Append-only set of tx outpoints, entries are not removed when spent */ |
64 | 0 | std::vector<std::pair<COutPoint, CTxOut>> txos; |
65 | | /** The utxo stats at the chain tip */ |
66 | 0 | kernel::CCoinsStats utxo_stats; |
67 | | /** The total amount of coins in the utxo set */ |
68 | 0 | CAmount circulation{0}; |
69 | | |
70 | | |
71 | | // Store the tx out in the txo map |
72 | 0 | const auto StoreLastTxo = [&]() { |
73 | | // get last tx |
74 | 0 | const CTransaction& tx = *current_block->vtx.back(); |
75 | | // get last out |
76 | 0 | const uint32_t i = tx.vout.size() - 1; |
77 | | // store it |
78 | 0 | txos.emplace_back(COutPoint{tx.GetHash(), i}, tx.vout.at(i)); |
79 | 0 | if (current_block->vtx.size() == 1 && tx.vout.at(i).scriptPubKey[0] == OP_RETURN) { Branch (79:13): [True: 0, False: 0]
Branch (79:47): [True: 0, False: 0]
|
80 | | // also store coinbase |
81 | 0 | const uint32_t i = tx.vout.size() - 2; |
82 | 0 | txos.emplace_back(COutPoint{tx.GetHash(), i}, tx.vout.at(i)); |
83 | 0 | } |
84 | 0 | }; |
85 | 0 | const auto AppendRandomTxo = [&](CMutableTransaction& tx) { |
86 | 0 | const auto& txo = txos.at(fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, txos.size() - 1)); |
87 | 0 | tx.vin.emplace_back(txo.first); |
88 | 0 | tx.vout.emplace_back(txo.second.nValue, txo.second.scriptPubKey); // "Forward" coin with no fee |
89 | 0 | }; |
90 | 0 | const auto UpdateUtxoStats = [&]() { |
91 | 0 | LOCK(chainman.GetMutex()); |
92 | 0 | chainman.ActiveChainstate().ForceFlushStateToDisk(); |
93 | 0 | utxo_stats = std::move( |
94 | 0 | *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 | 0 | assert(circulation == utxo_stats.total_amount); |
97 | 0 | }; |
98 | | |
99 | | |
100 | | // Update internal state to chain tip |
101 | 0 | StoreLastTxo(); |
102 | 0 | UpdateUtxoStats(); |
103 | 0 | 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 | 0 | 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 | 0 | const CScript duplicate_coinbase_script = CScript() << duplicate_coinbase_height << OP_0; |
110 | | // Mine the first block with this duplicate |
111 | 0 | current_block = PrepareNextBlock(); |
112 | 0 | StoreLastTxo(); |
113 | |
|
114 | 0 | { |
115 | | // Create duplicate (CScript should match exact format as in CreateNewBlock) |
116 | 0 | CMutableTransaction tx{*current_block->vtx.front()}; |
117 | 0 | tx.vin.at(0).scriptSig = duplicate_coinbase_script; |
118 | | |
119 | | // Mine block and create next block template |
120 | 0 | current_block->vtx.front() = MakeTransactionRef(tx); |
121 | 0 | } |
122 | 0 | current_block->hashMerkleRoot = BlockMerkleRoot(*current_block); |
123 | 0 | assert(!MineBlock(node, current_block).IsNull()); |
124 | 0 | circulation += GetBlockSubsidy(ActiveHeight(), Params().GetConsensus()); |
125 | |
|
126 | 0 | assert(ActiveHeight() == 1); |
127 | 0 | UpdateUtxoStats(); |
128 | 0 | current_block = PrepareNextBlock(); |
129 | 0 | StoreLastTxo(); |
130 | | |
131 | | // Limit to avoid timeout, but enough to cover duplicate_coinbase_height |
132 | | // and CVE-2018-17144. |
133 | 0 | LIMITED_WHILE(fuzzed_data_provider.remaining_bytes(), 2'00) |
134 | 0 | { |
135 | 0 | CallOneOf( |
136 | 0 | fuzzed_data_provider, |
137 | 0 | [&] { |
138 | | // Append an input-output pair to the last tx in the current block |
139 | 0 | CMutableTransaction tx{*current_block->vtx.back()}; |
140 | 0 | AppendRandomTxo(tx); |
141 | 0 | current_block->vtx.back() = MakeTransactionRef(tx); |
142 | 0 | StoreLastTxo(); |
143 | 0 | }, |
144 | 0 | [&] { |
145 | | // Append a tx to the list of txs in the current block |
146 | 0 | CMutableTransaction tx{}; |
147 | 0 | AppendRandomTxo(tx); |
148 | 0 | current_block->vtx.push_back(MakeTransactionRef(tx)); |
149 | 0 | StoreLastTxo(); |
150 | 0 | }, |
151 | 0 | [&] { |
152 | | // Append the current block to the active chain |
153 | 0 | node::RegenerateCommitments(*current_block, chainman); |
154 | 0 | const bool was_valid = !MineBlock(node, current_block).IsNull(); |
155 | |
|
156 | 0 | const auto prev_utxo_stats = utxo_stats; |
157 | 0 | if (was_valid) { Branch (157:21): [True: 0, False: 0]
|
158 | 0 | if (duplicate_coinbase_height == ActiveHeight()) { Branch (158:25): [True: 0, False: 0]
|
159 | | // we mined the duplicate coinbase |
160 | 0 | assert(current_block->vtx.at(0)->vin.at(0).scriptSig == duplicate_coinbase_script); |
161 | 0 | } |
162 | | |
163 | 0 | circulation += GetBlockSubsidy(ActiveHeight(), Params().GetConsensus()); |
164 | 0 | } |
165 | | |
166 | 0 | UpdateUtxoStats(); |
167 | |
|
168 | 0 | if (!was_valid) { Branch (168:21): [True: 0, False: 0]
|
169 | | // utxo stats must not change |
170 | 0 | assert(prev_utxo_stats.hashSerialized == utxo_stats.hashSerialized); |
171 | 0 | } |
172 | | |
173 | 0 | current_block = PrepareNextBlock(); |
174 | 0 | StoreLastTxo(); |
175 | 0 | }); |
176 | 0 | } |
177 | 0 | } |