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
@@ -273,6 +276,95 @@ def sanity_check_rbf_estimates(self, utxos):
273276 est_feerate = node .estimatesmartfee (2 )["feerate" ]
274277 assert_equal (est_feerate , high_feerate_kvb )
275278
279+ def test_old_fee_estimate_file (self ):
280+ # Get the initial fee rate while node is running
281+ fee_rate = self .nodes [0 ].estimatesmartfee (1 )["feerate" ]
282+
283+ # Restart node to ensure fee_estimate.dat file is read
284+ self .restart_node (0 )
285+ assert_equal (self .nodes [0 ].estimatesmartfee (1 )["feerate" ], fee_rate )
286+
287+ fee_dat = self .nodes [0 ].chain_path / "fee_estimates.dat"
288+
289+ # Stop the node and backdate the fee_estimates.dat file more than MAX_FILE_AGE
290+ self .stop_node (0 )
291+ last_modified_time = time .time () - (MAX_FILE_AGE + 1 ) * SECONDS_PER_HOUR
292+ os .utime (fee_dat , (last_modified_time , last_modified_time ))
293+
294+ # Start node and ensure the fee_estimates.dat file was not read
295+ self .start_node (0 )
296+ assert_equal (self .nodes [0 ].estimatesmartfee (1 )["errors" ], ["Insufficient data or no feerate found" ])
297+
298+
299+ def test_estimate_dat_is_flushed_periodically (self ):
300+ fee_dat = self .nodes [0 ].chain_path / "fee_estimates.dat"
301+ os .remove (fee_dat ) if os .path .exists (fee_dat ) else None
302+
303+ # Verify that fee_estimates.dat does not exist
304+ assert_equal (os .path .isfile (fee_dat ), False )
305+
306+ # Verify if the string "Flushed fee estimates to fee_estimates.dat." is present in the debug log file.
307+ # If present, it indicates that fee estimates have been successfully flushed to disk.
308+ with self .nodes [0 ].assert_debug_log (expected_msgs = ["Flushed fee estimates to fee_estimates.dat." ], timeout = 1 ):
309+ # Mock the scheduler for an hour to flush fee estimates to fee_estimates.dat
310+ self .nodes [0 ].mockscheduler (SECONDS_PER_HOUR )
311+
312+ # Verify that fee estimates were flushed and fee_estimates.dat file is created
313+ assert_equal (os .path .isfile (fee_dat ), True )
314+
315+ # Verify that the estimates remain the same if there are no blocks in the flush interval
316+ block_hash_before = self .nodes [0 ].getbestblockhash ()
317+ fee_dat_initial_content = open (fee_dat , "rb" ).read ()
318+ with self .nodes [0 ].assert_debug_log (expected_msgs = ["Flushed fee estimates to fee_estimates.dat." ], timeout = 1 ):
319+ # Mock the scheduler for an hour to flush fee estimates to fee_estimates.dat
320+ self .nodes [0 ].mockscheduler (SECONDS_PER_HOUR )
321+
322+ # Verify that there were no blocks in between the flush interval
323+ assert_equal (block_hash_before , self .nodes [0 ].getbestblockhash ())
324+
325+ fee_dat_current_content = open (fee_dat , "rb" ).read ()
326+ assert_equal (fee_dat_current_content , fee_dat_initial_content )
327+
328+ # Verify that the estimates remain the same after shutdown with no blocks before shutdown
329+ self .restart_node (0 )
330+ fee_dat_current_content = open (fee_dat , "rb" ).read ()
331+ assert_equal (fee_dat_current_content , fee_dat_initial_content )
332+
333+ # Verify that the estimates are not the same if new blocks were produced in the flush interval
334+ with self .nodes [0 ].assert_debug_log (expected_msgs = ["Flushed fee estimates to fee_estimates.dat." ], timeout = 1 ):
335+ # Mock the scheduler for an hour to flush fee estimates to fee_estimates.dat
336+ self .generate (self .nodes [0 ], 5 , sync_fun = self .no_op )
337+ self .nodes [0 ].mockscheduler (SECONDS_PER_HOUR )
338+
339+ fee_dat_current_content = open (fee_dat , "rb" ).read ()
340+ assert fee_dat_current_content != fee_dat_initial_content
341+
342+ fee_dat_initial_content = fee_dat_current_content
343+
344+ # Generate blocks before shutdown and verify that the fee estimates are not the same
345+ self .generate (self .nodes [0 ], 5 , sync_fun = self .no_op )
346+ self .restart_node (0 )
347+ fee_dat_current_content = open (fee_dat , "rb" ).read ()
348+ assert fee_dat_current_content != fee_dat_initial_content
349+
350+
351+ def test_acceptstalefeeestimates_option (self ):
352+ # Get the initial fee rate while node is running
353+ fee_rate = self .nodes [0 ].estimatesmartfee (1 )["feerate" ]
354+
355+ self .stop_node (0 )
356+
357+ fee_dat = self .nodes [0 ].chain_path / "fee_estimates.dat"
358+
359+ # Stop the node and backdate the fee_estimates.dat file more than MAX_FILE_AGE
360+ last_modified_time = time .time () - (MAX_FILE_AGE + 1 ) * SECONDS_PER_HOUR
361+ os .utime (fee_dat , (last_modified_time , last_modified_time ))
362+
363+ # Restart node with -acceptstalefeeestimates option to ensure fee_estimate.dat file is read
364+ self .start_node (0 ,extra_args = ["-acceptstalefeeestimates" ])
365+ assert_equal (self .nodes [0 ].estimatesmartfee (1 )["feerate" ], fee_rate )
366+
367+
276368 def run_test (self ):
277369 self .log .info ("This test is time consuming, please be patient" )
278370 self .log .info ("Splitting inputs so we can generate tx's" )
@@ -296,12 +388,21 @@ def run_test(self):
296388 self .log .info ("Testing estimates with single transactions." )
297389 self .sanity_check_estimates_range ()
298390
391+ self .log .info ("Test fee_estimates.dat is flushed periodically" )
392+ self .test_estimate_dat_is_flushed_periodically ()
393+
299394 # check that the effective feerate is greater than or equal to the mempoolminfee even for high mempoolminfee
300395 self .log .info (
301396 "Test fee rate estimation after restarting node with high MempoolMinFee"
302397 )
303398 self .test_feerate_mempoolminfee ()
304399
400+ self .log .info ("Test acceptstalefeeestimates option" )
401+ self .test_acceptstalefeeestimates_option ()
402+
403+ self .log .info ("Test reading old fee_estimates.dat" )
404+ self .test_old_fee_estimate_file ()
405+
305406 self .log .info ("Restarting node with fresh estimation" )
306407 self .stop_node (0 )
307408 fee_dat = os .path .join (self .nodes [0 ].datadir , self .chain , "fee_estimates.dat" )
0 commit comments