Skip to content

Commit fa2d8b6

Browse files
author
MarcoFalke
committed
fuzz: BIP 42, BIP 30, CVE-2018-17144
1 parent faae7d5 commit fa2d8b6

File tree

2 files changed

+166
-0
lines changed

2 files changed

+166
-0
lines changed

src/Makefile.test.include

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,7 @@ test_fuzz_fuzz_SOURCES = \
344344
test/fuzz/txorphan.cpp \
345345
test/fuzz/txrequest.cpp \
346346
test/fuzz/utxo_snapshot.cpp \
347+
test/fuzz/utxo_total_supply.cpp \
347348
test/fuzz/validation_load_mempool.cpp \
348349
test/fuzz/versionbits.cpp
349350
endif # ENABLE_FUZZ_BINARY

src/test/fuzz/utxo_total_supply.cpp

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
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 <validation.h>
18+
#include <version.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+
CBaseChainParams::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 2000 seems reasonable.
97+
int64_t duplicate_coinbase_height = fuzzed_data_provider.ConsumeIntegralInRange(0, 20 * COINBASE_MATURITY);
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+
LIMITED_WHILE(fuzzed_data_provider.remaining_bytes(), 100'000)
122+
{
123+
CallOneOf(
124+
fuzzed_data_provider,
125+
[&] {
126+
// Append an input-output pair to the last tx in the current block
127+
CMutableTransaction tx{*current_block->vtx.back()};
128+
AppendRandomTxo(tx);
129+
current_block->vtx.back() = MakeTransactionRef(tx);
130+
StoreLastTxo();
131+
},
132+
[&] {
133+
// Append a tx to the list of txs in the current block
134+
CMutableTransaction tx{};
135+
AppendRandomTxo(tx);
136+
current_block->vtx.push_back(MakeTransactionRef(tx));
137+
StoreLastTxo();
138+
},
139+
[&] {
140+
// Append the current block to the active chain
141+
node::RegenerateCommitments(*current_block, chainman);
142+
const bool was_valid = !MineBlock(node, current_block).IsNull();
143+
144+
const auto prev_utxo_stats = utxo_stats;
145+
if (was_valid) {
146+
circulation += GetBlockSubsidy(ActiveHeight(), Params().GetConsensus());
147+
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+
154+
UpdateUtxoStats();
155+
156+
if (!was_valid) {
157+
// utxo stats must not change
158+
assert(prev_utxo_stats.hashSerialized == utxo_stats.hashSerialized);
159+
}
160+
161+
current_block = PrepareNextBlock();
162+
StoreLastTxo();
163+
});
164+
}
165+
}

0 commit comments

Comments
 (0)