Skip to content

Commit 12c469b

Browse files
sdaftuarlaanwj
authored andcommitted
[Mempool] Fix mempool limiting and replace-by-fee for PrioritiseTransaction
1) Fix mempool limiting for PrioritiseTransaction Redo the feerate index to be based on mining score, rather than fee. Update mempool_packages.py to test prioritisetransaction's effect on package scores. 2) Update replace-by-fee logic to use fee deltas 3) Use fee deltas for determining mempool acceptance 4) Remove GetMinRelayFee One test in AcceptToMemoryPool was to compare a transaction's fee agains the value returned by GetMinRelayFee. This value was zero for all small transactions. For larger transactions (between DEFAULT_BLOCK_PRIORITY_SIZE and MAX_STANDARD_TX_SIZE), this function was preventing low fee transactions from ever being accepted. With this function removed, we will now allow transactions in that range with fees (including modifications via PrioritiseTransaction) below the minRelayTxFee, provided that they have sufficient priority. Github-Pull: #7062 Rebased-From: eb30666 9ef2a25 27fae34 901b01d
1 parent eccd671 commit 12c469b

File tree

8 files changed

+211
-92
lines changed

8 files changed

+211
-92
lines changed

qa/rpc-tests/mempool_packages.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,17 +64,41 @@ def run_test(self):
6464
for x in reversed(chain):
6565
assert_equal(mempool[x]['descendantcount'], descendant_count)
6666
descendant_fees += mempool[x]['fee']
67+
assert_equal(mempool[x]['modifiedfee'], mempool[x]['fee'])
6768
assert_equal(mempool[x]['descendantfees'], SATOSHIS*descendant_fees)
6869
descendant_size += mempool[x]['size']
6970
assert_equal(mempool[x]['descendantsize'], descendant_size)
7071
descendant_count += 1
7172

73+
# Check that descendant modified fees includes fee deltas from
74+
# prioritisetransaction
75+
self.nodes[0].prioritisetransaction(chain[-1], 0, 1000)
76+
mempool = self.nodes[0].getrawmempool(True)
77+
78+
descendant_fees = 0
79+
for x in reversed(chain):
80+
descendant_fees += mempool[x]['fee']
81+
assert_equal(mempool[x]['descendantfees'], SATOSHIS*descendant_fees+1000)
82+
7283
# Adding one more transaction on to the chain should fail.
7384
try:
7485
self.chain_transaction(self.nodes[0], txid, vout, value, fee, 1)
7586
except JSONRPCException as e:
7687
print "too-long-ancestor-chain successfully rejected"
7788

89+
# Check that prioritising a tx before it's added to the mempool works
90+
self.nodes[0].generate(1)
91+
self.nodes[0].prioritisetransaction(chain[-1], 0, 2000)
92+
self.nodes[0].invalidateblock(self.nodes[0].getbestblockhash())
93+
mempool = self.nodes[0].getrawmempool(True)
94+
95+
descendant_fees = 0
96+
for x in reversed(chain):
97+
descendant_fees += mempool[x]['fee']
98+
if (x == chain[-1]):
99+
assert_equal(mempool[x]['modifiedfee'], mempool[x]['fee']+satoshi_round(0.00002))
100+
assert_equal(mempool[x]['descendantfees'], SATOSHIS*descendant_fees+2000)
101+
78102
# TODO: check that node1's mempool is as expected
79103

80104
# TODO: test ancestor size limits

qa/rpc-tests/prioritise_transaction.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,5 +143,45 @@ def run_test(self):
143143
if (x != high_fee_tx):
144144
assert(x not in mempool)
145145

146+
# Create a free, low priority transaction. Should be rejected.
147+
utxo_list = self.nodes[0].listunspent()
148+
assert(len(utxo_list) > 0)
149+
utxo = utxo_list[0]
150+
151+
inputs = []
152+
outputs = {}
153+
inputs.append({"txid" : utxo["txid"], "vout" : utxo["vout"]})
154+
outputs[self.nodes[0].getnewaddress()] = utxo["amount"] - self.relayfee
155+
raw_tx = self.nodes[0].createrawtransaction(inputs, outputs)
156+
tx_hex = self.nodes[0].signrawtransaction(raw_tx)["hex"]
157+
txid = self.nodes[0].sendrawtransaction(tx_hex)
158+
159+
# A tx that spends an in-mempool tx has 0 priority, so we can use it to
160+
# test the effect of using prioritise transaction for mempool acceptance
161+
inputs = []
162+
inputs.append({"txid": txid, "vout": 0})
163+
outputs = {}
164+
outputs[self.nodes[0].getnewaddress()] = utxo["amount"] - self.relayfee
165+
raw_tx2 = self.nodes[0].createrawtransaction(inputs, outputs)
166+
tx2_hex = self.nodes[0].signrawtransaction(raw_tx2)["hex"]
167+
tx2_id = self.nodes[0].decoderawtransaction(tx2_hex)["txid"]
168+
169+
try:
170+
self.nodes[0].sendrawtransaction(tx2_hex)
171+
except JSONRPCException as exp:
172+
assert_equal(exp.error['code'], -26) # insufficient fee
173+
assert(tx2_id not in self.nodes[0].getrawmempool())
174+
else:
175+
assert(False)
176+
177+
# This is a less than 1000-byte transaction, so just set the fee
178+
# to be the minimum for a 1000 byte transaction and check that it is
179+
# accepted.
180+
self.nodes[0].prioritisetransaction(tx2_id, 0, int(self.relayfee*COIN))
181+
182+
print "Assert that prioritised free transaction is accepted to mempool"
183+
assert_equal(self.nodes[0].sendrawtransaction(tx2_hex), tx2_id)
184+
assert(tx2_id in self.nodes[0].getrawmempool())
185+
146186
if __name__ == '__main__':
147187
PrioritiseTransactionTest().main()

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: 15 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -800,32 +800,6 @@ void LimitMempoolSize(CTxMemPool& pool, size_t limit, unsigned long age) {
800800
pcoinsTip->Uncache(removed);
801801
}
802802

803-
CAmount GetMinRelayFee(const CTransaction& tx, const CTxMemPool& pool, unsigned int nBytes, bool fAllowFree)
804-
{
805-
uint256 hash = tx.GetHash();
806-
double dPriorityDelta = 0;
807-
CAmount nFeeDelta = 0;
808-
pool.ApplyDeltas(hash, dPriorityDelta, nFeeDelta);
809-
if (dPriorityDelta > 0 || nFeeDelta > 0)
810-
return 0;
811-
812-
CAmount nMinFee = ::minRelayTxFee.GetFee(nBytes);
813-
814-
if (fAllowFree)
815-
{
816-
// There is a free transaction area in blocks created by most miners,
817-
// * If we are relaying we allow transactions up to DEFAULT_BLOCK_PRIORITY_SIZE - 1000
818-
// to be considered to fall into this category. We don't want to encourage sending
819-
// multiple transactions instead of one big transaction to avoid fees.
820-
if (nBytes < (DEFAULT_BLOCK_PRIORITY_SIZE - 1000))
821-
nMinFee = 0;
822-
}
823-
824-
if (!MoneyRange(nMinFee))
825-
nMinFee = MAX_MONEY;
826-
return nMinFee;
827-
}
828-
829803
/** Convert CValidationState to a human-readable message for logging */
830804
std::string FormatStateMessage(const CValidationState &state)
831805
{
@@ -968,6 +942,11 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState &state, const C
968942

969943
CAmount nValueOut = tx.GetValueOut();
970944
CAmount nFees = nValueIn-nValueOut;
945+
// nModifiedFees includes any fee deltas from PrioritiseTransaction
946+
CAmount nModifiedFees = nFees;
947+
double nPriorityDummy = 0;
948+
pool.ApplyDeltas(hash, nPriorityDummy, nModifiedFees);
949+
971950
CAmount inChainInputValue;
972951
double dPriority = view.GetPriority(tx, chainActive.Height(), inChainInputValue);
973952

@@ -985,24 +964,18 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState &state, const C
985964
CTxMemPoolEntry entry(tx, nFees, GetTime(), dPriority, chainActive.Height(), pool.HasNoInputsOf(tx), inChainInputValue, fSpendsCoinbase, nSigOps);
986965
unsigned int nSize = entry.GetTxSize();
987966

988-
// Don't accept it if it can't get into a block
989-
CAmount txMinFee = GetMinRelayFee(tx, pool, nSize, true);
990-
if (fLimitFree && nFees < txMinFee)
991-
return state.DoS(0, false, REJECT_INSUFFICIENTFEE, "insufficient fee", false,
992-
strprintf("%d < %d", nFees, txMinFee));
993-
994967
CAmount mempoolRejectFee = pool.GetMinFee(GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000).GetFee(nSize);
995-
if (mempoolRejectFee > 0 && nFees < mempoolRejectFee) {
968+
if (mempoolRejectFee > 0 && nModifiedFees < mempoolRejectFee) {
996969
return state.DoS(0, false, REJECT_INSUFFICIENTFEE, "mempool min fee not met", false, strprintf("%d < %d", nFees, mempoolRejectFee));
997-
} else if (GetBoolArg("-relaypriority", DEFAULT_RELAYPRIORITY) && nFees < ::minRelayTxFee.GetFee(nSize) && !AllowFree(entry.GetPriority(chainActive.Height() + 1))) {
970+
} else if (GetBoolArg("-relaypriority", DEFAULT_RELAYPRIORITY) && nModifiedFees < ::minRelayTxFee.GetFee(nSize) && !AllowFree(entry.GetPriority(chainActive.Height() + 1))) {
998971
// Require that free transactions have sufficient priority to be mined in the next block.
999972
return state.DoS(0, false, REJECT_INSUFFICIENTFEE, "insufficient priority");
1000973
}
1001974

1002975
// Continuously rate-limit free (really, very-low-fee) transactions
1003976
// This mitigates 'penny-flooding' -- sending thousands of free transactions just to
1004977
// be annoying or make others' transactions take longer to confirm.
1005-
if (fLimitFree && nFees < ::minRelayTxFee.GetFee(nSize))
978+
if (fLimitFree && nModifiedFees < ::minRelayTxFee.GetFee(nSize))
1006979
{
1007980
static CCriticalSection csFreeLimiter;
1008981
static double dFreeCount;
@@ -1067,7 +1040,7 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState &state, const C
10671040
LOCK(pool.cs);
10681041
if (setConflicts.size())
10691042
{
1070-
CFeeRate newFeeRate(nFees, nSize);
1043+
CFeeRate newFeeRate(nModifiedFees, nSize);
10711044
set<uint256> setConflictsParents;
10721045
const int maxDescendantsToVisit = 100;
10731046
CTxMemPool::setEntries setIterConflicting;
@@ -1110,7 +1083,7 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState &state, const C
11101083
// ignored when deciding whether or not to replace, we do
11111084
// require the replacement to pay more overall fees too,
11121085
// mitigating most cases.
1113-
CFeeRate oldFeeRate(mi->GetFee(), mi->GetTxSize());
1086+
CFeeRate oldFeeRate(mi->GetModifiedFee(), mi->GetTxSize());
11141087
if (newFeeRate <= oldFeeRate)
11151088
{
11161089
return state.DoS(0,
@@ -1138,7 +1111,7 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState &state, const C
11381111
pool.CalculateDescendants(it, allConflicting);
11391112
}
11401113
BOOST_FOREACH(CTxMemPool::txiter it, allConflicting) {
1141-
nConflictingFees += it->GetFee();
1114+
nConflictingFees += it->GetModifiedFee();
11421115
nConflictingSize += it->GetTxSize();
11431116
}
11441117
} else {
@@ -1171,16 +1144,16 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState &state, const C
11711144
// The replacement must pay greater fees than the transactions it
11721145
// replaces - if we did the bandwidth used by those conflicting
11731146
// transactions would not be paid for.
1174-
if (nFees < nConflictingFees)
1147+
if (nModifiedFees < nConflictingFees)
11751148
{
11761149
return state.DoS(0, error("AcceptToMemoryPool: rejecting replacement %s, less fees than conflicting txs; %s < %s",
1177-
hash.ToString(), FormatMoney(nFees), FormatMoney(nConflictingFees)),
1150+
hash.ToString(), FormatMoney(nModifiedFees), FormatMoney(nConflictingFees)),
11781151
REJECT_INSUFFICIENTFEE, "insufficient fee");
11791152
}
11801153

11811154
// Finally in addition to paying more fees than the conflicts the
11821155
// new transaction must pay for its own bandwidth.
1183-
CAmount nDeltaFees = nFees - nConflictingFees;
1156+
CAmount nDeltaFees = nModifiedFees - nConflictingFees;
11841157
if (nDeltaFees < ::minRelayTxFee.GetFee(nSize))
11851158
{
11861159
return state.DoS(0,
@@ -1218,7 +1191,7 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState &state, const C
12181191
LogPrint("mempool", "replacing tx %s with %s for %s BTC additional fees, %d delta bytes\n",
12191192
it->GetTx().GetHash().ToString(),
12201193
hash.ToString(),
1221-
FormatMoney(nFees - nConflictingFees),
1194+
FormatMoney(nModifiedFees - nConflictingFees),
12221195
(int)nSize - (int)nConflictingSize);
12231196
}
12241197
pool.RemoveStaged(allConflicting);

src/main.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -300,8 +300,6 @@ struct CDiskTxPos : public CDiskBlockPos
300300
};
301301

302302

303-
CAmount GetMinRelayFee(const CTransaction& tx, unsigned int nBytes, bool fAllowFree);
304-
305303
/**
306304
* Count ECDSA signature operations the old-fashioned (pre-0.6) way
307305
* @return number of sigops this transaction's outputs will produce when spent

src/rpcblockchain.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ UniValue mempoolToJSON(bool fVerbose = false)
197197
info.push_back(Pair("currentpriority", e.GetPriority(chainActive.Height())));
198198
info.push_back(Pair("descendantcount", e.GetCountWithDescendants()));
199199
info.push_back(Pair("descendantsize", e.GetSizeWithDescendants()));
200-
info.push_back(Pair("descendantfees", e.GetFeesWithDescendants()));
200+
info.push_back(Pair("descendantfees", e.GetModFeesWithDescendants()));
201201
const CTransaction& tx = e.GetTx();
202202
set<string> setDepends;
203203
BOOST_FOREACH(const CTxIn& txin, tx.vin)
@@ -255,7 +255,7 @@ UniValue getrawmempool(const UniValue& params, bool fHelp)
255255
" \"currentpriority\" : n, (numeric) transaction priority now\n"
256256
" \"descendantcount\" : n, (numeric) number of in-mempool descendant transactions (including this one)\n"
257257
" \"descendantsize\" : n, (numeric) size of in-mempool descendants (including this one)\n"
258-
" \"descendantfees\" : n, (numeric) fees of in-mempool descendants (including this one)\n"
258+
" \"descendantfees\" : n, (numeric) modified fees (see above) of in-mempool descendants (including this one)\n"
259259
" \"depends\" : [ (array) unconfirmed transactions used as inputs for this transaction\n"
260260
" \"transactionid\", (string) parent transaction id\n"
261261
" ... ]\n"

0 commit comments

Comments
 (0)