Skip to content

Commit 81f4a3e

Browse files
committed
Merge bitcoin/bitcoin#22796: RBF move (1/3): extract BIP125 Rule 5 into policy/rbf
f293c68 MOVEONLY: getting mempool conflicts to policy/rbf (glozow) 8d71796 [validation] quit RBF logic earlier and separate loops (glozow) badb9b1 call SignalsOptInRBF instead of checking all inputs (glozow) e0df41d [validation] default conflicting fees and size to 0 (glozow) b001b9f MOVEONLY: BIP125 max conflicts limit to policy/rbf.h (glozow) Pull request description: See #22675 for motivation, this is one chunk of it. It extracts some BIP125 logic into policy/rbf: - Defines a constant for specifying the maximum number of mempool entries we'd consider replacing by RBF - Calls the available `SignalsOptInRBF` function instead of manually iterating through inputs - Moves the logic for getting the list of conflicting mempool entries to a helper function - Also does a bit of preparation for future moves - moving declarations around, etc Also see #22677 for addressing the circular dependency. ACKs for top commit: jnewbery: Code review ACK f293c68 theStack: Code-review ACK f293c68 📔 ariard: ACK f293c68 Tree-SHA512: a60370994569cfc91d4b2ad5e94542d4855a48927ae8b174880216074e4fa50d4523dd4ee36efdd6edf2bf7adb87a8beff9c3aaaf6dd323b286b287233e63790
2 parents 0f0c5d4 + f293c68 commit 81f4a3e

File tree

5 files changed

+86
-51
lines changed

5 files changed

+86
-51
lines changed

src/policy/rbf.cpp

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

55
#include <policy/rbf.h>
6+
7+
#include <policy/settings.h>
8+
#include <tinyformat.h>
9+
#include <util/moneystr.h>
610
#include <util/rbf.h>
711

812
RBFTransactionState IsRBFOptIn(const CTransaction& tx, const CTxMemPool& pool)
@@ -42,3 +46,34 @@ RBFTransactionState IsRBFOptInEmptyMempool(const CTransaction& tx)
4246
// If we don't have a local mempool we can only check the transaction itself.
4347
return SignalsOptInRBF(tx) ? RBFTransactionState::REPLACEABLE_BIP125 : RBFTransactionState::UNKNOWN;
4448
}
49+
50+
bool GetEntriesForConflicts(const CTransaction& tx,
51+
CTxMemPool& m_pool,
52+
const CTxMemPool::setEntries& setIterConflicting,
53+
CTxMemPool::setEntries& allConflicting,
54+
std::string& err_string)
55+
{
56+
AssertLockHeld(m_pool.cs);
57+
const uint256 hash = tx.GetHash();
58+
uint64_t nConflictingCount = 0;
59+
for (const auto& mi : setIterConflicting) {
60+
nConflictingCount += mi->GetCountWithDescendants();
61+
// This potentially overestimates the number of actual descendants
62+
// but we just want to be conservative to avoid doing too much
63+
// work.
64+
if (nConflictingCount > MAX_BIP125_REPLACEMENT_CANDIDATES) {
65+
err_string = strprintf("rejecting replacement %s; too many potential replacements (%d > %d)\n",
66+
hash.ToString(),
67+
nConflictingCount,
68+
MAX_BIP125_REPLACEMENT_CANDIDATES);
69+
return false;
70+
}
71+
}
72+
// If not too many to replace, then calculate the set of
73+
// transactions that would have to be evicted
74+
for (CTxMemPool::txiter it : setIterConflicting) {
75+
m_pool.CalculateDescendants(it, allConflicting);
76+
}
77+
return true;
78+
}
79+

src/policy/rbf.h

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@
77

88
#include <txmempool.h>
99

10+
/** Maximum number of transactions that can be replaced by BIP125 RBF (Rule #5). This includes all
11+
* mempool conflicts and their descendants. */
12+
static constexpr uint32_t MAX_BIP125_REPLACEMENT_CANDIDATES{100};
13+
1014
/** The rbf state of unconfirmed transactions */
1115
enum class RBFTransactionState {
1216
/** Unconfirmed tx that does not signal rbf and is not in the mempool */
@@ -31,4 +35,19 @@ enum class RBFTransactionState {
3135
RBFTransactionState IsRBFOptIn(const CTransaction& tx, const CTxMemPool& pool) EXCLUSIVE_LOCKS_REQUIRED(pool.cs);
3236
RBFTransactionState IsRBFOptInEmptyMempool(const CTransaction& tx);
3337

38+
/** Get all descendants of setIterConflicting. Also enforce BIP125 Rule #5, "The number of original
39+
* transactions to be replaced and their descendant transactions which will be evicted from the
40+
* mempool must not exceed a total of 100 transactions." Quit as early as possible. There cannot be
41+
* more than MAX_BIP125_REPLACEMENT_CANDIDATES potential entries.
42+
* @param[in] setIterConflicting The set of iterators to mempool entries.
43+
* @param[out] err_string Used to return errors, if any.
44+
* @param[out] allConflicting Populated with all the mempool entries that would be replaced,
45+
* which includes descendants of setIterConflicting. Not cleared at
46+
* the start; any existing mempool entries will remain in the set.
47+
* @returns false if Rule 5 is broken.
48+
*/
49+
bool GetEntriesForConflicts(const CTransaction& tx, CTxMemPool& m_pool,
50+
const CTxMemPool::setEntries& setIterConflicting,
51+
CTxMemPool::setEntries& allConflicting,
52+
std::string& err_string) EXCLUSIVE_LOCKS_REQUIRED(m_pool.cs);
3453
#endif // BITCOIN_POLICY_RBF_H

src/util/rbf.h

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,15 @@ class CTransaction;
1111

1212
static const uint32_t MAX_BIP125_RBF_SEQUENCE = 0xfffffffd;
1313

14-
// Check whether the sequence numbers on this transaction are signaling
15-
// opt-in to replace-by-fee, according to BIP 125
14+
/** Check whether the sequence numbers on this transaction are signaling
15+
* opt-in to replace-by-fee, according to BIP 125.
16+
* Allow opt-out of transaction replacement by setting
17+
* nSequence > MAX_BIP125_RBF_SEQUENCE (SEQUENCE_FINAL-2) on all inputs.
18+
*
19+
* SEQUENCE_FINAL-1 is picked to still allow use of nLockTime by
20+
* non-replaceable transactions. All inputs rather than just one
21+
* is for the sake of multi-party protocols, where we don't
22+
* want a single party to be able to disable replacement. */
1623
bool SignalsOptInRBF(const CTransaction &tx);
1724

1825
#endif // BITCOIN_UTIL_RBF_H

src/validation.cpp

Lines changed: 22 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include <node/coinstats.h>
2626
#include <node/ui_interface.h>
2727
#include <policy/policy.h>
28+
#include <policy/rbf.h>
2829
#include <policy/settings.h>
2930
#include <pow.h>
3031
#include <primitives/block.h>
@@ -474,8 +475,10 @@ class MemPoolAccept
474475
bool m_replacement_transaction;
475476
CAmount m_base_fees;
476477
CAmount m_modified_fees;
477-
CAmount m_conflicting_fees;
478-
size_t m_conflicting_size;
478+
/** Total modified fees of all transactions being replaced. */
479+
CAmount m_conflicting_fees{0};
480+
/** Total virtual size of all transactions being replaced. */
481+
size_t m_conflicting_size{0};
479482

480483
const CTransactionRef& m_ptx;
481484
const uint256& m_hash;
@@ -602,31 +605,14 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
602605
}
603606
if (!setConflicts.count(ptxConflicting->GetHash()))
604607
{
605-
// Allow opt-out of transaction replacement by setting
606-
// nSequence > MAX_BIP125_RBF_SEQUENCE (SEQUENCE_FINAL-2) on all inputs.
607-
//
608-
// SEQUENCE_FINAL-1 is picked to still allow use of nLockTime by
609-
// non-replaceable transactions. All inputs rather than just one
610-
// is for the sake of multi-party protocols, where we don't
611-
// want a single party to be able to disable replacement.
612-
//
613608
// Transactions that don't explicitly signal replaceability are
614609
// *not* replaceable with the current logic, even if one of their
615610
// unconfirmed ancestors signals replaceability. This diverges
616611
// from BIP125's inherited signaling description (see CVE-2021-31876).
617612
// Applications relying on first-seen mempool behavior should
618613
// check all unconfirmed ancestors; otherwise an opt-in ancestor
619614
// might be replaced, causing removal of this descendant.
620-
bool fReplacementOptOut = true;
621-
for (const CTxIn &_txin : ptxConflicting->vin)
622-
{
623-
if (_txin.nSequence <= MAX_BIP125_RBF_SEQUENCE)
624-
{
625-
fReplacementOptOut = false;
626-
break;
627-
}
628-
}
629-
if (fReplacementOptOut) {
615+
if (!SignalsOptInRBF(*ptxConflicting)) {
630616
return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "txn-mempool-conflict");
631617
}
632618

@@ -796,21 +782,15 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
796782
}
797783
}
798784

799-
// Check if it's economically rational to mine this transaction rather
800-
// than the ones it replaces.
801-
nConflictingFees = 0;
802-
nConflictingSize = 0;
803-
uint64_t nConflictingCount = 0;
804785

805786
// If we don't hold the lock allConflicting might be incomplete; the
806787
// subsequent RemoveStaged() and addUnchecked() calls don't guarantee
807788
// mempool consistency for us.
808789
fReplacementTransaction = setConflicts.size();
809790
if (fReplacementTransaction)
810791
{
792+
std::string err_string;
811793
CFeeRate newFeeRate(nModifiedFees, nSize);
812-
std::set<uint256> setConflictsParents;
813-
const int maxDescendantsToVisit = 100;
814794
for (const auto& mi : setIterConflicting) {
815795
// Don't allow the replacement to reduce the feerate of the
816796
// mempool.
@@ -835,33 +815,26 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
835815
newFeeRate.ToString(),
836816
oldFeeRate.ToString()));
837817
}
818+
}
819+
820+
// Calculate all conflicting entries and enforce Rule #5.
821+
if (!GetEntriesForConflicts(tx, m_pool, setIterConflicting, allConflicting, err_string)) {
822+
return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "too many potential replacements", err_string);
823+
}
824+
825+
// Check if it's economically rational to mine this transaction rather
826+
// than the ones it replaces.
827+
for (CTxMemPool::txiter it : allConflicting) {
828+
nConflictingFees += it->GetModifiedFee();
829+
nConflictingSize += it->GetTxSize();
830+
}
838831

832+
std::set<uint256> setConflictsParents;
833+
for (const auto& mi : setIterConflicting) {
839834
for (const CTxIn &txin : mi->GetTx().vin)
840835
{
841836
setConflictsParents.insert(txin.prevout.hash);
842837
}
843-
844-
nConflictingCount += mi->GetCountWithDescendants();
845-
}
846-
// This potentially overestimates the number of actual descendants
847-
// but we just want to be conservative to avoid doing too much
848-
// work.
849-
if (nConflictingCount <= maxDescendantsToVisit) {
850-
// If not too many to replace, then calculate the set of
851-
// transactions that would have to be evicted
852-
for (CTxMemPool::txiter it : setIterConflicting) {
853-
m_pool.CalculateDescendants(it, allConflicting);
854-
}
855-
for (CTxMemPool::txiter it : allConflicting) {
856-
nConflictingFees += it->GetModifiedFee();
857-
nConflictingSize += it->GetTxSize();
858-
}
859-
} else {
860-
return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "too many potential replacements",
861-
strprintf("rejecting replacement %s; too many potential replacements (%d > %d)\n",
862-
hash.ToString(),
863-
nConflictingCount,
864-
maxDescendantsToVisit));
865838
}
866839

867840
for (unsigned int j = 0; j < tx.vin.size(); j++)

test/lint/lint-circular-dependencies.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ EXPECTED_CIRCULAR_DEPENDENCIES=(
1515
"index/base -> validation -> index/blockfilterindex -> index/base"
1616
"index/coinstatsindex -> node/coinstats -> index/coinstatsindex"
1717
"policy/fees -> txmempool -> policy/fees"
18+
"policy/rbf -> txmempool -> validation -> policy/rbf"
1819
"qt/addresstablemodel -> qt/walletmodel -> qt/addresstablemodel"
1920
"qt/recentrequeststablemodel -> qt/walletmodel -> qt/recentrequeststablemodel"
2021
"qt/sendcoinsdialog -> qt/walletmodel -> qt/sendcoinsdialog"

0 commit comments

Comments
 (0)