Skip to content

Commit d2b39e0

Browse files
committed
test: ensure old fee_estimate.dat not read on restart and flushed
This commit adds tests to ensure that old fee_estimates.dat files are not read and that fee_estimates are periodically flushed to the fee_estimates.dat file. Additionaly it tests the -regtestonly option -acceptstalefeeestimates.
1 parent cf219f2 commit d2b39e0

File tree

1 file changed

+101
-0
lines changed

1 file changed

+101
-0
lines changed

test/functional/feature_fee_estimation.py

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from decimal import Decimal
88
import os
99
import random
10+
import time
1011

1112
from test_framework.messages import (
1213
COIN,
@@ -21,6 +22,8 @@
2122
)
2223
from test_framework.wallet import MiniWallet
2324

25+
MAX_FILE_AGE = 60
26+
SECONDS_PER_HOUR = 60 * 60
2427

2528
def 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

Comments
 (0)