Skip to content

Commit fc8c19a

Browse files
committed
Prevent low feerate txs from (directly) replacing high feerate txs
Previously all conflicting transactions were evaluated as a whole to determine if the feerate was being increased. This meant that low feerate children pulled the feerate down, potentially allowing a high transaction with a high feerate to be replaced by one with a lower feerate.
1 parent 0137e6f commit fc8c19a

File tree

2 files changed

+39
-25
lines changed

2 files changed

+39
-25
lines changed

qa/replace-by-fee/rbf-tests.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,7 @@ def branch(prevout, initial_value, max_txs, *, tree_width=5, fee=0.0001*COIN, _t
219219
self.proxy.getrawtransaction(tx.GetHash())
220220

221221
def test_replacement_feeperkb(self):
222-
"""Replacement requires overall fee-per-KB to be higher"""
222+
"""Replacement requires fee-per-KB to be higher"""
223223
tx0_outpoint = self.make_txout(1.1*COIN)
224224

225225
tx1a = CTransaction([CTxIn(tx0_outpoint, nSequence=0)],

src/main.cpp

Lines changed: 38 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1008,32 +1008,60 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa
10081008
{
10091009
LOCK(pool.cs);
10101010

1011-
// For efficiency we simply sum up the pre-calculated
1012-
// fees/size-with-descendants values from the mempool package
1013-
// tracking; this does mean the pathological case of diamond tx
1014-
// graphs will be overcounted.
1011+
CFeeRate newFeeRate(nFees, nSize);
10151012
BOOST_FOREACH(const uint256 hashConflicting, setConflicts)
10161013
{
10171014
CTxMemPool::txiter mi = pool.mapTx.find(hashConflicting);
10181015
if (mi == pool.mapTx.end())
10191016
continue;
1017+
1018+
// Don't allow the replacement to reduce the feerate of the
1019+
// mempool.
1020+
//
1021+
// We usually don't want to accept replacements with lower
1022+
// feerates than what they replaced as that would lower the
1023+
// feerate of the next block. Requiring that the feerate always
1024+
// be increased is also an easy-to-reason about way to prevent
1025+
// DoS attacks via replacements.
1026+
//
1027+
// The mining code doesn't (currently) take children into
1028+
// account (CPFP) so we only consider the feerates of
1029+
// transactions being directly replaced, not their indirect
1030+
// descendants. While that does mean high feerate children are
1031+
// ignored when deciding whether or not to replace, we do
1032+
// require the replacement to pay more overall fees too,
1033+
// mitigating most cases.
1034+
CFeeRate oldFeeRate(mi->GetFee(), mi->GetTxSize());
1035+
if (newFeeRate <= oldFeeRate)
1036+
{
1037+
return state.DoS(0,
1038+
error("AcceptToMemoryPool: rejecting replacement %s; new feerate %s <= old feerate %s",
1039+
hash.ToString(),
1040+
newFeeRate.ToString(),
1041+
oldFeeRate.ToString()),
1042+
REJECT_INSUFFICIENTFEE, "insufficient fee");
1043+
}
1044+
1045+
// For efficiency we simply sum up the pre-calculated
1046+
// fees/size-with-descendants values from the mempool package
1047+
// tracking; this does mean the pathological case of diamond tx
1048+
// graphs will be overcounted.
10201049
nConflictingFees += mi->GetFeesWithDescendants();
10211050
nConflictingSize += mi->GetSizeWithDescendants();
10221051
}
10231052

1024-
// First of all we can't allow a replacement unless it pays greater
1025-
// fees than the transactions it conflicts with - if we did the
1026-
// bandwidth used by those conflicting transactions would not be
1027-
// paid for
1053+
// The replacement must pay greater fees than the transactions it
1054+
// replaces - if we did the bandwidth used by those conflicting
1055+
// transactions would not be paid for.
10281056
if (nFees < nConflictingFees)
10291057
{
10301058
return state.DoS(0, error("AcceptToMemoryPool: rejecting replacement %s, less fees than conflicting txs; %s < %s",
10311059
hash.ToString(), FormatMoney(nFees), FormatMoney(nConflictingFees)),
10321060
REJECT_INSUFFICIENTFEE, "insufficient fee");
10331061
}
10341062

1035-
// Secondly in addition to paying more fees than the conflicts the
1036-
// new transaction must additionally pay for its own bandwidth.
1063+
// Finally in addition to paying more fees than the conflicts the
1064+
// new transaction must pay for its own bandwidth.
10371065
CAmount nDeltaFees = nFees - nConflictingFees;
10381066
if (nDeltaFees < ::minRelayTxFee.GetFee(nSize))
10391067
{
@@ -1044,20 +1072,6 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa
10441072
FormatMoney(::minRelayTxFee.GetFee(nSize))),
10451073
REJECT_INSUFFICIENTFEE, "insufficient fee");
10461074
}
1047-
1048-
// Finally replace only if we end up with a larger fees-per-kb than
1049-
// the replacements.
1050-
CFeeRate oldFeeRate(nConflictingFees, nConflictingSize);
1051-
CFeeRate newFeeRate(nFees, nSize);
1052-
if (newFeeRate <= oldFeeRate)
1053-
{
1054-
return state.DoS(0,
1055-
error("AcceptToMemoryPool: rejecting replacement %s; new feerate %s <= old feerate %s",
1056-
hash.ToString(),
1057-
newFeeRate.ToString(),
1058-
oldFeeRate.ToString()),
1059-
REJECT_INSUFFICIENTFEE, "insufficient fee");
1060-
}
10611075
}
10621076

10631077
// Check against previous transactions

0 commit comments

Comments
 (0)