Skip to content

Commit

Permalink
Account for zero fee HTLC transaction within dust limit calculation
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
wpaulino committed Sep 6, 2022
1 parent f43a7e8 commit d65e464
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 14 deletions.
78 changes: 64 additions & 14 deletions lightning/src/ln/channel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1466,7 +1466,12 @@ impl<Signer: Sign> Channel<Signer> {
($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 {
Expand All @@ -1475,7 +1480,12 @@ impl<Signer: Sign> Channel<Signer> {
}
} 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 {
Expand Down Expand Up @@ -2396,8 +2406,15 @@ impl<Signer: Sign> Channel<Signer> {
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 {
Expand All @@ -2421,8 +2438,15 @@ impl<Signer: Sign> Channel<Signer> {
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 {
Expand Down Expand Up @@ -2512,8 +2536,14 @@ impl<Signer: Sign> Channel<Signer> {
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; }
Expand Down Expand Up @@ -2603,8 +2633,14 @@ impl<Signer: Sign> Channel<Signer> {
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; }
Expand Down Expand Up @@ -2727,7 +2763,14 @@ impl<Signer: Sign> Channel<Signer> {
}
}

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() {
Expand All @@ -2737,7 +2780,7 @@ impl<Signer: Sign> Channel<Signer> {
}
}

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() {
Expand Down Expand Up @@ -5441,7 +5484,14 @@ impl<Signer: Sign> Channel<Signer> {
}
}

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() {
Expand All @@ -5450,7 +5500,7 @@ impl<Signer: Sign> Channel<Signer> {
}
}

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() {
Expand Down
6 changes: 6 additions & 0 deletions lightning/src/util/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit d65e464

Please sign in to comment.