From af2ff9b5b9ded15969031f329070af5024013626 Mon Sep 17 00:00:00 2001 From: Wilmer Paulino Date: Mon, 29 Aug 2022 12:34:34 -0700 Subject: [PATCH] Account for zero fee HTLC transaction within dust limit calculation With the zero fee HTLC transaction anchors variant, HTLCs can no longer be trimmed due to their amount being too low to have a mempool valid HTLC transaction. Now they can only be trimmed based on the dust limit of each party within the channel. --- lightning/src/ln/channel.rs | 78 +++++++++++++++++++++++++++++------- lightning/src/util/config.rs | 6 +++ 2 files changed, 70 insertions(+), 14 deletions(-) diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index b37550b0..6c17fd35 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -1466,7 +1466,12 @@ impl Channel { ($htlc: expr, $outbound: expr, $source: expr, $state_name: expr) => { if $outbound == local { // "offered HTLC output" let htlc_in_tx = get_htlc_in_commitment!($htlc, true); - if $htlc.amount_msat / 1000 >= broadcaster_dust_limit_satoshis + (feerate_per_kw as u64 * htlc_timeout_tx_weight(self.opt_anchors()) / 1000) { + let htlc_tx_fee = if self.opt_anchors() { + 0 + } else { + feerate_per_kw as u64 * htlc_timeout_tx_weight(false) / 1000 + }; + if $htlc.amount_msat / 1000 >= broadcaster_dust_limit_satoshis + htlc_tx_fee { log_trace!(logger, " ...including {} {} HTLC {} (hash {}) with value {}", if $outbound { "outbound" } else { "inbound" }, $state_name, $htlc.htlc_id, log_bytes!($htlc.payment_hash.0), $htlc.amount_msat); included_non_dust_htlcs.push((htlc_in_tx, $source)); } else { @@ -1475,7 +1480,12 @@ impl Channel { } } else { let htlc_in_tx = get_htlc_in_commitment!($htlc, false); - if $htlc.amount_msat / 1000 >= broadcaster_dust_limit_satoshis + (feerate_per_kw as u64 * htlc_success_tx_weight(self.opt_anchors()) / 1000) { + let htlc_tx_fee = if self.opt_anchors() { + 0 + } else { + feerate_per_kw as u64 * htlc_success_tx_weight(false) / 1000 + }; + if $htlc.amount_msat / 1000 >= broadcaster_dust_limit_satoshis + htlc_tx_fee { log_trace!(logger, " ...including {} {} HTLC {} (hash {}) with value {}", if $outbound { "outbound" } else { "inbound" }, $state_name, $htlc.htlc_id, log_bytes!($htlc.payment_hash.0), $htlc.amount_msat); included_non_dust_htlcs.push((htlc_in_tx, $source)); } else { @@ -2396,8 +2406,15 @@ impl Channel { on_holder_tx_holding_cell_htlcs_count: 0, }; - let counterparty_dust_limit_timeout_sat = (self.get_dust_buffer_feerate(outbound_feerate_update) as u64 * htlc_timeout_tx_weight(self.opt_anchors()) / 1000) + self.counterparty_dust_limit_satoshis; - let holder_dust_limit_success_sat = (self.get_dust_buffer_feerate(outbound_feerate_update) as u64 * htlc_success_tx_weight(self.opt_anchors()) / 1000) + self.holder_dust_limit_satoshis; + let (htlc_timeout_dust_limit, htlc_success_dust_limit) = if self.opt_anchors() { + (0, 0) + } else { + let dust_buffer_feerate = self.get_dust_buffer_feerate(outbound_feerate_update) as u64; + (dust_buffer_feerate * htlc_timeout_tx_weight(false) / 1000, + dust_buffer_feerate * htlc_success_tx_weight(false) / 1000) + }; + let counterparty_dust_limit_timeout_sat = htlc_timeout_dust_limit + self.counterparty_dust_limit_satoshis; + let holder_dust_limit_success_sat = htlc_success_dust_limit + self.holder_dust_limit_satoshis; for ref htlc in self.pending_inbound_htlcs.iter() { stats.pending_htlcs_value_msat += htlc.amount_msat; if htlc.amount_msat / 1000 < counterparty_dust_limit_timeout_sat { @@ -2421,8 +2438,15 @@ impl Channel { on_holder_tx_holding_cell_htlcs_count: 0, }; - let counterparty_dust_limit_success_sat = (self.get_dust_buffer_feerate(outbound_feerate_update) as u64 * htlc_success_tx_weight(self.opt_anchors()) / 1000) + self.counterparty_dust_limit_satoshis; - let holder_dust_limit_timeout_sat = (self.get_dust_buffer_feerate(outbound_feerate_update) as u64 * htlc_timeout_tx_weight(self.opt_anchors()) / 1000) + self.holder_dust_limit_satoshis; + let (htlc_timeout_dust_limit, htlc_success_dust_limit) = if self.opt_anchors() { + (0, 0) + } else { + let dust_buffer_feerate = self.get_dust_buffer_feerate(outbound_feerate_update) as u64; + (dust_buffer_feerate * htlc_timeout_tx_weight(false) / 1000, + dust_buffer_feerate * htlc_success_tx_weight(false) / 1000) + }; + let counterparty_dust_limit_success_sat = htlc_success_dust_limit + self.counterparty_dust_limit_satoshis; + let holder_dust_limit_timeout_sat = htlc_timeout_dust_limit + self.holder_dust_limit_satoshis; for ref htlc in self.pending_outbound_htlcs.iter() { stats.pending_htlcs_value_msat += htlc.amount_msat; if htlc.amount_msat / 1000 < counterparty_dust_limit_success_sat { @@ -2512,8 +2536,14 @@ impl Channel { fn next_local_commit_tx_fee_msat(&self, htlc: HTLCCandidate, fee_spike_buffer_htlc: Option<()>) -> u64 { assert!(self.is_outbound()); - let real_dust_limit_success_sat = (self.feerate_per_kw as u64 * htlc_success_tx_weight(self.opt_anchors()) / 1000) + self.holder_dust_limit_satoshis; - let real_dust_limit_timeout_sat = (self.feerate_per_kw as u64 * htlc_timeout_tx_weight(self.opt_anchors()) / 1000) + self.holder_dust_limit_satoshis; + let (htlc_success_dust_limit, htlc_timeout_dust_limit) = if self.opt_anchors() { + (0, 0) + } else { + (self.feerate_per_kw as u64 * htlc_success_tx_weight(false) / 1000, + self.feerate_per_kw as u64 * htlc_timeout_tx_weight(false) / 1000) + }; + let real_dust_limit_success_sat = htlc_success_dust_limit + self.holder_dust_limit_satoshis; + let real_dust_limit_timeout_sat = htlc_timeout_dust_limit + self.holder_dust_limit_satoshis; let mut addl_htlcs = 0; if fee_spike_buffer_htlc.is_some() { addl_htlcs += 1; } @@ -2603,8 +2633,14 @@ impl Channel { fn next_remote_commit_tx_fee_msat(&self, htlc: HTLCCandidate, fee_spike_buffer_htlc: Option<()>) -> u64 { assert!(!self.is_outbound()); - let real_dust_limit_success_sat = (self.feerate_per_kw as u64 * htlc_success_tx_weight(self.opt_anchors()) / 1000) + self.counterparty_dust_limit_satoshis; - let real_dust_limit_timeout_sat = (self.feerate_per_kw as u64 * htlc_timeout_tx_weight(self.opt_anchors()) / 1000) + self.counterparty_dust_limit_satoshis; + let (htlc_success_dust_limit, htlc_timeout_dust_limit) = if self.opt_anchors() { + (0, 0) + } else { + (self.feerate_per_kw as u64 * htlc_success_tx_weight(false) / 1000, + self.feerate_per_kw as u64 * htlc_timeout_tx_weight(false) / 1000) + }; + let real_dust_limit_success_sat = htlc_success_dust_limit + self.counterparty_dust_limit_satoshis; + let real_dust_limit_timeout_sat = htlc_timeout_dust_limit + self.counterparty_dust_limit_satoshis; let mut addl_htlcs = 0; if fee_spike_buffer_htlc.is_some() { addl_htlcs += 1; } @@ -2727,7 +2763,14 @@ impl Channel { } } - let exposure_dust_limit_timeout_sats = (self.get_dust_buffer_feerate(None) as u64 * htlc_timeout_tx_weight(self.opt_anchors()) / 1000) + self.counterparty_dust_limit_satoshis; + let (htlc_timeout_dust_limit, htlc_success_dust_limit) = if self.opt_anchors() { + (0, 0) + } else { + let dust_buffer_feerate = self.get_dust_buffer_feerate(None) as u64; + (dust_buffer_feerate * htlc_timeout_tx_weight(false) / 1000, + dust_buffer_feerate * htlc_success_tx_weight(false) / 1000) + }; + let exposure_dust_limit_timeout_sats = htlc_timeout_dust_limit + self.counterparty_dust_limit_satoshis; if msg.amount_msat / 1000 < exposure_dust_limit_timeout_sats { let on_counterparty_tx_dust_htlc_exposure_msat = inbound_stats.on_counterparty_tx_dust_exposure_msat + outbound_stats.on_counterparty_tx_dust_exposure_msat + msg.amount_msat; if on_counterparty_tx_dust_htlc_exposure_msat > self.get_max_dust_htlc_exposure_msat() { @@ -2737,7 +2780,7 @@ impl Channel { } } - let exposure_dust_limit_success_sats = (self.get_dust_buffer_feerate(None) as u64 * htlc_success_tx_weight(self.opt_anchors()) / 1000) + self.holder_dust_limit_satoshis; + let exposure_dust_limit_success_sats = htlc_success_dust_limit + self.holder_dust_limit_satoshis; if msg.amount_msat / 1000 < exposure_dust_limit_success_sats { let on_holder_tx_dust_htlc_exposure_msat = inbound_stats.on_holder_tx_dust_exposure_msat + outbound_stats.on_holder_tx_dust_exposure_msat + msg.amount_msat; if on_holder_tx_dust_htlc_exposure_msat > self.get_max_dust_htlc_exposure_msat() { @@ -5444,7 +5487,14 @@ impl Channel { } } - let exposure_dust_limit_success_sats = (self.get_dust_buffer_feerate(None) as u64 * htlc_success_tx_weight(self.opt_anchors()) / 1000) + self.counterparty_dust_limit_satoshis; + let (htlc_success_dust_limit, htlc_timeout_dust_limit) = if self.opt_anchors() { + (0, 0) + } else { + let dust_buffer_feerate = self.get_dust_buffer_feerate(None) as u64; + (dust_buffer_feerate * htlc_success_tx_weight(false) / 1000, + dust_buffer_feerate * htlc_timeout_tx_weight(false) / 1000) + }; + let exposure_dust_limit_success_sats = htlc_success_dust_limit + self.counterparty_dust_limit_satoshis; if amount_msat / 1000 < exposure_dust_limit_success_sats { let on_counterparty_dust_htlc_exposure_msat = inbound_stats.on_counterparty_tx_dust_exposure_msat + outbound_stats.on_counterparty_tx_dust_exposure_msat + amount_msat; if on_counterparty_dust_htlc_exposure_msat > self.get_max_dust_htlc_exposure_msat() { @@ -5453,7 +5503,7 @@ impl Channel { } } - let exposure_dust_limit_timeout_sats = (self.get_dust_buffer_feerate(None) as u64 * htlc_timeout_tx_weight(self.opt_anchors()) / 1000) + self.holder_dust_limit_satoshis; + let exposure_dust_limit_timeout_sats = htlc_timeout_dust_limit + self.holder_dust_limit_satoshis; if amount_msat / 1000 < exposure_dust_limit_timeout_sats { let on_holder_dust_htlc_exposure_msat = inbound_stats.on_holder_tx_dust_exposure_msat + outbound_stats.on_holder_tx_dust_exposure_msat + amount_msat; if on_holder_dust_htlc_exposure_msat > self.get_max_dust_htlc_exposure_msat() { diff --git a/lightning/src/util/config.rs b/lightning/src/util/config.rs index 54ea6178..b2004df3 100644 --- a/lightning/src/util/config.rs +++ b/lightning/src/util/config.rs @@ -325,6 +325,12 @@ pub struct ChannelConfig { /// to such payments may be sustantial if there are many dust HTLCs present when the /// channel is force-closed. /// + /// The dust threshold for each HTLC is based on the `dust_limit_satoshis` for each party in a + /// channel negotiated throughout the channel open process, along with the fees required to have + /// a broadcastable HTLC spending transaction. When a channel supports anchor outputs + /// (specifically the zero fee HTLC transaction variant), this threshold no longer takes into + /// account the HTLC transaction fee as it is zero. + /// /// This limit is applied for sent, forwarded, and received HTLCs and limits the total /// exposure across all three types per-channel. Setting this too low may prevent the /// sending or receipt of low-value HTLCs on high-traffic nodes, and this limit is very -- 2.30.2