@@ -129,6 +129,15 @@ def make_tx(wallet, utxo, feerate):
129
129
)
130
130
131
131
132
+ def send_tx (wallet , node , utxo , feerate ):
133
+ """Broadcast a 1in-1out transaction with a specific input and feerate (sat/vb)."""
134
+ return wallet .send_self_transfer (
135
+ from_node = node ,
136
+ utxo_to_spend = utxo ,
137
+ fee_rate = Decimal (feerate * 1000 ) / COIN ,
138
+ )
139
+
140
+
132
141
class EstimateFeeTest (BitcoinTestFramework ):
133
142
def set_test_params (self ):
134
143
self .num_nodes = 3
@@ -392,6 +401,79 @@ def test_acceptstalefeeestimates_option(self):
392
401
assert_equal (self .nodes [0 ].estimatesmartfee (1 )["feerate" ], fee_rate )
393
402
394
403
404
+ def send_and_mine_child_tx (self , broadcaster , miner , parent_tx , feerate ):
405
+ u = {"txid" : parent_tx ["txid" ], "vout" : 0 , "value" : Decimal (parent_tx ["tx" ].vout [0 ].nValue ) / COIN }
406
+ send_tx (wallet = self .wallet , node = broadcaster , utxo = u , feerate = feerate )
407
+ self .sync_mempools (wait = 0.1 , nodes = [broadcaster , miner ])
408
+ self .generate (miner , 1 )
409
+ assert_equal (broadcaster .estimaterawfee (1 )["short" ]["fail" ]["totalconfirmed" ], 0 )
410
+
411
+ def sanity_check_cpfp_estimates (self , utxos ):
412
+ """The BlockPolicyEstimator currently does not take CPFP into account. This test
413
+ sanity checks its behaviour when receiving transactions that were confirmed because
414
+ of their child's feerate.
415
+ """
416
+ # The broadcaster and block producer
417
+ broadcaster = self .nodes [0 ]
418
+ miner = self .nodes [1 ]
419
+ # In sat/vb
420
+ [low_feerate , med_feerate , high_feerate ] = [Decimal (2 ), Decimal (15 ), Decimal (20 )]
421
+
422
+ self .log .info ("Test that fee estimator will ignore all transaction with in block child" )
423
+ # If a transaction got mined and has a child in the same block it was mined
424
+ # it does not get accounted in the fee estimator.
425
+ low_fee_parent = send_tx (wallet = self .wallet , node = broadcaster , utxo = None , feerate = low_feerate )
426
+ self .send_and_mine_child_tx (broadcaster = broadcaster , miner = miner , parent_tx = low_fee_parent , feerate = high_feerate )
427
+
428
+ # If it has descendants which have a lower ancestor score, it also does not.
429
+ high_fee_parent = send_tx (wallet = self .wallet , node = broadcaster , utxo = None , feerate = high_feerate )
430
+ self .send_and_mine_child_tx (broadcaster = broadcaster , miner = miner , parent_tx = high_fee_parent , feerate = low_feerate )
431
+
432
+ # Even if it's equal fee rate.
433
+ med_fee_parent = send_tx (wallet = self .wallet , node = broadcaster , utxo = None , feerate = med_feerate )
434
+ self .send_and_mine_child_tx (broadcaster = broadcaster , miner = miner , parent_tx = med_fee_parent , feerate = med_feerate )
435
+
436
+ # Generate and mine packages of transactions, 80% of them are a [low fee, high fee] package
437
+ # which get mined because of the child transaction. 20% are single-transaction packages with
438
+ # a medium-high feerate.
439
+ # Test that we don't give the low feerate as estimate, assuming the low fee transactions
440
+ # got mined on their own.
441
+ for _ in range (5 ):
442
+ txs = [] # Batch the RPCs calls.
443
+ for _ in range (20 ):
444
+ u = utxos .pop (0 )
445
+ parent_tx = make_tx (wallet = self .wallet , utxo = u , feerate = low_feerate )
446
+ txs .append (parent_tx )
447
+ u = {
448
+ "txid" : parent_tx ["txid" ],
449
+ "vout" : 0 ,
450
+ "value" : Decimal (parent_tx ["tx" ].vout [0 ].nValue ) / COIN
451
+ }
452
+ child_tx = make_tx (wallet = self .wallet , utxo = u , feerate = high_feerate )
453
+ txs .append (child_tx )
454
+ for _ in range (5 ):
455
+ u = utxos .pop (0 )
456
+ tx = make_tx (wallet = self .wallet , utxo = u , feerate = med_feerate )
457
+ txs .append (tx )
458
+ batch_send_tx = (broadcaster .sendrawtransaction .get_request (tx ["hex" ]) for tx in txs )
459
+ broadcaster .batch (batch_send_tx )
460
+ self .sync_mempools (wait = 0.1 , nodes = [broadcaster , miner ])
461
+ self .generate (miner , 1 )
462
+ assert_equal (broadcaster .estimatesmartfee (2 )["feerate" ], med_feerate * 1000 / COIN )
463
+
464
+ def clear_first_node_estimates (self ):
465
+ """Restart node 0 without a fee_estimates.dat."""
466
+ self .log .info ("Restarting node with fresh estimation" )
467
+ self .stop_node (0 )
468
+ fee_dat = os .path .join (self .nodes [0 ].chain_path , "fee_estimates.dat" )
469
+ os .remove (fee_dat )
470
+ self .start_node (0 )
471
+ self .connect_nodes (0 , 1 )
472
+ self .connect_nodes (0 , 2 )
473
+ # Note: we need to get into the estimator's processBlock to set nBestSeenHeight or it
474
+ # will ignore all the txs of the first block we mine in the next test.
475
+ self .generate (self .nodes [0 ], 1 )
476
+
395
477
def run_test (self ):
396
478
self .log .info ("This test is time consuming, please be patient" )
397
479
self .log .info ("Splitting inputs so we can generate tx's" )
@@ -429,16 +511,15 @@ def run_test(self):
429
511
self .log .info ("Test reading old fee_estimates.dat" )
430
512
self .test_old_fee_estimate_file ()
431
513
432
- self .log .info ("Restarting node with fresh estimation" )
433
- self .stop_node (0 )
434
- fee_dat = os .path .join (self .nodes [0 ].chain_path , "fee_estimates.dat" )
435
- os .remove (fee_dat )
436
- self .start_node (0 )
437
- self .connect_nodes (0 , 1 )
438
- self .connect_nodes (0 , 2 )
514
+ self .clear_first_node_estimates ()
439
515
440
516
self .log .info ("Testing estimates with RBF." )
441
- self .sanity_check_rbf_estimates (self .confutxo + self .memutxo )
517
+ self .sanity_check_rbf_estimates (self .confutxo )
518
+
519
+ self .clear_first_node_estimates ()
520
+
521
+ self .log .info ("Testing estimates with CPFP." )
522
+ self .sanity_check_cpfp_estimates (self .confutxo )
442
523
443
524
self .log .info ("Testing that fee estimation is disabled in blocksonly." )
444
525
self .restart_node (0 , ["-blocksonly" ])
0 commit comments