4
4
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
5
5
"""Test fee estimation code."""
6
6
from decimal import Decimal
7
+ import os
7
8
import random
8
9
9
10
from test_framework .messages import (
@@ -155,6 +156,21 @@ def check_estimates(node, fees_seen):
155
156
check_raw_estimates (node , fees_seen )
156
157
check_smart_estimates (node , fees_seen )
157
158
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
+
158
174
class EstimateFeeTest (BitcoinTestFramework ):
159
175
def set_test_params (self ):
160
176
self .num_nodes = 3
@@ -271,6 +287,60 @@ def test_feerate_mempoolminfee(self):
271
287
high_val = 3 * self .nodes [1 ].estimatesmartfee (1 )['feerate' ]
272
288
self .restart_node (1 , extra_args = [f'-minrelaytxfee={ high_val } ' ])
273
289
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
274
344
275
345
def run_test (self ):
276
346
self .log .info ("This test is time consuming, please be patient" )
@@ -297,6 +367,17 @@ def run_test(self):
297
367
self .log .info ("Test fee rate estimation after restarting node with high MempoolMinFee" )
298
368
self .test_feerate_mempoolminfee ()
299
369
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
+
300
381
self .log .info ("Testing that fee estimation is disabled in blocksonly." )
301
382
self .restart_node (0 , ["-blocksonly" ])
302
383
assert_raises_rpc_error (- 32603 , "Fee estimation disabled" ,
0 commit comments