Skip to content

Commit 3cf46f6

Browse files
committed
[doc] improve RBF documentation
Document a few non-obvious things and delete no-longer-relevant comments (e.g. about taking a lock that we're already holding). No change in behavior.
1 parent c78eb86 commit 3cf46f6

File tree

4 files changed

+38
-32
lines changed

4 files changed

+38
-32
lines changed

src/policy/rbf.cpp

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -48,26 +48,27 @@ RBFTransactionState IsRBFOptInEmptyMempool(const CTransaction& tx)
4848
}
4949

5050
std::optional<std::string> GetEntriesForConflicts(const CTransaction& tx,
51-
CTxMemPool& pool,
52-
const CTxMemPool::setEntries& iters_conflicting,
53-
CTxMemPool::setEntries& all_conflicts)
51+
CTxMemPool& pool,
52+
const CTxMemPool::setEntries& iters_conflicting,
53+
CTxMemPool::setEntries& all_conflicts)
5454
{
5555
AssertLockHeld(pool.cs);
5656
const uint256 txid = tx.GetHash();
5757
uint64_t nConflictingCount = 0;
5858
for (const auto& mi : iters_conflicting) {
5959
nConflictingCount += mi->GetCountWithDescendants();
60-
// This potentially overestimates the number of actual descendants but we just want to be
61-
// conservative to avoid doing too much work.
60+
// BIP125 Rule #5: don't consider replacing more than MAX_BIP125_REPLACEMENT_CANDIDATES
61+
// entries from the mempool. This potentially overestimates the number of actual
62+
// descendants (i.e. if multiple conflicts share a descendant, it will be counted multiple
63+
// times), but we just want to be conservative to avoid doing too much work.
6264
if (nConflictingCount > MAX_BIP125_REPLACEMENT_CANDIDATES) {
6365
return strprintf("rejecting replacement %s; too many potential replacements (%d > %d)\n",
6466
txid.ToString(),
6567
nConflictingCount,
6668
MAX_BIP125_REPLACEMENT_CANDIDATES);
6769
}
6870
}
69-
// If not too many to replace, then calculate the set of
70-
// transactions that would have to be evicted
71+
// Calculate the set of all transactions that would have to be evicted.
7172
for (CTxMemPool::txiter it : iters_conflicting) {
7273
pool.CalculateDescendants(it, all_conflicts);
7374
}
@@ -81,19 +82,19 @@ std::optional<std::string> HasNoNewUnconfirmed(const CTransaction& tx,
8182
AssertLockHeld(pool.cs);
8283
std::set<uint256> parents_of_conflicts;
8384
for (const auto& mi : iters_conflicting) {
84-
for (const CTxIn &txin : mi->GetTx().vin) {
85+
for (const CTxIn& txin : mi->GetTx().vin) {
8586
parents_of_conflicts.insert(txin.prevout.hash);
8687
}
8788
}
8889

8990
for (unsigned int j = 0; j < tx.vin.size(); j++) {
90-
// We don't want to accept replacements that require low feerate junk to be mined first.
91-
// Ideally we'd keep track of the ancestor feerates and make the decision based on that, but
92-
// for now requiring all new inputs to be confirmed works.
91+
// BIP125 Rule #2: We don't want to accept replacements that require low feerate junk to be
92+
// mined first. Ideally we'd keep track of the ancestor feerates and make the decision
93+
// based on that, but for now requiring all new inputs to be confirmed works.
9394
//
9495
// Note that if you relax this to make RBF a little more useful, this may break the
95-
// CalculateMempoolAncestors RBF relaxation, above. See the comment above the first
96-
// CalculateMempoolAncestors call for more info.
96+
// CalculateMempoolAncestors RBF relaxation which subtracts the conflict count/size from the
97+
// descendant limit.
9798
if (!parents_of_conflicts.count(tx.vin[j].prevout.hash)) {
9899
// Rather than check the UTXO set - potentially expensive - it's cheaper to just check
99100
// if the new input refers to a tx that's in the mempool.
@@ -111,7 +112,7 @@ std::optional<std::string> EntriesAndTxidsDisjoint(const CTxMemPool::setEntries&
111112
const uint256& txid)
112113
{
113114
for (CTxMemPool::txiter ancestorIt : ancestors) {
114-
const uint256 &hashAncestor = ancestorIt->GetTx().GetHash();
115+
const uint256& hashAncestor = ancestorIt->GetTx().GetHash();
115116
if (direct_conflicts.count(hashAncestor)) {
116117
return strprintf("%s spends conflicting transaction %s",
117118
txid.ToString(),
@@ -153,16 +154,17 @@ std::optional<std::string> PaysForRBF(CAmount original_fees,
153154
CFeeRate relay_fee,
154155
const uint256& txid)
155156
{
156-
// The replacement must pay greater fees than the transactions it
157-
// replaces - if we did the bandwidth used by those conflicting
158-
// transactions would not be paid for.
157+
// BIP125 Rule #3: The replacement fees must be greater than or equal to fees of the
158+
// transactions it replaces, otherwise the bandwidth used by those conflicting transactions
159+
// would not be paid for.
159160
if (replacement_fees < original_fees) {
160161
return strprintf("rejecting replacement %s, less fees than conflicting txs; %s < %s",
161162
txid.ToString(), FormatMoney(replacement_fees), FormatMoney(original_fees));
162163
}
163164

164-
// Finally in addition to paying more fees than the conflicts the
165-
// new transaction must pay for its own bandwidth.
165+
// BIP125 Rule #4: The new transaction must pay for its own bandwidth. Otherwise, we have a DoS
166+
// vector where attackers can cause a transaction to be replaced (and relayed) repeatedly by
167+
// increasing the fee by tiny amounts.
166168
CAmount additional_fees = replacement_fees - original_fees;
167169
if (additional_fees < relay_fee.GetFee(replacement_vsize)) {
168170
return strprintf("rejecting replacement %s, not enough additional fees to relay; %s < %s",

src/policy/rbf.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,14 +48,14 @@ RBFTransactionState IsRBFOptInEmptyMempool(const CTransaction& tx);
4848
std::optional<std::string> GetEntriesForConflicts(const CTransaction& tx, CTxMemPool& pool,
4949
const CTxMemPool::setEntries& iters_conflicting,
5050
CTxMemPool::setEntries& all_conflicts)
51-
EXCLUSIVE_LOCKS_REQUIRED(pool.cs);
51+
EXCLUSIVE_LOCKS_REQUIRED(pool.cs);
5252

5353
/** BIP125 Rule #2: "The replacement transaction may only include an unconfirmed input if that input
5454
* was included in one of the original transactions."
5555
* @returns error message if Rule #2 is broken, otherwise std::nullopt. */
5656
std::optional<std::string> HasNoNewUnconfirmed(const CTransaction& tx, const CTxMemPool& pool,
5757
const CTxMemPool::setEntries& iters_conflicting)
58-
EXCLUSIVE_LOCKS_REQUIRED(pool.cs);
58+
EXCLUSIVE_LOCKS_REQUIRED(pool.cs);
5959

6060
/** Check the intersection between two sets of transactions (a set of mempool entries and a set of
6161
* txids) to make sure they are disjoint.

src/util/rbf.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ static const uint32_t MAX_BIP125_RBF_SEQUENCE = 0xfffffffd;
1717
*
1818
* SEQUENCE_FINAL-1 is picked to still allow use of nLockTime by non-replaceable transactions. All
1919
* inputs rather than just one is for the sake of multi-party protocols, where we don't want a single
20-
* party to be able to disable replacement. */
21-
bool SignalsOptInRBF(const CTransaction &tx);
20+
* party to be able to disable replacement by opting out in their own input. */
21+
bool SignalsOptInRBF(const CTransaction& tx);
2222

2323
#endif // BITCOIN_UTIL_RBF_H

src/validation.cpp

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -771,34 +771,38 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
771771
// pathological case by making sure setConflicts and setAncestors don't
772772
// intersect.
773773
if (const auto err_string{EntriesAndTxidsDisjoint(setAncestors, setConflicts, hash)}) {
774+
// We classify this as a consensus error because a transaction depending on something it
775+
// conflicts with would be inconsistent.
774776
return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-txns-spends-conflicting-tx", *err_string);
775777
}
776778

777779

778-
// If we don't hold the lock allConflicting might be incomplete; the
779-
// subsequent RemoveStaged() and addUnchecked() calls don't guarantee
780-
// mempool consistency for us.
781780
fReplacementTransaction = setConflicts.size();
782-
if (fReplacementTransaction)
783-
{
781+
if (fReplacementTransaction) {
784782
CFeeRate newFeeRate(nModifiedFees, nSize);
783+
// It's possible that the replacement pays more fees than its direct conflicts but not more
784+
// than all conflicts (i.e. the direct conflicts have high-fee descendants). However, if the
785+
// replacement doesn't pay more fees than its direct conflicts, then we can be sure it's not
786+
// more economically rational to mine. Before we go digging through the mempool for all
787+
// transactions that would need to be removed (direct conflicts and all descendants), check
788+
// that the replacement transaction pays more than its direct conflicts.
785789
if (const auto err_string{PaysMoreThanConflicts(setIterConflicting, newFeeRate, hash)}) {
786790
return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "insufficient fee", *err_string);
787791
}
788792

789-
// Calculate all conflicting entries and enforce Rule #5.
793+
// Calculate all conflicting entries and enforce BIP125 Rule #5.
790794
if (const auto err_string{GetEntriesForConflicts(tx, m_pool, setIterConflicting, allConflicting)}) {
791795
return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY,
792796
"too many potential replacements", *err_string);
793797
}
794-
// Enforce Rule #2.
798+
// Enforce BIP125 Rule #2.
795799
if (const auto err_string{HasNoNewUnconfirmed(tx, m_pool, setIterConflicting)}) {
796800
return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY,
797801
"replacement-adds-unconfirmed", *err_string);
798802
}
799803

800-
// Check if it's economically rational to mine this transaction rather
801-
// than the ones it replaces. Enforce Rules #3 and #4.
804+
// Check if it's economically rational to mine this transaction rather than the ones it
805+
// replaces and pays for its own relay fees. Enforce BIP125 Rules #3 and #4.
802806
for (CTxMemPool::txiter it : allConflicting) {
803807
nConflictingFees += it->GetModifiedFee();
804808
nConflictingSize += it->GetTxSize();

0 commit comments

Comments
 (0)