]> git.bitcoin.ninja Git - rust-lightning/commitdiff
Check we won't overflow `max_dust_htlc_exposure_msat` at outbound feerate update
authorAntoine Riard <dev@ariard.me>
Sat, 21 Aug 2021 22:52:05 +0000 (18:52 -0400)
committerAntoine Riard <dev@ariard.me>
Tue, 9 Nov 2021 00:29:58 +0000 (19:29 -0500)
lightning/src/ln/channel.rs

index 23c5a9dbb37143cb9dd6867b076ab3e65d411420..d8caffd64dff40bcfa62c87e42008034c481b7e6 100644 (file)
@@ -1913,7 +1913,7 @@ impl<Signer: Sign> Channel<Signer> {
        }
 
        /// Returns a HTLCStats about inbound pending htlcs
-       fn get_inbound_pending_htlc_stats(&self) -> HTLCStats {
+       fn get_inbound_pending_htlc_stats(&self, outbound_feerate_update: Option<u32>) -> HTLCStats {
                let mut stats = HTLCStats {
                        pending_htlcs: self.pending_inbound_htlcs.len() as u32,
                        pending_htlcs_value_msat: 0,
@@ -1921,8 +1921,8 @@ impl<Signer: Sign> Channel<Signer> {
                        on_holder_tx_dust_exposure_msat: 0,
                };
 
-               let counterparty_dust_limit_timeout_sat = (self.get_dust_buffer_feerate() as u64 * HTLC_TIMEOUT_TX_WEIGHT / 1000) + self.counterparty_dust_limit_satoshis;
-               let holder_dust_limit_success_sat = (self.get_dust_buffer_feerate() as u64 * HTLC_SUCCESS_TX_WEIGHT / 1000) + self.holder_dust_limit_satoshis;
+               let counterparty_dust_limit_timeout_sat = (self.get_dust_buffer_feerate(outbound_feerate_update) as u64 * HTLC_TIMEOUT_TX_WEIGHT / 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 / 1000) + 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 {
@@ -1936,7 +1936,7 @@ impl<Signer: Sign> Channel<Signer> {
        }
 
        /// Returns a HTLCStats about pending outbound htlcs, *including* pending adds in our holding cell.
-       fn get_outbound_pending_htlc_stats(&self) -> HTLCStats {
+       fn get_outbound_pending_htlc_stats(&self, outbound_feerate_update: Option<u32>) -> HTLCStats {
                let mut stats = HTLCStats {
                        pending_htlcs: self.pending_outbound_htlcs.len() as u32,
                        pending_htlcs_value_msat: 0,
@@ -1944,8 +1944,8 @@ impl<Signer: Sign> Channel<Signer> {
                        on_holder_tx_dust_exposure_msat: 0,
                };
 
-               let counterparty_dust_limit_success_sat = (self.get_dust_buffer_feerate() as u64 * HTLC_SUCCESS_TX_WEIGHT / 1000) + self.counterparty_dust_limit_satoshis;
-               let holder_dust_limit_timeout_sat = (self.get_dust_buffer_feerate() as u64 * HTLC_TIMEOUT_TX_WEIGHT / 1000) + self.holder_dust_limit_satoshis;
+               let counterparty_dust_limit_success_sat = (self.get_dust_buffer_feerate(outbound_feerate_update) as u64 * HTLC_SUCCESS_TX_WEIGHT / 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 / 1000) + 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 {
@@ -1980,11 +1980,11 @@ impl<Signer: Sign> Channel<Signer> {
                (
                        cmp::max(self.channel_value_satoshis as i64 * 1000
                                - self.value_to_self_msat as i64
-                               - self.get_inbound_pending_htlc_stats().pending_htlcs_value_msat as i64
+                               - self.get_inbound_pending_htlc_stats(None).pending_htlcs_value_msat as i64
                                - Self::get_holder_selected_channel_reserve_satoshis(self.channel_value_satoshis) as i64 * 1000,
                        0) as u64,
                        cmp::max(self.value_to_self_msat as i64
-                               - self.get_outbound_pending_htlc_stats().pending_htlcs_value_msat as i64
+                               - self.get_outbound_pending_htlc_stats(None).pending_htlcs_value_msat as i64
                                - self.counterparty_selected_channel_reserve_satoshis.unwrap_or(0) as i64 * 1000,
                        0) as u64
                )
@@ -2203,8 +2203,8 @@ impl<Signer: Sign> Channel<Signer> {
                        return Err(ChannelError::Close(format!("Remote side tried to send less than our minimum HTLC value. Lower limit: ({}). Actual: ({})", self.holder_htlc_minimum_msat, msg.amount_msat)));
                }
 
-               let inbound_stats = self.get_inbound_pending_htlc_stats();
-               let outbound_stats = self.get_outbound_pending_htlc_stats();
+               let inbound_stats = self.get_inbound_pending_htlc_stats(None);
+               let outbound_stats = self.get_outbound_pending_htlc_stats(None);
                if inbound_stats.pending_htlcs + 1 > OUR_MAX_HTLCS as u32 {
                        return Err(ChannelError::Close(format!("Remote tried to push more than our max accepted HTLCs ({})", OUR_MAX_HTLCS)));
                }
@@ -2233,7 +2233,7 @@ 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;
+               let exposure_dust_limit_timeout_sats = (self.get_dust_buffer_feerate(None) 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() {
@@ -2243,7 +2243,7 @@ impl<Signer: Sign> Channel<Signer> {
                        }
                }
 
-               let exposure_dust_limit_success_sats = (self.get_dust_buffer_feerate() as u64 * HTLC_SUCCESS_TX_WEIGHT / 1000) + self.holder_dust_limit_satoshis;
+               let exposure_dust_limit_success_sats = (self.get_dust_buffer_feerate(None) 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() {
@@ -2972,8 +2972,8 @@ impl<Signer: Sign> Channel<Signer> {
                }
 
                // Before proposing a feerate update, check that we can actually afford the new fee.
-               let inbound_stats = self.get_inbound_pending_htlc_stats();
-               let outbound_stats = self.get_outbound_pending_htlc_stats();
+               let inbound_stats = self.get_inbound_pending_htlc_stats(Some(feerate_per_kw));
+               let outbound_stats = self.get_outbound_pending_htlc_stats(Some(feerate_per_kw));
                // In case of a concurrent update_add_htlc proposed by our counterparty, we might
                // not have enough balance value remaining to cover the onchain cost of this new
                // HTLC weight. If this happens, our counterparty fails the reception of our
@@ -2991,6 +2991,21 @@ impl<Signer: Sign> Channel<Signer> {
                        return None;
                }
 
+               // If the new outbound feerate is inferior to the dust buffer feerate, pending HTLCs
+               // have been already evaluated with the dust buffer feerate and as such the proposed
+               // feerate is not affecting our dust exposure.
+               if feerate_per_kw > self.get_dust_buffer_feerate(None) {
+                       // Note, we evaluate pending htlc "preemptive" trimmed-to-dust threshold at the proposed `feerate_per_kw`.
+                       let holder_tx_dust_exposure = inbound_stats.on_holder_tx_dust_exposure_msat + outbound_stats.on_holder_tx_dust_exposure_msat;
+                       let counterparty_tx_dust_exposure = inbound_stats.on_counterparty_tx_dust_exposure_msat + outbound_stats.on_counterparty_tx_dust_exposure_msat;
+                       if holder_tx_dust_exposure > self.get_max_dust_htlc_exposure_msat() {
+                               return None;
+                       }
+                       if counterparty_tx_dust_exposure > self.get_max_dust_htlc_exposure_msat() {
+                               return None;
+                       }
+               }
+
                if (self.channel_state & (ChannelState::AwaitingRemoteRevoke as u32 | ChannelState::MonitorUpdateFailed as u32)) != 0 {
                        self.holding_cell_update_fee = Some(feerate_per_kw);
                        return None;
@@ -3158,7 +3173,7 @@ impl<Signer: Sign> Channel<Signer> {
                        return Err(ChannelError::Close("Peer sent update_fee when we needed a channel_reestablish".to_owned()));
                }
                Channel::<Signer>::check_remote_fee(fee_estimator, msg.feerate_per_kw)?;
-               let feerate_over_dust_buffer = msg.feerate_per_kw > self.get_dust_buffer_feerate();
+               let feerate_over_dust_buffer = msg.feerate_per_kw > self.get_dust_buffer_feerate(None);
 
                self.pending_update_fee = Some((msg.feerate_per_kw, FeeUpdateState::RemoteAnnounced));
                self.update_time_counter += 1;
@@ -3166,8 +3181,8 @@ impl<Signer: Sign> Channel<Signer> {
                // `get_dust_buffer_feerate` considers the `pending_update_fee` status), check that we
                // won't be pushed over our dust exposure limit by the feerate increase.
                if feerate_over_dust_buffer {
-                       let inbound_stats = self.get_inbound_pending_htlc_stats();
-                       let outbound_stats = self.get_outbound_pending_htlc_stats();
+                       let inbound_stats = self.get_inbound_pending_htlc_stats(None);
+                       let outbound_stats = self.get_outbound_pending_htlc_stats(None);
                        let holder_tx_dust_exposure = inbound_stats.on_holder_tx_dust_exposure_msat + outbound_stats.on_holder_tx_dust_exposure_msat;
                        let counterparty_tx_dust_exposure = inbound_stats.on_counterparty_tx_dust_exposure_msat + outbound_stats.on_counterparty_tx_dust_exposure_msat;
                        if holder_tx_dust_exposure > self.get_max_dust_htlc_exposure_msat() {
@@ -3868,7 +3883,7 @@ impl<Signer: Sign> Channel<Signer> {
                self.feerate_per_kw
        }
 
-       pub fn get_dust_buffer_feerate(&self) -> u32 {
+       pub fn get_dust_buffer_feerate(&self, outbound_feerate_update: Option<u32>) -> u32 {
                // When calculating our exposure to dust HTLCs, we assume that the channel feerate
                // may, at any point, increase by at least 10 sat/vB (i.e 2530 sat/kWU) or 25%,
                // whichever is higher. This ensures that we aren't suddenly exposed to significantly
@@ -3880,6 +3895,9 @@ impl<Signer: Sign> Channel<Signer> {
                if let Some((feerate, _)) = self.pending_update_fee {
                        feerate_per_kw = cmp::max(feerate_per_kw, feerate);
                }
+               if let Some(feerate) = outbound_feerate_update {
+                       feerate_per_kw = cmp::max(feerate_per_kw, feerate);
+               }
                cmp::max(2530, feerate_per_kw * 1250 / 1000)
        }
 
@@ -4537,8 +4555,8 @@ impl<Signer: Sign> Channel<Signer> {
                        return Err(ChannelError::Ignore("Cannot send an HTLC while disconnected from channel counterparty".to_owned()));
                }
 
-               let inbound_stats = self.get_inbound_pending_htlc_stats();
-               let outbound_stats = self.get_outbound_pending_htlc_stats();
+               let inbound_stats = self.get_inbound_pending_htlc_stats(None);
+               let outbound_stats = self.get_outbound_pending_htlc_stats(None);
                if outbound_stats.pending_htlcs + 1 > self.counterparty_max_accepted_htlcs as u32 {
                        return Err(ChannelError::Ignore(format!("Cannot push more than their max accepted HTLCs ({})", self.counterparty_max_accepted_htlcs)));
                }
@@ -4558,7 +4576,7 @@ 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;
+               let exposure_dust_limit_success_sats = (self.get_dust_buffer_feerate(None) 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() {
@@ -4567,7 +4585,7 @@ impl<Signer: Sign> Channel<Signer> {
                        }
                }
 
-               let exposure_dust_limit_timeout_sats = (self.get_dust_buffer_feerate() as u64 * HTLC_TIMEOUT_TX_WEIGHT / 1000) + self.holder_dust_limit_satoshis;
+               let exposure_dust_limit_timeout_sats = (self.get_dust_buffer_feerate(None) 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() {
@@ -4754,7 +4772,7 @@ impl<Signer: Sign> Channel<Signer> {
                        if let Some(pending_feerate) = self.pending_update_fee {
                                assert_eq!(pending_feerate.1, FeeUpdateState::Outbound);
                                let next_total_fee_sat = Channel::<Signer>::commit_tx_fee_sat(pending_feerate.0, self.pending_inbound_htlcs.len() + self.pending_outbound_htlcs.len());
-                               let outbound_stats = self.get_outbound_pending_htlc_stats();
+                               let outbound_stats = self.get_outbound_pending_htlc_stats(Some(pending_feerate.0));
                                let holder_balance_after_fee_sub_sats = (self.value_to_self_msat / 1000).checked_sub(next_total_fee_sat).map(|a| a.checked_sub(outbound_stats.pending_htlcs_value_msat / 1000)).unwrap_or(None).unwrap_or(u64::min_value());
                                if holder_balance_after_fee_sub_sats < self.counterparty_selected_channel_reserve_satoshis.unwrap() {
                                        log_debug!(logger, "Outbound update_fee HTLC buffer overflow - counterparty should force-close this channel");