Skip to content

Commit 831ecb0

Browse files
committed
policy: getmempoolentry return "correct" bip125-replaceable status
Now RBF signaling adheres to BIP125 RBF (Rule 5) which states that the number of original transactions to be replaced and their descendant transactions must not exceed `MAX_BIP125_REPLACEMENT_CANDIDATES`. Prior to this commit, the bip125-replaceable status of a given transaction indicated whether the transaction or _any_ of its unconfirmed ancestors opted-in to RBF, with no consideration of this limitation. Note: the bip125-replaceable status that `getmempoolentry` returns is still inconsistent with what the mempool will treat as replaceable, as the mempool does not (yet) allow inherited signaling.
1 parent 11ce427 commit 831ecb0

File tree

3 files changed

+15
-11
lines changed

3 files changed

+15
-11
lines changed

src/policy/rbf.cpp

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,20 +21,21 @@ RBFTransactionState IsRBFOptIn(const CTransaction& tx, const CTxMemPool& pool)
2121
return RBFTransactionState::UNKNOWN;
2222
}
2323

24-
// First check the transaction itself.
25-
if (SignalsOptInRBF(tx)) {
24+
CTxMemPoolEntry entry = *pool.mapTx.find(tx.GetHash());
25+
if (entry.GetCountWithDescendants() > MAX_BIP125_REPLACEMENT_CANDIDATES) {
26+
return RBFTransactionState::FINAL;
27+
} else if (SignalsOptInRBF(tx)) {
2628
return RBFTransactionState::REPLACEABLE_BIP125;
2729
}
2830

2931
// If all the inputs have nSequence >= maxint-1, it still might be
3032
// signaled for RBF if any unconfirmed parents have signaled.
3133
uint64_t noLimit = std::numeric_limits<uint64_t>::max();
3234
std::string dummy;
33-
CTxMemPoolEntry entry = *pool.mapTx.find(tx.GetHash());
3435
pool.CalculateMemPoolAncestors(entry, ancestors, noLimit, noLimit, noLimit, noLimit, dummy, false);
3536

3637
for (CTxMemPool::txiter it : ancestors) {
37-
if (SignalsOptInRBF(it->GetTx())) {
38+
if (SignalsOptInRBF(it->GetTx()) && it->GetCountWithDescendants() <= MAX_BIP125_REPLACEMENT_CANDIDATES) {
3839
return RBFTransactionState::REPLACEABLE_BIP125;
3940
}
4041
}

src/policy/rbf.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,10 @@ enum class RBFTransactionState {
3030
* Determine whether an unconfirmed transaction is signaling opt-in to RBF
3131
* according to BIP 125
3232
* This involves checking sequence numbers of the transaction, as well
33-
* as the sequence numbers of all in-mempool ancestors.
33+
* as the sequence numbers of all in-mempool ancestors for inherited signaling.
34+
* This also takes the number of original transactions to be replaced and their
35+
* descendant transactions into consideration as according to BIP125 RBF (Rule #5)
36+
* this must not exceed `MAX_BIP125_REPLACEMENT_CANDIDATES`.
3437
*
3538
* @param tx The unconfirmed transaction
3639
* @param pool The mempool, which may contain the tx

test/functional/feature_rbf.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -740,24 +740,24 @@ def test_reorged_inherited_signaling_and_descendant_limit(self):
740740

741741
# Create a chain the size of `MAX_REPLACEMENT_LIMIT` spending `joined_tx` - the BIP125 Rule #5 imposed limit
742742
tx = None
743-
for _ in range(MAX_REPLACEMENT_LIMIT - 1):
743+
for i in range(MAX_REPLACEMENT_LIMIT - 1):
744744
tx = self.wallet.send_self_transfer(
745745
from_node=self.nodes[0],
746746
utxo_to_spend=joined_utxo if tx is None else self.wallet.get_utxo(txid=tx['txid']), # a straight line of descendants
747747
sequence=0xffffffff,
748748
fee_rate=Decimal('0.0001'),
749749
)
750-
assert_equal(True, self.nodes[0].getmempoolentry(tx['txid'])['bip125-replaceable']) # inherited
750+
assert_equal(i < MAX_REPLACEMENT_LIMIT - 2, self.nodes[0].getmempoolentry(tx['txid'])['bip125-replaceable']) # inherited
751751
# Now we have a chain of: `optin_parent_tx`, `joined_tx`, and 99 txs. The last tx in the loop exceeded `MAX_REPLACEMENT_LIMIT`
752752

753753
# Attempting to replace the opt-in parent transaction will now result in more than `MAX_REPLACEMENT_LIMIT`
754-
# conflicting txns being evicted from the mempool. However, it (and all of its descendants) are still signaling replaceability.
755-
# We would've expected that once `MAX_REPLACEMENT_LIMIT` is exceeded, the opt-in parent txn stops signaling
754+
# conflicting txns being evicted from the mempool. Therefore, it (and all of its descendants) no longer signal replaceability.
755+
# Since `MAX_REPLACEMENT_LIMIT` is exceeded, the opt-in parent txn stops signaling
756756
# replaceability, along with _all_ of its descendants.
757757
entry = self.nodes[0].getmempoolentry(optin_parent_tx["txid"])
758758
assert_greater_than(entry['descendantcount'], MAX_REPLACEMENT_LIMIT)
759-
assert_equal(True, entry['bip125-replaceable'])
760-
assert_equal(True, self.nodes[0].getmempoolentry(tx["txid"])['bip125-replaceable'])
759+
assert_equal(False, entry['bip125-replaceable'])
760+
assert_equal(False, self.nodes[0].getmempoolentry(tx["txid"])['bip125-replaceable'])
761761

762762
# Case in point, we can't actually replace `optin_parent_tx` once it has `MAX_REPLACEMENT_LIMIT` descendants
763763
self.wallet.create_self_transfer(

0 commit comments

Comments
 (0)