diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index c5eb9de777a..078fbc6138c 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -3775,6 +3775,114 @@ impl ChannelContext where SP::Target: SignerProvider { ); } + fn validate_update_add_htlc( + &self, funding: &FundingScope, msg: &msgs::UpdateAddHTLC, + fee_estimator: &LowerBoundedFeeEstimator, + ) -> Result<(), ChannelError> + where + F::Target: FeeEstimator, + { + if msg.amount_msat > funding.get_value_satoshis() * 1000 { + return Err(ChannelError::close("Remote side tried to send more than the total value of the channel".to_owned())); + } + + let dust_exposure_limiting_feerate = self.get_dust_exposure_limiting_feerate(&fee_estimator); + let htlc_stats = self.get_pending_htlc_stats(funding, None, dust_exposure_limiting_feerate); + if htlc_stats.pending_inbound_htlcs + 1 > self.holder_max_accepted_htlcs as usize { + return Err(ChannelError::close(format!("Remote tried to push more than our max accepted HTLCs ({})", self.holder_max_accepted_htlcs))); + } + if htlc_stats.pending_inbound_htlcs_value_msat + msg.amount_msat > self.holder_max_htlc_value_in_flight_msat { + return Err(ChannelError::close(format!("Remote HTLC add would put them over our max HTLC value ({})", self.holder_max_htlc_value_in_flight_msat))); + } + + // Check holder_selected_channel_reserve_satoshis (we're getting paid, so they have to at least meet + // the reserve_satoshis we told them to always have as direct payment so that they lose + // something if we punish them for broadcasting an old state). + // Note that we don't really care about having a small/no to_remote output in our local + // commitment transactions, as the purpose of the channel reserve is to ensure we can + // punish *them* if they misbehave, so we discount any outbound HTLCs which will not be + // present in the next commitment transaction we send them (at least for fulfilled ones, + // failed ones won't modify value_to_self). + // Note that we will send HTLCs which another instance of rust-lightning would think + // violate the reserve value if we do not do this (as we forget inbound HTLCs from the + // Channel state once they will not be present in the next received commitment + // transaction). + let mut removed_outbound_total_msat = 0; + for ref htlc in self.pending_outbound_htlcs.iter() { + if let OutboundHTLCState::AwaitingRemoteRevokeToRemove(OutboundHTLCOutcome::Success(_)) = htlc.state { + removed_outbound_total_msat += htlc.amount_msat; + } else if let OutboundHTLCState::AwaitingRemovedRemoteRevoke(OutboundHTLCOutcome::Success(_)) = htlc.state { + removed_outbound_total_msat += htlc.amount_msat; + } + } + + let pending_value_to_self_msat = + funding.value_to_self_msat + htlc_stats.pending_inbound_htlcs_value_msat - removed_outbound_total_msat; + let pending_remote_value_msat = + funding.get_value_satoshis() * 1000 - pending_value_to_self_msat; + if pending_remote_value_msat < msg.amount_msat { + return Err(ChannelError::close("Remote HTLC add would overdraw remaining funds".to_owned())); + } + + // Check that the remote can afford to pay for this HTLC on-chain at the current + // feerate_per_kw, while maintaining their channel reserve (as required by the spec). + { + let remote_commit_tx_fee_msat = if funding.is_outbound() { 0 } else { + let htlc_candidate = HTLCCandidate::new(msg.amount_msat, HTLCInitiator::RemoteOffered); + self.next_remote_commit_tx_fee_msat(funding, Some(htlc_candidate), None) // Don't include the extra fee spike buffer HTLC in calculations + }; + let anchor_outputs_value_msat = if !funding.is_outbound() && funding.get_channel_type().supports_anchors_zero_fee_htlc_tx() { + ANCHOR_OUTPUT_VALUE_SATOSHI * 2 * 1000 + } else { + 0 + }; + if pending_remote_value_msat.saturating_sub(msg.amount_msat).saturating_sub(anchor_outputs_value_msat) < remote_commit_tx_fee_msat { + return Err(ChannelError::close("Remote HTLC add would not leave enough to pay for fees".to_owned())); + }; + if pending_remote_value_msat.saturating_sub(msg.amount_msat).saturating_sub(remote_commit_tx_fee_msat).saturating_sub(anchor_outputs_value_msat) < funding.holder_selected_channel_reserve_satoshis * 1000 { + return Err(ChannelError::close("Remote HTLC add would put them under remote reserve value".to_owned())); + } + } + + let anchor_outputs_value_msat = if funding.get_channel_type().supports_anchors_zero_fee_htlc_tx() { + ANCHOR_OUTPUT_VALUE_SATOSHI * 2 * 1000 + } else { + 0 + }; + if funding.is_outbound() { + // Check that they won't violate our local required channel reserve by adding this HTLC. + let htlc_candidate = HTLCCandidate::new(msg.amount_msat, HTLCInitiator::RemoteOffered); + let local_commit_tx_fee_msat = self.next_local_commit_tx_fee_msat(funding, htlc_candidate, None); + if funding.value_to_self_msat < funding.counterparty_selected_channel_reserve_satoshis.unwrap() * 1000 + local_commit_tx_fee_msat + anchor_outputs_value_msat { + return Err(ChannelError::close("Cannot accept HTLC that would put our balance under counterparty-announced channel reserve value".to_owned())); + } + } + + Ok(()) + } + + fn validate_update_fee( + &self, funding: &FundingScope, fee_estimator: &LowerBoundedFeeEstimator, + msg: &msgs::UpdateFee, + ) -> Result<(), ChannelError> + where + F::Target: FeeEstimator, + { + // Check that we won't be pushed over our dust exposure limit by the feerate increase. + let dust_exposure_limiting_feerate = self.get_dust_exposure_limiting_feerate(&fee_estimator); + let htlc_stats = self.get_pending_htlc_stats(funding, None, dust_exposure_limiting_feerate); + let max_dust_htlc_exposure_msat = self.get_max_dust_htlc_exposure_msat(dust_exposure_limiting_feerate); + if htlc_stats.on_holder_tx_dust_exposure_msat > max_dust_htlc_exposure_msat { + return Err(ChannelError::close(format!("Peer sent update_fee with a feerate ({}) which may over-expose us to dust-in-flight on our own transactions (totaling {} msat)", + msg.feerate_per_kw, htlc_stats.on_holder_tx_dust_exposure_msat))); + } + if htlc_stats.on_counterparty_tx_dust_exposure_msat > max_dust_htlc_exposure_msat { + return Err(ChannelError::close(format!("Peer sent update_fee with a feerate ({}) which may over-expose us to dust-in-flight on our counterparty's transactions (totaling {} msat)", + msg.feerate_per_kw, htlc_stats.on_counterparty_tx_dust_exposure_msat))); + } + Ok(()) + } + fn validate_commitment_signed( &self, funding: &FundingScope, holder_commitment_point: &HolderCommitmentPoint, msg: &msgs::CommitmentSigned, logger: &L, @@ -3885,6 +3993,116 @@ impl ChannelContext where SP::Target: SignerProvider { }) } + fn can_send_update_fee( + &self, funding: &FundingScope, holder_commitment_point: &HolderCommitmentPoint, + feerate_per_kw: u32, fee_estimator: &LowerBoundedFeeEstimator, logger: &L, + ) -> bool + where + F::Target: FeeEstimator, + L::Target: Logger, + { + // Before proposing a feerate update, check that we can actually afford the new fee. + let dust_exposure_limiting_feerate = self.get_dust_exposure_limiting_feerate(&fee_estimator); + let htlc_stats = self.get_pending_htlc_stats(funding, Some(feerate_per_kw), dust_exposure_limiting_feerate); + let commitment_data = self.build_commitment_transaction( + funding, holder_commitment_point.transaction_number(), + &holder_commitment_point.current_point(), true, true, logger, + ); + let buffer_fee_msat = commit_tx_fee_sat(feerate_per_kw, commitment_data.tx.nondust_htlcs().len() + htlc_stats.on_holder_tx_outbound_holding_cell_htlcs_count as usize + CONCURRENT_INBOUND_HTLC_FEE_BUFFER as usize, funding.get_channel_type()) * 1000; + let holder_balance_msat = commitment_data.stats.local_balance_before_fee_anchors_msat - htlc_stats.outbound_holding_cell_msat; + if holder_balance_msat < buffer_fee_msat + commitment_data.stats.total_anchors_sat * 1000 + funding.counterparty_selected_channel_reserve_satoshis.unwrap() * 1000 { + //TODO: auto-close after a number of failures? + log_debug!(logger, "Cannot afford to send new feerate at {}", feerate_per_kw); + return false; + } + + // Note, we evaluate pending htlc "preemptive" trimmed-to-dust threshold at the proposed `feerate_per_kw`. + let max_dust_htlc_exposure_msat = self.get_max_dust_htlc_exposure_msat(dust_exposure_limiting_feerate); + if htlc_stats.on_holder_tx_dust_exposure_msat > max_dust_htlc_exposure_msat { + log_debug!(logger, "Cannot afford to send new feerate at {} without infringing max dust htlc exposure", feerate_per_kw); + return false; + } + if htlc_stats.on_counterparty_tx_dust_exposure_msat > max_dust_htlc_exposure_msat { + log_debug!(logger, "Cannot afford to send new feerate at {} without infringing max dust htlc exposure", feerate_per_kw); + return false; + } + + return true; + } + + fn can_accept_incoming_htlc( + &self, funding: &FundingScope, msg: &msgs::UpdateAddHTLC, + dust_exposure_limiting_feerate: u32, logger: &L, + ) -> Result<(), LocalHTLCFailureReason> + where + L::Target: Logger, + { + let htlc_stats = self.get_pending_htlc_stats(funding, None, dust_exposure_limiting_feerate); + let max_dust_htlc_exposure_msat = self.get_max_dust_htlc_exposure_msat(dust_exposure_limiting_feerate); + let on_counterparty_tx_dust_htlc_exposure_msat = htlc_stats.on_counterparty_tx_dust_exposure_msat; + if on_counterparty_tx_dust_htlc_exposure_msat > max_dust_htlc_exposure_msat { + // Note that the total dust exposure includes both the dust HTLCs and the excess mining fees of the counterparty commitment transaction + log_info!(logger, "Cannot accept value that would put our total dust exposure at {} over the limit {} on counterparty commitment tx", + on_counterparty_tx_dust_htlc_exposure_msat, max_dust_htlc_exposure_msat); + return Err(LocalHTLCFailureReason::DustLimitCounterparty) + } + let htlc_success_dust_limit = if funding.get_channel_type().supports_anchors_zero_fee_htlc_tx() { + 0 + } else { + let dust_buffer_feerate = self.get_dust_buffer_feerate(None) as u64; + dust_buffer_feerate * htlc_success_tx_weight(funding.get_channel_type()) / 1000 + }; + 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 = htlc_stats.on_holder_tx_dust_exposure_msat; + if on_holder_tx_dust_htlc_exposure_msat > 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, max_dust_htlc_exposure_msat); + return Err(LocalHTLCFailureReason::DustLimitHolder) + } + } + + let anchor_outputs_value_msat = if funding.get_channel_type().supports_anchors_zero_fee_htlc_tx() { + ANCHOR_OUTPUT_VALUE_SATOSHI * 2 * 1000 + } else { + 0 + }; + + let mut removed_outbound_total_msat = 0; + for ref htlc in self.pending_outbound_htlcs.iter() { + if let OutboundHTLCState::AwaitingRemoteRevokeToRemove(OutboundHTLCOutcome::Success(_)) = htlc.state { + removed_outbound_total_msat += htlc.amount_msat; + } else if let OutboundHTLCState::AwaitingRemovedRemoteRevoke(OutboundHTLCOutcome::Success(_)) = htlc.state { + removed_outbound_total_msat += htlc.amount_msat; + } + } + + let pending_value_to_self_msat = + funding.value_to_self_msat + htlc_stats.pending_inbound_htlcs_value_msat - removed_outbound_total_msat; + let pending_remote_value_msat = + funding.get_value_satoshis() * 1000 - pending_value_to_self_msat; + + if !funding.is_outbound() { + // `Some(())` is for the fee spike buffer we keep for the remote. This deviates from + // the spec because the fee spike buffer requirement doesn't exist on the receiver's + // side, only on the sender's. Note that with anchor outputs we are no longer as + // sensitive to fee spikes, so we need to account for them. + // + // A `None` `HTLCCandidate` is used as in this case because we're already accounting for + // the incoming HTLC as it has been fully committed by both sides. + let mut remote_fee_cost_incl_stuck_buffer_msat = self.next_remote_commit_tx_fee_msat(funding, None, Some(())); + if !funding.get_channel_type().supports_anchors_zero_fee_htlc_tx() { + remote_fee_cost_incl_stuck_buffer_msat *= FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE; + } + if pending_remote_value_msat.saturating_sub(funding.holder_selected_channel_reserve_satoshis * 1000).saturating_sub(anchor_outputs_value_msat) < remote_fee_cost_incl_stuck_buffer_msat { + log_info!(logger, "Attempting to fail HTLC due to fee spike buffer violation in channel {}. Rebalancing is required.", &self.channel_id()); + return Err(LocalHTLCFailureReason::FeeSpikeBuffer); + } + } + + Ok(()) + } + /// Builds stats on a potential commitment transaction build, without actually building the /// commitment transaction. See `build_commitment_transaction` for further docs. #[inline] @@ -5765,87 +5983,12 @@ impl FundedChannel where if self.context.channel_state.is_peer_disconnected() { return Err(ChannelError::close("Peer sent update_add_htlc when we needed a channel_reestablish".to_owned())); } - if msg.amount_msat > self.funding.get_value_satoshis() * 1000 { - return Err(ChannelError::close("Remote side tried to send more than the total value of the channel".to_owned())); - } if msg.amount_msat == 0 { return Err(ChannelError::close("Remote side tried to send a 0-msat HTLC".to_owned())); } if msg.amount_msat < self.context.holder_htlc_minimum_msat { return Err(ChannelError::close(format!("Remote side tried to send less than our minimum HTLC value. Lower limit: ({}). Actual: ({})", self.context.holder_htlc_minimum_msat, msg.amount_msat))); } - - let dust_exposure_limiting_feerate = self.context.get_dust_exposure_limiting_feerate(&fee_estimator); - let htlc_stats = self.context.get_pending_htlc_stats(&self.funding, None, dust_exposure_limiting_feerate); - if htlc_stats.pending_inbound_htlcs + 1 > self.context.holder_max_accepted_htlcs as usize { - return Err(ChannelError::close(format!("Remote tried to push more than our max accepted HTLCs ({})", self.context.holder_max_accepted_htlcs))); - } - if htlc_stats.pending_inbound_htlcs_value_msat + msg.amount_msat > self.context.holder_max_htlc_value_in_flight_msat { - return Err(ChannelError::close(format!("Remote HTLC add would put them over our max HTLC value ({})", self.context.holder_max_htlc_value_in_flight_msat))); - } - - // Check holder_selected_channel_reserve_satoshis (we're getting paid, so they have to at least meet - // the reserve_satoshis we told them to always have as direct payment so that they lose - // something if we punish them for broadcasting an old state). - // Note that we don't really care about having a small/no to_remote output in our local - // commitment transactions, as the purpose of the channel reserve is to ensure we can - // punish *them* if they misbehave, so we discount any outbound HTLCs which will not be - // present in the next commitment transaction we send them (at least for fulfilled ones, - // failed ones won't modify value_to_self). - // Note that we will send HTLCs which another instance of rust-lightning would think - // violate the reserve value if we do not do this (as we forget inbound HTLCs from the - // Channel state once they will not be present in the next received commitment - // transaction). - let mut removed_outbound_total_msat = 0; - for ref htlc in self.context.pending_outbound_htlcs.iter() { - if let OutboundHTLCState::AwaitingRemoteRevokeToRemove(OutboundHTLCOutcome::Success(_)) = htlc.state { - removed_outbound_total_msat += htlc.amount_msat; - } else if let OutboundHTLCState::AwaitingRemovedRemoteRevoke(OutboundHTLCOutcome::Success(_)) = htlc.state { - removed_outbound_total_msat += htlc.amount_msat; - } - } - - let pending_value_to_self_msat = - self.funding.value_to_self_msat + htlc_stats.pending_inbound_htlcs_value_msat - removed_outbound_total_msat; - let pending_remote_value_msat = - self.funding.get_value_satoshis() * 1000 - pending_value_to_self_msat; - if pending_remote_value_msat < msg.amount_msat { - return Err(ChannelError::close("Remote HTLC add would overdraw remaining funds".to_owned())); - } - - // Check that the remote can afford to pay for this HTLC on-chain at the current - // feerate_per_kw, while maintaining their channel reserve (as required by the spec). - { - let remote_commit_tx_fee_msat = if self.funding.is_outbound() { 0 } else { - let htlc_candidate = HTLCCandidate::new(msg.amount_msat, HTLCInitiator::RemoteOffered); - self.context.next_remote_commit_tx_fee_msat(&self.funding, Some(htlc_candidate), None) // Don't include the extra fee spike buffer HTLC in calculations - }; - let anchor_outputs_value_msat = if !self.funding.is_outbound() && self.funding.get_channel_type().supports_anchors_zero_fee_htlc_tx() { - ANCHOR_OUTPUT_VALUE_SATOSHI * 2 * 1000 - } else { - 0 - }; - if pending_remote_value_msat.saturating_sub(msg.amount_msat).saturating_sub(anchor_outputs_value_msat) < remote_commit_tx_fee_msat { - return Err(ChannelError::close("Remote HTLC add would not leave enough to pay for fees".to_owned())); - }; - if pending_remote_value_msat.saturating_sub(msg.amount_msat).saturating_sub(remote_commit_tx_fee_msat).saturating_sub(anchor_outputs_value_msat) < self.funding.holder_selected_channel_reserve_satoshis * 1000 { - return Err(ChannelError::close("Remote HTLC add would put them under remote reserve value".to_owned())); - } - } - - let anchor_outputs_value_msat = if self.funding.get_channel_type().supports_anchors_zero_fee_htlc_tx() { - ANCHOR_OUTPUT_VALUE_SATOSHI * 2 * 1000 - } else { - 0 - }; - if self.funding.is_outbound() { - // Check that they won't violate our local required channel reserve by adding this HTLC. - let htlc_candidate = HTLCCandidate::new(msg.amount_msat, HTLCInitiator::RemoteOffered); - let local_commit_tx_fee_msat = self.context.next_local_commit_tx_fee_msat(&self.funding, htlc_candidate, None); - if self.funding.value_to_self_msat < self.funding.counterparty_selected_channel_reserve_satoshis.unwrap() * 1000 + local_commit_tx_fee_msat + anchor_outputs_value_msat { - return Err(ChannelError::close("Cannot accept HTLC that would put our balance under counterparty-announced channel reserve value".to_owned())); - } - } if self.context.next_counterparty_htlc_id != msg.htlc_id { return Err(ChannelError::close(format!("Remote skipped HTLC ID (skipped ID: {})", self.context.next_counterparty_htlc_id))); } @@ -5853,6 +5996,10 @@ impl FundedChannel where return Err(ChannelError::close("Remote provided CLTV expiry in seconds instead of block height".to_owned())); } + core::iter::once(&self.funding) + .chain(self.pending_funding.iter()) + .try_for_each(|funding| self.context.validate_update_add_htlc(funding, msg, fee_estimator))?; + // Now update local state: self.context.next_counterparty_htlc_id += 1; self.context.pending_inbound_htlcs.push(InboundHTLCOutput { @@ -6349,8 +6496,10 @@ impl FundedChannel where #[cfg(any(test, fuzzing))] { - *self.funding.next_local_commitment_tx_fee_info_cached.lock().unwrap() = None; - *self.funding.next_remote_commitment_tx_fee_info_cached.lock().unwrap() = None; + for funding in core::iter::once(&mut self.funding).chain(self.pending_funding.iter_mut()) { + *funding.next_local_commitment_tx_fee_info_cached.lock().unwrap() = None; + *funding.next_remote_commitment_tx_fee_info_cached.lock().unwrap() = None; + } } match &self.context.holder_signer { @@ -6500,7 +6649,10 @@ impl FundedChannel where } } } - self.funding.value_to_self_msat = (self.funding.value_to_self_msat as i64 + value_to_self_msat_diff) as u64; + + for funding in core::iter::once(&mut self.funding).chain(self.pending_funding.iter_mut()) { + funding.value_to_self_msat = (funding.value_to_self_msat as i64 + value_to_self_msat_diff) as u64; + } if let Some((feerate, update_state)) = self.context.pending_update_fee { match update_state { @@ -6717,29 +6869,10 @@ impl FundedChannel where panic!("Cannot update fee while peer is disconnected/we're awaiting a monitor update (ChannelManager should have caught this)"); } - // Before proposing a feerate update, check that we can actually afford the new fee. - let dust_exposure_limiting_feerate = self.context.get_dust_exposure_limiting_feerate(&fee_estimator); - let htlc_stats = self.context.get_pending_htlc_stats(&self.funding, Some(feerate_per_kw), dust_exposure_limiting_feerate); - let commitment_data = self.context.build_commitment_transaction( - &self.funding, self.holder_commitment_point.transaction_number(), - &self.holder_commitment_point.current_point(), true, true, logger, - ); - let buffer_fee_msat = commit_tx_fee_sat(feerate_per_kw, commitment_data.tx.nondust_htlcs().len() + htlc_stats.on_holder_tx_outbound_holding_cell_htlcs_count as usize + CONCURRENT_INBOUND_HTLC_FEE_BUFFER as usize, self.funding.get_channel_type()) * 1000; - let holder_balance_msat = commitment_data.stats.local_balance_before_fee_anchors_msat - htlc_stats.outbound_holding_cell_msat; - if holder_balance_msat < buffer_fee_msat + commitment_data.stats.total_anchors_sat * 1000 + self.funding.counterparty_selected_channel_reserve_satoshis.unwrap() * 1000 { - //TODO: auto-close after a number of failures? - log_debug!(logger, "Cannot afford to send new feerate at {}", feerate_per_kw); - return None; - } - - // Note, we evaluate pending htlc "preemptive" trimmed-to-dust threshold at the proposed `feerate_per_kw`. - let max_dust_htlc_exposure_msat = self.context.get_max_dust_htlc_exposure_msat(dust_exposure_limiting_feerate); - if htlc_stats.on_holder_tx_dust_exposure_msat > max_dust_htlc_exposure_msat { - log_debug!(logger, "Cannot afford to send new feerate at {} without infringing max dust htlc exposure", feerate_per_kw); - return None; - } - if htlc_stats.on_counterparty_tx_dust_exposure_msat > max_dust_htlc_exposure_msat { - log_debug!(logger, "Cannot afford to send new feerate at {} without infringing max dust htlc exposure", feerate_per_kw); + let can_send_update_fee = core::iter::once(&self.funding) + .chain(self.pending_funding.iter()) + .all(|funding| self.context.can_send_update_fee(funding, &self.holder_commitment_point, feerate_per_kw, fee_estimator, logger)); + if !can_send_update_fee { return None; } @@ -7016,23 +7149,17 @@ impl FundedChannel where if self.context.channel_state.is_remote_stfu_sent() || self.context.channel_state.is_quiescent() { return Err(ChannelError::WarnAndDisconnect("Got fee update message while quiescent".to_owned())); } - FundedChannel::::check_remote_fee(self.funding.get_channel_type(), fee_estimator, msg.feerate_per_kw, Some(self.context.feerate_per_kw), logger)?; + + core::iter::once(&self.funding) + .chain(self.pending_funding.iter()) + .try_for_each(|funding| FundedChannel::::check_remote_fee(funding.get_channel_type(), fee_estimator, msg.feerate_per_kw, Some(self.context.feerate_per_kw), logger))?; self.context.pending_update_fee = Some((msg.feerate_per_kw, FeeUpdateState::RemoteAnnounced)); self.context.update_time_counter += 1; - // Check that we won't be pushed over our dust exposure limit by the feerate increase. - let dust_exposure_limiting_feerate = self.context.get_dust_exposure_limiting_feerate(&fee_estimator); - let htlc_stats = self.context.get_pending_htlc_stats(&self.funding, None, dust_exposure_limiting_feerate); - let max_dust_htlc_exposure_msat = self.context.get_max_dust_htlc_exposure_msat(dust_exposure_limiting_feerate); - if htlc_stats.on_holder_tx_dust_exposure_msat > max_dust_htlc_exposure_msat { - return Err(ChannelError::close(format!("Peer sent update_fee with a feerate ({}) which may over-expose us to dust-in-flight on our own transactions (totaling {} msat)", - msg.feerate_per_kw, htlc_stats.on_holder_tx_dust_exposure_msat))); - } - if htlc_stats.on_counterparty_tx_dust_exposure_msat > max_dust_htlc_exposure_msat { - return Err(ChannelError::close(format!("Peer sent update_fee with a feerate ({}) which may over-expose us to dust-in-flight on our counterparty's transactions (totaling {} msat)", - msg.feerate_per_kw, htlc_stats.on_counterparty_tx_dust_exposure_msat))); - } - Ok(()) + + core::iter::once(&self.funding) + .chain(self.pending_funding.iter()) + .try_for_each(|funding| self.context.validate_update_fee(funding, fee_estimator, msg)) } /// Indicates that the signer may have some signatures for us, so we should retry if we're @@ -8080,70 +8207,10 @@ impl FundedChannel where } let dust_exposure_limiting_feerate = self.context.get_dust_exposure_limiting_feerate(&fee_estimator); - let htlc_stats = self.context.get_pending_htlc_stats(&self.funding, None, dust_exposure_limiting_feerate); - let max_dust_htlc_exposure_msat = self.context.get_max_dust_htlc_exposure_msat(dust_exposure_limiting_feerate); - let on_counterparty_tx_dust_htlc_exposure_msat = htlc_stats.on_counterparty_tx_dust_exposure_msat; - if on_counterparty_tx_dust_htlc_exposure_msat > max_dust_htlc_exposure_msat { - // Note that the total dust exposure includes both the dust HTLCs and the excess mining fees of the counterparty commitment transaction - log_info!(logger, "Cannot accept value that would put our total dust exposure at {} over the limit {} on counterparty commitment tx", - on_counterparty_tx_dust_htlc_exposure_msat, max_dust_htlc_exposure_msat); - return Err(LocalHTLCFailureReason::DustLimitCounterparty) - } - let htlc_success_dust_limit = if self.funding.get_channel_type().supports_anchors_zero_fee_htlc_tx() { - 0 - } else { - let dust_buffer_feerate = self.context.get_dust_buffer_feerate(None) as u64; - dust_buffer_feerate * htlc_success_tx_weight(self.funding.get_channel_type()) / 1000 - }; - let exposure_dust_limit_success_sats = htlc_success_dust_limit + self.context.holder_dust_limit_satoshis; - if msg.amount_msat / 1000 < exposure_dust_limit_success_sats { - let on_holder_tx_dust_htlc_exposure_msat = htlc_stats.on_holder_tx_dust_exposure_msat; - if on_holder_tx_dust_htlc_exposure_msat > 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, max_dust_htlc_exposure_msat); - return Err(LocalHTLCFailureReason::DustLimitHolder) - } - } - - let anchor_outputs_value_msat = if self.funding.get_channel_type().supports_anchors_zero_fee_htlc_tx() { - ANCHOR_OUTPUT_VALUE_SATOSHI * 2 * 1000 - } else { - 0 - }; - - let mut removed_outbound_total_msat = 0; - for ref htlc in self.context.pending_outbound_htlcs.iter() { - if let OutboundHTLCState::AwaitingRemoteRevokeToRemove(OutboundHTLCOutcome::Success(_)) = htlc.state { - removed_outbound_total_msat += htlc.amount_msat; - } else if let OutboundHTLCState::AwaitingRemovedRemoteRevoke(OutboundHTLCOutcome::Success(_)) = htlc.state { - removed_outbound_total_msat += htlc.amount_msat; - } - } - - let pending_value_to_self_msat = - self.funding.value_to_self_msat + htlc_stats.pending_inbound_htlcs_value_msat - removed_outbound_total_msat; - let pending_remote_value_msat = - self.funding.get_value_satoshis() * 1000 - pending_value_to_self_msat; - - if !self.funding.is_outbound() { - // `Some(())` is for the fee spike buffer we keep for the remote. This deviates from - // the spec because the fee spike buffer requirement doesn't exist on the receiver's - // side, only on the sender's. Note that with anchor outputs we are no longer as - // sensitive to fee spikes, so we need to account for them. - // - // A `None` `HTLCCandidate` is used as in this case because we're already accounting for - // the incoming HTLC as it has been fully committed by both sides. - let mut remote_fee_cost_incl_stuck_buffer_msat = self.context.next_remote_commit_tx_fee_msat(&self.funding, None, Some(())); - if !self.funding.get_channel_type().supports_anchors_zero_fee_htlc_tx() { - remote_fee_cost_incl_stuck_buffer_msat *= FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE; - } - if pending_remote_value_msat.saturating_sub(self.funding.holder_selected_channel_reserve_satoshis * 1000).saturating_sub(anchor_outputs_value_msat) < remote_fee_cost_incl_stuck_buffer_msat { - log_info!(logger, "Attempting to fail HTLC due to fee spike buffer violation in channel {}. Rebalancing is required.", &self.context.channel_id()); - return Err(LocalHTLCFailureReason::FeeSpikeBuffer); - } - } - Ok(()) + core::iter::once(&self.funding) + .chain(self.pending_funding.iter()) + .try_for_each(|funding| self.context.can_accept_incoming_htlc(funding, msg, dust_exposure_limiting_feerate, &logger)) } pub fn get_cur_holder_commitment_transaction_number(&self) -> u64 { @@ -9073,11 +9140,6 @@ impl FundedChannel where return Err((LocalHTLCFailureReason::ChannelNotReady, "Cannot send HTLC until channel is fully established and we haven't started shutting down".to_owned())); } - let channel_total_msat = self.funding.get_value_satoshis() * 1000; - if amount_msat > channel_total_msat { - return Err((LocalHTLCFailureReason::AmountExceedsCapacity, - format!("Cannot send amount {}, because it is more than the total value of the channel {}", amount_msat, channel_total_msat))); - } if amount_msat == 0 { return Err((LocalHTLCFailureReason::ZeroAmount, "Cannot send 0-msat HTLC".to_owned())); diff --git a/lightning/src/ln/onion_utils.rs b/lightning/src/ln/onion_utils.rs index b3dbd107a76..e9bc0f6a853 100644 --- a/lightning/src/ln/onion_utils.rs +++ b/lightning/src/ln/onion_utils.rs @@ -1590,8 +1590,6 @@ pub enum LocalHTLCFailureReason { /// The HTLC was failed back because its expiry height was reached and funds were timed out /// on chain. OnChainTimeout, - /// The HTLC was failed because its amount is greater than the capacity of the channel. - AmountExceedsCapacity, /// The HTLC was failed because zero amount HTLCs are not allowed. ZeroAmount, /// The HTLC was failed because its amount is less than the smallest HTLC that the channel @@ -1625,7 +1623,6 @@ impl LocalHTLCFailureReason { | Self::DustLimitCounterparty | Self::FeeSpikeBuffer | Self::ChannelNotReady - | Self::AmountExceedsCapacity | Self::ZeroAmount | Self::HTLCMinimum | Self::HTLCMaximum @@ -1780,11 +1777,10 @@ impl_writeable_tlv_based_enum!(LocalHTLCFailureReason, (71, OutgoingCLTVTooSoon) => {}, (73, ChannelClosed) => {}, (75, OnChainTimeout) => {}, - (77, AmountExceedsCapacity) => {}, - (79, ZeroAmount) => {}, - (81, HTLCMinimum) => {}, - (83, HTLCMaximum) => {}, - (85, PeerOffline) => {}, + (77, ZeroAmount) => {}, + (79, HTLCMinimum) => {}, + (81, HTLCMaximum) => {}, + (83, PeerOffline) => {}, ); impl From<&HTLCFailReason> for HTLCHandlingFailureReason { @@ -1897,7 +1893,6 @@ impl HTLCFailReason { | LocalHTLCFailureReason::DustLimitCounterparty | LocalHTLCFailureReason::FeeSpikeBuffer | LocalHTLCFailureReason::ChannelNotReady - | LocalHTLCFailureReason::AmountExceedsCapacity | LocalHTLCFailureReason::ZeroAmount | LocalHTLCFailureReason::HTLCMinimum | LocalHTLCFailureReason::HTLCMaximum