use std::collections::HashMap;
use std::sync::RwLock;
use bitcoin::FeeRate;
use lightning::chain::chaininterface::{
ConfirmationTarget as LdkConfirmationTarget, FeeEstimator as LdkFeeEstimator,
FEERATE_FLOOR_SATS_PER_KW,
};
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
pub(crate) enum ConfirmationTarget {
OnchainPayment,
ChannelFunding,
Lightning(LdkConfirmationTarget),
}
pub(crate) trait FeeEstimator {
fn estimate_fee_rate(&self, confirmation_target: ConfirmationTarget) -> FeeRate;
}
impl From<LdkConfirmationTarget> for ConfirmationTarget {
fn from(value: LdkConfirmationTarget) -> Self {
Self::Lightning(value)
}
}
pub(crate) struct OnchainFeeEstimator {
fee_rate_cache: RwLock<HashMap<ConfirmationTarget, FeeRate>>,
}
impl OnchainFeeEstimator {
pub(crate) fn new() -> Self {
let fee_rate_cache = RwLock::new(HashMap::new());
Self { fee_rate_cache }
}
pub(crate) fn set_fee_rate_cache(
&self, fee_rate_cache_update: HashMap<ConfirmationTarget, FeeRate>,
) -> bool {
let mut locked_fee_rate_cache = self.fee_rate_cache.write().unwrap();
if fee_rate_cache_update != *locked_fee_rate_cache {
*locked_fee_rate_cache = fee_rate_cache_update;
true
} else {
false
}
}
}
impl FeeEstimator for OnchainFeeEstimator {
fn estimate_fee_rate(&self, confirmation_target: ConfirmationTarget) -> FeeRate {
let locked_fee_rate_cache = self.fee_rate_cache.read().unwrap();
let fallback_sats_kwu = get_fallback_rate_for_target(confirmation_target);
let fallback_rate = FeeRate::from_sat_per_kwu(fallback_sats_kwu as u64);
let estimate = *locked_fee_rate_cache.get(&confirmation_target).unwrap_or(&fallback_rate);
FeeRate::from_sat_per_kwu(estimate.to_sat_per_kwu().max(FEERATE_FLOOR_SATS_PER_KW as u64))
}
}
impl LdkFeeEstimator for OnchainFeeEstimator {
fn get_est_sat_per_1000_weight(&self, confirmation_target: LdkConfirmationTarget) -> u32 {
self.estimate_fee_rate(confirmation_target.into())
.to_sat_per_kwu()
.try_into()
.unwrap_or_else(|_| get_fallback_rate_for_ldk_target(confirmation_target))
}
}
pub(crate) fn get_num_block_defaults_for_target(target: ConfirmationTarget) -> usize {
match target {
ConfirmationTarget::OnchainPayment => 6,
ConfirmationTarget::ChannelFunding => 12,
ConfirmationTarget::Lightning(ldk_target) => match ldk_target {
LdkConfirmationTarget::MaximumFeeEstimate => 1,
LdkConfirmationTarget::UrgentOnChainSweep => 6,
LdkConfirmationTarget::MinAllowedAnchorChannelRemoteFee => 1008,
LdkConfirmationTarget::MinAllowedNonAnchorChannelRemoteFee => 144,
LdkConfirmationTarget::AnchorChannelFee => 1008,
LdkConfirmationTarget::NonAnchorChannelFee => 12,
LdkConfirmationTarget::ChannelCloseMinimum => 144,
LdkConfirmationTarget::OutputSpendingFee => 12,
},
}
}
pub(crate) fn get_fallback_rate_for_target(target: ConfirmationTarget) -> u32 {
match target {
ConfirmationTarget::OnchainPayment => 5000,
ConfirmationTarget::ChannelFunding => 1000,
ConfirmationTarget::Lightning(ldk_target) => get_fallback_rate_for_ldk_target(ldk_target),
}
}
pub(crate) fn get_fallback_rate_for_ldk_target(target: LdkConfirmationTarget) -> u32 {
match target {
LdkConfirmationTarget::MaximumFeeEstimate => 8000,
LdkConfirmationTarget::UrgentOnChainSweep => 5000,
LdkConfirmationTarget::MinAllowedAnchorChannelRemoteFee => FEERATE_FLOOR_SATS_PER_KW,
LdkConfirmationTarget::MinAllowedNonAnchorChannelRemoteFee => FEERATE_FLOOR_SATS_PER_KW,
LdkConfirmationTarget::AnchorChannelFee => 500,
LdkConfirmationTarget::NonAnchorChannelFee => 1000,
LdkConfirmationTarget::ChannelCloseMinimum => 500,
LdkConfirmationTarget::OutputSpendingFee => 1000,
}
}
pub(crate) fn get_all_conf_targets() -> [ConfirmationTarget; 10] {
[
ConfirmationTarget::OnchainPayment,
ConfirmationTarget::ChannelFunding,
LdkConfirmationTarget::MaximumFeeEstimate.into(),
LdkConfirmationTarget::UrgentOnChainSweep.into(),
LdkConfirmationTarget::MinAllowedAnchorChannelRemoteFee.into(),
LdkConfirmationTarget::MinAllowedNonAnchorChannelRemoteFee.into(),
LdkConfirmationTarget::AnchorChannelFee.into(),
LdkConfirmationTarget::NonAnchorChannelFee.into(),
LdkConfirmationTarget::ChannelCloseMinimum.into(),
LdkConfirmationTarget::OutputSpendingFee.into(),
]
}
pub(crate) fn apply_post_estimation_adjustments(
target: ConfirmationTarget, estimated_rate: FeeRate,
) -> FeeRate {
match target {
ConfirmationTarget::Lightning(
LdkConfirmationTarget::MinAllowedNonAnchorChannelRemoteFee,
) => {
let slightly_less_than_background = estimated_rate
.to_sat_per_kwu()
.saturating_sub(250)
.max(FEERATE_FLOOR_SATS_PER_KW as u64);
FeeRate::from_sat_per_kwu(slightly_less_than_background)
},
ConfirmationTarget::Lightning(LdkConfirmationTarget::MaximumFeeEstimate) => {
let slightly_bump = estimated_rate
.to_sat_per_kwu()
.saturating_mul(11)
.saturating_div(10)
.saturating_add(2500);
FeeRate::from_sat_per_kwu(slightly_bump)
},
_ => estimated_rate,
}
}