The issue arises when an old fee_estimates.dat file is sometimes read during initialization.
Or after an unclean shutdown, the latest fee estimates are not flushed to fee_estimates.dat.
If the fee estimates in the old file are old, they can cause transactions to become stuck in the mempool.
This PR ensures that nodes do not use stale estimates from the old file during initialization. If fee_estimates.dat
has not been updated for 60 hours or more, it is considered stale and will not be read during initialization. To avoid
having old estimates, the fee_estimates.dat file will be flushed periodically every hour. As mentioned #27555
“The immediate improvement would be to store fee estimates to disk once an hour or so to reduce the chance of having an old file. From there, this case could probably be detected, and refuse to serve estimates until we sync.”
In addition, I will follow-up PR to persist the mempoolminfee across restarts.
DrahtBot
commented at 10:17 pm on May 10, 2023:
contributor
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.
Reviews
See the guideline for information on the review process.
If your review is incorrectly listed, please react with 👎 to this comment and the bot will ignore it on the next update.
Conflicts
Reviewers, this pull request conflicts with the following ones:
#25380 (Detect and ignore transactions that were CPFP’d in the fee estimator by darosior)
#10443 (Add fee_est tool for debugging fee estimation code by ryanofsky)
If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.
fanquake
commented at 9:27 am on May 11, 2023:
member
I think it’s a bit strange for policy/fees to have a dependency on scheduler. Maybe register FlushFeeEstimates() with the scheduler within AppInitMain() instead?
ismaelsadeeq
commented at 9:42 pm on May 11, 2023:
Sure :+1: , fixed.
glozow added the label
TX fees and policy
on May 11, 2023
nit (feel free to ignore): An alternative to adding a function here would be to call the already existing Ticks<std::chrono::hours>(a) over count_hours(a), but either seems fine.
in
test/functional/feature_fee_estimation.py:303
in
a2ad5e475foutdated
in
test/functional/feature_fee_estimation.py:323
in
a2ad5e475foutdated
318+ # If it is, it indicates that fee estimates have been successfully flushed to disk.
319+ with self.nodes[0].assert_debug_log(expected_msgs=["Flushed fee estimates to fee_estimates.dat."]):
320+ # Mock the scheduler for an hour, sleep for a short period to allow the flush to finish
321+ hour = 3600
322+ self.nodes[0].mockscheduler(hour)
323+ time.sleep(1)
nit: I am not a fan of uninterruptible sleep in tests. assert_debug_log can be used to wait, if you supply a timeout argument (or rely on the default timeout)
ismaelsadeeq
commented at 6:53 pm on May 11, 2023:
My bad, thanks.
maflcko approved
maflcko
commented at 3:32 pm on May 11, 2023:
member
lgtm. Left some nits
ismaelsadeeq force-pushed
on May 11, 2023
in
src/policy/fees.h:26
in
0d2329aecaoutdated
21 #include <string>
22 #include <vector>
2324+
25+// How often to flush fee estimates to fee_estimates.dat.
26+static constexpr std::chrono::milliseconds FLUSH_INTERVAL{std::chrono::hours{1}};
Seems ok, but would have to think about this more. I’d be ok with longer too, to start.
ismaelsadeeq
commented at 11:10 am on May 18, 2023:
Yeah, to enhance flexibility, I can create a follow-up PR to introduce a configuration variable for MAX_FILE_AGE, This way users can specify the maximum age of estimates they do not want to accept?
What is the use case for being able to configure the maximum file age?
ismaelsadeeq
commented at 5:19 pm on May 19, 2023:
I was thinking that introducing flexibility would enable users to define their preferred threshold for considering fee estimates as stale. As @instagibbs suggested extending the default 12 hours further. what is your view on it?
Not sure if we should load old (i.e. potentially bad) data as it can skew the fee estimates both at startup and after more data has been collected. For testing, they could mock the time right? Just worried about adding a config option that’s a bit footgunny.
ismaelsadeeq
commented at 10:24 pm on June 1, 2023:
Yes, I agree with you. stale estimates should not be read. I have updated the MAX_FILE_AGE to 60 hours, 2.5 days, as @instagibbs suggested that longer would be okay
Can you explain the rationale behind the 60 hours chosen? Also perhaps you can update OP as it still mentions 12 hours :)
ismaelsadeeq
commented at 1:21 pm on June 2, 2023:
Estimates that are 2.5 days old have nearly half the decay weight of the most recent block.
Thank you I updated the PR description.
in
test/functional/feature_fee_estimation.py:323
in
aecd92e8e1outdated
314+
315+
316+ def test_estimate_dat_is_flushed_periodically(self):
317+ # Check if the string "Flushed fee estimates to fee_estimates.dat." is present in the debug log file.
318+ # If it is, it indicates that fee estimates have been successfully flushed to disk.
319+ with self.nodes[0].assert_debug_log(expected_msgs=["Flushed fee estimates to fee_estimates.dat."], timeout=1):
ismaelsadeeq
commented at 4:52 pm on May 19, 2023:
thanks fixed with
0LogPrintf("Flushed fee estimates to %s.", fs::PathToString(m_estimation_filepath.filename()));
so as to get the filename only not the dir
in
src/policy/fees.h:26
in
4614a64bbfoutdated
21 #include <string>
22 #include <vector>
2324+
25+// How often to flush fee estimates to fee_estimates.dat.
26+static constexpr std::chrono::milliseconds FEE_FLUSH_INTERVAL{std::chrono::hours{1}};
It could also be constexpr auto FEE_FLUSH_INTERVAL{1h};
ismaelsadeeq
commented at 11:02 pm on May 18, 2023:
Still a nub on cpp,
constexpr auto FEE_FLUSH_INTERVAL{1h}; does not compile
I added instead static constexpr std::chrono::hours FEE_FLUSH_INTERVAL{1};
in
test/functional/feature_fee_estimation.py:300
in
b02e3fc91foutdated
295+ # Get the initial fee rate while node is running
296+ fee_rate = self.nodes[0].estimatesmartfee(1)["feerate"]
297+
298+ # Restart node to ensure fee_estimate.dat file is read
299+ self.stop_node(0)
300+ self.start_node(0)
ismaelsadeeq
commented at 11:03 pm on May 18, 2023:
Thanks fixed
pinheadmz
commented at 6:15 pm on May 18, 2023:
member
concept ACK
This should solve the stale fee issue, the test is clever and effective. I ensured the test fails without the patch and also checked the test failed when the mtime was set < 12 hours. Reviewed code and left some notes / questions
ismaelsadeeq force-pushed
on May 18, 2023
ismaelsadeeq force-pushed
on May 18, 2023
DrahtBot added the label
CI failed
on May 18, 2023
DrahtBot removed the label
CI failed
on May 19, 2023
in
src/policy/fees.cpp:553
in
accf995cf0outdated
550 AutoFile est_file{fsbridge::fopen(m_estimation_filepath, "rb")};
551- if (est_file.IsNull() || !Read(est_file)) {
552+
553+ // Whenever the fee estimation file is not present return early
554+ if (est_file.IsNull()) {
555+ LogPrintf("Failed to read fee estimates from %s. Continue anyway.\n", fs::PathToString(m_estimation_filepath));
to make this test less order-dependent, you can attempt to delete the file, then run the test
ismaelsadeeq
commented at 11:02 pm on June 2, 2023:
Thank you, I added the delete attempt
in
test/functional/feature_fee_estimation.py:329
in
8c51cd00fboutdated
324+ # Mock the scheduler for an hour, fee estimates will be flushed to fee_estimate.dat
325+ hour = 3600
326+ self.nodes[0].mockscheduler(hour)
327+
328+ # Verify that fee estimates were flushed and fee_estimates.dat file is created
329+ assert_equal(os.path.isfile(fee_dat), True)
might be good to test that it does it every hour, and that the output files should be matching provided we don’t see blocks in the interim, also, check that what is written on node shutdown also matches?
That way we can have higher assurance on the timing of the flushing and the contents matching what we already do.
ismaelsadeeq
commented at 11:03 pm on June 2, 2023:
Sure, thank you, I updated the test.
instagibbs
commented at 1:58 pm on June 2, 2023:
member
Looking good, just a couple of test suggestions.
Also wondering if we should include a hidden argument to allow older files for testing reasons, re: #27415
I’ve used it for systems I’ve built for integration testing, and not having to touch a file during testing to refresh it would be a simplification.
For testing, they could mock the time right?
Depends on your test setup, really. It might interfere during integration tests which require a realistic clock?
glozow
commented at 2:38 pm on June 2, 2023:
member
Also wondering if we should include a hidden argument to allow older files for testing reasons, re: #27415
I’ve used it for systems I’ve built for integration testing, and not having to touch a file during testing to refresh it would be a simplification.
For testing, they could mock the time right?
Depends on your test setup, really. It might interfere during integration tests which require a realistic clock?
Makes sense, seems fine as a regtest-only option for testing.
ismaelsadeeq force-pushed
on Jun 2, 2023
ismaelsadeeq force-pushed
on Jun 2, 2023
in
test/functional/feature_fee_estimation.py:307
in
451ab8b513outdated
302+ fee_dat = self.nodes[0].chain_path / "fee_estimates.dat"
303+
304+ # Stop the node and backdate the fee_estimates.dat file for 61 hours
305+ self.stop_node(0)
306+ hour = 60 * 60
307+ last_modified_time = time.time() - 61 * hour
I know we only use it once in the test, but if you re-touch this file it could be nice to use a named variable for the 60 hours, to make it clear where it comes from? (e.g. MAX_FILE_AGE would shadow the name used in fees.h)
ismaelsadeeq
commented at 12:53 pm on June 6, 2023:
I agree it’s better than the magic number, I have updated it.
in
test/functional/feature_fee_estimation.py:347
in
451ab8b513outdated
342+ # Verify that the estimates remain the same after shutdown with no blocks before shutdown
343+ self.restart_node(0)
344+ fee_dat_current_content = open(fee_dat, "rb").read()
345+ assert_equal(fee_dat_current_content, fee_dat_initial_content)
346+
347+ # Verify that the estimates are not the same if there are no blocks in the flush interval
I’d really prefer we have a regtest-only option to skip the check to impact integration tests the least.
DrahtBot requested review from willcl-ark
on Jun 6, 2023
DrahtBot added the label
CI failed
on Jun 6, 2023
DrahtBot removed the label
CI failed
on Jun 6, 2023
ismaelsadeeq force-pushed
on Jun 7, 2023
ismaelsadeeq force-pushed
on Jun 7, 2023
DrahtBot added the label
CI failed
on Jun 7, 2023
ismaelsadeeq
commented at 8:52 am on June 8, 2023:
member
I’d really prefer we have a regtest-only option to skip the check to impact integration tests the least.
I added a support for skipping the check only on regtest in 2fec7f362d8540cda5ff7e3e4dfb2cb751364e06 by passing -acceptstaleestimates option and it’s tested in dea3accb1f2eeef74f293262c168d68c0ec444cb
in
src/policy/fees.h:29
in
2fec7f362doutdated
24@@ -25,6 +25,9 @@
25 // How often to flush fee estimates to fee_estimates.dat.
26 static constexpr std::chrono::hours FEE_FLUSH_INTERVAL{1};
2728+// Stale fee estimates are not going to be read on mainnet
29+static constexpr bool ACCEPT_STALE_FEE_ESTIMATES = false;
prefix with DEFAULT_, make doxygen compatible, use braced initialization. Documentation about where the option can be used belongs where that code lives, not here.
0/** Whether we allow importing a fee_estimates file older than MAX_FILE_AGE.*/1static constexpr bool DEFAULT_ACCEPT_STALE_FEE_ESTIMATES{false};
ismaelsadeeq
commented at 12:46 pm on June 8, 2023:
Thank you, I updated it.
in
src/init.cpp:589
in
dea3accb1foutdated
585@@ -586,6 +586,7 @@ void SetupServerArgs(ArgsManager& argsman)
586 argsman.AddArg("-acceptnonstdtxn", strprintf("Relay and mine \"non-standard\" transactions (%sdefault: %u)", "testnet/regtest only; ", !testnetChainParams->RequireStandard()), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::NODE_RELAY);
587 argsman.AddArg("-incrementalrelayfee=<amt>", strprintf("Fee rate (in %s/kvB) used to define cost of relay, used for mempool limiting and replacement policy. (default: %s)", CURRENCY_UNIT, FormatMoney(DEFAULT_INCREMENTAL_RELAY_FEE)), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::NODE_RELAY);
588 argsman.AddArg("-dustrelayfee=<amt>", strprintf("Fee rate (in %s/kvB) used to define dust, the value of an output such that it will cost more than its value in fees at this fee rate to spend it. (default: %s)", CURRENCY_UNIT, FormatMoney(DUST_RELAY_TX_FEE)), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::NODE_RELAY);
589+ argsman.AddArg("-acceptstalefeeestimates", "Read fee estimates even if they are stale supported only in regtest", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST);
maybe replace this hour and the one in the other tests with a constant at the top of the file, SECONDS_PER_HOUR
in
test/functional/feature_fee_estimation.py:332
in
dea3accb1foutdated
329+ self.nodes[0].mockscheduler(hour)
330+
331+ # Verify that fee estimates were flushed and fee_estimates.dat file is created
332+ assert_equal(os.path.isfile(fee_dat), True)
333+
334+ # Verify that the estimates remain the same if there are no blocks in the flush interval
Given that you comment “there are no blocks” you should probably also assert that is true, e.g. assert_equal the result from getbestblockhash before and after
ismaelsadeeq
commented at 12:48 pm on June 8, 2023:
sure fixed
glozow
commented at 9:56 am on June 8, 2023:
member
Looks pretty good, thanks for adding the option.
Please clean up the commit messages. dea3accb1f2eeef74f293262c168d68c0ec444cb claims to add the regtest option, but that is done in 2fec7f362d8540cda5ff7e3e4dfb2cb751364e06.
ismaelsadeeq force-pushed
on Jun 8, 2023
ismaelsadeeq force-pushed
on Jun 8, 2023
instagibbs
commented at 1:16 pm on June 8, 2023:
member
1254@@ -1253,7 +1255,18 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
1255 assert(!node.fee_estimator);
1256 // Don't initialize fee estimation with old data if we don't relay transactions,
1257 // as they would never get updated.
1258- if (!ignores_incoming_txs) node.fee_estimator = std::make_unique<CBlockPolicyEstimator>(FeeestPath(args));
1259+ if (!ignores_incoming_txs) {
1260+ bool read_stale_estimates = args.GetBoolArg("-acceptstalefeeestimates", DEFAULT_ACCEPT_STALE_FEE_ESTIMATES);
1261+ if (read_stale_estimates && (chainparams.GetChainType() != ChainType::REGTEST)) {
1262+ LogPrintf("Reading stale fee estimates is only supported in regtest \n");
I think this should fail rather than being ignored?
ismaelsadeeq
commented at 10:18 pm on June 8, 2023:
I agree, fixed it.
DrahtBot removed the label
CI failed
on Jun 8, 2023
ismaelsadeeq force-pushed
on Jun 8, 2023
in
src/policy/fees.h:28
in
efe5f373e1outdated
2324+
25+// How often to flush fee estimates to fee_estimates.dat.
26+static constexpr std::chrono::hours FEE_FLUSH_INTERVAL{1};
27+
28+/** fee_estimates.dat that are more than 60 hours old, 2 and half days will not be read,
I think if you retouch there is some style nit too:
0- /** fee_estimates.dat that are more than 60 hours (2.5 days) will not be read,
1+ /**
2+ * fee_estimates.dat that are more than 60 hours (2.5 days) will not be read,
ismaelsadeeq
commented at 8:51 am on June 15, 2023:
willcl-ark
commented at 7:44 am on June 9, 2023:
member
re-ACKefe5f373e1
Changes look good. Left a nit not to be addressed unless you re-touch :)
I also checked how much time the new tests were adding to feature_fee_estimation.py, as this is often one of the longest-running tests on my machine. It looks like it’s <=1second, so that seems fine.
DrahtBot requested review from instagibbs
on Jun 9, 2023
instagibbs
commented at 12:56 pm on June 9, 2023:
member
Does this need to be a class property since we only use it one time, during object construction?
ismaelsadeeq
commented at 8:53 am on June 15, 2023:
No, It does not thank you.
pinheadmz approved
pinheadmz
commented at 7:13 pm on June 13, 2023:
member
ACKefe5f373e12329104f1c706145eee75c2d2079a7
Great work on this! A few non-breaking questions below.
Ran all tests locally, ensured all 3 new tests fail on master.
Reviewed each commit, confirmed regtest-only argument is only accepted on regtest!
achow101
commented at 7:33 pm on June 14, 2023:
member
@ismaelsadeeq Are you planning on making any further changes here?
ismaelsadeeq
commented at 8:34 pm on June 14, 2023:
member
@ismaelsadeeq Are you planning on making any further changes here?
Yes I will add all recent feedbacks in next push.
tx fees, policy: periodically flush fee estimates to fee_estimates.dat
This reduces chances of having old estimates in fee_estimates.dat.
5b886f2b43
tx fees, policy: do not read estimates of old fee_estimates.dat
Old fee estimates could cause transactions to become stuck in the
mempool. This commit prevents the node from using stale estimates
from an old file.
3eb241a141
tx fees, policy: read stale fee estimates with a regtest-only option
If -acceptstalefeeestimates option is passed stale fee estimates can now
be read when operating in regtest environments.
Additionally, this commit updates all declarations of the CBlockPolicyEstimator
class to include a the second constructor variable.
cf219f29f3
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.
d2b39e09bc
ismaelsadeeq force-pushed
on Jun 14, 2023
instagibbs
commented at 10:33 am on June 15, 2023:
member
@ismaelsadeeq just FYI if you don’t have to rebase on master due to a conflict, don’t. It’s extra review burden and makes getting re-ACKs harder.
ismaelsadeeq
commented at 10:53 am on June 15, 2023:
member
@ismaelsadeeq just FYI if you don’t have to rebase on master due to a conflict, don’t. It’s extra review burden and makes getting re-ACKs harder.
Ohh Sorry, Thank you for pointing this out.
Its okay now I think and any further nit changes I would address in a follow-up.
willcl-ark
commented at 11:18 am on June 15, 2023:
member
Just noting my understanding of this and the followup pull and how they work together.
In this PR we will regularly persist fee estimations to fee_estimates.dat, providing more protection in an unclean shutdown scenario. We then reload these on startup, under then new condition that they are less than 2.5 days old.
In the case where the restart is instant/very fast this let’s us keep accurate fee estimation after a crash.
In the case where the restart is > 60 hours then we are improved in that we don’t serve known stale data.
In the case where fees start high (or low), and remain high (or low), this will help us with more accurate fee estimation after restart.
If fees start high and descrease then we may have too-high fee estimation, which still helps us avoid unwated stuck transactions.
If fees start low and increased then our fee estimation may report too-low. Although this pull doesn’t help directly in this case, it’s not worse than current behaviour and the expiry should make it strictly better.
In the followup pull, persisting mempool_min_fee to disk is much more sensitive than fee estimation data. If we used outdated rates here we may start rejecting valid tx from the p2p network from our mempool which may be undesirable. Therefore IMO it makes sense there to be much more strict about the lifetime of mempool_min_fee rates read from disk at startup; perhaps something more in the order of 1 hour?
RandyMcMillan
commented at 5:49 pm on June 15, 2023:
contributor
concept ACK
RandyMcMillan
commented at 7:02 pm on June 15, 2023:
contributor
In this scenario - the node is going to have to sync - whether it was shut down for 1 hour or 60+ hours. IMO it would be ideal to not have one more thing dependent upon system time when booting up the node (*There are other issues related to system time when booting up the node). Since we know that the node must connect and sync - I think the ideal is to delay the file age check later into the life cycle of the node.
*In the scenario where system time has been skewed - off by many hours - there is a possibility that the file age is “in the future”. You may want to use absolute value in the file age check… this will remove the possibility of a negative value for file age.
instagibbs
commented at 7:06 pm on June 15, 2023:
member
Since we know that the node must connect and sync - I think the ideal is to delay the file age check later into the life cycle of the node.
Not sure what the suggestion is. Can you be specific?
in
src/policy/fees.h:29
in
3eb241a141outdated
24@@ -25,6 +25,11 @@
25 // How often to flush fee estimates to fee_estimates.dat.
26 static constexpr std::chrono::hours FEE_FLUSH_INTERVAL{1};
2728+/** fee_estimates.dat that are more than 60 hours (2.5 days) will not be read,
29+ * as the estimates in the file are stale.
nit in 3eb241a141defa564c94cb95c5bbaf4c5bd9682e:
I think the word “old” may be missing. Also could elaborate a tiny bit on why stale is bad.
0/** fee_estimates.dat that are more than 60 hours (2.5 days) old will not be read,
1 * as fee estimates are based on historical data and may be inaccurate if
2 * network activity has changed.
ismaelsadeeq
commented at 7:48 am on July 26, 2023:
glozow
commented at 12:31 pm on June 19, 2023:
member
In this scenario - the node is going to have to sync - whether it was shut down for 1 hour or 60+ hours. IMO it would be ideal to not have one more thing dependent upon system time when booting up the node
I don’t see how this is very problematic - we are checking the timestamp we put on our own file, which is only used for fee estimation. The data in that file isn’t really concerned with what the current time is, it’s tracking based on a relative number of blocks.
*In the scenario where system time has been skewed - off by many hours - there is a possibility that the file age is “in the future”. You may want to use absolute value in the file age check… this will remove the possibility of a negative value for file age.
If you manage to be off by more than 60 hours, your node will have much worse problems. We reject blocks if they are MAX_FUTURE_BLOCK_TIME i.e. 2 hours into the future. Clocks don’t need to be completely synced, but they’re not supposed to be wildly off.
in
src/policy/fees.cpp:551
in
3eb241a141outdated
545@@ -545,9 +546,22 @@ CBlockPolicyEstimator::CBlockPolicyEstimator(const fs::path& estimation_filepath
546 shortStats = std::unique_ptr<TxConfirmStats>(new TxConfirmStats(buckets, bucketMap, SHORT_BLOCK_PERIODS, SHORT_DECAY, SHORT_SCALE));
547 longStats = std::unique_ptr<TxConfirmStats>(new TxConfirmStats(buckets, bucketMap, LONG_BLOCK_PERIODS, LONG_DECAY, LONG_SCALE));
548549- // If the fee estimation file is present, read recorded estimations
550 AutoFile est_file{fsbridge::fopen(m_estimation_filepath, "rb")};
551- if (est_file.IsNull() || !Read(est_file)) {
552+
553+ // Whenever the fee estimation file is not present return early
willcl-ark
commented at 12:37 pm on June 20, 2023:
nit: Personally I don’t think comments like this are particularly needed/useful, as they are just describing exactly the code below. I ususally try and save them for when something reads as unexpected or I want to add a reason why I am doing something.
In the comment 7 lines below it could be a useful comment (in my opinion) if it read something like: “serving stale fee estimates can cause transactions to become stuck in the bottom of the mempool in the worst case. Don’t do that.” As this explains why we expire fee estimates.
Not blocking, but I agree. Comments are generally required when the code fails to self-document. This whole section is self-explanatory, which is good, meaning comments aren’t really required.
ismaelsadeeq
commented at 7:49 am on July 26, 2023:
willcl-ark
commented at 12:49 pm on June 20, 2023:
nit: There’s no test to ensure that stale estimates aren’t accepted on non-regtest networks, and I’m not sure there particularly needs to be. But I verified manually that this was the case:
0$./src/bitcoind -testnet -acceptstalefeeestimates
12023-06-20T12:47:07Z Error: acceptstalefeeestimates is not supported on test chain.
22023-06-20T12:47:07Z Shutdown: In progress...
32023-06-20T12:47:07Z scheduler thread exit
42023-06-20T12:47:07Z Shutdown: done
ismaelsadeeq
commented at 7:47 am on July 26, 2023:
willcl-ark
commented at 12:55 pm on June 20, 2023:
member
ACKd2b39e09bc
Looks good to me now.
Using stale fee estimates is a good way to get your transaction stuck in the mempool, and therefore not using those makes sense. I also agree with flushing the data periodically and not just on shutdown, which can help us have access to current estimates even after an unclean shutdown.
Left a few nits which don’t need addressing
DrahtBot requested review from instagibbs
on Jun 20, 2023
DrahtBot requested review from pinheadmz
on Jun 20, 2023
instagibbs approved
instagibbs
commented at 1:30 pm on June 20, 2023:
member
This is a metadata mirror of the GitHub repository
bitcoin/bitcoin.
This site is not affiliated with GitHub.
Content is generated from a GitHub metadata backup.
generated: 2025-06-26 15:13 UTC
This site is hosted by @0xB10C More mirrored repositories can be found on mirror.b10c.me