Skip to content

Commit 4556406

Browse files
committed
qa: test fee estimation with replacement transactions
Signed-off-by: Antoine Poinsot <[email protected]>
1 parent 053415b commit 4556406

File tree

1 file changed

+81
-0
lines changed

1 file changed

+81
-0
lines changed

test/functional/feature_fee_estimation.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
55
"""Test fee estimation code."""
66
from decimal import Decimal
7+
import os
78
import random
89

910
from test_framework.messages import (
@@ -155,6 +156,21 @@ def check_estimates(node, fees_seen):
155156
check_raw_estimates(node, fees_seen)
156157
check_smart_estimates(node, fees_seen)
157158

159+
160+
def send_tx(node, utxo, feerate):
161+
"""Broadcast a 1in-1out transaction with a specific input and feerate (sat/vb)."""
162+
overhead, op, scriptsig, nseq, value, spk = 10, 36, 5, 4, 8, 24
163+
tx_size = overhead + op + scriptsig + nseq + value + spk
164+
fee = tx_size * feerate
165+
166+
tx = CTransaction()
167+
tx.vin = [CTxIn(COutPoint(int(utxo["txid"], 16), utxo["vout"]), SCRIPT_SIG[utxo["vout"]])]
168+
tx.vout = [CTxOut(int(utxo["amount"] * COIN) - fee, P2SH_1)]
169+
txid = node.sendrawtransaction(tx.serialize().hex())
170+
171+
return txid
172+
173+
158174
class EstimateFeeTest(BitcoinTestFramework):
159175
def set_test_params(self):
160176
self.num_nodes = 3
@@ -271,6 +287,60 @@ def test_feerate_mempoolminfee(self):
271287
high_val = 3*self.nodes[1].estimatesmartfee(1)['feerate']
272288
self.restart_node(1, extra_args=[f'-minrelaytxfee={high_val}'])
273289
check_estimates(self.nodes[1], self.fees_per_kb)
290+
self.restart_node(1)
291+
292+
def sanity_check_rbf_estimates(self, utxos):
293+
"""During 5 blocks, broadcast low fee transactions. Only 10% of them get
294+
confirmed and the remaining ones get RBF'd with a high fee transaction at
295+
the next block.
296+
The block policy estimator should return the high feerate.
297+
"""
298+
# The broadcaster and block producer
299+
node = self.nodes[0]
300+
miner = self.nodes[1]
301+
# In sat/vb
302+
low_feerate = 1
303+
high_feerate = 10
304+
# Cache the utxos of which to replace the spender after it failed to get
305+
# confirmed
306+
utxos_to_respend = []
307+
txids_to_replace = []
308+
309+
assert len(utxos) >= 250
310+
for _ in range(5):
311+
# Broadcast 45 low fee transactions that will need to be RBF'd
312+
for _ in range(45):
313+
u = utxos.pop(0)
314+
txid = send_tx(node, u, low_feerate)
315+
utxos_to_respend.append(u)
316+
txids_to_replace.append(txid)
317+
# Broadcast 5 low fee transaction which don't need to
318+
for _ in range(5):
319+
send_tx(node, utxos.pop(0), low_feerate)
320+
# Mine the transactions on another node
321+
self.sync_mempools(wait=.1, nodes=[node, miner])
322+
for txid in txids_to_replace:
323+
miner.prioritisetransaction(txid=txid, fee_delta=-COIN)
324+
self.generate(miner, 1)
325+
self.sync_blocks(wait=.1, nodes=[node, miner])
326+
# RBF the low-fee transactions
327+
while True:
328+
try:
329+
u = utxos_to_respend.pop(0)
330+
send_tx(node, u, high_feerate)
331+
except IndexError:
332+
break
333+
334+
# Mine the last replacement txs
335+
self.sync_mempools(wait=.1, nodes=[node, miner])
336+
self.generate(miner, 1)
337+
self.sync_blocks(wait=.1, nodes=[node, miner])
338+
339+
# Only 10% of the transactions were really confirmed with a low feerate,
340+
# the rest needed to be RBF'd. We must return the 90% conf rate feerate.
341+
high_feerate_kvb = Decimal(high_feerate) / COIN * 10**3
342+
est_feerate = node.estimatesmartfee(2)["feerate"]
343+
assert est_feerate == high_feerate_kvb
274344

275345
def run_test(self):
276346
self.log.info("This test is time consuming, please be patient")
@@ -297,6 +367,17 @@ def run_test(self):
297367
self.log.info("Test fee rate estimation after restarting node with high MempoolMinFee")
298368
self.test_feerate_mempoolminfee()
299369

370+
self.log.info("Restarting node with fresh estimation")
371+
self.stop_node(0)
372+
fee_dat = os.path.join(self.nodes[0].datadir, self.chain, "fee_estimates.dat")
373+
os.remove(fee_dat)
374+
self.start_node(0)
375+
self.connect_nodes(0, 1)
376+
self.connect_nodes(0, 2)
377+
378+
self.log.info("Testing estimates with RBF.")
379+
self.sanity_check_rbf_estimates(self.confutxo + self.memutxo)
380+
300381
self.log.info("Testing that fee estimation is disabled in blocksonly.")
301382
self.restart_node(0, ["-blocksonly"])
302383
assert_raises_rpc_error(-32603, "Fee estimation disabled",

0 commit comments

Comments
 (0)