mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-04-29 14:59:39 -04:00
rpc: update estimatefeesmartfee
to use forecaster_man
- Given a confirmation target, we use fee estimator module that call all available fee estimator forcasters and return the lowest fee rate that if a transaction use will likely confirm in a given confirmation target. - This also provides backward compatibility Co-authored-by: willcl-ark <will@256k1.dev>
This commit is contained in:
parent
821d58a825
commit
88aad65959
7 changed files with 65 additions and 29 deletions
|
@ -731,6 +731,7 @@ ForecastResult CBlockPolicyEstimator::ForecastFeeRate(int target, bool conservat
|
|||
FeeCalculation feeCalcConservative;
|
||||
CFeeRate feerate{estimateSmartFee(target, &feeCalcConservative, conservative)};
|
||||
result.current_block_height = feeCalcConservative.bestheight;
|
||||
result.returned_target = feeCalcConservative.returnedTarget;
|
||||
if (feerate == CFeeRate(0)) {
|
||||
result.m_error = "Insufficient data or no feerate found";
|
||||
return result;
|
||||
|
|
|
@ -33,6 +33,9 @@ struct ForecastResult {
|
|||
//! The chain tip at which the forecast was made.
|
||||
unsigned int current_block_height{0};
|
||||
|
||||
//! The confirmation target the forecast was made for.
|
||||
unsigned int returned_target{0};
|
||||
|
||||
std::optional<std::string> m_error; ///< Optional error message.
|
||||
|
||||
/**
|
||||
|
|
|
@ -47,6 +47,7 @@ ForecastResult MemPoolForecaster::ForecastFeeRate(int target, bool conservative)
|
|||
return result;
|
||||
}
|
||||
|
||||
result.returned_target = target;
|
||||
LogDebug(BCLog::MEMPOOL,
|
||||
"%s: Block height %s, Block template 25th percentile fee rate: %s %s/kvB, "
|
||||
"50th percentile fee rate: %s %s/kvB, 75th percentile fee rate: %s %s/kvB, "
|
||||
|
|
|
@ -259,6 +259,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
|
|||
{ "getrawmempool", 1, "mempool_sequence" },
|
||||
{ "getorphantxs", 0, "verbosity" },
|
||||
{ "estimatesmartfee", 0, "conf_target" },
|
||||
{ "estimatesmartfee", 2, "block_policy_only" },
|
||||
{ "estimaterawfee", 0, "conf_target" },
|
||||
{ "estimaterawfee", 1, "threshold" },
|
||||
{ "prioritisetransaction", 1, "dummy" },
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#include <policy/feerate.h>
|
||||
#include <policy/fees/block_policy_estimator.h>
|
||||
#include <policy/fees/forecaster_man.h>
|
||||
#include <policy/fees/forecaster_util.h>
|
||||
#include <rpc/protocol.h>
|
||||
#include <rpc/request.h>
|
||||
#include <rpc/server.h>
|
||||
|
@ -39,6 +40,7 @@ static RPCHelpMan estimatesmartfee()
|
|||
{"conf_target", RPCArg::Type::NUM, RPCArg::Optional::NO, "Confirmation target in blocks (1 - 1008)"},
|
||||
{"estimate_mode", RPCArg::Type::STR, RPCArg::Default{"economical"}, "The fee estimate mode.\n"
|
||||
+ FeeModesDetail(std::string("default mode will be used"))},
|
||||
{"block_policy_only", RPCArg::Type::BOOL, RPCArg::Default{false}, "Whether to use block policy estimator only.\n"}
|
||||
},
|
||||
RPCResult{
|
||||
RPCResult::Type::OBJ, "", "",
|
||||
|
@ -48,6 +50,7 @@ static RPCHelpMan estimatesmartfee()
|
|||
{
|
||||
{RPCResult::Type::STR, "", "error"},
|
||||
}},
|
||||
{RPCResult::Type::STR, "forecaster", /*optional=*/true, "the forecaster that provide the fee rate.\n"},
|
||||
{RPCResult::Type::NUM, "blocks", "block number where estimate was found\n"
|
||||
"The request target will be clamped between 2 and the highest target\n"
|
||||
"fee estimation is able to return based on how long it has been running.\n"
|
||||
|
@ -60,36 +63,63 @@ static RPCHelpMan estimatesmartfee()
|
|||
},
|
||||
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||
{
|
||||
CBlockPolicyEstimator& block_policy_fee_estimator = *(EnsureAnyForecasterMan(request.context).GetBlockPolicyEstimator());
|
||||
FeeRateForecasterManager& forecaster_man = EnsureAnyForecasterMan(request.context);
|
||||
const NodeContext& node = EnsureAnyNodeContext(request.context);
|
||||
const CTxMemPool& mempool = EnsureMemPool(node);
|
||||
|
||||
CHECK_NONFATAL(mempool.m_opts.signals)->SyncWithValidationInterfaceQueue();
|
||||
unsigned int max_target = block_policy_fee_estimator.MaximumTarget();
|
||||
|
||||
unsigned int max_target = forecaster_man.MaximumTarget();
|
||||
unsigned int conf_target = ParseConfirmTarget(request.params[0], max_target);
|
||||
|
||||
// Parse estimation mode
|
||||
bool conservative = false;
|
||||
if (!request.params[1].isNull()) {
|
||||
FeeEstimateMode fee_mode;
|
||||
if (!FeeModeFromString(request.params[1].get_str(), fee_mode)) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, InvalidEstimateModeErrorMessage());
|
||||
}
|
||||
if (fee_mode == FeeEstimateMode::CONSERVATIVE) conservative = true;
|
||||
conservative = (fee_mode == FeeEstimateMode::CONSERVATIVE);
|
||||
}
|
||||
|
||||
UniValue result(UniValue::VOBJ);
|
||||
UniValue errors(UniValue::VARR);
|
||||
FeeCalculation feeCalc;
|
||||
CFeeRate feeRate{block_policy_fee_estimator.estimateSmartFee(conf_target, &feeCalc, conservative)};
|
||||
if (feeRate != CFeeRate(0)) {
|
||||
CFeeRate min_mempool_feerate{mempool.GetMinFee()};
|
||||
CFeeRate min_relay_feerate{mempool.m_opts.min_relay_feerate};
|
||||
feeRate = std::max({feeRate, min_mempool_feerate, min_relay_feerate});
|
||||
result.pushKV("feerate", ValueFromAmount(feeRate.GetFeePerK()));
|
||||
} else {
|
||||
errors.push_back("Insufficient data or no feerate found");
|
||||
result.pushKV("errors", std::move(errors));
|
||||
|
||||
// Use block policy only if requested
|
||||
bool use_block_policy_only = false;
|
||||
if (!request.params[2].isNull()) {
|
||||
use_block_policy_only = request.params[2].get_bool();
|
||||
}
|
||||
result.pushKV("blocks", feeCalc.returnedTarget);
|
||||
CFeeRate feerate;
|
||||
std::vector<std::string> errs;
|
||||
|
||||
if (use_block_policy_only) {
|
||||
FeeCalculation feeCalc;
|
||||
feerate = forecaster_man.GetBlockPolicyEstimator()->estimateSmartFee(conf_target, &feeCalc, conservative);
|
||||
if (feerate == CFeeRate(0)) {
|
||||
errors.push_back("Insufficient data or no feerate found");
|
||||
}
|
||||
result.pushKV("blocks", feeCalc.returnedTarget);
|
||||
} else {
|
||||
auto results = forecaster_man.ForecastFeeRateFromForecasters(conf_target, conservative);
|
||||
feerate = CFeeRate(results.first.feerate.fee, results.first.feerate.size);
|
||||
if (feerate != CFeeRate(0)) {
|
||||
result.pushKV("forecaster", forecastTypeToString(results.first.forecaster));
|
||||
}
|
||||
result.pushKV("blocks", results.first.returned_target);
|
||||
errs = results.second;
|
||||
}
|
||||
|
||||
if (feerate != CFeeRate(0)) {
|
||||
CFeeRate min_mempool_feerate = mempool.GetMinFee();
|
||||
CFeeRate min_relay_feerate = mempool.m_opts.min_relay_feerate;
|
||||
feerate = std::max({feerate, min_mempool_feerate, min_relay_feerate});
|
||||
result.pushKV("feerate", ValueFromAmount(feerate.GetFeePerK()));
|
||||
}
|
||||
// Add any additional errors from forecasters
|
||||
for (const auto& err : errs) {
|
||||
errors.push_back(err);
|
||||
}
|
||||
result.pushKV("errors", std::move(errors));
|
||||
return result;
|
||||
},
|
||||
};
|
||||
|
|
|
@ -88,11 +88,11 @@ def check_raw_estimates(node, fees_seen):
|
|||
|
||||
|
||||
def check_smart_estimates(node, fees_seen):
|
||||
"""Call estimatesmartfee and verify that the estimates meet certain invariants."""
|
||||
"""Call estimatesmartfee a, "economical", Truend verify that the estimates meet certain invariants."""
|
||||
|
||||
delta = 1.0e-6 # account for rounding error
|
||||
last_feerate = float(max(fees_seen))
|
||||
all_smart_estimates = [node.estimatesmartfee(i) for i in range(1, 26)]
|
||||
all_smart_estimates = [node.estimatesmartfee(i, "economical", True) for i in range(1, 26)]
|
||||
mempoolMinFee = node.getmempoolinfo()["mempoolminfee"]
|
||||
minRelaytxFee = node.getmempoolinfo()["minrelaytxfee"]
|
||||
for i, e in enumerate(all_smart_estimates): # estimate is for i+1
|
||||
|
@ -130,9 +130,9 @@ def make_tx(wallet, utxo, feerate):
|
|||
)
|
||||
|
||||
def check_fee_estimates_btw_modes(node, expected_conservative, expected_economical):
|
||||
fee_est_conservative = node.estimatesmartfee(1, estimate_mode="conservative")['feerate']
|
||||
fee_est_economical = node.estimatesmartfee(1, estimate_mode="economical")['feerate']
|
||||
fee_est_default = node.estimatesmartfee(1)['feerate']
|
||||
fee_est_conservative = node.estimatesmartfee(1, estimate_mode="conservative", block_policy_only=True)['feerate']
|
||||
fee_est_economical = node.estimatesmartfee(1, estimate_mode="economical", block_policy_only=True)['feerate']
|
||||
fee_est_default = node.estimatesmartfee(1, estimate_mode="economical", block_policy_only=True)['feerate'] # Default is economical.
|
||||
assert_equal(fee_est_conservative, expected_conservative)
|
||||
assert_equal(fee_est_economical, expected_economical)
|
||||
assert_equal(fee_est_default, expected_economical)
|
||||
|
@ -239,7 +239,7 @@ class EstimateFeeTest(BitcoinTestFramework):
|
|||
check_estimates(self.nodes[1], self.fees_per_kb)
|
||||
|
||||
def test_feerate_mempoolminfee(self):
|
||||
high_val = 3 * self.nodes[1].estimatesmartfee(1)["feerate"]
|
||||
high_val = 3 * self.nodes[1].estimatesmartfee(1, "economical", True)["feerate"]
|
||||
self.restart_node(1, extra_args=[f"-minrelaytxfee={high_val}"])
|
||||
check_estimates(self.nodes[1], self.fees_per_kb)
|
||||
self.restart_node(1)
|
||||
|
@ -300,16 +300,16 @@ class EstimateFeeTest(BitcoinTestFramework):
|
|||
# Only 10% of the transactions were really confirmed with a low feerate,
|
||||
# the rest needed to be RBF'd. We must return the 90% conf rate feerate.
|
||||
high_feerate_kvb = Decimal(high_feerate) / COIN * 10 ** 3
|
||||
est_feerate = node.estimatesmartfee(2)["feerate"]
|
||||
est_feerate = node.estimatesmartfee(2, "economical", True)["feerate"]
|
||||
assert_equal(est_feerate, high_feerate_kvb)
|
||||
|
||||
def test_old_fee_estimate_file(self):
|
||||
# Get the initial fee rate while node is running
|
||||
fee_rate = self.nodes[0].estimatesmartfee(1)["feerate"]
|
||||
fee_rate = self.nodes[0].estimatesmartfee(1, "economical", True)["feerate"]
|
||||
|
||||
# Restart node to ensure fee_estimate.dat file is read
|
||||
self.restart_node(0)
|
||||
assert_equal(self.nodes[0].estimatesmartfee(1)["feerate"], fee_rate)
|
||||
assert_equal(self.nodes[0].estimatesmartfee(1, "economical", True)["feerate"], fee_rate)
|
||||
|
||||
fee_dat = self.nodes[0].chain_path / "fee_estimates.dat"
|
||||
|
||||
|
@ -320,7 +320,7 @@ class EstimateFeeTest(BitcoinTestFramework):
|
|||
|
||||
# Start node and ensure the fee_estimates.dat file was not read
|
||||
self.start_node(0)
|
||||
assert_equal(self.nodes[0].estimatesmartfee(1)["errors"], ["Insufficient data or no feerate found"])
|
||||
assert_equal(self.nodes[0].estimatesmartfee(1, "economical", True)["errors"], ["Insufficient data or no feerate found"])
|
||||
|
||||
|
||||
def test_estimate_dat_is_flushed_periodically(self):
|
||||
|
@ -377,7 +377,7 @@ class EstimateFeeTest(BitcoinTestFramework):
|
|||
|
||||
def test_acceptstalefeeestimates_option(self):
|
||||
# Get the initial fee rate while node is running
|
||||
fee_rate = self.nodes[0].estimatesmartfee(1)["feerate"]
|
||||
fee_rate = self.nodes[0].estimatesmartfee(1, "economical", True)["feerate"]
|
||||
|
||||
self.stop_node(0)
|
||||
|
||||
|
@ -389,7 +389,7 @@ class EstimateFeeTest(BitcoinTestFramework):
|
|||
|
||||
# Restart node with -acceptstalefeeestimates option to ensure fee_estimate.dat file is read
|
||||
self.start_node(0,extra_args=["-acceptstalefeeestimates"])
|
||||
assert_equal(self.nodes[0].estimatesmartfee(1)["feerate"], fee_rate)
|
||||
assert_equal(self.nodes[0].estimatesmartfee(1, "economical", True)["feerate"], fee_rate)
|
||||
|
||||
def clear_estimates(self):
|
||||
self.log.info("Restarting node with fresh estimation")
|
||||
|
@ -400,7 +400,7 @@ class EstimateFeeTest(BitcoinTestFramework):
|
|||
self.connect_nodes(0, 1)
|
||||
self.connect_nodes(0, 2)
|
||||
self.sync_blocks()
|
||||
assert_equal(self.nodes[0].estimatesmartfee(1)["errors"], ["Insufficient data or no feerate found"])
|
||||
assert_equal(self.nodes[0].estimatesmartfee(1, "economical", True)["errors"], ["Insufficient data or no feerate found"])
|
||||
|
||||
def broadcast_and_mine(self, broadcaster, miner, feerate, count):
|
||||
"""Broadcast and mine some number of transactions with a specified fee rate."""
|
||||
|
|
|
@ -33,7 +33,7 @@ class EstimateFeeTest(BitcoinTestFramework):
|
|||
assert_raises_rpc_error(-3, "JSON value of type string is not of expected type number", self.nodes[0].estimaterawfee, 1, 'foo')
|
||||
|
||||
# extra params
|
||||
assert_raises_rpc_error(-1, "estimatesmartfee", self.nodes[0].estimatesmartfee, 1, 'ECONOMICAL', 1)
|
||||
assert_raises_rpc_error(-1, "estimatesmartfee", self.nodes[0].estimatesmartfee, 1, 'ECONOMICAL', True, 1)
|
||||
assert_raises_rpc_error(-1, "estimaterawfee", self.nodes[0].estimaterawfee, 1, 1, 1)
|
||||
|
||||
# max value of 1008 per src/policy/fees.h
|
||||
|
|
Loading…
Add table
Reference in a new issue