Skip to content

Commit f7ab3ba

Browse files
committed
Merge bitcoin/bitcoin#30275: Fee Estimation: change estimatesmartfee default mode to economical
25bf86a [test]: ensure `estimatesmartfee` default mode is `economical` (ismaelsadeeq) 41a2545 [fees]: change `estimatesmartfee` default mode to `economical` (ismaelsadeeq) Pull request description: Fixes #30009 This PR changes the `estimatesmartfee` default mode to `economical`. This was also suggested on IRC https://bitcoin-irc.chaincode.com/bitcoin-core-dev/2024-04-26#1021609 - `conservative` mode: This is the `estimatesmartfee` RPC mode which considers a longer history of blocks. It potentially returns a higher fee rate and is more likely to be sufficient for the desired target, but it is not as responsive to short-term drops in the prevailing fee market. - `economical` mode: This is the `estimatesmartfee` RPC mode where estimates are potentially lower and more responsive to short-term drops in the prevailing fee market. Since users are likely to use the default mode, this change will reduce overestimation for many users. The conservative mode remains available for those who wish to opt-in. For an in-depth analysis of how significantly the `conservative` mode overestimates, see https://delvingbitcoin.org/t/bitcoind-policy-estimator-modes-analysis/964. ACKs for top commit: instagibbs: reACK bitcoin/bitcoin@25bf86a glozow: ACK 25bf86a willcl-ark: ACK 25bf86a Tree-SHA512: 78ebda667eb9c8f87dcc2f0e6c14968bd1de30358dc77a13611b186fb8427ad97d9f537bad6e32e0a1aa477ccd8c64fee4d41e19308ef3cb184ff1664e6ba8a6
2 parents 1ca1df9 + 25bf86a commit f7ab3ba

File tree

2 files changed

+50
-10
lines changed

2 files changed

+50
-10
lines changed

src/rpc/fees.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ static RPCHelpMan estimatesmartfee()
3636
"in BIP 141 (witness data is discounted).\n",
3737
{
3838
{"conf_target", RPCArg::Type::NUM, RPCArg::Optional::NO, "Confirmation target in blocks (1 - 1008)"},
39-
{"estimate_mode", RPCArg::Type::STR, RPCArg::Default{"conservative"}, "The fee estimate mode.\n"
39+
{"estimate_mode", RPCArg::Type::STR, RPCArg::Default{"economical"}, "The fee estimate mode.\n"
4040
"Whether to return a more conservative estimate which also satisfies\n"
4141
"a longer history. A conservative estimate potentially returns a\n"
4242
"higher feerate and is more likely to be sufficient for the desired\n"
@@ -71,13 +71,13 @@ static RPCHelpMan estimatesmartfee()
7171
CHECK_NONFATAL(mempool.m_opts.signals)->SyncWithValidationInterfaceQueue();
7272
unsigned int max_target = fee_estimator.HighestTargetTracked(FeeEstimateHorizon::LONG_HALFLIFE);
7373
unsigned int conf_target = ParseConfirmTarget(request.params[0], max_target);
74-
bool conservative = true;
74+
bool conservative = false;
7575
if (!request.params[1].isNull()) {
7676
FeeEstimateMode fee_mode;
7777
if (!FeeModeFromString(request.params[1].get_str(), fee_mode)) {
7878
throw JSONRPCError(RPC_INVALID_PARAMETER, InvalidEstimateModeErrorMessage());
7979
}
80-
if (fee_mode == FeeEstimateMode::ECONOMICAL) conservative = false;
80+
if (fee_mode == FeeEstimateMode::CONSERVATIVE) conservative = true;
8181
}
8282

8383
UniValue result(UniValue::VOBJ);

test/functional/feature_fee_estimation.py

Lines changed: 47 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,14 @@ def make_tx(wallet, utxo, feerate):
128128
fee_rate=Decimal(feerate * 1000) / COIN,
129129
)
130130

131+
def check_fee_estimates_btw_modes(node, expected_conservative, expected_economical):
132+
fee_est_conservative = node.estimatesmartfee(1, estimate_mode="conservative")['feerate']
133+
fee_est_economical = node.estimatesmartfee(1, estimate_mode="economical")['feerate']
134+
fee_est_default = node.estimatesmartfee(1)['feerate']
135+
assert_equal(fee_est_conservative, expected_conservative)
136+
assert_equal(fee_est_economical, expected_economical)
137+
assert_equal(fee_est_default, expected_economical)
138+
131139

132140
class EstimateFeeTest(BitcoinTestFramework):
133141
def set_test_params(self):
@@ -382,6 +390,40 @@ def test_acceptstalefeeestimates_option(self):
382390
self.start_node(0,extra_args=["-acceptstalefeeestimates"])
383391
assert_equal(self.nodes[0].estimatesmartfee(1)["feerate"], fee_rate)
384392

393+
def clear_estimates(self):
394+
self.log.info("Restarting node with fresh estimation")
395+
self.stop_node(0)
396+
fee_dat = self.nodes[0].chain_path / "fee_estimates.dat"
397+
os.remove(fee_dat)
398+
self.start_node(0)
399+
self.connect_nodes(0, 1)
400+
self.connect_nodes(0, 2)
401+
assert_equal(self.nodes[0].estimatesmartfee(1)["errors"], ["Insufficient data or no feerate found"])
402+
403+
def broadcast_and_mine(self, broadcaster, miner, feerate, count):
404+
"""Broadcast and mine some number of transactions with a specified fee rate."""
405+
for _ in range(count):
406+
self.wallet.send_self_transfer(from_node=broadcaster, fee_rate=feerate)
407+
self.sync_mempools()
408+
self.generate(miner, 1)
409+
410+
def test_estimation_modes(self):
411+
low_feerate = Decimal("0.001")
412+
high_feerate = Decimal("0.005")
413+
tx_count = 24
414+
# Broadcast and mine high fee transactions for the first 12 blocks.
415+
for _ in range(12):
416+
self.broadcast_and_mine(self.nodes[1], self.nodes[2], high_feerate, tx_count)
417+
check_fee_estimates_btw_modes(self.nodes[0], high_feerate, high_feerate)
418+
419+
# We now track 12 blocks; short horizon stats will start decaying.
420+
# Broadcast and mine low fee transactions for the next 4 blocks.
421+
for _ in range(4):
422+
self.broadcast_and_mine(self.nodes[1], self.nodes[2], low_feerate, tx_count)
423+
# conservative mode will consider longer time horizons while economical mode does not
424+
# Check the fee estimates for both modes after mining low fee transactions.
425+
check_fee_estimates_btw_modes(self.nodes[0], high_feerate, low_feerate)
426+
385427

386428
def run_test(self):
387429
self.log.info("This test is time consuming, please be patient")
@@ -420,17 +462,15 @@ def run_test(self):
420462
self.log.info("Test reading old fee_estimates.dat")
421463
self.test_old_fee_estimate_file()
422464

423-
self.log.info("Restarting node with fresh estimation")
424-
self.stop_node(0)
425-
fee_dat = os.path.join(self.nodes[0].chain_path, "fee_estimates.dat")
426-
os.remove(fee_dat)
427-
self.start_node(0)
428-
self.connect_nodes(0, 1)
429-
self.connect_nodes(0, 2)
465+
self.clear_estimates()
430466

431467
self.log.info("Testing estimates with RBF.")
432468
self.sanity_check_rbf_estimates(self.confutxo + self.memutxo)
433469

470+
self.clear_estimates()
471+
self.log.info("Test estimatesmartfee modes")
472+
self.test_estimation_modes()
473+
434474
self.log.info("Testing that fee estimation is disabled in blocksonly.")
435475
self.restart_node(0, ["-blocksonly"])
436476
assert_raises_rpc_error(

0 commit comments

Comments
 (0)