Skip to content

Commit

Permalink
Enforce max_balance_dust_htlc_msat at HTLC reception/forward
Browse files Browse the repository at this point in the history
At `update_add_htlc()`/`send_htlc()`, we verify that the inbound/
outbound dust or the sum of both, on either sides of the link isn't
above new config setting `max_balance_dust_htlc_msat`.

A dust HTLC is hence defined as a trimmed-to-dust one, i.e including
the fee cost to publish its claiming transaction.
  • Loading branch information
Antoine Riard committed Aug 10, 2021
1 parent 29e755b commit 1cf2b53
Show file tree
Hide file tree
Showing 3 changed files with 42 additions and 0 deletions.
1 change: 1 addition & 0 deletions fuzz/src/chanmon_consistency.rs
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,7 @@ fn check_api_err(api_err: APIError) {
_ if err.starts_with("Cannot send value that would put counterparty balance under holder-announced channel reserve value") => {},
_ if err.starts_with("Cannot send value that would overdraw remaining funds.") => {},
_ if err.starts_with("Cannot send value that would not leave enough to pay for fees.") => {},
_ if err.starts_with("Cannot send value that would put our exposure to dust HTLCs at") => {},
_ => panic!("{}", err),
}
},
Expand Down
38 changes: 38 additions & 0 deletions lightning/src/ln/channel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2138,6 +2138,26 @@ impl<Signer: Sign> Channel<Signer> {
}
}

let exposure_dust_limit_timeout_sats = (self.get_dust_buffer_feerate() as u64 * HTLC_TIMEOUT_TX_WEIGHT / 1000) + 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() {
log_info!(logger, "Cannot accept value that would put our exposure to dust HTLCs at {} over the limit {} on counterparty commitment tx",
on_counterparty_tx_dust_htlc_exposure_msat, self.get_max_dust_htlc_exposure_msat());
pending_forward_status = create_pending_htlc_status(self, pending_forward_status, 0x1000|7);
}
}

let exposure_dust_limit_success_sats = (self.get_dust_buffer_feerate() as u64 * HTLC_SUCCESS_TX_WEIGHT / 1000) + 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() {
log_info!(logger, "Cannot accept value that would put our exposure to dust HTLCs at {} over the limit {} on holder commitment tx",
on_holder_tx_dust_htlc_exposure_msat, self.get_max_dust_htlc_exposure_msat());
pending_forward_status = create_pending_htlc_status(self, pending_forward_status, 0x1000|7);
}
}

let pending_value_to_self_msat =
self.value_to_self_msat + inbound_stats.pending_htlcs_value_msat - removed_outbound_total_msat;
let pending_remote_value_msat =
Expand Down Expand Up @@ -4219,6 +4239,24 @@ impl<Signer: Sign> Channel<Signer> {
}
}

let exposure_dust_limit_success_sats = (self.get_dust_buffer_feerate() as u64 * HTLC_SUCCESS_TX_WEIGHT / 1000) + 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() {
return Err(ChannelError::Ignore(format!("Cannot send value that would put our exposure to dust HTLCs at {} over the limit {} on counterparty commitment tx",
on_counterparty_dust_htlc_exposure_msat, self.get_max_dust_htlc_exposure_msat())));
}
}

let exposure_dust_limit_timeout_sats = (self.get_dust_buffer_feerate() as u64 * HTLC_TIMEOUT_TX_WEIGHT / 1000) + 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() {
return Err(ChannelError::Ignore(format!("Cannot send value that would put our exposure to dust HTLCs at {} over the limit {} on holder commitment tx",
on_holder_dust_htlc_exposure_msat, self.get_max_dust_htlc_exposure_msat())));
}
}

let pending_value_to_self_msat = self.value_to_self_msat - outbound_stats.pending_htlcs_value_msat;
if pending_value_to_self_msat < amount_msat {
return Err(ChannelError::Ignore(format!("Cannot send value that would overdraw remaining funds. Amount: {}, pending value to self {}", amount_msat, pending_value_to_self_msat)));
Expand Down
3 changes: 3 additions & 0 deletions lightning/src/ln/functional_test_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1384,6 +1384,9 @@ pub fn test_default_channel_config() -> UserConfig {
// When most of our tests were written, the default HTLC minimum was fixed at 1000.
// It now defaults to 1, so we simply set it to the expected value here.
default_config.own_channel_config.our_htlc_minimum_msat = 1000;
// When most of our tests were written, we didn't have the notion of a `max_dust_htlc_exposure_msat`,
// It now defaults to 5_000_000 msat; to avoid interfering with tests we bump it to 50_000_000 msat.
default_config.channel_options.max_dust_htlc_exposure_msat = 50_000_000;
default_config
}

Expand Down

0 comments on commit 1cf2b53

Please sign in to comment.