Skip to content

Commit 07858b8

Browse files
glozowClaude Code
authored andcommitted
Merge bitcoin#17786: refactor: Nuke policy/fees->mempool circular dependencies
1 parent e3d61f2 commit 07858b8

File tree

18 files changed

+403
-161
lines changed

18 files changed

+403
-161
lines changed

src/Makefile.am

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,7 @@ BITCOIN_CORE_H = \
372372
torcontrol.h \
373373
txdb.h \
374374
txmempool.h \
375+
txmempool_entry.h \
375376
txorphanage.h \
376377
undo.h \
377378
unordered_lru_cache.h \

src/bench/mempool_eviction.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include <policy/policy.h>
77
#include <test/util/setup_common.h>
88
#include <txmempool.h>
9+
#include <txmempool_entry.h>
910

1011

1112
static void AddTx(const CTransactionRef& tx, const CAmount& nFee, CTxMemPool& pool) EXCLUSIVE_LOCKS_REQUIRED(cs_main, pool.cs)

src/bench/mempool_stress.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include <policy/policy.h>
77
#include <test/util/setup_common.h>
88
#include <txmempool.h>
9+
#include <txmempool_entry.h>
910
#include <validation.h>
1011

1112
#include <vector>

src/bench/rpc_mempool.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include <rpc/mempool.h>
88
#include <test/util/setup_common.h>
99
#include <txmempool.h>
10+
#include <txmempool_entry.h>
1011

1112
#include <univalue.h>
1213

src/net_processing.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
#include <timedata.h>
3333
#include <tinyformat.h>
3434
#include <txmempool.h>
35+
#include <txmempool_entry.h>
3536
#include <txorphanage.h>
3637
#include <util/check.h>
3738
#include <util/strencodings.h>

src/node/interfaces.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
#include <support/allocators/secure.h>
5050
#include <sync.h>
5151
#include <txmempool.h>
52+
#include <txmempool_entry.h>
5253
#include <uint256.h>
5354
#include <util/check.h>
5455
#include <util/system.h>

src/policy/fees.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
#include <streams.h>
1717
#include <sync.h>
1818
#include <tinyformat.h>
19-
#include <txmempool.h>
19+
#include <txmempool_entry.h>
2020
#include <uint256.h>
2121
#include <util/serfloat.h>
2222
#include <util/system.h>

src/policy/rbf.cpp

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
// Copyright (c) 2016-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 <policy/rbf.h>
6+
7+
#include <consensus/amount.h>
8+
#include <policy/feerate.h>
9+
#include <primitives/transaction.h>
10+
#include <sync.h>
11+
#include <tinyformat.h>
12+
#include <txmempool.h>
13+
#include <txmempool_entry.h>
14+
#include <uint256.h>
15+
#include <util/moneystr.h>
16+
#include <util/rbf.h>
17+
18+
#include <limits>
19+
#include <vector>
20+
21+
RBFTransactionState IsRBFOptIn(const CTransaction& tx, const CTxMemPool& pool)
22+
{
23+
AssertLockHeld(pool.cs);
24+
25+
CTxMemPool::setEntries ancestors;
26+
27+
// First check the transaction itself.
28+
if (SignalsOptInRBF(tx)) {
29+
return RBFTransactionState::REPLACEABLE_BIP125;
30+
}
31+
32+
// If this transaction is not in our mempool, then we can't be sure
33+
// we will know about all its inputs.
34+
if (!pool.exists(GenTxid::Txid(tx.GetHash()))) {
35+
return RBFTransactionState::UNKNOWN;
36+
}
37+
38+
// If all the inputs have nSequence >= maxint-1, it still might be
39+
// signaled for RBF if any unconfirmed parents have signaled.
40+
std::string dummy;
41+
CTxMemPoolEntry entry = *pool.mapTx.find(tx.GetHash());
42+
pool.CalculateMemPoolAncestors(entry, ancestors, CTxMemPool::Limits::NoLimits(), dummy, false);
43+
44+
for (CTxMemPool::txiter it : ancestors) {
45+
if (SignalsOptInRBF(it->GetTx())) {
46+
return RBFTransactionState::REPLACEABLE_BIP125;
47+
}
48+
}
49+
return RBFTransactionState::FINAL;
50+
}
51+
52+
RBFTransactionState IsRBFOptInEmptyMempool(const CTransaction& tx)
53+
{
54+
// If we don't have a local mempool we can only check the transaction itself.
55+
return SignalsOptInRBF(tx) ? RBFTransactionState::REPLACEABLE_BIP125 : RBFTransactionState::UNKNOWN;
56+
}
57+
58+
std::optional<std::string> GetEntriesForConflicts(const CTransaction& tx,
59+
CTxMemPool& pool,
60+
const CTxMemPool::setEntries& iters_conflicting,
61+
CTxMemPool::setEntries& all_conflicts)
62+
{
63+
AssertLockHeld(pool.cs);
64+
const uint256 txid = tx.GetHash();
65+
uint64_t nConflictingCount = 0;
66+
for (const auto& mi : iters_conflicting) {
67+
nConflictingCount += mi->GetCountWithDescendants();
68+
// Rule #5: don't consider replacing more than MAX_REPLACEMENT_CANDIDATES
69+
// entries from the mempool. This potentially overestimates the number of actual
70+
// descendants (i.e. if multiple conflicts share a descendant, it will be counted multiple
71+
// times), but we just want to be conservative to avoid doing too much work.
72+
if (nConflictingCount > MAX_REPLACEMENT_CANDIDATES) {
73+
return strprintf("rejecting replacement %s; too many potential replacements (%d > %d)\n",
74+
txid.ToString(),
75+
nConflictingCount,
76+
MAX_REPLACEMENT_CANDIDATES);
77+
}
78+
}
79+
// Calculate the set of all transactions that would have to be evicted.
80+
for (CTxMemPool::txiter it : iters_conflicting) {
81+
pool.CalculateDescendants(it, all_conflicts);
82+
}
83+
return std::nullopt;
84+
}
85+
86+
std::optional<std::string> HasNoNewUnconfirmed(const CTransaction& tx,
87+
const CTxMemPool& pool,
88+
const CTxMemPool::setEntries& iters_conflicting)
89+
{
90+
AssertLockHeld(pool.cs);
91+
std::set<uint256> parents_of_conflicts;
92+
for (const auto& mi : iters_conflicting) {
93+
for (const CTxIn& txin : mi->GetTx().vin) {
94+
parents_of_conflicts.insert(txin.prevout.hash);
95+
}
96+
}
97+
98+
for (unsigned int j = 0; j < tx.vin.size(); j++) {
99+
// Rule #2: We don't want to accept replacements that require low feerate junk to be
100+
// mined first. Ideally we'd keep track of the ancestor feerates and make the decision
101+
// based on that, but for now requiring all new inputs to be confirmed works.
102+
//
103+
// Note that if you relax this to make RBF a little more useful, this may break the
104+
// CalculateMempoolAncestors RBF relaxation which subtracts the conflict count/size from the
105+
// descendant limit.
106+
if (!parents_of_conflicts.count(tx.vin[j].prevout.hash)) {
107+
// Rather than check the UTXO set - potentially expensive - it's cheaper to just check
108+
// if the new input refers to a tx that's in the mempool.
109+
if (pool.exists(GenTxid::Txid(tx.vin[j].prevout.hash))) {
110+
return strprintf("replacement %s adds unconfirmed input, idx %d",
111+
tx.GetHash().ToString(), j);
112+
}
113+
}
114+
}
115+
return std::nullopt;
116+
}
117+
118+
std::optional<std::string> EntriesAndTxidsDisjoint(const CTxMemPool::setEntries& ancestors,
119+
const std::set<uint256>& direct_conflicts,
120+
const uint256& txid)
121+
{
122+
for (CTxMemPool::txiter ancestorIt : ancestors) {
123+
const uint256& hashAncestor = ancestorIt->GetTx().GetHash();
124+
if (direct_conflicts.count(hashAncestor)) {
125+
return strprintf("%s spends conflicting transaction %s",
126+
txid.ToString(),
127+
hashAncestor.ToString());
128+
}
129+
}
130+
return std::nullopt;
131+
}
132+
133+
std::optional<std::string> PaysMoreThanConflicts(const CTxMemPool::setEntries& iters_conflicting,
134+
CFeeRate replacement_feerate,
135+
const uint256& txid)
136+
{
137+
for (const auto& mi : iters_conflicting) {
138+
// Don't allow the replacement to reduce the feerate of the mempool.
139+
//
140+
// We usually don't want to accept replacements with lower feerates than what they replaced
141+
// as that would lower the feerate of the next block. Requiring that the feerate always be
142+
// increased is also an easy-to-reason about way to prevent DoS attacks via replacements.
143+
//
144+
// We only consider the feerates of transactions being directly replaced, not their indirect
145+
// descendants. While that does mean high feerate children are ignored when deciding whether
146+
// or not to replace, we do require the replacement to pay more overall fees too, mitigating
147+
// most cases.
148+
CFeeRate original_feerate(mi->GetModifiedFee(), mi->GetTxSize());
149+
if (replacement_feerate <= original_feerate) {
150+
return strprintf("rejecting replacement %s; new feerate %s <= old feerate %s",
151+
txid.ToString(),
152+
replacement_feerate.ToString(),
153+
original_feerate.ToString());
154+
}
155+
}
156+
return std::nullopt;
157+
}
158+
159+
std::optional<std::string> PaysForRBF(CAmount original_fees,
160+
CAmount replacement_fees,
161+
size_t replacement_vsize,
162+
CFeeRate relay_fee,
163+
const uint256& txid)
164+
{
165+
// Rule #3: The replacement fees must be greater than or equal to fees of the
166+
// transactions it replaces, otherwise the bandwidth used by those conflicting transactions
167+
// would not be paid for.
168+
if (replacement_fees < original_fees) {
169+
return strprintf("rejecting replacement %s, less fees than conflicting txs; %s < %s",
170+
txid.ToString(), FormatMoney(replacement_fees), FormatMoney(original_fees));
171+
}
172+
173+
// Rule #4: The new transaction must pay for its own bandwidth. Otherwise, we have a DoS
174+
// vector where attackers can cause a transaction to be replaced (and relayed) repeatedly by
175+
// increasing the fee by tiny amounts.
176+
CAmount additional_fees = replacement_fees - original_fees;
177+
if (additional_fees < relay_fee.GetFee(replacement_vsize)) {
178+
return strprintf("rejecting replacement %s, not enough additional fees to relay; %s < %s",
179+
txid.ToString(),
180+
FormatMoney(additional_fees),
181+
FormatMoney(relay_fee.GetFee(replacement_vsize)));
182+
}
183+
return std::nullopt;
184+
}

src/rpc/mempool.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include <rpc/server_util.h>
1414
#include <rpc/util.h>
1515
#include <txmempool.h>
16+
#include <txmempool_entry.h>
1617
#include <univalue.h>
1718
#include <util/moneystr.h>
1819
#include <validation.h>

src/test/fuzz/policy_estimator.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include <test/fuzz/util.h>
1010
#include <test/util/setup_common.h>
1111
#include <txmempool.h>
12+
#include <txmempool_entry.h>
1213

1314
#include <cstdint>
1415
#include <optional>

0 commit comments

Comments
 (0)