mempool: disable full-RBF, require BIP125 opt-in signaling

Bitcoin Core defaults to accepting any fee-bumping replacement
regardless of signaling (full-RBF). Revert this for Elements: only
replace mempool transactions that explicitly opt in via BIP125
nSequence signaling (or TRUC), and report fullrbf=false in
getmempoolinfo.

Update feature_rbf.py accordingly, and add a regression test for
CVE-2021-31876 confirming that inherited signaling from an
unconfirmed parent does not make a non-signaling child replaceable.
This commit is contained in:
Byron Hambly 2026-06-25 15:10:01 +02:00
parent 88066059e9
commit 0e0d01913a
No known key found for this signature in database
GPG key ID: DE8F6EA20A661697
3 changed files with 87 additions and 9 deletions

View file

@ -986,7 +986,24 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
// Transaction conflicts with a mempool tx, but we're not allowing replacements in this context.
return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "bip125-replacement-disallowed");
}
ws.m_conflicts.insert(ptxConflicting->GetHash());
if (!ws.m_conflicts.count(ptxConflicting->GetHash()))
{
// Transactions that don't explicitly signal replaceability are
// *not* replaceable with the current logic, even if one of their
// unconfirmed ancestors signals replaceability. This diverges
// from BIP125's inherited signaling description (see CVE-2021-31876).
// Applications relying on first-seen mempool behavior should
// check all unconfirmed ancestors; otherwise an opt-in ancestor
// might be replaced, causing removal of this descendant.
//
// All TRUC transactions are considered replaceable.
const bool allow_rbf{SignalsOptInRBF(*ptxConflicting) || ptxConflicting->version == TRUC_VERSION};
if (!allow_rbf) {
return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "txn-mempool-conflict");
}
ws.m_conflicts.insert(ptxConflicting->GetHash());
}
}
}