aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPeter Todd <pete@petertodd.org>2015-10-22 14:13:18 -0400
committerPeter Todd <pete@petertodd.org>2015-11-10 14:14:06 -0500
commit5891f870d68d90408aa5ce5b597fb574f2d2cbca (patch)
treeeb7c975bcace8a191f1d88db8a78c73c58ccb8fe
parentde7d4591a7ce064ba64c36ccb729f1d94c21aa90 (diff)
downloadbitcoin-5891f870d68d90408aa5ce5b597fb574f2d2cbca.tar.xz
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)
-rw-r--r--src/main.cpp126
1 files changed, 121 insertions, 5 deletions
diff --git a/src/main.cpp b/src/main.cpp
index b8abcff596..274a336ee2 100644
--- a/src/main.cpp
+++ b/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());