From 2cb6b831e0304c7caf7953a991b97c15c0c24550 Mon Sep 17 00:00:00 2001 From: ismaelsadeeq Date: Mon, 17 Nov 2025 14:19:57 +0000 Subject: [PATCH] fees: add EstimateFeeRate and MaximumTarget to CBlockPolicyEstimator Introduce that common interface: - FeeRateEstimatorType identifies the source estimator in a result. - FeeRateEstimation carries the feerate and returned target of a successful estimate; FeeRateEstimationError carries the error message alongside a zero-value estimation. - EstimateFeeRate wraps estimateSmartFee and returns util::Expected. - MaximumTarget delegates to HighestTargetTracked(LONG_HALFLIFE) so callers do not need to know about block policy horizons. Update call sites in rpc/fees.cpp and node/interfaces.cpp. A later commit introduces FeeRateEstimatorManager, which selects between multiple fee rate estimators. To compare estimates and report which estimator produced them, the manager needs each fee rate estimator to expose a uniform output, whereas estimateSmartFee's CFeeRate/FeeCalculation output is specific to the block policy fee rate estimator. Co-authored-by: willcl-ark --- src/node/interfaces.cpp | 2 +- src/policy/fees/block_policy_estimator.cpp | 15 +++++++ src/policy/fees/block_policy_estimator.h | 10 +++++ src/rpc/fees.cpp | 4 +- src/util/fees.h | 51 +++++++++++++++++++++- 5 files changed, 77 insertions(+), 5 deletions(-) diff --git a/src/node/interfaces.cpp b/src/node/interfaces.cpp index afb6ea48e93..4ac4fd5f57a 100644 --- a/src/node/interfaces.cpp +++ b/src/node/interfaces.cpp @@ -744,7 +744,7 @@ public: unsigned int estimateMaxBlocks() override { if (!m_node.fee_estimator) return 0; - return m_node.fee_estimator->HighestTargetTracked(FeeEstimateHorizon::LONG_HALFLIFE); + return m_node.fee_estimator->MaximumTarget(); } CFeeRate mempoolMinFee() override { diff --git a/src/policy/fees/block_policy_estimator.cpp b/src/policy/fees/block_policy_estimator.cpp index 7eb26c7dd23..2d812517df5 100644 --- a/src/policy/fees/block_policy_estimator.cpp +++ b/src/policy/fees/block_policy_estimator.cpp @@ -974,6 +974,21 @@ CFeeRate CBlockPolicyEstimator::estimateSmartFee(int confTarget, FeeCalculation return CFeeRate(llround(median)); } +util::Expected CBlockPolicyEstimator::EstimateFeeRate(int target, bool conservative) const +{ + FeeCalculation fee_calc; + CFeeRate feerate{estimateSmartFee(target, &fee_calc, conservative)}; + if (feerate == CFeeRate(0)) { + return EstimationError(FeeRateEstimatorType::BLOCK_POLICY, fee_calc.returnedTarget, "Insufficient data or no feerate found"); + } + return FeeRateEstimation{FeeRateEstimatorType::BLOCK_POLICY, feerate.GetFeePerVSize(), fee_calc.returnedTarget}; +} + +unsigned int CBlockPolicyEstimator::MaximumTarget() const +{ + return HighestTargetTracked(FeeEstimateHorizon::LONG_HALFLIFE); +} + void CBlockPolicyEstimator::Flush() { FlushUnconfirmed(); FlushFeeEstimates(); diff --git a/src/policy/fees/block_policy_estimator.h b/src/policy/fees/block_policy_estimator.h index a92efd9c40d..2b5417cc950 100644 --- a/src/policy/fees/block_policy_estimator.h +++ b/src/policy/fees/block_policy_estimator.h @@ -10,6 +10,8 @@ #include #include #include +#include +#include #include #include @@ -262,6 +264,14 @@ public: /** Calculates the age of the file, since last modified */ std::chrono::hours GetFeeEstimatorFileAge(); + /** Return the highest confirmation target for which an estimate can be provided. */ + unsigned int MaximumTarget() const + EXCLUSIVE_LOCKS_REQUIRED(!m_cs_fee_estimator); + + /** Estimate the feerate needed to confirm within @p target blocks; wraps estimateSmartFee into a FeeRateEstimation. */ + util::Expected EstimateFeeRate(int target, bool conservative) const + EXCLUSIVE_LOCKS_REQUIRED(!m_cs_fee_estimator); + protected: /** Overridden from CValidationInterface. */ void TransactionAddedToMempool(const NewMempoolTransactionInfo& tx, uint64_t /*unused*/) override diff --git a/src/rpc/fees.cpp b/src/rpc/fees.cpp index 450af530d94..f4c135444bb 100644 --- a/src/rpc/fees.cpp +++ b/src/rpc/fees.cpp @@ -67,7 +67,7 @@ static RPCMethod estimatesmartfee() const CTxMemPool& mempool = EnsureMemPool(node); CHECK_NONFATAL(mempool.m_opts.signals)->SyncWithValidationInterfaceQueue(); - unsigned int max_target = fee_estimator.HighestTargetTracked(FeeEstimateHorizon::LONG_HALFLIFE); + unsigned int max_target = fee_estimator.MaximumTarget(); unsigned int conf_target = ParseConfirmTarget(request.params[0], max_target); FeeEstimateMode fee_mode; if (!FeeModeFromString(self.Arg("estimate_mode"), fee_mode)) { @@ -159,7 +159,7 @@ static RPCMethod estimaterawfee() const NodeContext& node = EnsureAnyNodeContext(request.context); CHECK_NONFATAL(node.validation_signals)->SyncWithValidationInterfaceQueue(); - unsigned int max_target = fee_estimator.HighestTargetTracked(FeeEstimateHorizon::LONG_HALFLIFE); + unsigned int max_target = fee_estimator.MaximumTarget(); unsigned int conf_target = ParseConfirmTarget(request.params[0], max_target); double threshold = 0.95; if (!request.params[1].isNull()) { diff --git a/src/util/fees.h b/src/util/fees.h index 85c3d59d462..332b3927781 100644 --- a/src/util/fees.h +++ b/src/util/fees.h @@ -5,11 +5,18 @@ #ifndef BITCOIN_UTIL_FEES_H #define BITCOIN_UTIL_FEES_H +#include +#include +#include + +#include +#include + /* Used to determine type of fee estimation requested */ enum class FeeEstimateMode { UNSET, //!< Use default settings based on other criteria - ECONOMICAL, //!< Force estimateSmartFee to use non-conservative estimates - CONSERVATIVE, //!< Force estimateSmartFee to use conservative estimates + ECONOMICAL, //!< Force Fee rate estimator to return non-conservative estimates + CONSERVATIVE, //!< Force Fee rate estimator to return conservative estimates }; /* Used to determine the reason a wallet selected a transaction fee rate */ @@ -21,4 +28,44 @@ enum class FeeReason { REQUIRED, }; +/** + * @enum FeeRateEstimatorType + * Identifier for fee rate estimator. + */ +enum class FeeRateEstimatorType { + BLOCK_POLICY, +}; + +/** + * @struct FeeRateEstimation + * A successful fee rate estimate returned by a fee rate estimator. + */ +struct FeeRateEstimation { + //! This identifies which fee rate estimator is providing this feerate estimate + FeeRateEstimatorType feerate_estimator; + //! Fee rate sufficient to confirm a package within target + FeePerVSize feerate; + //! The returned target at which the package is likely to confirm within + int returned_target; +}; + +/** + * @struct FeeRateEstimationError + * A failed fee rate estimation, carrying the zero-value estimation that + * identifies the estimator and target alongside the error reason. + */ +struct FeeRateEstimationError { + FeeRateEstimation estimation; + std::string reason; +}; + +/** + * Build a fee rate estimation error result: a zero-value estimation + * identifying the estimator and target, alongside the error message. + */ +inline util::Unexpected EstimationError(FeeRateEstimatorType estimator, int returned_target, std::string error) +{ + return util::Unexpected{FeeRateEstimationError{{estimator, FeePerVSize{0, 0}, returned_target}, std::move(error)}}; +} + #endif // BITCOIN_UTIL_FEES_H