Enforce `max_balance_dust_htlc_msat` at HTLC reception/forward
authorAntoine Riard <dev@ariard.me>
Wed, 28 Jul 2021 23:55:11 +0000 (19:55 -0400)
committerAntoine Riard <dev@ariard.me>
Tue, 10 Aug 2021 21:30:15 +0000 (17:30 -0400)
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.

fuzz/src/chanmon_consistency.rs
lightning/src/ln/channel.rs
lightning/src/ln/functional_test_utils.rs

index 70ddac5d204225365a5be66b9c0348d69d3642d5..36d12ad5bb00c9567f141e68634ef7fbe0b6f7ef 100644 (file)
@@ -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),
                        }
                },
index 95b0c573a7871369b0b4c9b0f02b55a3f3516430..d14b4fa953cb77c5211a78626fea4d6c4a150551 100644 (file)
@@ -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 =
@@ -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)));
index 807bb20f7ece8f460facfa7dc8d955f4dff7c642..dfda381bfc72d63945e057c9e72a0ee5f406bfd2 100644 (file)
@@ -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
 }