Skip to content

Commit 322ec63

Browse files
committed
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
2 parents e460c0a + fa2d8b6 commit 322ec63

File tree

9 files changed

+230
-22
lines changed

9 files changed

+230
-22
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/bench/block_assemble.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ static void AssembleBlock(benchmark::Bench& bench)
2727
std::array<CTransactionRef, NUM_BLOCKS - COINBASE_MATURITY + 1> txs;
2828
for (size_t b{0}; b < NUM_BLOCKS; ++b) {
2929
CMutableTransaction tx;
30-
tx.vin.push_back(MineBlock(test_setup->m_node, P2WSH_OP_TRUE));
30+
tx.vin.push_back(CTxIn{MineBlock(test_setup->m_node, P2WSH_OP_TRUE)});
3131
tx.vin.back().scriptWitness = witness;
3232
tx.vout.emplace_back(1337, P2WSH_OP_TRUE);
3333
if (NUM_BLOCKS - b >= COINBASE_MATURITY)

src/kernel/coinstats.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ static bool ComputeUTXOStats(CCoinsView* view, CCoinsStats& stats, T hash_obj, c
123123
uint256 prevkey;
124124
std::map<uint32_t, Coin> outputs;
125125
while (pcursor->Valid()) {
126-
interruption_point();
126+
if (interruption_point) interruption_point();
127127
COutPoint key;
128128
Coin coin;
129129
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
@@ -42,12 +42,12 @@ void initialize_tx_pool()
4242
g_setup = testing_setup.get();
4343

4444
for (int i = 0; i < 2 * COINBASE_MATURITY; ++i) {
45-
CTxIn in = MineBlock(g_setup->m_node, P2WSH_OP_TRUE);
45+
COutPoint prevout{MineBlock(g_setup->m_node, P2WSH_OP_TRUE)};
4646
// Remember the txids to avoid expensive disk access later on
4747
auto& outpoints = i < COINBASE_MATURITY ?
4848
g_outpoints_coinbase_init_mature :
4949
g_outpoints_coinbase_init_immature;
50-
outpoints.push_back(in.prevout);
50+
outpoints.push_back(prevout);
5151
}
5252
SyncWithValidationInterfaceQueue();
5353
}

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+
}

src/test/util/mining.cpp

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

77
#include <chainparams.h>
88
#include <consensus/merkle.h>
9+
#include <consensus/validation.h>
910
#include <key_io.h>
1011
#include <node/context.h>
1112
#include <pow.h>
13+
#include <primitives/transaction.h>
1214
#include <script/standard.h>
1315
#include <test/util/script.h>
1416
#include <util/check.h>
1517
#include <validation.h>
18+
#include <validationinterface.h>
1619
#include <versionbits.h>
1720

1821
using node::BlockAssembler;
1922
using node::NodeContext;
2023

21-
CTxIn generatetoaddress(const NodeContext& node, const std::string& address)
24+
COutPoint generatetoaddress(const NodeContext& node, const std::string& address)
2225
{
2326
const auto dest = DecodeDestination(address);
2427
assert(IsValidDestination(dest));
@@ -58,19 +61,52 @@ std::vector<std::shared_ptr<CBlock>> CreateBlockChain(size_t total_height, const
5861
return ret;
5962
}
6063

61-
CTxIn MineBlock(const NodeContext& node, const CScript& coinbase_scriptPubKey)
64+
COutPoint MineBlock(const NodeContext& node, const CScript& coinbase_scriptPubKey)
6265
{
6366
auto block = PrepareBlock(node, coinbase_scriptPubKey);
67+
auto valid = MineBlock(node, block);
68+
assert(!valid.IsNull());
69+
return valid;
70+
}
71+
72+
struct BlockValidationStateCatcher : public CValidationInterface {
73+
const uint256 m_hash;
74+
std::optional<BlockValidationState> m_state;
6475

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

70-
bool processed{Assert(node.chainman)->ProcessNewBlock(block, true, true, nullptr)};
71-
assert(processed);
72-
73-
return CTxIn{block->vtx[0]->GetHash(), 0};
95+
auto& chainman{*Assert(node.chainman)};
96+
const auto old_height = WITH_LOCK(chainman.GetMutex(), return chainman.ActiveHeight());
97+
bool new_block;
98+
BlockValidationStateCatcher bvsc{block->GetHash()};
99+
RegisterValidationInterface(&bvsc);
100+
const bool processed{chainman.ProcessNewBlock(block, true, true, &new_block)};
101+
const bool duplicate{!new_block && processed};
102+
assert(!duplicate);
103+
UnregisterValidationInterface(&bvsc);
104+
SyncWithValidationInterfaceQueue();
105+
const bool was_valid{bvsc.m_state && bvsc.m_state->IsValid()};
106+
assert(old_height + was_valid == WITH_LOCK(chainman.GetMutex(), return chainman.ActiveHeight()));
107+
108+
if (was_valid) return {block->vtx[0]->GetHash(), 0};
109+
return {};
74110
}
75111

76112
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
@@ -13,8 +13,8 @@
1313

1414
class CBlock;
1515
class CChainParams;
16+
class COutPoint;
1617
class CScript;
17-
class CTxIn;
1818
namespace node {
1919
struct NodeContext;
2020
} // namespace node
@@ -23,14 +23,20 @@ struct NodeContext;
2323
std::vector<std::shared_ptr<CBlock>> CreateBlockChain(size_t total_height, const CChainParams& params);
2424

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

2834
/** Prepare a block to be mined */
2935
std::shared_ptr<CBlock> PrepareBlock(const node::NodeContext&, const CScript& coinbase_scriptPubKey);
3036
std::shared_ptr<CBlock> PrepareBlock(const node::NodeContext& node, const CScript& coinbase_scriptPubKey,
3137
const node::BlockAssembler::Options& assembler_options);
3238

3339
/** RPC-like helper function, returns the generated coin */
34-
CTxIn generatetoaddress(const node::NodeContext&, const std::string& address);
40+
COutPoint generatetoaddress(const node::NodeContext&, const std::string& address);
3541

3642
#endif // BITCOIN_TEST_UTIL_MINING_H

src/test/util/setup_common.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ ChainTestingSetup::~ChainTestingSetup()
214214
m_node.chainman.reset();
215215
}
216216

217-
void TestingSetup::LoadVerifyActivateChainstate()
217+
void ChainTestingSetup::LoadVerifyActivateChainstate()
218218
{
219219
auto& chainman{*Assert(m_node.chainman)};
220220
node::ChainstateLoadOptions options;
@@ -244,10 +244,10 @@ TestingSetup::TestingSetup(
244244
const std::vector<const char*>& extra_args,
245245
const bool coins_db_in_memory,
246246
const bool block_tree_db_in_memory)
247-
: ChainTestingSetup(chainName, extra_args),
248-
m_coins_db_in_memory(coins_db_in_memory),
249-
m_block_tree_db_in_memory(block_tree_db_in_memory)
247+
: ChainTestingSetup(chainName, extra_args)
250248
{
249+
m_coins_db_in_memory = coins_db_in_memory;
250+
m_block_tree_db_in_memory = block_tree_db_in_memory;
251251
// Ideally we'd move all the RPC tests to the functional testing framework
252252
// instead of unit tests, but for now we need these here.
253253
RegisterAllCoreRPCCommands(tableRPC);

src/test/util/setup_common.h

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -93,19 +93,19 @@ struct BasicTestingSetup {
9393
*/
9494
struct ChainTestingSetup : public BasicTestingSetup {
9595
node::CacheSizes m_cache_sizes{};
96+
bool m_coins_db_in_memory{true};
97+
bool m_block_tree_db_in_memory{true};
9698

9799
explicit ChainTestingSetup(const std::string& chainName = CBaseChainParams::MAIN, const std::vector<const char*>& extra_args = {});
98100
~ChainTestingSetup();
101+
102+
// Supplies a chainstate, if one is needed
103+
void LoadVerifyActivateChainstate();
99104
};
100105

101106
/** Testing setup that configures a complete environment.
102107
*/
103108
struct TestingSetup : public ChainTestingSetup {
104-
bool m_coins_db_in_memory{true};
105-
bool m_block_tree_db_in_memory{true};
106-
107-
void LoadVerifyActivateChainstate();
108-
109109
explicit TestingSetup(
110110
const std::string& chainName = CBaseChainParams::MAIN,
111111
const std::vector<const char*>& extra_args = {},

0 commit comments

Comments
 (0)