Skip to content

Commit 433411a

Browse files
committed
[unit tests] individual RBF Rules in isolation
Test each component of the RBF policy in isolation. Unlike the RBF functional tests, these do not rely on things like RPC results, mempool submission, etc.
1 parent d1e4265 commit 433411a

File tree

2 files changed

+226
-0
lines changed

2 files changed

+226
-0
lines changed

src/Makefile.test.include

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ BITCOIN_TESTS =\
117117
test/prevector_tests.cpp \
118118
test/raii_event_tests.cpp \
119119
test/random_tests.cpp \
120+
test/rbf_tests.cpp \
120121
test/rest_tests.cpp \
121122
test/reverselock_tests.cpp \
122123
test/rpc_tests.cpp \

src/test/rbf_tests.cpp

Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
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+
#include <policy/policy.h>
5+
#include <policy/rbf.h>
6+
#include <random.h>
7+
#include <txmempool.h>
8+
#include <util/system.h>
9+
#include <util/time.h>
10+
11+
#include <test/util/setup_common.h>
12+
13+
#include <boost/test/unit_test.hpp>
14+
#include <optional>
15+
#include <vector>
16+
17+
BOOST_FIXTURE_TEST_SUITE(rbf_tests, TestingSetup)
18+
19+
inline CTransactionRef make_tx(const std::vector<CAmount>& output_values,
20+
const std::vector<CTransactionRef>& inputs)
21+
{
22+
CMutableTransaction tx = CMutableTransaction();
23+
tx.vin.resize(inputs.size());
24+
tx.vout.resize(output_values.size());
25+
for (size_t i = 0; i < inputs.size(); ++i) {
26+
tx.vin[i].prevout.hash = inputs[i]->GetHash();
27+
tx.vin[i].prevout.n = 0;
28+
// Add a witness so wtxid != txid
29+
CScriptWitness witness;
30+
witness.stack.push_back(std::vector<unsigned char>(i + 10));
31+
tx.vin[i].scriptWitness = witness;
32+
}
33+
for (size_t i = 0; i < output_values.size(); ++i) {
34+
tx.vout[i].scriptPubKey = CScript() << OP_11 << OP_EQUAL;
35+
tx.vout[i].nValue = output_values[i];
36+
}
37+
return MakeTransactionRef(tx);
38+
}
39+
40+
void add_descendants(const CTransactionRef& tx, CTxMemPool& pool, int32_t num_descendants)
41+
EXCLUSIVE_LOCKS_REQUIRED(cs_main, pool.cs)
42+
{
43+
AssertLockHeld(cs_main);
44+
AssertLockHeld(pool.cs);
45+
TestMemPoolEntryHelper entry;
46+
// Assumes this isn't already spent in mempool
47+
auto tx_to_spend = tx;
48+
for (int32_t i{0}; i < num_descendants; ++i) {
49+
auto next_tx = make_tx(/*output_values=*/ {(50 - i) * CENT}, /*inputs=*/ {tx_to_spend});
50+
pool.addUnchecked(entry.FromTx(next_tx));
51+
tx_to_spend = next_tx;
52+
}
53+
}
54+
55+
BOOST_FIXTURE_TEST_CASE(rbf_helper_functions, TestChain100Setup)
56+
{
57+
CTxMemPool& pool = *Assert(m_node.mempool);
58+
LOCK2(cs_main, pool.cs);
59+
TestMemPoolEntryHelper entry;
60+
61+
const CAmount low_fee{100};
62+
const CAmount normal_fee{10000};
63+
const CAmount high_fee{1 * COIN};
64+
65+
// Create a parent tx1 and child tx2 with normal fees:
66+
CTransactionRef tx1 = make_tx(/*output_values=*/ {10 * COIN}, /*inputs=*/ {m_coinbase_txns[0]});
67+
pool.addUnchecked(entry.Fee(normal_fee).FromTx(tx1));
68+
CTransactionRef tx2 = make_tx(/*output_values=*/ {995 * CENT}, /*inputs=*/ {tx1});
69+
pool.addUnchecked(entry.Fee(normal_fee).FromTx(tx2));
70+
71+
// Create a low-feerate parent tx3 and high-feerate child tx4 (cpfp)
72+
CTransactionRef tx3 = make_tx(/*output_values=*/ {1099 * CENT}, /*inputs=*/ {m_coinbase_txns[1]});
73+
pool.addUnchecked(entry.Fee(low_fee).FromTx(tx3));
74+
CTransactionRef tx4 = make_tx(/*output_values=*/ {999 * CENT}, /*inputs=*/ {tx3});
75+
pool.addUnchecked(entry.Fee(high_fee).FromTx(tx4));
76+
77+
// Create a parent tx5 and child tx6 where both have very low fees
78+
CTransactionRef tx5 = make_tx(/*output_values=*/ {1099 * CENT}, /*inputs=*/ {m_coinbase_txns[2]});
79+
pool.addUnchecked(entry.Fee(low_fee).FromTx(tx5));
80+
CTransactionRef tx6 = make_tx(/*output_values=*/ {1098 * CENT}, /*inputs=*/ {tx3});
81+
pool.addUnchecked(entry.Fee(low_fee).FromTx(tx6));
82+
// Make tx6's modified fee much higher than its base fee. This should cause it to pass
83+
// the fee-related checks despite being low-feerate.
84+
pool.PrioritiseTransaction(tx6->GetHash(), 1 * COIN);
85+
86+
// Two independent high-feerate transactions, tx7 and tx8
87+
CTransactionRef tx7 = make_tx(/*output_values=*/ {999 * CENT}, /*inputs=*/ {m_coinbase_txns[3]});
88+
pool.addUnchecked(entry.Fee(high_fee).FromTx(tx7));
89+
CTransactionRef tx8 = make_tx(/*output_values=*/ {999 * CENT}, /*inputs=*/ {m_coinbase_txns[4]});
90+
pool.addUnchecked(entry.Fee(high_fee).FromTx(tx8));
91+
92+
const auto entry1 = pool.GetIter(tx1->GetHash()).value();
93+
const auto entry2 = pool.GetIter(tx2->GetHash()).value();
94+
const auto entry3 = pool.GetIter(tx3->GetHash()).value();
95+
const auto entry4 = pool.GetIter(tx4->GetHash()).value();
96+
const auto entry5 = pool.GetIter(tx5->GetHash()).value();
97+
const auto entry6 = pool.GetIter(tx6->GetHash()).value();
98+
const auto entry7 = pool.GetIter(tx7->GetHash()).value();
99+
const auto entry8 = pool.GetIter(tx8->GetHash()).value();
100+
101+
BOOST_CHECK_EQUAL(entry1->GetFee(), normal_fee);
102+
BOOST_CHECK_EQUAL(entry2->GetFee(), normal_fee);
103+
BOOST_CHECK_EQUAL(entry3->GetFee(), low_fee);
104+
BOOST_CHECK_EQUAL(entry4->GetFee(), high_fee);
105+
BOOST_CHECK_EQUAL(entry5->GetFee(), low_fee);
106+
BOOST_CHECK_EQUAL(entry6->GetFee(), low_fee);
107+
BOOST_CHECK_EQUAL(entry7->GetFee(), high_fee);
108+
BOOST_CHECK_EQUAL(entry8->GetFee(), high_fee);
109+
110+
CTxMemPool::setEntries set_12_normal{entry1, entry2};
111+
CTxMemPool::setEntries set_34_cpfp{entry3, entry4};
112+
CTxMemPool::setEntries set_56_low{entry5, entry6};
113+
CTxMemPool::setEntries all_entries{entry1, entry2, entry3, entry4, entry5, entry6, entry7, entry8};
114+
CTxMemPool::setEntries empty_set;
115+
116+
const auto unused_txid{GetRandHash()};
117+
118+
// Tests for PaysMoreThanConflicts
119+
// These tests use feerate, not absolute fee.
120+
BOOST_CHECK(PaysMoreThanConflicts(/*iters_conflicting=*/set_12_normal,
121+
/*replacement_feerate=*/CFeeRate(entry1->GetModifiedFee() + 1, entry1->GetTxSize() + 2),
122+
/*txid=*/unused_txid).has_value());
123+
// Replacement must be strictly greater than the originals.
124+
BOOST_CHECK(PaysMoreThanConflicts(set_12_normal, CFeeRate(entry1->GetModifiedFee(), entry1->GetTxSize()), unused_txid).has_value());
125+
BOOST_CHECK(PaysMoreThanConflicts(set_12_normal, CFeeRate(entry1->GetModifiedFee() + 1, entry1->GetTxSize()), unused_txid) == std::nullopt);
126+
// These tests use modified fees (including prioritisation), not base fees.
127+
BOOST_CHECK(PaysMoreThanConflicts({entry5}, CFeeRate(entry5->GetModifiedFee() + 1, entry5->GetTxSize()), unused_txid) == std::nullopt);
128+
BOOST_CHECK(PaysMoreThanConflicts({entry6}, CFeeRate(entry6->GetFee() + 1, entry6->GetTxSize()), unused_txid).has_value());
129+
BOOST_CHECK(PaysMoreThanConflicts({entry6}, CFeeRate(entry6->GetModifiedFee() + 1, entry6->GetTxSize()), unused_txid) == std::nullopt);
130+
// These tests only check individual feerate. Ancestor feerate does not matter.
131+
BOOST_CHECK(PaysMoreThanConflicts(set_34_cpfp, CFeeRate(entry4->GetModifiedFee(), entry4->GetTxSize()), unused_txid).has_value());
132+
133+
// Tests for EntriesAndTxidsDisjoint
134+
BOOST_CHECK(EntriesAndTxidsDisjoint(empty_set, {tx1->GetHash()}, unused_txid) == std::nullopt);
135+
BOOST_CHECK(EntriesAndTxidsDisjoint(set_12_normal, {tx3->GetHash()}, unused_txid) == std::nullopt);
136+
// EntriesAndTxidsDisjoint uses txids, not wtxids.
137+
BOOST_CHECK(EntriesAndTxidsDisjoint({entry2}, {tx2->GetWitnessHash()}, unused_txid) == std::nullopt);
138+
BOOST_CHECK(EntriesAndTxidsDisjoint({entry2}, {tx2->GetHash()}, unused_txid).has_value());
139+
// If entry2 is an ancestor of a tx, that tx cannot replace entry1. However,
140+
// EntriesAndTxidsDisjoint uses the ancestors directly. It does not calculate descendants.
141+
BOOST_CHECK(EntriesAndTxidsDisjoint(set_12_normal, {tx1->GetHash()}, unused_txid).has_value());
142+
BOOST_CHECK(EntriesAndTxidsDisjoint(set_12_normal, {tx2->GetHash()}, unused_txid).has_value());
143+
BOOST_CHECK(EntriesAndTxidsDisjoint({entry2}, {tx1->GetHash()}, unused_txid) == std::nullopt);
144+
145+
// Tests for PaysForRBF
146+
const auto incremental_relay_feerate{CFeeRate(DEFAULT_INCREMENTAL_RELAY_FEE)};
147+
const auto higher_relay_feerate{CFeeRate(2 * DEFAULT_INCREMENTAL_RELAY_FEE)};
148+
// Must pay at least as much as the original.
149+
BOOST_CHECK(PaysForRBF(/*original_fees=*/high_fee,
150+
/*replacement_fees=*/high_fee,
151+
/*replacement_vsize=*/1,
152+
/*relay_fee=*/CFeeRate(0),
153+
/*txid=*/unused_txid)
154+
== std::nullopt);
155+
BOOST_CHECK(PaysForRBF(high_fee, high_fee - 1, 1, CFeeRate(0), unused_txid).has_value());
156+
BOOST_CHECK(PaysForRBF(high_fee + 1, high_fee, 1, CFeeRate(0), unused_txid).has_value());
157+
// Additional fees must cover the replacement's vsize at incremental relay fee
158+
BOOST_CHECK(PaysForRBF(high_fee, high_fee + 1, 2, incremental_relay_feerate, unused_txid).has_value());
159+
BOOST_CHECK(PaysForRBF(high_fee, high_fee + 2, 2, incremental_relay_feerate, unused_txid) == std::nullopt);
160+
BOOST_CHECK(PaysForRBF(high_fee, high_fee + 2, 2, higher_relay_feerate, unused_txid).has_value());
161+
BOOST_CHECK(PaysForRBF(high_fee, high_fee + 4, 2, higher_relay_feerate, unused_txid) == std::nullopt);
162+
BOOST_CHECK(PaysForRBF(low_fee, high_fee, 99999999, incremental_relay_feerate, unused_txid).has_value());
163+
164+
// Tests for GetEntriesForConflicts
165+
CTxMemPool::setEntries all_parents{entry1, entry3, entry5, entry7, entry8};
166+
CTxMemPool::setEntries all_children{entry2, entry4, entry6};
167+
std::vector<CTransactionRef> parent_inputs({m_coinbase_txns[0], m_coinbase_txns[1], m_coinbase_txns[2],
168+
m_coinbase_txns[3], m_coinbase_txns[4]});
169+
CTransactionRef conflicts_with_parents = make_tx({50 * CENT}, parent_inputs);
170+
CTxMemPool::setEntries all_conflicts;
171+
BOOST_CHECK(GetEntriesForConflicts(/*tx=*/ *conflicts_with_parents.get(),
172+
/*pool=*/ pool,
173+
/*iters_conflicting=*/ all_parents,
174+
/*all_conflicts=*/ all_conflicts) == std::nullopt);
175+
BOOST_CHECK(all_conflicts == all_entries);
176+
auto conflicts_size = all_conflicts.size();
177+
all_conflicts.clear();
178+
179+
add_descendants(tx2, pool, 23);
180+
BOOST_CHECK(GetEntriesForConflicts(*conflicts_with_parents.get(), pool, all_parents, all_conflicts) == std::nullopt);
181+
conflicts_size += 23;
182+
BOOST_CHECK_EQUAL(all_conflicts.size(), conflicts_size);
183+
all_conflicts.clear();
184+
185+
add_descendants(tx4, pool, 23);
186+
BOOST_CHECK(GetEntriesForConflicts(*conflicts_with_parents.get(), pool, all_parents, all_conflicts) == std::nullopt);
187+
conflicts_size += 23;
188+
BOOST_CHECK_EQUAL(all_conflicts.size(), conflicts_size);
189+
all_conflicts.clear();
190+
191+
add_descendants(tx6, pool, 23);
192+
BOOST_CHECK(GetEntriesForConflicts(*conflicts_with_parents.get(), pool, all_parents, all_conflicts) == std::nullopt);
193+
conflicts_size += 23;
194+
BOOST_CHECK_EQUAL(all_conflicts.size(), conflicts_size);
195+
all_conflicts.clear();
196+
197+
add_descendants(tx7, pool, 23);
198+
BOOST_CHECK(GetEntriesForConflicts(*conflicts_with_parents.get(), pool, all_parents, all_conflicts) == std::nullopt);
199+
conflicts_size += 23;
200+
BOOST_CHECK_EQUAL(all_conflicts.size(), conflicts_size);
201+
BOOST_CHECK_EQUAL(all_conflicts.size(), 100);
202+
all_conflicts.clear();
203+
204+
// Exceeds maximum number of conflicts.
205+
add_descendants(tx8, pool, 1);
206+
BOOST_CHECK(GetEntriesForConflicts(*conflicts_with_parents.get(), pool, all_parents, all_conflicts).has_value());
207+
208+
// Tests for HasNoNewUnconfirmed
209+
CTransactionRef spends_unconfirmed = make_tx({36 * CENT}, {tx1});
210+
for (const auto& input : spends_unconfirmed->vin) {
211+
// Spends unconfirmed inputs.
212+
BOOST_CHECK(pool.exists(GenTxid::Txid(input.prevout.hash)));
213+
}
214+
BOOST_CHECK(HasNoNewUnconfirmed(/*tx=*/ *spends_unconfirmed.get(),
215+
/*pool=*/ pool,
216+
/*iters_conflicting=*/ all_entries) == std::nullopt);
217+
BOOST_CHECK(HasNoNewUnconfirmed(*spends_unconfirmed.get(), pool, {entry2}) == std::nullopt);
218+
BOOST_CHECK(HasNoNewUnconfirmed(*spends_unconfirmed.get(), pool, empty_set).has_value());
219+
220+
CTransactionRef spends_new_unconfirmed = make_tx({36 * CENT}, {tx1, tx8});
221+
BOOST_CHECK(HasNoNewUnconfirmed(*spends_new_unconfirmed.get(), pool, {entry2}).has_value());
222+
BOOST_CHECK(HasNoNewUnconfirmed(*spends_new_unconfirmed.get(), pool, all_entries).has_value());
223+
}
224+
225+
BOOST_AUTO_TEST_SUITE_END()

0 commit comments

Comments
 (0)