Skip to content

Commit 5891f87

Browse files
committed
Add opt-in full-RBF to mempool
Replaces transactions already in the mempool if a new transaction seen with a higher fee, specifically both a higher fee per KB and a higher absolute fee. Children are evaluateed for replacement as well, using the mempool package tracking to calculate replaced fees/size. Transactions can opt-out of transaction replacement by setting nSequence >= maxint-1 on all inputs. (which all wallets do already)
1 parent de7d459 commit 5891f87

File tree

1 file changed

+121
-5
lines changed

1 file changed

+121
-5
lines changed

src/main.cpp

Lines changed: 121 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -831,15 +831,42 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa
831831
return state.Invalid(false, REJECT_ALREADY_KNOWN, "txn-already-in-mempool");
832832

833833
// Check for conflicts with in-memory transactions
834+
set<uint256> setConflicts;
834835
{
835836
LOCK(pool.cs); // protect pool.mapNextTx
836-
for (unsigned int i = 0; i < tx.vin.size(); i++)
837+
BOOST_FOREACH(const CTxIn &txin, tx.vin)
837838
{
838-
COutPoint outpoint = tx.vin[i].prevout;
839-
if (pool.mapNextTx.count(outpoint))
839+
if (pool.mapNextTx.count(txin.prevout))
840840
{
841-
// Disable replacement feature for now
842-
return state.Invalid(false, REJECT_CONFLICT, "txn-mempool-conflict");
841+
const CTransaction *ptxConflicting = pool.mapNextTx[txin.prevout].ptx;
842+
if (!setConflicts.count(ptxConflicting->GetHash()))
843+
{
844+
// Allow opt-out of transaction replacement by setting
845+
// nSequence >= maxint-1 on all inputs.
846+
//
847+
// maxint-1 is picked to still allow use of nLockTime by
848+
// non-replacable transactions. All inputs rather than just one
849+
// is for the sake of multi-party protocols, where we don't
850+
// want a single party to be able to disable replacement.
851+
//
852+
// The opt-out ignores descendants as anyone relying on
853+
// first-seen mempool behavior should be checking all
854+
// unconfirmed ancestors anyway; doing otherwise is hopelessly
855+
// insecure.
856+
bool fReplacementOptOut = true;
857+
BOOST_FOREACH(const CTxIn &txin, ptxConflicting->vin)
858+
{
859+
if (txin.nSequence < std::numeric_limits<unsigned int>::max()-1)
860+
{
861+
fReplacementOptOut = false;
862+
break;
863+
}
864+
}
865+
if (fReplacementOptOut)
866+
return state.Invalid(false, REJECT_CONFLICT, "txn-mempool-conflict");
867+
868+
setConflicts.insert(ptxConflicting->GetHash());
869+
}
843870
}
844871
}
845872
}
@@ -957,6 +984,82 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa
957984
return state.DoS(0, false, REJECT_NONSTANDARD, "too-long-mempool-chain", false, errString);
958985
}
959986

987+
// A transaction that spends outputs that would be replaced by it is invalid. Now
988+
// that we have the set of all ancestors we can detect this
989+
// pathological case by making sure setConflicts and setAncestors don't
990+
// intersect.
991+
BOOST_FOREACH(CTxMemPool::txiter ancestorIt, setAncestors)
992+
{
993+
const uint256 &hashAncestor = ancestorIt->GetTx().GetHash();
994+
if (setConflicts.count(hashAncestor))
995+
{
996+
return state.DoS(10, error("AcceptToMemoryPool: %s spends conflicting transaction %s",
997+
hash.ToString(),
998+
hashAncestor.ToString()),
999+
REJECT_INVALID, "bad-txns-spends-conflicting-tx");
1000+
}
1001+
}
1002+
1003+
// Check if it's economically rational to mine this transaction rather
1004+
// than the ones it replaces.
1005+
CAmount nConflictingFees = 0;
1006+
size_t nConflictingSize = 0;
1007+
if (setConflicts.size())
1008+
{
1009+
LOCK(pool.cs);
1010+
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.
1015+
BOOST_FOREACH(const uint256 hashConflicting, setConflicts)
1016+
{
1017+
CTxMemPool::txiter mi = pool.mapTx.find(hashConflicting);
1018+
if (mi == pool.mapTx.end())
1019+
continue;
1020+
nConflictingFees += mi->GetFeesWithDescendants();
1021+
nConflictingSize += mi->GetSizeWithDescendants();
1022+
}
1023+
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
1028+
if (nFees < nConflictingFees)
1029+
{
1030+
return state.DoS(0, error("AcceptToMemoryPool: rejecting replacement %s, less fees than conflicting txs; %s < %s",
1031+
hash.ToString(), FormatMoney(nFees), FormatMoney(nConflictingFees)),
1032+
REJECT_INSUFFICIENTFEE, "insufficient fee");
1033+
}
1034+
1035+
// Secondly in addition to paying more fees than the conflicts the
1036+
// new transaction must additionally pay for its own bandwidth.
1037+
CAmount nDeltaFees = nFees - nConflictingFees;
1038+
if (nDeltaFees < ::minRelayTxFee.GetFee(nSize))
1039+
{
1040+
return state.DoS(0,
1041+
error("AcceptToMemoryPool: rejecting replacement %s, not enough additional fees to relay; %s < %s",
1042+
hash.ToString(),
1043+
FormatMoney(nDeltaFees),
1044+
FormatMoney(::minRelayTxFee.GetFee(nSize))),
1045+
REJECT_INSUFFICIENTFEE, "insufficient fee");
1046+
}
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+
}
1061+
}
1062+
9601063
// Check against previous transactions
9611064
// This is done last to help prevent CPU exhaustion denial-of-service attacks.
9621065
if (!CheckInputs(tx, state, view, true, STANDARD_SCRIPT_VERIFY_FLAGS, true))
@@ -977,6 +1080,19 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa
9771080
__func__, hash.ToString(), FormatStateMessage(state));
9781081
}
9791082

1083+
// Remove conflicting transactions from the mempool
1084+
list<CTransaction> ltxConflicted;
1085+
pool.removeConflicts(tx, ltxConflicted);
1086+
1087+
BOOST_FOREACH(const CTransaction &txConflicted, ltxConflicted)
1088+
{
1089+
LogPrint("mempool", "replacing tx %s with %s for %s BTC additional fees, %d delta bytes\n",
1090+
txConflicted.GetHash().ToString(),
1091+
hash.ToString(),
1092+
FormatMoney(nFees - nConflictingFees),
1093+
(int)nSize - (int)nConflictingSize);
1094+
}
1095+
9801096
// Store transaction in memory
9811097
pool.addUnchecked(hash, entry, setAncestors, !IsInitialBlockDownload());
9821098

0 commit comments

Comments
 (0)