Skip to content

Commit faa9ef4

Browse files
author
MarcoFalke
committed
fuzz: Add tx_pool fuzz targets
1 parent 6834e02 commit faa9ef4

File tree

5 files changed

+381
-6
lines changed

5 files changed

+381
-6
lines changed

src/Makefile.test.include

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,7 @@ test_fuzz_fuzz_SOURCES = \
297297
test/fuzz/transaction.cpp \
298298
test/fuzz/tx_in.cpp \
299299
test/fuzz/tx_out.cpp \
300+
test/fuzz/tx_pool.cpp \
300301
test/fuzz/txrequest.cpp \
301302
test/fuzz/validation_load_mempool.cpp
302303
endif # ENABLE_FUZZ_BINARY

src/test/fuzz/tx_pool.cpp

Lines changed: 284 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,284 @@
1+
// Copyright (c) 2021 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 <consensus/validation.h>
6+
#include <test/fuzz/FuzzedDataProvider.h>
7+
#include <test/fuzz/fuzz.h>
8+
#include <test/fuzz/util.h>
9+
#include <test/util/mining.h>
10+
#include <test/util/script.h>
11+
#include <test/util/setup_common.h>
12+
#include <util/rbf.h>
13+
#include <validation.h>
14+
#include <validationinterface.h>
15+
16+
namespace {
17+
18+
const TestingSetup* g_setup;
19+
std::vector<COutPoint> g_outpoints_coinbase_init;
20+
21+
struct MockedTxPool : public CTxMemPool {
22+
void RollingFeeUpdate()
23+
{
24+
lastRollingFeeUpdate = GetTime();
25+
blockSinceLastRollingFeeBump = true;
26+
}
27+
};
28+
29+
void initialize_tx_pool()
30+
{
31+
static const auto testing_setup = MakeNoLogFileContext<const TestingSetup>();
32+
g_setup = testing_setup.get();
33+
34+
for (int i = 0; i < 2 * COINBASE_MATURITY; ++i) {
35+
CTxIn in = MineBlock(g_setup->m_node, P2WSH_OP_TRUE);
36+
// Remember the txids to avoid expensive disk acess later on
37+
g_outpoints_coinbase_init.push_back(in.prevout);
38+
}
39+
SyncWithValidationInterfaceQueue();
40+
}
41+
42+
struct TransactionsDelta final : public CValidationInterface {
43+
std::set<CTransactionRef>& m_removed;
44+
std::set<CTransactionRef>& m_added;
45+
46+
explicit TransactionsDelta(std::set<CTransactionRef>& r, std::set<CTransactionRef>& a)
47+
: m_removed{r}, m_added{a} {}
48+
49+
void TransactionAddedToMempool(const CTransactionRef& tx, uint64_t /* mempool_sequence */) override
50+
{
51+
Assert(m_added.insert(tx).second);
52+
}
53+
54+
void TransactionRemovedFromMempool(const CTransactionRef& tx, MemPoolRemovalReason reason, uint64_t /* mempool_sequence */) override
55+
{
56+
Assert(m_removed.insert(tx).second);
57+
}
58+
};
59+
60+
void SetMempoolConstraints(ArgsManager& args, FuzzedDataProvider& fuzzed_data_provider)
61+
{
62+
args.ForceSetArg("-limitancestorcount",
63+
ToString(fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, 50)));
64+
args.ForceSetArg("-limitancestorsize",
65+
ToString(fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, 202)));
66+
args.ForceSetArg("-limitdescendantcount",
67+
ToString(fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, 50)));
68+
args.ForceSetArg("-limitdescendantsize",
69+
ToString(fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, 202)));
70+
args.ForceSetArg("-maxmempool",
71+
ToString(fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, 200)));
72+
args.ForceSetArg("-mempoolexpiry",
73+
ToString(fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, 999)));
74+
}
75+
76+
FUZZ_TARGET_INIT(tx_pool_standard, initialize_tx_pool)
77+
{
78+
FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
79+
const auto& node = g_setup->m_node;
80+
auto& chainstate = node.chainman->ActiveChainstate();
81+
82+
SetMockTime(ConsumeTime(fuzzed_data_provider));
83+
SetMempoolConstraints(*node.args, fuzzed_data_provider);
84+
85+
// All RBF-spendable outpoints
86+
std::set<COutPoint> outpoints_rbf;
87+
// All outpoints counting toward the total supply (subset of outpoints_rbf)
88+
std::set<COutPoint> outpoints_supply;
89+
for (const auto& outpoint : g_outpoints_coinbase_init) {
90+
Assert(outpoints_supply.insert(outpoint).second);
91+
if (outpoints_supply.size() >= COINBASE_MATURITY) break;
92+
}
93+
outpoints_rbf = outpoints_supply;
94+
95+
// The sum of the values of all spendable outpoints
96+
constexpr CAmount SUPPLY_TOTAL{COINBASE_MATURITY * 50 * COIN};
97+
98+
CTxMemPool tx_pool_{/* estimator */ nullptr, /* check_ratio */ 1};
99+
MockedTxPool& tx_pool = *(MockedTxPool*)&tx_pool_;
100+
101+
// Helper to query an amount
102+
const CCoinsViewMemPool amount_view{WITH_LOCK(::cs_main, return &chainstate.CoinsTip()), tx_pool};
103+
const auto GetAmount = [&](const COutPoint& outpoint) {
104+
Coin c;
105+
amount_view.GetCoin(outpoint, c);
106+
Assert(!c.IsSpent());
107+
return c.out.nValue;
108+
};
109+
110+
while (fuzzed_data_provider.ConsumeBool()) {
111+
{
112+
// Total supply is the mempool fee + all outpoints
113+
CAmount supply_now{WITH_LOCK(tx_pool.cs, return tx_pool.GetTotalFee())};
114+
for (const auto& op : outpoints_supply) {
115+
supply_now += GetAmount(op);
116+
}
117+
Assert(supply_now == SUPPLY_TOTAL);
118+
}
119+
Assert(!outpoints_supply.empty());
120+
121+
// Create transaction to add to the mempool
122+
const CTransactionRef tx = [&] {
123+
CMutableTransaction tx_mut;
124+
tx_mut.nVersion = CTransaction::CURRENT_VERSION;
125+
tx_mut.nLockTime = fuzzed_data_provider.ConsumeBool() ? 0 : fuzzed_data_provider.ConsumeIntegral<uint32_t>();
126+
const auto num_in = fuzzed_data_provider.ConsumeIntegralInRange<int>(1, outpoints_rbf.size());
127+
const auto num_out = fuzzed_data_provider.ConsumeIntegralInRange<int>(1, outpoints_rbf.size() * 2);
128+
129+
CAmount amount_in{0};
130+
for (int i = 0; i < num_in; ++i) {
131+
// Pop random outpoint
132+
auto pop = outpoints_rbf.begin();
133+
std::advance(pop, fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, outpoints_rbf.size() - 1));
134+
const auto outpoint = *pop;
135+
outpoints_rbf.erase(pop);
136+
amount_in += GetAmount(outpoint);
137+
138+
// Create input
139+
const auto sequence = ConsumeSequence(fuzzed_data_provider);
140+
const auto script_sig = CScript{};
141+
const auto script_wit_stack = std::vector<std::vector<uint8_t>>{WITNESS_STACK_ELEM_OP_TRUE};
142+
CTxIn in;
143+
in.prevout = outpoint;
144+
in.nSequence = sequence;
145+
in.scriptSig = script_sig;
146+
in.scriptWitness.stack = script_wit_stack;
147+
148+
tx_mut.vin.push_back(in);
149+
}
150+
const auto amount_fee = fuzzed_data_provider.ConsumeIntegralInRange<CAmount>(-1000, amount_in);
151+
const auto amount_out = (amount_in - amount_fee) / num_out;
152+
for (int i = 0; i < num_out; ++i) {
153+
tx_mut.vout.emplace_back(amount_out, P2WSH_OP_TRUE);
154+
}
155+
const auto tx = MakeTransactionRef(tx_mut);
156+
// Restore previously removed outpoints
157+
for (const auto& in : tx->vin) {
158+
Assert(outpoints_rbf.insert(in.prevout).second);
159+
}
160+
return tx;
161+
}();
162+
163+
if (fuzzed_data_provider.ConsumeBool()) {
164+
SetMockTime(ConsumeTime(fuzzed_data_provider));
165+
}
166+
if (fuzzed_data_provider.ConsumeBool()) {
167+
SetMempoolConstraints(*node.args, fuzzed_data_provider);
168+
}
169+
if (fuzzed_data_provider.ConsumeBool()) {
170+
tx_pool.RollingFeeUpdate();
171+
}
172+
if (fuzzed_data_provider.ConsumeBool()) {
173+
const auto& txid = fuzzed_data_provider.ConsumeBool() ?
174+
tx->GetHash() :
175+
PickValue(fuzzed_data_provider, outpoints_rbf).hash;
176+
const auto delta = fuzzed_data_provider.ConsumeIntegralInRange<CAmount>(-50 * COIN, +50 * COIN);
177+
tx_pool.PrioritiseTransaction(txid, delta);
178+
}
179+
180+
// Remember all removed and added transactions
181+
std::set<CTransactionRef> removed;
182+
std::set<CTransactionRef> added;
183+
auto txr = std::make_shared<TransactionsDelta>(removed, added);
184+
RegisterSharedValidationInterface(txr);
185+
const bool bypass_limits = fuzzed_data_provider.ConsumeBool();
186+
::fRequireStandard = fuzzed_data_provider.ConsumeBool();
187+
const auto res = WITH_LOCK(::cs_main, return AcceptToMemoryPool(chainstate, tx_pool, tx, bypass_limits));
188+
const bool accepted = res.m_result_type == MempoolAcceptResult::ResultType::VALID;
189+
SyncWithValidationInterfaceQueue();
190+
UnregisterSharedValidationInterface(txr);
191+
192+
Assert(accepted != added.empty());
193+
Assert(accepted == res.m_state.IsValid());
194+
Assert(accepted != res.m_state.IsInvalid());
195+
if (accepted) {
196+
Assert(added.size() == 1); // For now, no package acceptance
197+
Assert(tx == *added.begin());
198+
} else {
199+
// Do not consider rejected transaction removed
200+
removed.erase(tx);
201+
}
202+
203+
// Helper to insert spent and created outpoints of a tx into collections
204+
using Sets = std::vector<std::reference_wrapper<std::set<COutPoint>>>;
205+
const auto insert_tx = [](Sets created_by_tx, Sets consumed_by_tx, const auto& tx) {
206+
for (size_t i{0}; i < tx.vout.size(); ++i) {
207+
for (auto& set : created_by_tx) {
208+
Assert(set.get().emplace(tx.GetHash(), i).second);
209+
}
210+
}
211+
for (const auto& in : tx.vin) {
212+
for (auto& set : consumed_by_tx) {
213+
Assert(set.get().insert(in.prevout).second);
214+
}
215+
}
216+
};
217+
// Add created outpoints, remove spent outpoints
218+
{
219+
// Outpoints that no longer exist at all
220+
std::set<COutPoint> consumed_erased;
221+
// Outpoints that no longer count toward the total supply
222+
std::set<COutPoint> consumed_supply;
223+
for (const auto& removed_tx : removed) {
224+
insert_tx(/* created_by_tx */ {consumed_erased}, /* consumed_by_tx */ {outpoints_supply}, /* tx */ *removed_tx);
225+
}
226+
for (const auto& added_tx : added) {
227+
insert_tx(/* created_by_tx */ {outpoints_supply, outpoints_rbf}, /* consumed_by_tx */ {consumed_supply}, /* tx */ *added_tx);
228+
}
229+
for (const auto& p : consumed_erased) {
230+
Assert(outpoints_supply.erase(p) == 1);
231+
Assert(outpoints_rbf.erase(p) == 1);
232+
}
233+
for (const auto& p : consumed_supply) {
234+
Assert(outpoints_supply.erase(p) == 1);
235+
}
236+
}
237+
}
238+
WITH_LOCK(::cs_main, tx_pool.check(chainstate));
239+
const auto info_all = tx_pool.infoAll();
240+
if (!info_all.empty()) {
241+
const auto& tx_to_remove = *PickValue(fuzzed_data_provider, info_all).tx;
242+
WITH_LOCK(tx_pool.cs, tx_pool.removeRecursive(tx_to_remove, /* dummy */ MemPoolRemovalReason::BLOCK));
243+
std::vector<uint256> all_txids;
244+
tx_pool.queryHashes(all_txids);
245+
assert(all_txids.size() < info_all.size());
246+
WITH_LOCK(::cs_main, tx_pool.check(chainstate));
247+
}
248+
SyncWithValidationInterfaceQueue();
249+
}
250+
251+
FUZZ_TARGET_INIT(tx_pool, initialize_tx_pool)
252+
{
253+
FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
254+
const auto& node = g_setup->m_node;
255+
256+
std::vector<uint256> txids;
257+
for (const auto& outpoint : g_outpoints_coinbase_init) {
258+
txids.push_back(outpoint.hash);
259+
if (txids.size() >= COINBASE_MATURITY) break;
260+
}
261+
for (int i{0}; i <= 3; ++i) {
262+
// Add some immature and non-existent outpoints
263+
txids.push_back(g_outpoints_coinbase_init.at(i).hash);
264+
txids.push_back(ConsumeUInt256(fuzzed_data_provider));
265+
}
266+
267+
CTxMemPool tx_pool{/* estimator */ nullptr, /* check_ratio */ 1};
268+
269+
while (fuzzed_data_provider.ConsumeBool()) {
270+
const auto mut_tx = ConsumeTransaction(fuzzed_data_provider, txids);
271+
272+
const auto tx = MakeTransactionRef(mut_tx);
273+
const bool bypass_limits = fuzzed_data_provider.ConsumeBool();
274+
::fRequireStandard = fuzzed_data_provider.ConsumeBool();
275+
const auto res = WITH_LOCK(::cs_main, return AcceptToMemoryPool(node.chainman->ActiveChainstate(), tx_pool, tx, bypass_limits));
276+
const bool accepted = res.m_result_type == MempoolAcceptResult::ResultType::VALID;
277+
if (accepted) {
278+
txids.push_back(tx->GetHash());
279+
}
280+
281+
SyncWithValidationInterfaceQueue();
282+
}
283+
}
284+
} // namespace

src/test/fuzz/util.cpp

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@
33
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
44

55
#include <test/fuzz/util.h>
6+
#include <test/util/script.h>
7+
#include <util/rbf.h>
68
#include <version.h>
79

10+
811
void FillNode(FuzzedDataProvider& fuzzed_data_provider, CNode& node, bool init_version) noexcept
912
{
1013
const ServiceFlags remote_services = ConsumeWeakEnum(fuzzed_data_provider, ALL_SERVICE_FLAGS);
@@ -23,3 +26,78 @@ void FillNode(FuzzedDataProvider& fuzzed_data_provider, CNode& node, bool init_v
2326
node.m_tx_relay->fRelayTxes = filter_txs;
2427
}
2528
}
29+
30+
CMutableTransaction ConsumeTransaction(FuzzedDataProvider& fuzzed_data_provider, const std::optional<std::vector<uint256>>& prevout_txids, const int max_num_in, const int max_num_out) noexcept
31+
{
32+
CMutableTransaction tx_mut;
33+
const auto p2wsh_op_true = fuzzed_data_provider.ConsumeBool();
34+
tx_mut.nVersion = fuzzed_data_provider.ConsumeBool() ?
35+
CTransaction::CURRENT_VERSION :
36+
fuzzed_data_provider.ConsumeIntegral<int32_t>();
37+
tx_mut.nLockTime = fuzzed_data_provider.ConsumeIntegral<uint32_t>();
38+
const auto num_in = fuzzed_data_provider.ConsumeIntegralInRange<int>(0, max_num_in);
39+
const auto num_out = fuzzed_data_provider.ConsumeIntegralInRange<int>(0, max_num_out);
40+
for (int i = 0; i < num_in; ++i) {
41+
const auto& txid_prev = prevout_txids ?
42+
PickValue(fuzzed_data_provider, *prevout_txids) :
43+
ConsumeUInt256(fuzzed_data_provider);
44+
const auto index_out = fuzzed_data_provider.ConsumeIntegralInRange<uint32_t>(0, max_num_out);
45+
const auto sequence = ConsumeSequence(fuzzed_data_provider);
46+
const auto script_sig = p2wsh_op_true ? CScript{} : ConsumeScript(fuzzed_data_provider);
47+
CScriptWitness script_wit;
48+
if (p2wsh_op_true) {
49+
script_wit.stack = std::vector<std::vector<uint8_t>>{WITNESS_STACK_ELEM_OP_TRUE};
50+
} else {
51+
script_wit = ConsumeScriptWitness(fuzzed_data_provider);
52+
}
53+
CTxIn in;
54+
in.prevout = COutPoint{txid_prev, index_out};
55+
in.nSequence = sequence;
56+
in.scriptSig = script_sig;
57+
in.scriptWitness = script_wit;
58+
59+
tx_mut.vin.push_back(in);
60+
}
61+
for (int i = 0; i < num_out; ++i) {
62+
const auto amount = fuzzed_data_provider.ConsumeIntegralInRange<CAmount>(-10, 50 * COIN + 10);
63+
const auto script_pk = p2wsh_op_true ?
64+
P2WSH_OP_TRUE :
65+
ConsumeScript(fuzzed_data_provider, /* max_length */ 128, /* maybe_p2wsh */ true);
66+
tx_mut.vout.emplace_back(amount, script_pk);
67+
}
68+
return tx_mut;
69+
}
70+
71+
CScriptWitness ConsumeScriptWitness(FuzzedDataProvider& fuzzed_data_provider, const size_t max_stack_elem_size) noexcept
72+
{
73+
CScriptWitness ret;
74+
const auto n_elements = fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, max_stack_elem_size);
75+
for (size_t i = 0; i < n_elements; ++i) {
76+
ret.stack.push_back(ConsumeRandomLengthByteVector(fuzzed_data_provider));
77+
}
78+
return ret;
79+
}
80+
81+
CScript ConsumeScript(FuzzedDataProvider& fuzzed_data_provider, const size_t max_length, const bool maybe_p2wsh) noexcept
82+
{
83+
const std::vector<uint8_t> b = ConsumeRandomLengthByteVector(fuzzed_data_provider);
84+
CScript r_script{b.begin(), b.end()};
85+
if (maybe_p2wsh && fuzzed_data_provider.ConsumeBool()) {
86+
uint256 script_hash;
87+
CSHA256().Write(&r_script[0], r_script.size()).Finalize(script_hash.begin());
88+
r_script.clear();
89+
r_script << OP_0 << ToByteVector(script_hash);
90+
}
91+
return r_script;
92+
}
93+
94+
uint32_t ConsumeSequence(FuzzedDataProvider& fuzzed_data_provider) noexcept
95+
{
96+
return fuzzed_data_provider.ConsumeBool() ?
97+
fuzzed_data_provider.PickValueInArray({
98+
CTxIn::SEQUENCE_FINAL,
99+
CTxIn::SEQUENCE_FINAL - 1,
100+
MAX_BIP125_RBF_SEQUENCE,
101+
}) :
102+
fuzzed_data_provider.ConsumeIntegral<uint32_t>();
103+
}

0 commit comments

Comments
 (0)