44# file COPYING or http://www.opensource.org/licenses/mit-license.php.
55"""Test fee estimation code."""
66from decimal import Decimal
7+ import os
78import random
89
910from 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+
158174class 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