77from decimal import Decimal
88import os
99import random
10+ import time
1011
1112from test_framework .messages import (
1213 COIN ,
2122)
2223from test_framework .wallet import MiniWallet
2324
25+ MAX_FILE_AGE = 60
26+ SECONDS_PER_HOUR = 60 * 60
2427
2528def small_txpuzzle_randfee (
2629 wallet , from_node , conflist , unconflist , amount , min_fee , fee_increment , batch_reqs
@@ -290,6 +293,95 @@ def sanity_check_rbf_estimates(self, utxos):
290293 est_feerate = node .estimatesmartfee (2 )["feerate" ]
291294 assert_equal (est_feerate , high_feerate_kvb )
292295
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+
293385 def run_test (self ):
294386 self .log .info ("This test is time consuming, please be patient" )
295387 self .log .info ("Splitting inputs so we can generate tx's" )
@@ -312,12 +404,21 @@ def run_test(self):
312404 self .log .info ("Testing estimates with single transactions." )
313405 self .sanity_check_estimates_range ()
314406
407+ self .log .info ("Test fee_estimates.dat is flushed periodically" )
408+ self .test_estimate_dat_is_flushed_periodically ()
409+
315410 # check that the effective feerate is greater than or equal to the mempoolminfee even for high mempoolminfee
316411 self .log .info (
317412 "Test fee rate estimation after restarting node with high MempoolMinFee"
318413 )
319414 self .test_feerate_mempoolminfee ()
320415
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+
321422 self .log .info ("Restarting node with fresh estimation" )
322423 self .stop_node (0 )
323424 fee_dat = os .path .join (self .nodes [0 ].datadir , self .chain , "fee_estimates.dat" )
0 commit comments