Skip to content

Commit

Permalink
Refactor FeeEstimator to introduce local target variants
Browse files Browse the repository at this point in the history
.. previously we used LDK's `FeeEstimator` and `ConfirmationTarget` and
~misused some of the latter's variants for our non-Lightning operations.

Here, we introduce our own `FeeEstimator` and `ConfirmationTarget`
allowing to add specific variants for `ChannelFunding` and
`OnchainPayment`s, for example.
  • Loading branch information
tnull committed Aug 30, 2024
1 parent 7202c83 commit 42a695e
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 43 deletions.
4 changes: 2 additions & 2 deletions src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use crate::{
};

use crate::connection::ConnectionManager;
use crate::fee_estimator::ConfirmationTarget;

use crate::payment::store::{
PaymentDetails, PaymentDetailsUpdate, PaymentDirection, PaymentKind, PaymentStatus,
Expand All @@ -18,7 +19,6 @@ use crate::io::{
};
use crate::logger::{log_debug, log_error, log_info, Logger};

use lightning::chain::chaininterface::ConfirmationTarget;
use lightning::events::bump_transaction::BumpTransactionEvent;
use lightning::events::{ClosureReason, PaymentPurpose};
use lightning::events::{Event as LdkEvent, PaymentFailureReason};
Expand Down Expand Up @@ -398,7 +398,7 @@ where
} => {
// Construct the raw transaction with the output that is paid the amount of the
// channel.
let confirmation_target = ConfirmationTarget::NonAnchorChannelFee;
let confirmation_target = ConfirmationTarget::ChannelFunding;

// We set nLockTime to the current height to discourage fee sniping.
let cur_height = self.channel_manager.current_best_block().height;
Expand Down
111 changes: 80 additions & 31 deletions src/fee_estimator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ use crate::config::FEE_RATE_CACHE_UPDATE_TIMEOUT_SECS;
use crate::logger::{log_error, log_trace, Logger};
use crate::{Config, Error};

use lightning::chain::chaininterface::{
ConfirmationTarget, FeeEstimator, FEERATE_FLOOR_SATS_PER_KW,
};
use lightning::chain::chaininterface::ConfirmationTarget as LdkConfirmationTarget;
use lightning::chain::chaininterface::FeeEstimator as LdkFeeEstimator;
use lightning::chain::chaininterface::FEERATE_FLOOR_SATS_PER_KW;

use bdk::FeeRate;
use esplora_client::AsyncClient as EsploraClient;
Expand All @@ -17,6 +17,26 @@ use std::ops::Deref;
use std::sync::{Arc, RwLock};
use std::time::Duration;

#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
pub(crate) enum ConfirmationTarget {
/// The default target for onchain payments.
OnchainPayment,
/// The target used for funding transactions.
ChannelFunding,
/// Targets used by LDK.
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<L: Deref>
where
L::Target: Logger,
Expand Down Expand Up @@ -61,23 +81,30 @@ where
}

let confirmation_targets = vec![
ConfirmationTarget::OnChainSweep,
ConfirmationTarget::MinAllowedAnchorChannelRemoteFee,
ConfirmationTarget::MinAllowedNonAnchorChannelRemoteFee,
ConfirmationTarget::AnchorChannelFee,
ConfirmationTarget::NonAnchorChannelFee,
ConfirmationTarget::ChannelCloseMinimum,
ConfirmationTarget::OutputSpendingFee,
ConfirmationTarget::OnchainPayment,
ConfirmationTarget::ChannelFunding,
LdkConfirmationTarget::OnChainSweep.into(),
LdkConfirmationTarget::MinAllowedAnchorChannelRemoteFee.into(),
LdkConfirmationTarget::MinAllowedNonAnchorChannelRemoteFee.into(),
LdkConfirmationTarget::AnchorChannelFee.into(),
LdkConfirmationTarget::NonAnchorChannelFee.into(),
LdkConfirmationTarget::ChannelCloseMinimum.into(),
LdkConfirmationTarget::OutputSpendingFee.into(),
];

for target in confirmation_targets {
let num_blocks = match target {
ConfirmationTarget::OnChainSweep => 6,
ConfirmationTarget::MinAllowedAnchorChannelRemoteFee => 1008,
ConfirmationTarget::MinAllowedNonAnchorChannelRemoteFee => 144,
ConfirmationTarget::AnchorChannelFee => 1008,
ConfirmationTarget::NonAnchorChannelFee => 12,
ConfirmationTarget::ChannelCloseMinimum => 144,
ConfirmationTarget::OutputSpendingFee => 12,
ConfirmationTarget::OnchainPayment => 6,
ConfirmationTarget::ChannelFunding => 12,
ConfirmationTarget::Lightning(ldk_target) => match ldk_target {
LdkConfirmationTarget::OnChainSweep => 6,
LdkConfirmationTarget::MinAllowedAnchorChannelRemoteFee => 1008,
LdkConfirmationTarget::MinAllowedNonAnchorChannelRemoteFee => 144,
LdkConfirmationTarget::AnchorChannelFee => 1008,
LdkConfirmationTarget::NonAnchorChannelFee => 12,
LdkConfirmationTarget::ChannelCloseMinimum => 144,
LdkConfirmationTarget::OutputSpendingFee => 12,
},
};

let converted_estimates =
Expand All @@ -96,7 +123,9 @@ where
// LDK 0.0.118 introduced changes to the `ConfirmationTarget` semantics that
// require some post-estimation adjustments to the fee rates, which we do here.
let adjusted_fee_rate = match target {
ConfirmationTarget::MinAllowedNonAnchorChannelRemoteFee => {
ConfirmationTarget::Lightning(
LdkConfirmationTarget::MinAllowedNonAnchorChannelRemoteFee,
) => {
let slightly_less_than_background =
fee_rate.fee_wu(Weight::from_wu(1000)) - 250;
FeeRate::from_sat_per_kwu(slightly_less_than_background as f32)
Expand All @@ -115,33 +144,53 @@ where
}
Ok(())
}
}

pub(crate) fn estimate_fee_rate(&self, confirmation_target: ConfirmationTarget) -> FeeRate {
impl<L: Deref> FeeEstimator for OnchainFeeEstimator<L>
where
L::Target: Logger,
{
fn estimate_fee_rate(&self, confirmation_target: ConfirmationTarget) -> FeeRate {
let locked_fee_rate_cache = self.fee_rate_cache.read().unwrap();

let fallback_sats_kwu = match confirmation_target {
ConfirmationTarget::OnChainSweep => 5000,
ConfirmationTarget::MinAllowedAnchorChannelRemoteFee => FEERATE_FLOOR_SATS_PER_KW,
ConfirmationTarget::MinAllowedNonAnchorChannelRemoteFee => FEERATE_FLOOR_SATS_PER_KW,
ConfirmationTarget::AnchorChannelFee => 500,
ConfirmationTarget::NonAnchorChannelFee => 1000,
ConfirmationTarget::ChannelCloseMinimum => 500,
ConfirmationTarget::OutputSpendingFee => 1000,
ConfirmationTarget::OnchainPayment => 5000,
ConfirmationTarget::ChannelFunding => 1000,
ConfirmationTarget::Lightning(ldk_target) => match ldk_target {
LdkConfirmationTarget::OnChainSweep => 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,
},
};

// We'll fall back on this, if we really don't have any other information.
let fallback_rate = FeeRate::from_sat_per_kwu(fallback_sats_kwu as f32);

*locked_fee_rate_cache.get(&confirmation_target).unwrap_or(&fallback_rate)
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`.
let weight_units = Weight::from_wu(1000);
FeeRate::from_wu(
estimate.fee_wu(weight_units).max(FEERATE_FLOOR_SATS_PER_KW as u64),
weight_units,
)
}
}

impl<L: Deref> FeeEstimator for OnchainFeeEstimator<L>
impl<L: Deref> LdkFeeEstimator for OnchainFeeEstimator<L>
where
L::Target: Logger,
{
fn get_est_sat_per_1000_weight(&self, confirmation_target: ConfirmationTarget) -> u32 {
(self.estimate_fee_rate(confirmation_target).fee_wu(Weight::from_wu(1000)) as u32)
.max(FEERATE_FLOOR_SATS_PER_KW)
fn get_est_sat_per_1000_weight(&self, confirmation_target: LdkConfirmationTarget) -> u32 {
self.estimate_fee_rate(confirmation_target.into()).fee_wu(Weight::from_wu(1000)) as u32
}
}
16 changes: 6 additions & 10 deletions src/wallet.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use crate::logger::{log_error, log_info, log_trace, Logger};

use crate::config::BDK_WALLET_SYNC_TIMEOUT_SECS;
use crate::fee_estimator::{ConfirmationTarget, FeeEstimator};
use crate::Error;

use lightning::chain::chaininterface::{BroadcasterInterface, ConfirmationTarget, FeeEstimator};
use lightning::chain::chaininterface::BroadcasterInterface;

use lightning::events::bump_transaction::{Utxo, WalletSource};
use lightning::ln::msgs::{DecodeError, UnsignedGossipMessage};
Expand All @@ -18,8 +19,7 @@ use lightning::util::message_signing;
use bdk::blockchain::EsploraBlockchain;
use bdk::database::BatchDatabase;
use bdk::wallet::AddressIndex;
use bdk::{Balance, FeeRate};
use bdk::{SignOptions, SyncOptions};
use bdk::{Balance, SignOptions, SyncOptions};

use bitcoin::address::{Payload, WitnessVersion};
use bitcoin::bech32::u5;
Expand Down Expand Up @@ -153,9 +153,7 @@ where
&self, output_script: ScriptBuf, value_sats: u64, confirmation_target: ConfirmationTarget,
locktime: LockTime,
) -> Result<Transaction, Error> {
let fee_rate = FeeRate::from_sat_per_kwu(
self.fee_estimator.get_est_sat_per_1000_weight(confirmation_target) as f32,
);
let fee_rate = self.fee_estimator.estimate_fee_rate(confirmation_target);

let locked_wallet = self.inner.lock().unwrap();
let mut tx_builder = locked_wallet.build_tx();
Expand Down Expand Up @@ -240,10 +238,8 @@ where
pub(crate) fn send_to_address(
&self, address: &bitcoin::Address, amount_msat_or_drain: Option<u64>,
) -> Result<Txid, Error> {
let confirmation_target = ConfirmationTarget::OutputSpendingFee;
let fee_rate = FeeRate::from_sat_per_kwu(
self.fee_estimator.get_est_sat_per_1000_weight(confirmation_target) as f32,
);
let confirmation_target = ConfirmationTarget::OnchainPayment;
let fee_rate = self.fee_estimator.estimate_fee_rate(confirmation_target);

let tx = {
let locked_wallet = self.inner.lock().unwrap();
Expand Down

0 comments on commit 42a695e

Please sign in to comment.