7
7
from decimal import Decimal
8
8
import os
9
9
import random
10
+ import time
10
11
11
12
from test_framework .messages import (
12
13
COIN ,
21
22
)
22
23
from test_framework .wallet import MiniWallet
23
24
25
+ MAX_FILE_AGE = 60
26
+ SECONDS_PER_HOUR = 60 * 60
24
27
25
28
def small_txpuzzle_randfee (
26
29
wallet , from_node , conflist , unconflist , amount , min_fee , fee_increment , batch_reqs
@@ -290,6 +293,95 @@ def sanity_check_rbf_estimates(self, utxos):
290
293
est_feerate = node .estimatesmartfee (2 )["feerate" ]
291
294
assert_equal (est_feerate , high_feerate_kvb )
292
295
296
+ def test_old_fee_estimate_file (self ):
297
+ # Get the initial fee rate while node is running
298
+ fee_rate = self .nodes [0 ].estimatesmartfee (1 )["feerate" ]
299
+
300
+ # Restart node to ensure fee_estimate.dat file is read
301
+ self .restart_node (0 )
302
+ assert_equal (self .nodes [0 ].estimatesmartfee (1 )["feerate" ], fee_rate )
303
+
304
+ fee_dat = self .nodes [0 ].chain_path / "fee_estimates.dat"
305
+
306
+ # Stop the node and backdate the fee_estimates.dat file more than MAX_FILE_AGE
307
+ self .stop_node (0 )
308
+ last_modified_time = time .time () - (MAX_FILE_AGE + 1 ) * SECONDS_PER_HOUR
309
+ os .utime (fee_dat , (last_modified_time , last_modified_time ))
310
+
311
+ # Start node and ensure the fee_estimates.dat file was not read
312
+ self .start_node (0 )
313
+ assert_equal (self .nodes [0 ].estimatesmartfee (1 )["errors" ], ["Insufficient data or no feerate found" ])
314
+
315
+
316
+ def test_estimate_dat_is_flushed_periodically (self ):
317
+ fee_dat = self .nodes [0 ].chain_path / "fee_estimates.dat"
318
+ os .remove (fee_dat ) if os .path .exists (fee_dat ) else None
319
+
320
+ # Verify that fee_estimates.dat does not exist
321
+ assert_equal (os .path .isfile (fee_dat ), False )
322
+
323
+ # Verify if the string "Flushed fee estimates to fee_estimates.dat." is present in the debug log file.
324
+ # If present, it indicates that fee estimates have been successfully flushed to disk.
325
+ with self .nodes [0 ].assert_debug_log (expected_msgs = ["Flushed fee estimates to fee_estimates.dat." ], timeout = 1 ):
326
+ # Mock the scheduler for an hour to flush fee estimates to fee_estimates.dat
327
+ self .nodes [0 ].mockscheduler (SECONDS_PER_HOUR )
328
+
329
+ # Verify that fee estimates were flushed and fee_estimates.dat file is created
330
+ assert_equal (os .path .isfile (fee_dat ), True )
331
+
332
+ # Verify that the estimates remain the same if there are no blocks in the flush interval
333
+ block_hash_before = self .nodes [0 ].getbestblockhash ()
334
+ fee_dat_initial_content = open (fee_dat , "rb" ).read ()
335
+ with self .nodes [0 ].assert_debug_log (expected_msgs = ["Flushed fee estimates to fee_estimates.dat." ], timeout = 1 ):
336
+ # Mock the scheduler for an hour to flush fee estimates to fee_estimates.dat
337
+ self .nodes [0 ].mockscheduler (SECONDS_PER_HOUR )
338
+
339
+ # Verify that there were no blocks in between the flush interval
340
+ assert_equal (block_hash_before , self .nodes [0 ].getbestblockhash ())
341
+
342
+ fee_dat_current_content = open (fee_dat , "rb" ).read ()
343
+ assert_equal (fee_dat_current_content , fee_dat_initial_content )
344
+
345
+ # Verify that the estimates remain the same after shutdown with no blocks before shutdown
346
+ self .restart_node (0 )
347
+ fee_dat_current_content = open (fee_dat , "rb" ).read ()
348
+ assert_equal (fee_dat_current_content , fee_dat_initial_content )
349
+
350
+ # Verify that the estimates are not the same if new blocks were produced in the flush interval
351
+ with self .nodes [0 ].assert_debug_log (expected_msgs = ["Flushed fee estimates to fee_estimates.dat." ], timeout = 1 ):
352
+ # Mock the scheduler for an hour to flush fee estimates to fee_estimates.dat
353
+ self .generate (self .nodes [0 ], 5 , sync_fun = self .no_op )
354
+ self .nodes [0 ].mockscheduler (SECONDS_PER_HOUR )
355
+
356
+ fee_dat_current_content = open (fee_dat , "rb" ).read ()
357
+ assert fee_dat_current_content != fee_dat_initial_content
358
+
359
+ fee_dat_initial_content = fee_dat_current_content
360
+
361
+ # Generate blocks before shutdown and verify that the fee estimates are not the same
362
+ self .generate (self .nodes [0 ], 5 , sync_fun = self .no_op )
363
+ self .restart_node (0 )
364
+ fee_dat_current_content = open (fee_dat , "rb" ).read ()
365
+ assert fee_dat_current_content != fee_dat_initial_content
366
+
367
+
368
+ def test_acceptstalefeeestimates_option (self ):
369
+ # Get the initial fee rate while node is running
370
+ fee_rate = self .nodes [0 ].estimatesmartfee (1 )["feerate" ]
371
+
372
+ self .stop_node (0 )
373
+
374
+ fee_dat = self .nodes [0 ].chain_path / "fee_estimates.dat"
375
+
376
+ # Stop the node and backdate the fee_estimates.dat file more than MAX_FILE_AGE
377
+ last_modified_time = time .time () - (MAX_FILE_AGE + 1 ) * SECONDS_PER_HOUR
378
+ os .utime (fee_dat , (last_modified_time , last_modified_time ))
379
+
380
+ # Restart node with -acceptstalefeeestimates option to ensure fee_estimate.dat file is read
381
+ self .start_node (0 ,extra_args = ["-acceptstalefeeestimates" ])
382
+ assert_equal (self .nodes [0 ].estimatesmartfee (1 )["feerate" ], fee_rate )
383
+
384
+
293
385
def run_test (self ):
294
386
self .log .info ("This test is time consuming, please be patient" )
295
387
self .log .info ("Splitting inputs so we can generate tx's" )
@@ -312,12 +404,21 @@ def run_test(self):
312
404
self .log .info ("Testing estimates with single transactions." )
313
405
self .sanity_check_estimates_range ()
314
406
407
+ self .log .info ("Test fee_estimates.dat is flushed periodically" )
408
+ self .test_estimate_dat_is_flushed_periodically ()
409
+
315
410
# check that the effective feerate is greater than or equal to the mempoolminfee even for high mempoolminfee
316
411
self .log .info (
317
412
"Test fee rate estimation after restarting node with high MempoolMinFee"
318
413
)
319
414
self .test_feerate_mempoolminfee ()
320
415
416
+ self .log .info ("Test acceptstalefeeestimates option" )
417
+ self .test_acceptstalefeeestimates_option ()
418
+
419
+ self .log .info ("Test reading old fee_estimates.dat" )
420
+ self .test_old_fee_estimate_file ()
421
+
321
422
self .log .info ("Restarting node with fresh estimation" )
322
423
self .stop_node (0 )
323
424
fee_dat = os .path .join (self .nodes [0 ].datadir , self .chain , "fee_estimates.dat" )
0 commit comments