Skip to content

Commit 6e6ae95

Browse files
fanquakeClaude Code
authored andcommitted
Merge bitcoin#17860: fuzz: BIP 30, CVE-2018-17144
fa2d8b6 fuzz: BIP 42, BIP 30, CVE-2018-17144 (MarcoFalke) faae7d5 Move LoadVerifyActivateChainstate to ChainTestingSetup (MarcoFalke) fa26e34 Avoid dereferencing interruption_point if it is nullptr (MarcoFalke) fa846ee test: Add util to mine invalid blocks (MarcoFalke) Pull request description: Add a validation fuzz test for BIP 30 and CVE-2018-17144 ACKs for top commit: dergoegge: Code review ACK fa2d8b6 mzumsande: Tested ACK fa2d8b6 Tree-SHA512: 1f4620cc078709487abff24b304a6bb4eeab2e7628b392e2bc6de9cc0ce6745c413388ede6e93025d0c56eec905607ba9786633ef183e5779bf5183cc9ff92c0
1 parent 54e2588 commit 6e6ae95

File tree

9 files changed

+244
-16
lines changed

9 files changed

+244
-16
lines changed

src/Makefile.test.include

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,7 @@ test_fuzz_fuzz_SOURCES = \
374374
test/fuzz/tx_pool.cpp \
375375
test/fuzz/txorphan.cpp \
376376
test/fuzz/utxo_snapshot.cpp \
377+
test/fuzz/utxo_total_supply.cpp \
377378
test/fuzz/validation_load_mempool.cpp \
378379
test/fuzz/versionbits.cpp
379380
endif # ENABLE_FUZZ_BINARY

src/bench/block_assemble.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ static void AssembleBlock(benchmark::Bench& bench)
3232
std::array<CTransactionRef, NUM_BLOCKS - COINBASE_MATURITY + 1> txs;
3333
for (size_t b{0}; b < NUM_BLOCKS; ++b) {
3434
CMutableTransaction tx;
35-
tx.vin.push_back(MineBlock(test_setup->m_node, SCRIPT_PUB));
35+
tx.vin.push_back(CTxIn{MineBlock(test_setup->m_node, SCRIPT_PUB)});
3636
tx.vin.back().scriptSig = scriptSig;
3737
tx.vout.emplace_back(1337, SCRIPT_PUB);
3838
if (NUM_BLOCKS - b >= COINBASE_MATURITY)

src/node/coinstats.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ static bool GetUTXOStats(CCoinsView* view, BlockManager& blockman, CCoinsStats&
117117
uint256 prevkey;
118118
std::map<uint32_t, Coin> outputs;
119119
while (pcursor->Valid()) {
120-
interruption_point();
120+
if (interruption_point) interruption_point();
121121
COutPoint key;
122122
Coin coin;
123123
if (pcursor->GetKey(key) && pcursor->GetValue(coin)) {

src/test/fuzz/tx_pool.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,12 @@ void initialize_tx_pool()
4646
g_setup = testing_setup.get();
4747

4848
for (int i = 0; i < 2 * COINBASE_MATURITY; ++i) {
49-
CTxIn in = MineBlock(g_setup->m_node, CScript() << OP_TRUE);
49+
COutPoint prevout{MineBlock(g_setup->m_node, CScript() << OP_TRUE)};
5050
// Remember the txids to avoid expensive disk access later on
5151
auto& outpoints = i < COINBASE_MATURITY ?
5252
g_outpoints_coinbase_init_mature :
5353
g_outpoints_coinbase_init_immature;
54-
outpoints.push_back(in.prevout);
54+
outpoints.push_back(prevout);
5555
}
5656
SyncWithValidationInterfaceQueue();
5757
}
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+
}

src/test/util/mining.cpp

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,25 @@
66

77
#include <chainparams.h>
88
#include <consensus/merkle.h>
9+
#include <consensus/validation.h>
910
#include <evo/evodb.h>
1011
#include <key_io.h>
1112
#include <node/context.h>
1213
#include <node/miner.h>
1314
#include <pow.h>
15+
#include <primitives/transaction.h>
1416
#include <script/standard.h>
1517
#include <spork.h>
1618
#include <test/util/script.h>
1719
#include <util/check.h>
1820
#include <validation.h>
21+
#include <validationinterface.h>
1922
#include <versionbits.h>
2023

2124
using node::BlockAssembler;
2225
using node::NodeContext;
2326

24-
CTxIn generatetoaddress(const NodeContext& node, const std::string& address)
27+
COutPoint generatetoaddress(const NodeContext& node, const std::string& address)
2528
{
2629
const auto dest = DecodeDestination(address);
2730
assert(IsValidDestination(dest));
@@ -61,19 +64,52 @@ std::vector<std::shared_ptr<CBlock>> CreateBlockChain(size_t total_height, const
6164
return ret;
6265
}
6366

64-
CTxIn MineBlock(const NodeContext& node, const CScript& coinbase_scriptPubKey)
67+
COutPoint MineBlock(const NodeContext& node, const CScript& coinbase_scriptPubKey)
6568
{
6669
auto block = PrepareBlock(node, coinbase_scriptPubKey);
70+
auto valid = MineBlock(node, block);
71+
assert(!valid.IsNull());
72+
return valid;
73+
}
74+
75+
struct BlockValidationStateCatcher : public CValidationInterface {
76+
const uint256 m_hash;
77+
std::optional<BlockValidationState> m_state;
6778

79+
BlockValidationStateCatcher(const uint256& hash)
80+
: m_hash{hash},
81+
m_state{} {}
82+
83+
protected:
84+
void BlockChecked(const CBlock& block, const BlockValidationState& state) override
85+
{
86+
if (block.GetHash() != m_hash) return;
87+
m_state = state;
88+
}
89+
};
90+
91+
COutPoint MineBlock(const NodeContext& node, std::shared_ptr<CBlock>& block)
92+
{
6893
while (!CheckProofOfWork(block->GetHash(), block->nBits, Params().GetConsensus())) {
6994
++block->nNonce;
7095
assert(block->nNonce);
7196
}
7297

73-
bool processed{Assert(node.chainman)->ProcessNewBlock(Params(), block, true, nullptr)};
74-
assert(processed);
75-
76-
return CTxIn{block->vtx[0]->GetHash(), 0};
98+
auto& chainman{*Assert(node.chainman)};
99+
const auto old_height = WITH_LOCK(chainman.GetMutex(), return chainman.ActiveHeight());
100+
bool new_block;
101+
BlockValidationStateCatcher bvsc{block->GetHash()};
102+
RegisterValidationInterface(&bvsc);
103+
const bool processed{chainman.ProcessNewBlock(block, true, true, &new_block)};
104+
const bool duplicate{!new_block && processed};
105+
assert(!duplicate);
106+
UnregisterValidationInterface(&bvsc);
107+
SyncWithValidationInterfaceQueue();
108+
const bool was_valid{bvsc.m_state && bvsc.m_state->IsValid()};
109+
assert(old_height + was_valid == WITH_LOCK(chainman.GetMutex(), return chainman.ActiveHeight()));
110+
111+
if (was_valid) return {block->vtx[0]->GetHash(), 0};
112+
return {};
77113
}
78114

79115
std::shared_ptr<CBlock> PrepareBlock(const NodeContext& node, const CScript& coinbase_scriptPubKey)

src/test/util/mining.h

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@
1111

1212
class CBlock;
1313
class CChainParams;
14+
class COutPoint;
1415
class CScript;
15-
class CTxIn;
1616
namespace node {
1717
struct NodeContext;
1818
} // namespace node
@@ -21,12 +21,18 @@ struct NodeContext;
2121
std::vector<std::shared_ptr<CBlock>> CreateBlockChain(size_t total_height, const CChainParams& params);
2222

2323
/** Returns the generated coin */
24-
CTxIn MineBlock(const node::NodeContext&, const CScript& coinbase_scriptPubKey);
24+
COutPoint MineBlock(const node::NodeContext&, const CScript& coinbase_scriptPubKey);
25+
26+
/**
27+
* Returns the generated coin (or Null if the block was invalid).
28+
* It is recommended to call RegenerateCommitments before mining the block to avoid merkle tree mismatches.
29+
**/
30+
COutPoint MineBlock(const node::NodeContext&, std::shared_ptr<CBlock>& block);
2531

2632
/** Prepare a block to be mined */
2733
std::shared_ptr<CBlock> PrepareBlock(const node::NodeContext&, const CScript& coinbase_scriptPubKey);
2834

2935
/** RPC-like helper function, returns the generated coin */
30-
CTxIn generatetoaddress(const node::NodeContext&, const std::string& address);
36+
COutPoint generatetoaddress(const node::NodeContext&, const std::string& address);
3137

3238
#endif // BITCOIN_TEST_UTIL_MINING_H

src/test/util/setup_common.cpp

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -282,8 +282,7 @@ ChainTestingSetup::~ChainTestingSetup()
282282
m_node.chainman.reset();
283283
}
284284

285-
TestingSetup::TestingSetup(const std::string& chainName, const std::vector<const char*>& extra_args)
286-
: ChainTestingSetup(chainName, extra_args)
285+
void ChainTestingSetup::LoadVerifyActivateChainstate()
287286
{
288287
const CChainParams& chainparams = Params();
289288
// Ideally we'd move all the RPC tests to the functional testing framework
@@ -367,6 +366,18 @@ TestingSetup::TestingSetup(const std::string& chainName, const std::vector<const
367366
}
368367
}
369368

369+
TestingSetup::TestingSetup(
370+
const std::string& chainName,
371+
const std::vector<const char*>& extra_args,
372+
const bool coins_db_in_memory,
373+
const bool block_tree_db_in_memory)
374+
: ChainTestingSetup(chainName, extra_args)
375+
{
376+
m_coins_db_in_memory = coins_db_in_memory;
377+
m_block_tree_db_in_memory = block_tree_db_in_memory;
378+
LoadVerifyActivateChainstate();
379+
}
380+
370381
TestingSetup::~TestingSetup()
371382
{
372383
#ifdef ENABLE_WALLET

src/test/util/setup_common.h

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,15 +111,24 @@ struct BasicTestingSetup {
111111
*/
112112
struct ChainTestingSetup : public BasicTestingSetup {
113113
node::CacheSizes m_cache_sizes{};
114+
bool m_coins_db_in_memory{true};
115+
bool m_block_tree_db_in_memory{true};
114116

115117
explicit ChainTestingSetup(const std::string& chainName = CBaseChainParams::MAIN, const std::vector<const char*>& extra_args = {});
116118
~ChainTestingSetup();
119+
120+
// Supplies a chainstate, if one is needed
121+
void LoadVerifyActivateChainstate();
117122
};
118123

119124
/** Testing setup that configures a complete environment.
120125
*/
121126
struct TestingSetup : public ChainTestingSetup {
122-
explicit TestingSetup(const std::string& chainName = CBaseChainParams::MAIN, const std::vector<const char*>& extra_args = {});
127+
explicit TestingSetup(
128+
const std::string& chainName = CBaseChainParams::MAIN,
129+
const std::vector<const char*>& extra_args = {},
130+
const bool coins_db_in_memory = true,
131+
const bool block_tree_db_in_memory = true);
123132
~TestingSetup();
124133
};
125134

0 commit comments

Comments
 (0)