Skip to content

Commit 81f8b67

Browse files
authored
Merge pull request #3572 from tankyleo/25-01-htlc-dust-exposure
Tweak htlc dust exposure due to excess fees
2 parents 609f89d + f931db5 commit 81f8b67

File tree

3 files changed

+322
-74
lines changed

3 files changed

+322
-74
lines changed

lightning/src/ln/chan_utils.rs

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -196,15 +196,16 @@ pub(crate) fn commit_tx_fee_sat(feerate_per_kw: u32, num_htlcs: usize, channel_t
196196
/ 1000
197197
}
198198

199-
pub(crate) fn per_outbound_htlc_counterparty_commit_tx_fee_msat(feerate_per_kw: u32, channel_type_features: &ChannelTypeFeatures) -> u64 {
200-
// Note that we need to divide before multiplying to round properly,
201-
// since the lowest denomination of bitcoin on-chain is the satoshi.
202-
let commitment_tx_fee = COMMITMENT_TX_WEIGHT_PER_HTLC * feerate_per_kw as u64 / 1000 * 1000;
203-
if channel_type_features.supports_anchors_zero_fee_htlc_tx() {
204-
commitment_tx_fee + htlc_success_tx_weight(channel_type_features) * feerate_per_kw as u64 / 1000
199+
pub(crate) fn commit_and_htlc_tx_fees_sat(feerate_per_kw: u32, num_accepted_htlcs: usize, num_offered_htlcs: usize, channel_type_features: &ChannelTypeFeatures) -> u64 {
200+
let num_htlcs = num_accepted_htlcs + num_offered_htlcs;
201+
let commit_tx_fees_sat = commit_tx_fee_sat(feerate_per_kw, num_htlcs, channel_type_features);
202+
let htlc_tx_fees_sat = if !channel_type_features.supports_anchors_zero_fee_htlc_tx() {
203+
num_accepted_htlcs as u64 * htlc_success_tx_weight(channel_type_features) * feerate_per_kw as u64 / 1000
204+
+ num_offered_htlcs as u64 * htlc_timeout_tx_weight(channel_type_features) * feerate_per_kw as u64 / 1000
205205
} else {
206-
commitment_tx_fee
207-
}
206+
0
207+
};
208+
commit_tx_fees_sat + htlc_tx_fees_sat
208209
}
209210

210211
// Various functions for key derivation and transaction creation for use within channels. Primarily

lightning/src/ln/channel.rs

Lines changed: 27 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ use crate::ln::chan_utils::{
4646
HolderCommitmentTransaction, ChannelTransactionParameters,
4747
CounterpartyChannelTransactionParameters, MAX_HTLCS,
4848
get_commitment_transaction_number_obscure_factor,
49-
ClosingTransaction, commit_tx_fee_sat, per_outbound_htlc_counterparty_commit_tx_fee_msat,
49+
ClosingTransaction, commit_tx_fee_sat,
5050
};
5151
use crate::ln::chan_utils;
5252
use crate::ln::onion_utils::HTLCFailReason;
@@ -866,6 +866,10 @@ struct HTLCStats {
866866
pending_inbound_htlcs_value_msat: u64,
867867
pending_outbound_htlcs_value_msat: u64,
868868
on_counterparty_tx_dust_exposure_msat: u64,
869+
// If the counterparty sets a feerate on the channel in excess of our dust_exposure_limiting_feerate,
870+
// this will be set to the dust exposure that would result from us adding an additional nondust outbound
871+
// htlc on the counterparty's commitment transaction.
872+
extra_nondust_htlc_on_counterparty_tx_dust_exposure_msat: Option<u64>,
869873
on_holder_tx_dust_exposure_msat: u64,
870874
outbound_holding_cell_msat: u64,
871875
on_holder_tx_outbound_holding_cell_htlcs_count: u32, // dust HTLCs *non*-included
@@ -3794,27 +3798,21 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
37943798
.or(self.pending_update_fee.map(|(fee, _)| fee))
37953799
.unwrap_or(self.feerate_per_kw)
37963800
.checked_sub(dust_exposure_limiting_feerate);
3797-
if let Some(excess_feerate) = excess_feerate_opt {
3798-
let on_counterparty_tx_nondust_htlcs =
3799-
on_counterparty_tx_accepted_nondust_htlcs + on_counterparty_tx_offered_nondust_htlcs;
3800-
on_counterparty_tx_dust_exposure_msat +=
3801-
commit_tx_fee_sat(excess_feerate, on_counterparty_tx_nondust_htlcs, &self.channel_type) * 1000;
3802-
if !self.channel_type.supports_anchors_zero_fee_htlc_tx() {
3803-
on_counterparty_tx_dust_exposure_msat +=
3804-
on_counterparty_tx_accepted_nondust_htlcs as u64 * htlc_success_tx_weight(&self.channel_type)
3805-
* excess_feerate as u64 / 1000;
3806-
on_counterparty_tx_dust_exposure_msat +=
3807-
on_counterparty_tx_offered_nondust_htlcs as u64 * htlc_timeout_tx_weight(&self.channel_type)
3808-
* excess_feerate as u64 / 1000;
3809-
}
3810-
}
3801+
let extra_nondust_htlc_on_counterparty_tx_dust_exposure_msat = excess_feerate_opt.map(|excess_feerate| {
3802+
let extra_htlc_dust_exposure = on_counterparty_tx_dust_exposure_msat
3803+
+ chan_utils::commit_and_htlc_tx_fees_sat(excess_feerate, on_counterparty_tx_accepted_nondust_htlcs + 1, on_counterparty_tx_offered_nondust_htlcs, &self.channel_type) * 1000;
3804+
on_counterparty_tx_dust_exposure_msat
3805+
+= chan_utils::commit_and_htlc_tx_fees_sat(excess_feerate, on_counterparty_tx_accepted_nondust_htlcs, on_counterparty_tx_offered_nondust_htlcs, &self.channel_type) * 1000;
3806+
extra_htlc_dust_exposure
3807+
});
38113808

38123809
HTLCStats {
38133810
pending_inbound_htlcs: self.pending_inbound_htlcs.len(),
38143811
pending_outbound_htlcs,
38153812
pending_inbound_htlcs_value_msat,
38163813
pending_outbound_htlcs_value_msat,
38173814
on_counterparty_tx_dust_exposure_msat,
3815+
extra_nondust_htlc_on_counterparty_tx_dust_exposure_msat,
38183816
on_holder_tx_dust_exposure_msat,
38193817
outbound_holding_cell_msat,
38203818
on_holder_tx_outbound_holding_cell_htlcs_count,
@@ -4019,13 +4017,8 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
40194017
context.holder_dust_limit_satoshis + dust_buffer_feerate * htlc_timeout_tx_weight(context.get_channel_type()) / 1000)
40204018
};
40214019

4022-
let excess_feerate_opt = self.feerate_per_kw.checked_sub(dust_exposure_limiting_feerate);
4023-
if let Some(excess_feerate) = excess_feerate_opt {
4024-
let htlc_dust_exposure_msat =
4025-
per_outbound_htlc_counterparty_commit_tx_fee_msat(excess_feerate, &context.channel_type);
4026-
let nondust_htlc_counterparty_tx_dust_exposure =
4027-
htlc_stats.on_counterparty_tx_dust_exposure_msat.saturating_add(htlc_dust_exposure_msat);
4028-
if nondust_htlc_counterparty_tx_dust_exposure > max_dust_htlc_exposure_msat {
4020+
if let Some(extra_htlc_dust_exposure) = htlc_stats.extra_nondust_htlc_on_counterparty_tx_dust_exposure_msat {
4021+
if extra_htlc_dust_exposure > max_dust_htlc_exposure_msat {
40294022
// If adding an extra HTLC would put us over the dust limit in total fees, we cannot
40304023
// send any non-dust HTLCs.
40314024
available_capacity_msat = cmp::min(available_capacity_msat, htlc_success_dust_limit * 1000);
@@ -7486,6 +7479,8 @@ impl<SP: Deref> FundedChannel<SP> where
74867479
})
74877480
}
74887481

7482+
/// When this function is called, the HTLC is already irrevocably committed to the channel;
7483+
/// this function determines whether to fail the HTLC, or forward / claim it.
74897484
pub fn can_accept_incoming_htlc<F: Deref, L: Deref>(
74907485
&self, msg: &msgs::UpdateAddHTLC, fee_estimator: &LowerBoundedFeeEstimator<F>, logger: L
74917486
) -> Result<(), (&'static str, u16)>
@@ -7500,33 +7495,19 @@ impl<SP: Deref> FundedChannel<SP> where
75007495
let dust_exposure_limiting_feerate = self.context.get_dust_exposure_limiting_feerate(&fee_estimator);
75017496
let htlc_stats = self.context.get_pending_htlc_stats(None, dust_exposure_limiting_feerate);
75027497
let max_dust_htlc_exposure_msat = self.context.get_max_dust_htlc_exposure_msat(dust_exposure_limiting_feerate);
7503-
let (htlc_timeout_dust_limit, htlc_success_dust_limit) = if self.context.get_channel_type().supports_anchors_zero_fee_htlc_tx() {
7504-
(0, 0)
7498+
let on_counterparty_tx_dust_htlc_exposure_msat = htlc_stats.on_counterparty_tx_dust_exposure_msat;
7499+
if on_counterparty_tx_dust_htlc_exposure_msat > max_dust_htlc_exposure_msat {
7500+
// Note that the total dust exposure includes both the dust HTLCs and the excess mining fees of the counterparty commitment transaction
7501+
log_info!(logger, "Cannot accept value that would put our total dust exposure at {} over the limit {} on counterparty commitment tx",
7502+
on_counterparty_tx_dust_htlc_exposure_msat, max_dust_htlc_exposure_msat);
7503+
return Err(("Exceeded our total dust exposure limit on counterparty commitment tx", 0x1000|7))
7504+
}
7505+
let htlc_success_dust_limit = if self.context.get_channel_type().supports_anchors_zero_fee_htlc_tx() {
7506+
0
75057507
} else {
75067508
let dust_buffer_feerate = self.context.get_dust_buffer_feerate(None) as u64;
7507-
(dust_buffer_feerate * htlc_timeout_tx_weight(self.context.get_channel_type()) / 1000,
7508-
dust_buffer_feerate * htlc_success_tx_weight(self.context.get_channel_type()) / 1000)
7509+
dust_buffer_feerate * htlc_success_tx_weight(self.context.get_channel_type()) / 1000
75097510
};
7510-
let exposure_dust_limit_timeout_sats = htlc_timeout_dust_limit + self.context.counterparty_dust_limit_satoshis;
7511-
if msg.amount_msat / 1000 < exposure_dust_limit_timeout_sats {
7512-
let on_counterparty_tx_dust_htlc_exposure_msat = htlc_stats.on_counterparty_tx_dust_exposure_msat;
7513-
if on_counterparty_tx_dust_htlc_exposure_msat > max_dust_htlc_exposure_msat {
7514-
log_info!(logger, "Cannot accept value that would put our exposure to dust HTLCs at {} over the limit {} on counterparty commitment tx",
7515-
on_counterparty_tx_dust_htlc_exposure_msat, max_dust_htlc_exposure_msat);
7516-
return Err(("Exceeded our dust exposure limit on counterparty commitment tx", 0x1000|7))
7517-
}
7518-
} else {
7519-
let htlc_dust_exposure_msat =
7520-
per_outbound_htlc_counterparty_commit_tx_fee_msat(self.context.feerate_per_kw, &self.context.channel_type);
7521-
let counterparty_tx_dust_exposure =
7522-
htlc_stats.on_counterparty_tx_dust_exposure_msat.saturating_add(htlc_dust_exposure_msat);
7523-
if counterparty_tx_dust_exposure > max_dust_htlc_exposure_msat {
7524-
log_info!(logger, "Cannot accept value that would put our exposure to tx fee dust at {} over the limit {} on counterparty commitment tx",
7525-
counterparty_tx_dust_exposure, max_dust_htlc_exposure_msat);
7526-
return Err(("Exceeded our tx fee dust exposure limit on counterparty commitment tx", 0x1000|7))
7527-
}
7528-
}
7529-
75307511
let exposure_dust_limit_success_sats = htlc_success_dust_limit + self.context.holder_dust_limit_satoshis;
75317512
if msg.amount_msat / 1000 < exposure_dust_limit_success_sats {
75327513
let on_holder_tx_dust_htlc_exposure_msat = htlc_stats.on_holder_tx_dust_exposure_msat;

0 commit comments

Comments
 (0)