Skip to content

Commit 9ef2a25

Browse files
committed
Update replace-by-fee logic to use fee deltas
1 parent eb30666 commit 9ef2a25

File tree

2 files changed

+89
-9
lines changed

2 files changed

+89
-9
lines changed

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

Lines changed: 78 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,16 +63,22 @@ def make_utxo(node, amount, confirmed=True, scriptPubKey=CScript([1])):
6363

6464
# If requested, ensure txouts are confirmed.
6565
if confirmed:
66-
while len(node.getrawmempool()):
66+
mempool_size = len(node.getrawmempool())
67+
while mempool_size > 0:
6768
node.generate(1)
69+
new_size = len(node.getrawmempool())
70+
# Error out if we have something stuck in the mempool, as this
71+
# would likely be a bug.
72+
assert(new_size < mempool_size)
73+
mempool_size = new_size
6874

6975
return COutPoint(int(txid, 16), 0)
7076

7177
class ReplaceByFeeTest(BitcoinTestFramework):
7278

7379
def setup_network(self):
7480
self.nodes = []
75-
self.nodes.append(start_node(0, self.options.tmpdir, ["-maxorphantx=1000",
81+
self.nodes.append(start_node(0, self.options.tmpdir, ["-maxorphantx=1000", "-debug",
7682
"-relaypriority=0", "-whitelist=127.0.0.1",
7783
"-limitancestorcount=50",
7884
"-limitancestorsize=101",
@@ -108,6 +114,9 @@ def run_test(self):
108114
print "Running test opt-in..."
109115
self.test_opt_in()
110116

117+
print "Running test prioritised transactions..."
118+
self.test_prioritised_transactions()
119+
111120
print "Passed\n"
112121

113122
def test_simple_doublespend(self):
@@ -513,5 +522,72 @@ def test_opt_in(self):
513522
# but make sure it is accepted anyway
514523
self.nodes[0].sendrawtransaction(tx3c_hex, True)
515524

525+
def test_prioritised_transactions(self):
526+
# Ensure that fee deltas used via prioritisetransaction are
527+
# correctly used by replacement logic
528+
529+
# 1. Check that feeperkb uses modified fees
530+
tx0_outpoint = make_utxo(self.nodes[0], 1.1*COIN)
531+
532+
tx1a = CTransaction()
533+
tx1a.vin = [CTxIn(tx0_outpoint, nSequence=0)]
534+
tx1a.vout = [CTxOut(1*COIN, CScript([b'a']))]
535+
tx1a_hex = txToHex(tx1a)
536+
tx1a_txid = self.nodes[0].sendrawtransaction(tx1a_hex, True)
537+
538+
# Higher fee, but the actual fee per KB is much lower.
539+
tx1b = CTransaction()
540+
tx1b.vin = [CTxIn(tx0_outpoint, nSequence=0)]
541+
tx1b.vout = [CTxOut(0.001*COIN, CScript([b'a'*740000]))]
542+
tx1b_hex = txToHex(tx1b)
543+
544+
# Verify tx1b cannot replace tx1a.
545+
try:
546+
tx1b_txid = self.nodes[0].sendrawtransaction(tx1b_hex, True)
547+
except JSONRPCException as exp:
548+
assert_equal(exp.error['code'], -26)
549+
else:
550+
assert(False)
551+
552+
# Use prioritisetransaction to set tx1a's fee to 0.
553+
self.nodes[0].prioritisetransaction(tx1a_txid, 0, int(-0.1*COIN))
554+
555+
# Now tx1b should be able to replace tx1a
556+
tx1b_txid = self.nodes[0].sendrawtransaction(tx1b_hex, True)
557+
558+
assert(tx1b_txid in self.nodes[0].getrawmempool())
559+
560+
# 2. Check that absolute fee checks use modified fee.
561+
tx1_outpoint = make_utxo(self.nodes[0], 1.1*COIN)
562+
563+
tx2a = CTransaction()
564+
tx2a.vin = [CTxIn(tx1_outpoint, nSequence=0)]
565+
tx2a.vout = [CTxOut(1*COIN, CScript([b'a']))]
566+
tx2a_hex = txToHex(tx2a)
567+
tx2a_txid = self.nodes[0].sendrawtransaction(tx2a_hex, True)
568+
569+
# Lower fee, but we'll prioritise it
570+
tx2b = CTransaction()
571+
tx2b.vin = [CTxIn(tx1_outpoint, nSequence=0)]
572+
tx2b.vout = [CTxOut(1.01*COIN, CScript([b'a']))]
573+
tx2b.rehash()
574+
tx2b_hex = txToHex(tx2b)
575+
576+
# Verify tx2b cannot replace tx2a.
577+
try:
578+
tx2b_txid = self.nodes[0].sendrawtransaction(tx2b_hex, True)
579+
except JSONRPCException as exp:
580+
assert_equal(exp.error['code'], -26)
581+
else:
582+
assert(False)
583+
584+
# Now prioritise tx2b to have a higher modified fee
585+
self.nodes[0].prioritisetransaction(tx2b.hash, 0, int(0.1*COIN))
586+
587+
# tx2b should now be accepted
588+
tx2b_txid = self.nodes[0].sendrawtransaction(tx2b_hex, True)
589+
590+
assert(tx2b_txid in self.nodes[0].getrawmempool())
591+
516592
if __name__ == '__main__':
517593
ReplaceByFeeTest().main()

src/main.cpp

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1061,13 +1061,17 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState &state, const C
10611061
uint64_t nConflictingCount = 0;
10621062
CTxMemPool::setEntries allConflicting;
10631063

1064+
CAmount nModifiedFees = nFees;
1065+
double nPriorityDummy = 0;
1066+
pool.ApplyDeltas(hash, nPriorityDummy, nModifiedFees);
1067+
10641068
// If we don't hold the lock allConflicting might be incomplete; the
10651069
// subsequent RemoveStaged() and addUnchecked() calls don't guarantee
10661070
// mempool consistency for us.
10671071
LOCK(pool.cs);
10681072
if (setConflicts.size())
10691073
{
1070-
CFeeRate newFeeRate(nFees, nSize);
1074+
CFeeRate newFeeRate(nModifiedFees, nSize);
10711075
set<uint256> setConflictsParents;
10721076
const int maxDescendantsToVisit = 100;
10731077
CTxMemPool::setEntries setIterConflicting;
@@ -1110,7 +1114,7 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState &state, const C
11101114
// ignored when deciding whether or not to replace, we do
11111115
// require the replacement to pay more overall fees too,
11121116
// mitigating most cases.
1113-
CFeeRate oldFeeRate(mi->GetFee(), mi->GetTxSize());
1117+
CFeeRate oldFeeRate(mi->GetModifiedFee(), mi->GetTxSize());
11141118
if (newFeeRate <= oldFeeRate)
11151119
{
11161120
return state.DoS(0,
@@ -1138,7 +1142,7 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState &state, const C
11381142
pool.CalculateDescendants(it, allConflicting);
11391143
}
11401144
BOOST_FOREACH(CTxMemPool::txiter it, allConflicting) {
1141-
nConflictingFees += it->GetFee();
1145+
nConflictingFees += it->GetModifiedFee();
11421146
nConflictingSize += it->GetTxSize();
11431147
}
11441148
} else {
@@ -1171,16 +1175,16 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState &state, const C
11711175
// The replacement must pay greater fees than the transactions it
11721176
// replaces - if we did the bandwidth used by those conflicting
11731177
// transactions would not be paid for.
1174-
if (nFees < nConflictingFees)
1178+
if (nModifiedFees < nConflictingFees)
11751179
{
11761180
return state.DoS(0, error("AcceptToMemoryPool: rejecting replacement %s, less fees than conflicting txs; %s < %s",
1177-
hash.ToString(), FormatMoney(nFees), FormatMoney(nConflictingFees)),
1181+
hash.ToString(), FormatMoney(nModifiedFees), FormatMoney(nConflictingFees)),
11781182
REJECT_INSUFFICIENTFEE, "insufficient fee");
11791183
}
11801184

11811185
// Finally in addition to paying more fees than the conflicts the
11821186
// new transaction must pay for its own bandwidth.
1183-
CAmount nDeltaFees = nFees - nConflictingFees;
1187+
CAmount nDeltaFees = nModifiedFees - nConflictingFees;
11841188
if (nDeltaFees < ::minRelayTxFee.GetFee(nSize))
11851189
{
11861190
return state.DoS(0,
@@ -1218,7 +1222,7 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState &state, const C
12181222
LogPrint("mempool", "replacing tx %s with %s for %s BTC additional fees, %d delta bytes\n",
12191223
it->GetTx().GetHash().ToString(),
12201224
hash.ToString(),
1221-
FormatMoney(nFees - nConflictingFees),
1225+
FormatMoney(nModifiedFees - nConflictingFees),
12221226
(int)nSize - (int)nConflictingSize);
12231227
}
12241228
pool.RemoveStaged(allConflicting);

0 commit comments

Comments
 (0)