From 4d910cc16cfd174f17dae79cf7dce4ba274595bc Mon Sep 17 00:00:00 2001 From: ajaysehwal Date: Tue, 20 Oct 2026 21:50:39 +0530 Subject: [PATCH] Cap fee rate estimates at a maximum Previously, we only enforced a lower bound (`FEERATE_FLOOR_SATS_PER_KW`) on the fee rates reported by our chain source. A faulty or malicious Esplora/Electrum server could therefore return an absurdly high estimate and make any transaction using it (on-chain sends without an explicit fee rate, funding and splice transactions, sweeps, anchor bumps, etc.) pay a fee limited only by our spendable balance. Here, we additionally cap all estimates at `MAX_FEERATE_SATS_PER_KW` (2,000 sat/vB) when handing them out in `estimate_fee_rate`, which covers all chain sources and consumers, including LDK's `FeeEstimator`. This also ensures the `u32` conversion for LDK can no longer overflow. This change was prepared with the assistance of AI tools (Claude Code). --- src/fee_estimator.rs | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/fee_estimator.rs b/src/fee_estimator.rs index b785bfca40..072fea15bd 100644 --- a/src/fee_estimator.rs +++ b/src/fee_estimator.rs @@ -14,6 +14,13 @@ use lightning::chain::chaininterface::{ FEERATE_FLOOR_SATS_PER_KW, }; +/// The highest fee rate we use for any confirmation target (2,000 sats/vB), in sats per 1000 +/// weight units. +/// +/// This is roughly twice the highest estimates seen on mainnet, so it should never bind in +/// practice. +pub(crate) const MAX_FEERATE_SATS_PER_KW: u64 = 2_000 * 250; + #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] pub(crate) enum ConfirmationTarget { /// The default target for onchain payments. @@ -69,9 +76,14 @@ impl FeeEstimator for OnchainFeeEstimator { let estimate = *locked_fee_rate_cache.get(&confirmation_target).unwrap_or(&fallback_rate); - // Currently we assume every transaction needs to at least be relayable, which is why we - // enforce a lower bound of `FEERATE_FLOOR_SATS_PER_KW`. - FeeRate::from_sat_per_kwu(estimate.to_sat_per_kwu().max(FEERATE_FLOOR_SATS_PER_KW as u64)) + // Every transaction needs to be relayable, so we enforce a lower bound of + // `FEERATE_FLOOR_SATS_PER_KW`, and an upper bound to limit what a bad chain source can make + // us pay. + FeeRate::from_sat_per_kwu( + estimate + .to_sat_per_kwu() + .clamp(FEERATE_FLOOR_SATS_PER_KW as u64, MAX_FEERATE_SATS_PER_KW), + ) } }