TLDR
On mainnet, the Esplora fee estimator rejects a completely empty estimate map but accepts a non-empty map that lacks estimates for important confirmation targets.
When conversion fails for one of those missing targets, the code silently substitutes 1 sat/vB. This applies even to urgent targets such as UrgentOnChainSweep, whose normal fallback is substantially higher.
Affected code
src/chain/esplora.rs:368:
if estimates.is_empty() && self.config.network == Network::Bitcoin {
return Err(Error::FeerateEstimationUpdateFailed);
}
for target in confirmation_targets {
let num_blocks = get_num_block_defaults_for_target(target);
let converted_estimate_sat_vb =
esplora_client::convert_fee_rate(num_blocks, estimates.clone())
.map_or(1.0, |converted| converted.max(1.0));
let fee_rate =
FeeRate::from_sat_per_kwu((converted_estimate_sat_vb * 250.0) as u64);
let adjusted_fee_rate = apply_post_estimation_adjustments(target, fee_rate);
new_fee_rate_cache.insert(target, adjusted_fee_rate);
}
Urgent targets include six-block confirmation estimates:
src/fee_estimator.rs:87:
match target {
ConfirmationTarget::OnchainPayment => 6,
ConfirmationTarget::ChannelFunding => 12,
ConfirmationTarget::Lightning(ldk_target) => match ldk_target {
LdkConfirmationTarget::MaximumFeeEstimate => 1,
LdkConfirmationTarget::UrgentOnChainSweep => 6,
// ...
},
}
The existing target-specific fallback for UrgentOnChainSweep is 5,000 sat/kwu:
src/fee_estimator.rs:112:
LdkConfirmationTarget::UrgentOnChainSweep => 5000,
Impact
A partial or malformed Esplora response can replace urgent fee estimates with the relay-floor value instead of the target-specific fallback.
During a high-fee period, this can delay on-chain payments or time-sensitive channel sweeps. LDK fee bumping and the available confirmation windows reduce the likelihood of immediate loss, but the fallback moves in the unsafe direction for urgent transactions.
An arbitrary Lightning counterparty cannot directly supply this response. Exploitation would require control of, or interference with, the configured Esplora source. The issue can also occur non-maliciously if the source returns a sparse estimate map.
Issue #331 and PR #249 concern fee estimation generally but do not appear to track this sparse-map fallback behavior.
Proposed fix
On conversion failure, use the existing fallback for the requested target:
let fee_rate = esplora_client::convert_fee_rate(num_blocks, estimates.clone())
.map(|estimate| {
FeeRate::from_sat_per_kwu((estimate.max(1.0) * 250.0) as u64)
})
.unwrap_or_else(|| {
FeeRate::from_sat_per_kwu(get_fallback_rate_for_target(target) as u64)
});
For mainnet, also consider validating that the response provides sufficient coverage for all critical targets before replacing the current cache.
TLDR
On mainnet, the Esplora fee estimator rejects a completely empty estimate map but accepts a non-empty map that lacks estimates for important confirmation targets.
When conversion fails for one of those missing targets, the code silently substitutes 1 sat/vB. This applies even to urgent targets such as
UrgentOnChainSweep, whose normal fallback is substantially higher.Affected code
src/chain/esplora.rs:368:Urgent targets include six-block confirmation estimates:
src/fee_estimator.rs:87:The existing target-specific fallback for
UrgentOnChainSweepis 5,000 sat/kwu:src/fee_estimator.rs:112:Impact
A partial or malformed Esplora response can replace urgent fee estimates with the relay-floor value instead of the target-specific fallback.
During a high-fee period, this can delay on-chain payments or time-sensitive channel sweeps. LDK fee bumping and the available confirmation windows reduce the likelihood of immediate loss, but the fallback moves in the unsafe direction for urgent transactions.
An arbitrary Lightning counterparty cannot directly supply this response. Exploitation would require control of, or interference with, the configured Esplora source. The issue can also occur non-maliciously if the source returns a sparse estimate map.
Issue #331 and PR #249 concern fee estimation generally but do not appear to track this sparse-map fallback behavior.
Proposed fix
On conversion failure, use the existing fallback for the requested target:
For mainnet, also consider validating that the response provides sufficient coverage for all critical targets before replacing the current cache.