Skip to content

Commit 73d9040

Browse files
sdaftuarpetertodd
authored andcommitted
Improve RBF replacement criteria
Fix the calculation of conflicting size/conflicting fees.
1 parent b272ecf commit 73d9040

File tree

2 files changed

+52
-16
lines changed

2 files changed

+52
-16
lines changed

src/main.cpp

Lines changed: 47 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1004,18 +1004,39 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa
10041004
// than the ones it replaces.
10051005
CAmount nConflictingFees = 0;
10061006
size_t nConflictingSize = 0;
1007+
uint64_t nConflictingCount = 0;
1008+
CTxMemPool::setEntries allConflicting;
10071009
if (setConflicts.size())
10081010
{
10091011
LOCK(pool.cs);
10101012

10111013
CFeeRate newFeeRate(nFees, nSize);
10121014
set<uint256> setConflictsParents;
1013-
BOOST_FOREACH(const uint256 hashConflicting, setConflicts)
1015+
const int maxDescendantsToVisit = 100;
1016+
CTxMemPool::setEntries setIterConflicting;
1017+
BOOST_FOREACH(const uint256 &hashConflicting, setConflicts)
10141018
{
10151019
CTxMemPool::txiter mi = pool.mapTx.find(hashConflicting);
10161020
if (mi == pool.mapTx.end())
10171021
continue;
10181022

1023+
// Save these to avoid repeated lookups
1024+
setIterConflicting.insert(mi);
1025+
1026+
// If this entry is "dirty", then we don't have descendant
1027+
// state for this transaction, which means we probably have
1028+
// lots of in-mempool descendants.
1029+
// Don't allow replacements of dirty transactions, to ensure
1030+
// that we don't spend too much time walking descendants.
1031+
// This should be rare.
1032+
if (mi->IsDirty()) {
1033+
return state.DoS(0,
1034+
error("AcceptToMemoryPool: rejecting replacement %s; cannot replace tx %s with untracked descendants",
1035+
hash.ToString(),
1036+
mi->GetTx().GetHash().ToString()),
1037+
REJECT_NONSTANDARD, "too many potential replacements");
1038+
}
1039+
10191040
// Don't allow the replacement to reduce the feerate of the
10201041
// mempool.
10211042
//
@@ -1048,12 +1069,28 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa
10481069
setConflictsParents.insert(txin.prevout.hash);
10491070
}
10501071

1051-
// For efficiency we simply sum up the pre-calculated
1052-
// fees/size-with-descendants values from the mempool package
1053-
// tracking; this does mean the pathological case of diamond tx
1054-
// graphs will be overcounted.
1055-
nConflictingFees += mi->GetFeesWithDescendants();
1056-
nConflictingSize += mi->GetSizeWithDescendants();
1072+
nConflictingCount += mi->GetCountWithDescendants();
1073+
}
1074+
// This potentially overestimates the number of actual descendants
1075+
// but we just want to be conservative to avoid doing too much
1076+
// work.
1077+
if (nConflictingCount <= maxDescendantsToVisit) {
1078+
// If not too many to replace, then calculate the set of
1079+
// transactions that would have to be evicted
1080+
BOOST_FOREACH(CTxMemPool::txiter it, setIterConflicting) {
1081+
pool.CalculateDescendants(it, allConflicting);
1082+
}
1083+
BOOST_FOREACH(CTxMemPool::txiter it, allConflicting) {
1084+
nConflictingFees += it->GetFee();
1085+
nConflictingSize += it->GetTxSize();
1086+
}
1087+
} else {
1088+
return state.DoS(0,
1089+
error("AcceptToMemoryPool: rejecting replacement %s; too many potential replacements (%d > %d)\n",
1090+
hash.ToString(),
1091+
nConflictingCount,
1092+
maxDescendantsToVisit),
1093+
REJECT_NONSTANDARD, "too many potential replacements");
10571094
}
10581095

10591096
for (unsigned int j = 0; j < tx.vin.size(); j++)
@@ -1119,17 +1156,15 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa
11191156
}
11201157

11211158
// Remove conflicting transactions from the mempool
1122-
list<CTransaction> ltxConflicted;
1123-
pool.removeConflicts(tx, ltxConflicted);
1124-
1125-
BOOST_FOREACH(const CTransaction &txConflicted, ltxConflicted)
1159+
BOOST_FOREACH(const CTxMemPool::txiter it, allConflicting)
11261160
{
11271161
LogPrint("mempool", "replacing tx %s with %s for %s BTC additional fees, %d delta bytes\n",
1128-
txConflicted.GetHash().ToString(),
1162+
it->GetTx().GetHash().ToString(),
11291163
hash.ToString(),
11301164
FormatMoney(nFees - nConflictingFees),
11311165
(int)nSize - (int)nConflictingSize);
11321166
}
1167+
pool.RemoveStaged(allConflicting);
11331168

11341169
// Store transaction in memory
11351170
pool.addUnchecked(hash, entry, setAncestors, !IsInitialBlockDownload());

src/txmempool.h

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,11 @@ class CTxMemPool
420420
*/
421421
bool CalculateMemPoolAncestors(const CTxMemPoolEntry &entry, setEntries &setAncestors, uint64_t limitAncestorCount, uint64_t limitAncestorSize, uint64_t limitDescendantCount, uint64_t limitDescendantSize, std::string &errString, bool fSearchForParents = true);
422422

423+
/** Populate setDescendants with all in-mempool descendants of hash.
424+
* Assumes that setDescendants includes all in-mempool descendants of anything
425+
* already in it. */
426+
void CalculateDescendants(txiter it, setEntries &setDescendants);
427+
423428
/** The minimum fee to get into the mempool, which may itself not be enough
424429
* for larger-sized transactions.
425430
* The minReasonableRelayFee constructor arg is used to bound the time it
@@ -493,10 +498,6 @@ class CTxMemPool
493498
void UpdateForRemoveFromMempool(const setEntries &entriesToRemove);
494499
/** Sever link between specified transaction and direct children. */
495500
void UpdateChildrenForRemoval(txiter entry);
496-
/** Populate setDescendants with all in-mempool descendants of hash.
497-
* Assumes that setDescendants includes all in-mempool descendants of anything
498-
* already in it. */
499-
void CalculateDescendants(txiter it, setEntries &setDescendants);
500501

501502
/** Before calling removeUnchecked for a given transaction,
502503
* UpdateForRemoveFromMempool must be called on the entire (dependent) set

0 commit comments

Comments
 (0)