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)
This commit is contained in:
parent
de7d4591a7
commit
5891f870d6
126
src/main.cpp
126
src/main.cpp
@ -831,15 +831,42 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa
|
||||
return state.Invalid(false, REJECT_ALREADY_KNOWN, "txn-already-in-mempool");
|
||||
|
||||
// Check for conflicts with in-memory transactions
|
||||
set<uint256> setConflicts;
|
||||
{
|
||||
LOCK(pool.cs); // protect pool.mapNextTx
|
||||
for (unsigned int i = 0; i < tx.vin.size(); i++)
|
||||
BOOST_FOREACH(const CTxIn &txin, tx.vin)
|
||||
{
|
||||
COutPoint outpoint = tx.vin[i].prevout;
|
||||
if (pool.mapNextTx.count(outpoint))
|
||||
if (pool.mapNextTx.count(txin.prevout))
|
||||
{
|
||||
// Disable replacement feature for now
|
||||
return state.Invalid(false, REJECT_CONFLICT, "txn-mempool-conflict");
|
||||
const CTransaction *ptxConflicting = pool.mapNextTx[txin.prevout].ptx;
|
||||
if (!setConflicts.count(ptxConflicting->GetHash()))
|
||||
{
|
||||
// Allow opt-out of transaction replacement by setting
|
||||
// nSequence >= maxint-1 on all inputs.
|
||||
//
|
||||
// maxint-1 is picked to still allow use of nLockTime by
|
||||
// non-replacable transactions. All inputs rather than just one
|
||||
// is for the sake of multi-party protocols, where we don't
|
||||
// want a single party to be able to disable replacement.
|
||||
//
|
||||
// The opt-out ignores descendants as anyone relying on
|
||||
// first-seen mempool behavior should be checking all
|
||||
// unconfirmed ancestors anyway; doing otherwise is hopelessly
|
||||
// insecure.
|
||||
bool fReplacementOptOut = true;
|
||||
BOOST_FOREACH(const CTxIn &txin, ptxConflicting->vin)
|
||||
{
|
||||
if (txin.nSequence < std::numeric_limits<unsigned int>::max()-1)
|
||||
{
|
||||
fReplacementOptOut = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (fReplacementOptOut)
|
||||
return state.Invalid(false, REJECT_CONFLICT, "txn-mempool-conflict");
|
||||
|
||||
setConflicts.insert(ptxConflicting->GetHash());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -957,6 +984,82 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa
|
||||
return state.DoS(0, false, REJECT_NONSTANDARD, "too-long-mempool-chain", false, errString);
|
||||
}
|
||||
|
||||
// A transaction that spends outputs that would be replaced by it is invalid. Now
|
||||
// that we have the set of all ancestors we can detect this
|
||||
// pathological case by making sure setConflicts and setAncestors don't
|
||||
// intersect.
|
||||
BOOST_FOREACH(CTxMemPool::txiter ancestorIt, setAncestors)
|
||||
{
|
||||
const uint256 &hashAncestor = ancestorIt->GetTx().GetHash();
|
||||
if (setConflicts.count(hashAncestor))
|
||||
{
|
||||
return state.DoS(10, error("AcceptToMemoryPool: %s spends conflicting transaction %s",
|
||||
hash.ToString(),
|
||||
hashAncestor.ToString()),
|
||||
REJECT_INVALID, "bad-txns-spends-conflicting-tx");
|
||||
}
|
||||
}
|
||||
|
||||
// Check if it's economically rational to mine this transaction rather
|
||||
// than the ones it replaces.
|
||||
CAmount nConflictingFees = 0;
|
||||
size_t nConflictingSize = 0;
|
||||
if (setConflicts.size())
|
||||
{
|
||||
LOCK(pool.cs);
|
||||
|
||||
// For efficiency we simply sum up the pre-calculated
|
||||
// fees/size-with-descendants values from the mempool package
|
||||
// tracking; this does mean the pathological case of diamond tx
|
||||
// graphs will be overcounted.
|
||||
BOOST_FOREACH(const uint256 hashConflicting, setConflicts)
|
||||
{
|
||||
CTxMemPool::txiter mi = pool.mapTx.find(hashConflicting);
|
||||
if (mi == pool.mapTx.end())
|
||||
continue;
|
||||
nConflictingFees += mi->GetFeesWithDescendants();
|
||||
nConflictingSize += mi->GetSizeWithDescendants();
|
||||
}
|
||||
|
||||
// First of all we can't allow a replacement unless it pays greater
|
||||
// fees than the transactions it conflicts with - if we did the
|
||||
// bandwidth used by those conflicting transactions would not be
|
||||
// paid for
|
||||
if (nFees < nConflictingFees)
|
||||
{
|
||||
return state.DoS(0, error("AcceptToMemoryPool: rejecting replacement %s, less fees than conflicting txs; %s < %s",
|
||||
hash.ToString(), FormatMoney(nFees), FormatMoney(nConflictingFees)),
|
||||
REJECT_INSUFFICIENTFEE, "insufficient fee");
|
||||
}
|
||||
|
||||
// Secondly in addition to paying more fees than the conflicts the
|
||||
// new transaction must additionally pay for its own bandwidth.
|
||||
CAmount nDeltaFees = nFees - nConflictingFees;
|
||||
if (nDeltaFees < ::minRelayTxFee.GetFee(nSize))
|
||||
{
|
||||
return state.DoS(0,
|
||||
error("AcceptToMemoryPool: rejecting replacement %s, not enough additional fees to relay; %s < %s",
|
||||
hash.ToString(),
|
||||
FormatMoney(nDeltaFees),
|
||||
FormatMoney(::minRelayTxFee.GetFee(nSize))),
|
||||
REJECT_INSUFFICIENTFEE, "insufficient fee");
|
||||
}
|
||||
|
||||
// Finally replace only if we end up with a larger fees-per-kb than
|
||||
// the replacements.
|
||||
CFeeRate oldFeeRate(nConflictingFees, nConflictingSize);
|
||||
CFeeRate newFeeRate(nFees, nSize);
|
||||
if (newFeeRate <= oldFeeRate)
|
||||
{
|
||||
return state.DoS(0,
|
||||
error("AcceptToMemoryPool: rejecting replacement %s; new feerate %s <= old feerate %s",
|
||||
hash.ToString(),
|
||||
newFeeRate.ToString(),
|
||||
oldFeeRate.ToString()),
|
||||
REJECT_INSUFFICIENTFEE, "insufficient fee");
|
||||
}
|
||||
}
|
||||
|
||||
// Check against previous transactions
|
||||
// This is done last to help prevent CPU exhaustion denial-of-service attacks.
|
||||
if (!CheckInputs(tx, state, view, true, STANDARD_SCRIPT_VERIFY_FLAGS, true))
|
||||
@ -977,6 +1080,19 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa
|
||||
__func__, hash.ToString(), FormatStateMessage(state));
|
||||
}
|
||||
|
||||
// Remove conflicting transactions from the mempool
|
||||
list<CTransaction> ltxConflicted;
|
||||
pool.removeConflicts(tx, ltxConflicted);
|
||||
|
||||
BOOST_FOREACH(const CTransaction &txConflicted, ltxConflicted)
|
||||
{
|
||||
LogPrint("mempool", "replacing tx %s with %s for %s BTC additional fees, %d delta bytes\n",
|
||||
txConflicted.GetHash().ToString(),
|
||||
hash.ToString(),
|
||||
FormatMoney(nFees - nConflictingFees),
|
||||
(int)nSize - (int)nConflictingSize);
|
||||
}
|
||||
|
||||
// Store transaction in memory
|
||||
pool.addUnchecked(hash, entry, setAncestors, !IsInitialBlockDownload());
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user