diff --git a/lightning/src/chain/package.rs b/lightning/src/chain/package.rs index 08695f21361..a62c9a0775b 100644 --- a/lightning/src/chain/package.rs +++ b/lightning/src/chain/package.rs @@ -99,6 +99,9 @@ pub(crate) fn verify_channel_type_features(channel_type_features: &Option u64 { let num_htlcs = num_accepted_htlcs + num_offered_htlcs; let commit_tx_fees_sat = commit_tx_fee_sat(feerate_per_kw, num_htlcs, channel_type_features); - let htlc_tx_fees_sat = if !channel_type_features.supports_anchors_zero_fee_htlc_tx() { - num_accepted_htlcs as u64 * htlc_success_tx_weight(channel_type_features) * feerate_per_kw as u64 / 1000 - + num_offered_htlcs as u64 * htlc_timeout_tx_weight(channel_type_features) * feerate_per_kw as u64 / 1000 - } else { + let (htlc_success_tx_fee_sat, htlc_timeout_tx_fee_sat) = second_stage_tx_fees_sat( + channel_type_features, feerate_per_kw, + ); + + commit_tx_fees_sat + + num_accepted_htlcs as u64 * htlc_success_tx_fee_sat + + num_offered_htlcs as u64 * htlc_timeout_tx_fee_sat +} + +/// Returns a fee estimate for the commitment transaction depending on channel type. +pub(super) fn commitment_sat_per_1000_weight_for_type<'a, F: Deref>( + fee_estimator: &'a LowerBoundedFeeEstimator, channel_type: &ChannelTypeFeatures, +) -> u32 +where + F::Target: FeeEstimator, +{ + if channel_type.supports_anchor_zero_fee_commitments() { 0 - }; - commit_tx_fees_sat + htlc_tx_fees_sat + } else if channel_type.supports_anchors_zero_fee_htlc_tx() { + fee_estimator.bounded_sat_per_1000_weight(ConfirmationTarget::AnchorChannelFee) + } else { + fee_estimator.bounded_sat_per_1000_weight(ConfirmationTarget::NonAnchorChannelFee) + } } // Various functions for key derivation and transaction creation for use within channels. Primarily @@ -808,16 +827,17 @@ pub(crate) fn build_htlc_input(commitment_txid: &Txid, htlc: &HTLCOutputInCommit pub(crate) fn build_htlc_output( feerate_per_kw: u32, contest_delay: u16, htlc: &HTLCOutputInCommitment, channel_type_features: &ChannelTypeFeatures, broadcaster_delayed_payment_key: &DelayedPaymentKey, revocation_key: &RevocationKey ) -> TxOut { - let weight = if htlc.offered { - htlc_timeout_tx_weight(channel_type_features) - } else { - htlc_success_tx_weight(channel_type_features) - }; - let output_value = if channel_type_features.supports_anchors_zero_fee_htlc_tx() && !channel_type_features.supports_anchors_nonzero_fee_htlc_tx() { - htlc.to_bitcoin_amount() - } else { - let total_fee = Amount::from_sat(feerate_per_kw as u64 * weight / 1000); - htlc.to_bitcoin_amount() - total_fee + let (htlc_success_tx_fee_sat, htlc_timeout_tx_fee_sat) = second_stage_tx_fees_sat( + channel_type_features, feerate_per_kw, + ); + + let output_value = { + let total_fee = if htlc.offered { + htlc_timeout_tx_fee_sat + } else { + htlc_success_tx_fee_sat + }; + htlc.to_bitcoin_amount() - Amount::from_sat(total_fee) }; TxOut { diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index f396d8dab98..b3951c95179 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -40,7 +40,8 @@ use crate::ln::chan_utils; #[cfg(splicing)] use crate::ln::chan_utils::FUNDING_TRANSACTION_WITNESS_WEIGHT; use crate::ln::chan_utils::{ - commit_tx_fee_sat, get_commitment_transaction_number_obscure_factor, htlc_success_tx_weight, + commit_tx_fee_sat, commitment_sat_per_1000_weight_for_type, + get_commitment_transaction_number_obscure_factor, htlc_success_tx_weight, htlc_timeout_tx_weight, max_htlcs, ChannelPublicKeys, ChannelTransactionParameters, ClosingTransaction, CommitmentTransaction, CounterpartyChannelTransactionParameters, CounterpartyCommitmentSecrets, HTLCOutputInCommitment, HolderCommitmentTransaction, @@ -133,6 +134,22 @@ enum FeeUpdateState { Outbound, } +/// Returns the fees for success and timeout second stage HTLC transactions. +pub(super) fn second_stage_tx_fees_sat( + channel_type: &ChannelTypeFeatures, feerate_sat_per_1000_weight: u32, +) -> (u64, u64) { + if channel_type.supports_anchors_zero_fee_htlc_tx() + || channel_type.supports_anchor_zero_fee_commitments() + { + (0, 0) + } else { + ( + feerate_sat_per_1000_weight as u64 * htlc_success_tx_weight(channel_type) / 1000, + feerate_sat_per_1000_weight as u64 * htlc_timeout_tx_weight(channel_type) / 1000, + ) + } +} + enum InboundHTLCRemovalReason { FailRelay(msgs::OnionErrorPacket), FailMalformed(([u8; 32], u16)), @@ -284,18 +301,11 @@ impl InboundHTLCOutput { &self, local: bool, feerate_per_kw: u32, broadcaster_dust_limit_sat: u64, features: &ChannelTypeFeatures, ) -> bool { - let htlc_tx_fee_sat = if features.supports_anchors_zero_fee_htlc_tx() { - 0 - } else { - let htlc_tx_weight = if !local { - // this is an offered htlc - htlc_timeout_tx_weight(features) - } else { - htlc_success_tx_weight(features) - }; - // As required by the spec, round down - feerate_per_kw as u64 * htlc_tx_weight / 1000 - }; + let (htlc_success_tx_fee_sat, htlc_timeout_tx_fee_sat) = + second_stage_tx_fees_sat(features, feerate_per_kw); + + let htlc_tx_fee_sat = + if !local { htlc_timeout_tx_fee_sat } else { htlc_success_tx_fee_sat }; self.amount_msat / 1000 < broadcaster_dust_limit_sat + htlc_tx_fee_sat } } @@ -431,17 +441,14 @@ impl OutboundHTLCOutput { &self, local: bool, feerate_per_kw: u32, broadcaster_dust_limit_sat: u64, features: &ChannelTypeFeatures, ) -> bool { - let htlc_tx_fee_sat = if features.supports_anchors_zero_fee_htlc_tx() { - 0 + let (htlc_success_tx_fee_sat, htlc_timeout_tx_fee_sat) = + second_stage_tx_fees_sat(features, feerate_per_kw); + + let htlc_tx_fee_sat = if local { + // This is an offered HTLC. + htlc_timeout_tx_fee_sat } else { - let htlc_tx_weight = if local { - // this is an offered htlc - htlc_timeout_tx_weight(features) - } else { - htlc_success_tx_weight(features) - }; - // As required by the spec, round down - feerate_per_kw as u64 * htlc_tx_weight / 1000 + htlc_success_tx_fee_sat }; self.amount_msat / 1000 < broadcaster_dust_limit_sat + htlc_tx_fee_sat } @@ -3240,18 +3247,12 @@ where debug_assert!(!channel_type.supports_any_optional_bits()); debug_assert!(!channel_type.requires_unknown_bits_from(&channelmanager::provided_channel_type_features(&config))); - let (commitment_feerate, anchor_outputs_value_msat) = - if channel_type.supports_anchor_zero_fee_commitments() { - (0, 0) - } else if channel_type.supports_anchors_zero_fee_htlc_tx() { - let feerate = fee_estimator - .bounded_sat_per_1000_weight(ConfirmationTarget::AnchorChannelFee); - (feerate, ANCHOR_OUTPUT_VALUE_SATOSHI * 2 * 1000) - } else { - let feerate = fee_estimator - .bounded_sat_per_1000_weight(ConfirmationTarget::NonAnchorChannelFee); - (feerate, 0) - }; + let commitment_feerate = commitment_sat_per_1000_weight_for_type(&fee_estimator, &channel_type); + let anchor_outputs_value_msat = if channel_type.supports_anchors_zero_fee_htlc_tx() { + ANCHOR_OUTPUT_VALUE_SATOSHI * 2 * 1000 + } else { + 0 + }; let value_to_self_msat = channel_value_satoshis * 1000 - push_msat; let commitment_tx_fee = commit_tx_fee_sat(commitment_feerate, MIN_AFFORDABLE_HTLC_COUNT, &channel_type) * 1000; @@ -3836,19 +3837,30 @@ where cmp::max(self.config.options.cltv_expiry_delta, MIN_CLTV_EXPIRY_DELTA) } + /// Returns a maximum "sane" fee rate used to reason about our dust exposure. + /// Will be Some if the `channel_type`'s dust exposure depends on its commitment fee rate, and + /// None otherwise. fn get_dust_exposure_limiting_feerate( - &self, fee_estimator: &LowerBoundedFeeEstimator, - ) -> u32 + &self, fee_estimator: &LowerBoundedFeeEstimator, channel_type: &ChannelTypeFeatures, + ) -> Option where F::Target: FeeEstimator, { - fee_estimator.bounded_sat_per_1000_weight(ConfirmationTarget::MaximumFeeEstimate) + if channel_type.supports_anchor_zero_fee_commitments() { + None + } else { + Some(fee_estimator.bounded_sat_per_1000_weight(ConfirmationTarget::MaximumFeeEstimate)) + } } - pub fn get_max_dust_htlc_exposure_msat(&self, limiting_feerate_sat_per_kw: u32) -> u64 { + /// Returns the maximum configured dust exposure. + /// + /// Uses a default of 1 sat/vbyte if `limiting_feerate_sat_per_kw` is `None` and the dust + /// exposure policy depends on fee rate. + pub fn get_max_dust_htlc_exposure_msat(&self, limiting_feerate_sat_per_kw: Option) -> u64 { match self.config.options.max_dust_htlc_exposure { MaxDustHTLCExposure::FeeRateMultiplier(multiplier) => { - (limiting_feerate_sat_per_kw as u64).saturating_mul(multiplier) + (limiting_feerate_sat_per_kw.unwrap_or(250) as u64).saturating_mul(multiplier) }, MaxDustHTLCExposure::FixedLimitMsat(limit) => limit, } @@ -3975,7 +3987,9 @@ where 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 dust_exposure_limiting_feerate = self.get_dust_exposure_limiting_feerate( + &fee_estimator, funding.get_channel_type(), + ); 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))); @@ -4059,7 +4073,9 @@ 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 dust_exposure_limiting_feerate = self.get_dust_exposure_limiting_feerate( + &fee_estimator, funding.get_channel_type(), + ); 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 { @@ -4193,8 +4209,14 @@ where F::Target: FeeEstimator, L::Target: Logger, { + if funding.get_channel_type().supports_anchor_zero_fee_commitments() { + return false; + } + // 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 dust_exposure_limiting_feerate = self.get_dust_exposure_limiting_feerate( + &fee_estimator, funding.get_channel_type(), + ); 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(), @@ -4225,7 +4247,7 @@ where #[rustfmt::skip] fn can_accept_incoming_htlc( &self, funding: &FundingScope, msg: &msgs::UpdateAddHTLC, - dust_exposure_limiting_feerate: u32, logger: &L, + dust_exposure_limiting_feerate: Option, logger: &L, ) -> Result<(), LocalHTLCFailureReason> where L::Target: Logger, @@ -4239,13 +4261,11 @@ where 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; + let dust_buffer_feerate = self.get_dust_buffer_feerate(None); + let (htlc_success_tx_fee_sat, _) = second_stage_tx_fees_sat( + &funding.get_channel_type(), dust_buffer_feerate, + ); + let exposure_dust_limit_success_sats = htlc_success_tx_fee_sat + 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 { @@ -4276,14 +4296,22 @@ where 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. + // `Some(())` is for the fee spike buffer we keep for the remote if the channel is not + // zero fee. 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(())); + let fee_spike_buffer_htlc = if funding.get_channel_type().supports_anchor_zero_fee_commitments() { + None + } else { + Some(()) + }; + + let mut remote_fee_cost_incl_stuck_buffer_msat = self.next_remote_commit_tx_fee_msat( + funding, None, fee_spike_buffer_htlc, + ); if !funding.get_channel_type().supports_anchors_zero_fee_htlc_tx() { remote_fee_cost_incl_stuck_buffer_msat *= FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE; } @@ -4588,18 +4616,14 @@ where #[rustfmt::skip] fn get_pending_htlc_stats( &self, funding: &FundingScope, outbound_feerate_update: Option, - dust_exposure_limiting_feerate: u32, + dust_exposure_limiting_feerate: Option, ) -> HTLCStats { let context = self; - let uses_0_htlc_fee_anchors = funding.get_channel_type().supports_anchors_zero_fee_htlc_tx(); - let dust_buffer_feerate = context.get_dust_buffer_feerate(outbound_feerate_update); - let (htlc_timeout_dust_limit, htlc_success_dust_limit) = if uses_0_htlc_fee_anchors { - (0, 0) - } else { - (dust_buffer_feerate as u64 * htlc_timeout_tx_weight(funding.get_channel_type()) / 1000, - dust_buffer_feerate as u64 * htlc_success_tx_weight(funding.get_channel_type()) / 1000) - }; + let dust_buffer_feerate = self.get_dust_buffer_feerate(outbound_feerate_update); + let (htlc_success_tx_fee_sat, htlc_timeout_tx_fee_sat) = second_stage_tx_fees_sat( + funding.get_channel_type(), dust_buffer_feerate, + ); let mut on_holder_tx_dust_exposure_msat = 0; let mut on_counterparty_tx_dust_exposure_msat = 0; @@ -4610,8 +4634,8 @@ where let mut pending_inbound_htlcs_value_msat = 0; { - let counterparty_dust_limit_timeout_sat = htlc_timeout_dust_limit + context.counterparty_dust_limit_satoshis; - let holder_dust_limit_success_sat = htlc_success_dust_limit + context.holder_dust_limit_satoshis; + let counterparty_dust_limit_timeout_sat = htlc_timeout_tx_fee_sat + context.counterparty_dust_limit_satoshis; + let holder_dust_limit_success_sat = htlc_success_tx_fee_sat + context.holder_dust_limit_satoshis; for ref htlc in context.pending_inbound_htlcs.iter() { pending_inbound_htlcs_value_msat += htlc.amount_msat; if htlc.amount_msat / 1000 < counterparty_dust_limit_timeout_sat { @@ -4630,8 +4654,8 @@ where let mut on_holder_tx_outbound_holding_cell_htlcs_count = 0; let mut pending_outbound_htlcs = self.pending_outbound_htlcs.len(); { - let counterparty_dust_limit_success_sat = htlc_success_dust_limit + context.counterparty_dust_limit_satoshis; - let holder_dust_limit_timeout_sat = htlc_timeout_dust_limit + context.holder_dust_limit_satoshis; + let counterparty_dust_limit_success_sat = htlc_success_tx_fee_sat + context.counterparty_dust_limit_satoshis; + let holder_dust_limit_timeout_sat = htlc_timeout_tx_fee_sat + context.holder_dust_limit_satoshis; for ref htlc in context.pending_outbound_htlcs.iter() { pending_outbound_htlcs_value_msat += htlc.amount_msat; if htlc.amount_msat / 1000 < counterparty_dust_limit_success_sat { @@ -4667,7 +4691,11 @@ where let excess_feerate_opt = outbound_feerate_update .or(self.pending_update_fee.map(|(fee, _)| fee)) .unwrap_or(self.feerate_per_kw) - .checked_sub(dust_exposure_limiting_feerate); + .checked_sub(dust_exposure_limiting_feerate.unwrap_or(0)); + + if funding.get_channel_type().supports_anchor_zero_fee_commitments() { + debug_assert!(dust_exposure_limiting_feerate.is_none()); + } let extra_nondust_htlc_on_counterparty_tx_dust_exposure_msat = excess_feerate_opt.map(|excess_feerate| { let extra_htlc_dust_exposure = on_counterparty_tx_dust_exposure_msat + chan_utils::commit_and_htlc_tx_fees_sat(excess_feerate, on_counterparty_tx_accepted_nondust_htlcs + 1, on_counterparty_tx_offered_nondust_htlcs, funding.get_channel_type()) * 1000; @@ -4718,13 +4746,12 @@ where } } let mut inbound_details = Vec::new(); - 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 holder_dust_limit_success_sat = htlc_success_dust_limit + self.holder_dust_limit_satoshis; + + let dust_buffer_feerate = self.get_dust_buffer_feerate(None); + let (htlc_success_tx_fee_sat, _) = second_stage_tx_fees_sat( + funding.get_channel_type(), dust_buffer_feerate, + ); + let holder_dust_limit_success_sat = htlc_success_tx_fee_sat + self.holder_dust_limit_satoshis; for htlc in self.pending_inbound_htlcs.iter() { if let Some(state_details) = (&htlc.state).into() { inbound_details.push(InboundHTLCDetails{ @@ -4744,13 +4771,12 @@ where #[rustfmt::skip] pub fn get_pending_outbound_htlc_details(&self, funding: &FundingScope) -> Vec { let mut outbound_details = Vec::new(); - let htlc_timeout_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 holder_dust_limit_timeout_sat = htlc_timeout_dust_limit + self.holder_dust_limit_satoshis; + + let dust_buffer_feerate = self.get_dust_buffer_feerate(None); + let (_, htlc_timeout_tx_fee_sat) = second_stage_tx_fees_sat( + funding.get_channel_type(), dust_buffer_feerate, + ); + let holder_dust_limit_timeout_sat = htlc_timeout_tx_fee_sat + self.holder_dust_limit_satoshis; for htlc in self.pending_outbound_htlcs.iter() { outbound_details.push(OutboundHTLCDetails{ htlc_id: Some(htlc.htlc_id), @@ -4795,7 +4821,9 @@ where // Note that we have to handle overflow due to the case mentioned in the docs in general // here. - let dust_exposure_limiting_feerate = self.get_dust_exposure_limiting_feerate(&fee_estimator); + let dust_exposure_limiting_feerate = self.get_dust_exposure_limiting_feerate( + &fee_estimator, funding.get_channel_type(), + ); let htlc_stats = context.get_pending_htlc_stats(funding, None, dust_exposure_limiting_feerate); let outbound_capacity_msat = funding.value_to_self_msat @@ -4810,6 +4838,13 @@ where } else { 0 }; + + let (mut real_htlc_success_tx_fee_sat, mut real_htlc_timeout_tx_fee_sat) = second_stage_tx_fees_sat( + funding.get_channel_type(), context.feerate_per_kw, + ); + real_htlc_success_tx_fee_sat += context.counterparty_dust_limit_satoshis; + real_htlc_timeout_tx_fee_sat += context.holder_dust_limit_satoshis; + if funding.is_outbound() { // We should mind channel commit tx fee when computing how much of the available capacity // can be used in the next htlc. Mirrors the logic in send_htlc. @@ -4818,15 +4853,15 @@ where // and the answer will in turn change the amount itself — making it a circular // dependency. // This complicates the computation around dust-values, up to the one-htlc-value. - let mut real_dust_limit_timeout_sat = context.holder_dust_limit_satoshis; - if !funding.get_channel_type().supports_anchors_zero_fee_htlc_tx() { - real_dust_limit_timeout_sat += context.feerate_per_kw as u64 * htlc_timeout_tx_weight(funding.get_channel_type()) / 1000; - } - - let htlc_above_dust = HTLCCandidate::new(real_dust_limit_timeout_sat * 1000, HTLCInitiator::LocalOffered); - let mut max_reserved_commit_tx_fee_msat = context.next_local_commit_tx_fee_msat(&funding, htlc_above_dust, Some(())); - let htlc_dust = HTLCCandidate::new(real_dust_limit_timeout_sat * 1000 - 1, HTLCInitiator::LocalOffered); - let mut min_reserved_commit_tx_fee_msat = context.next_local_commit_tx_fee_msat(&funding, htlc_dust, Some(())); + let fee_spike_buffer_htlc = if funding.get_channel_type().supports_anchor_zero_fee_commitments() { + None + } else { + Some(()) + }; + let htlc_above_dust = HTLCCandidate::new(real_htlc_timeout_tx_fee_sat * 1000, HTLCInitiator::LocalOffered); + let mut max_reserved_commit_tx_fee_msat = context.next_local_commit_tx_fee_msat(&funding, htlc_above_dust, fee_spike_buffer_htlc); + let htlc_dust = HTLCCandidate::new(real_htlc_timeout_tx_fee_sat * 1000 - 1, HTLCInitiator::LocalOffered); + let mut min_reserved_commit_tx_fee_msat = context.next_local_commit_tx_fee_msat(&funding, htlc_dust, fee_spike_buffer_htlc); if !funding.get_channel_type().supports_anchors_zero_fee_htlc_tx() { max_reserved_commit_tx_fee_msat *= FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE; min_reserved_commit_tx_fee_msat *= FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE; @@ -4837,11 +4872,11 @@ where // match the value to right-below-dust. let mut capacity_minus_commitment_fee_msat: i64 = available_capacity_msat as i64 - max_reserved_commit_tx_fee_msat as i64 - anchor_outputs_value_msat as i64; - if capacity_minus_commitment_fee_msat < (real_dust_limit_timeout_sat as i64) * 1000 { + if capacity_minus_commitment_fee_msat < (real_htlc_timeout_tx_fee_sat as i64) * 1000 { let one_htlc_difference_msat = max_reserved_commit_tx_fee_msat - min_reserved_commit_tx_fee_msat; debug_assert!(one_htlc_difference_msat != 0); capacity_minus_commitment_fee_msat += one_htlc_difference_msat as i64; - capacity_minus_commitment_fee_msat = cmp::min(real_dust_limit_timeout_sat as i64 * 1000 - 1, capacity_minus_commitment_fee_msat); + capacity_minus_commitment_fee_msat = cmp::min(real_htlc_timeout_tx_fee_sat as i64 * 1000 - 1, capacity_minus_commitment_fee_msat); available_capacity_msat = cmp::max(0, cmp::min(capacity_minus_commitment_fee_msat, available_capacity_msat as i64)) as u64; } else { available_capacity_msat = capacity_minus_commitment_fee_msat as u64; @@ -4849,12 +4884,7 @@ where } else { // If the channel is inbound (i.e. counterparty pays the fee), we need to make sure // sending a new HTLC won't reduce their balance below our reserve threshold. - let mut real_dust_limit_success_sat = context.counterparty_dust_limit_satoshis; - if !funding.get_channel_type().supports_anchors_zero_fee_htlc_tx() { - real_dust_limit_success_sat += context.feerate_per_kw as u64 * htlc_success_tx_weight(funding.get_channel_type()) / 1000; - } - - let htlc_above_dust = HTLCCandidate::new(real_dust_limit_success_sat * 1000, HTLCInitiator::LocalOffered); + let htlc_above_dust = HTLCCandidate::new(real_htlc_success_tx_fee_sat * 1000, HTLCInitiator::LocalOffered); let max_reserved_commit_tx_fee_msat = context.next_remote_commit_tx_fee_msat(&funding, Some(htlc_above_dust), None); let holder_selected_chan_reserve_msat = funding.holder_selected_channel_reserve_satoshis * 1000; @@ -4864,7 +4894,7 @@ where if remote_balance_msat < max_reserved_commit_tx_fee_msat + holder_selected_chan_reserve_msat + anchor_outputs_value_msat { // If another HTLC's fee would reduce the remote's balance below the reserve limit // we've selected for them, we can only send dust HTLCs. - available_capacity_msat = cmp::min(available_capacity_msat, real_dust_limit_success_sat * 1000 - 1); + available_capacity_msat = cmp::min(available_capacity_msat, real_htlc_success_tx_fee_sat * 1000 - 1); } } @@ -4878,35 +4908,34 @@ where let mut dust_exposure_dust_limit_msat = 0; let max_dust_htlc_exposure_msat = context.get_max_dust_htlc_exposure_msat(dust_exposure_limiting_feerate); - let (htlc_success_dust_limit, htlc_timeout_dust_limit) = if funding.get_channel_type().supports_anchors_zero_fee_htlc_tx() { - (context.counterparty_dust_limit_satoshis, context.holder_dust_limit_satoshis) - } else { - let dust_buffer_feerate = context.get_dust_buffer_feerate(None) as u64; - (context.counterparty_dust_limit_satoshis + dust_buffer_feerate * htlc_success_tx_weight(funding.get_channel_type()) / 1000, - context.holder_dust_limit_satoshis + dust_buffer_feerate * htlc_timeout_tx_weight(funding.get_channel_type()) / 1000) - }; + let dust_buffer_feerate = self.get_dust_buffer_feerate(None); + let (mut buffer_htlc_success_tx_fee_sat, mut buffer_htlc_timeout_tx_fee_sat) = second_stage_tx_fees_sat( + funding.get_channel_type(), dust_buffer_feerate, + ); + buffer_htlc_success_tx_fee_sat += context.counterparty_dust_limit_satoshis; + buffer_htlc_timeout_tx_fee_sat += context.holder_dust_limit_satoshis; if let Some(extra_htlc_dust_exposure) = htlc_stats.extra_nondust_htlc_on_counterparty_tx_dust_exposure_msat { if extra_htlc_dust_exposure > max_dust_htlc_exposure_msat { // If adding an extra HTLC would put us over the dust limit in total fees, we cannot // send any non-dust HTLCs. - available_capacity_msat = cmp::min(available_capacity_msat, htlc_success_dust_limit * 1000); + available_capacity_msat = cmp::min(available_capacity_msat, buffer_htlc_success_tx_fee_sat * 1000); } } - if htlc_stats.on_counterparty_tx_dust_exposure_msat.saturating_add(htlc_success_dust_limit * 1000) > max_dust_htlc_exposure_msat.saturating_add(1) { + if htlc_stats.on_counterparty_tx_dust_exposure_msat.saturating_add(buffer_htlc_success_tx_fee_sat * 1000) > max_dust_htlc_exposure_msat.saturating_add(1) { // Note that we don't use the `counterparty_tx_dust_exposure` (with // `htlc_dust_exposure_msat`) here as it only applies to non-dust HTLCs. remaining_msat_below_dust_exposure_limit = Some(max_dust_htlc_exposure_msat.saturating_sub(htlc_stats.on_counterparty_tx_dust_exposure_msat)); - dust_exposure_dust_limit_msat = cmp::max(dust_exposure_dust_limit_msat, htlc_success_dust_limit * 1000); + dust_exposure_dust_limit_msat = cmp::max(dust_exposure_dust_limit_msat, buffer_htlc_success_tx_fee_sat * 1000); } - if htlc_stats.on_holder_tx_dust_exposure_msat as i64 + htlc_timeout_dust_limit as i64 * 1000 - 1 > max_dust_htlc_exposure_msat.try_into().unwrap_or(i64::max_value()) { + if htlc_stats.on_holder_tx_dust_exposure_msat as i64 + buffer_htlc_timeout_tx_fee_sat as i64 * 1000 - 1 > max_dust_htlc_exposure_msat.try_into().unwrap_or(i64::max_value()) { remaining_msat_below_dust_exposure_limit = Some(cmp::min( remaining_msat_below_dust_exposure_limit.unwrap_or(u64::max_value()), max_dust_htlc_exposure_msat.saturating_sub(htlc_stats.on_holder_tx_dust_exposure_msat))); - dust_exposure_dust_limit_msat = cmp::max(dust_exposure_dust_limit_msat, htlc_timeout_dust_limit * 1000); + dust_exposure_dust_limit_msat = cmp::max(dust_exposure_dust_limit_msat, buffer_htlc_timeout_tx_fee_sat * 1000); } if let Some(remaining_limit_msat) = remaining_msat_below_dust_exposure_limit { @@ -4954,14 +4983,17 @@ where let context = &self; assert!(funding.is_outbound()); - let (htlc_success_dust_limit, htlc_timeout_dust_limit) = if funding.get_channel_type().supports_anchors_zero_fee_htlc_tx() { - (0, 0) - } else { - (context.feerate_per_kw as u64 * htlc_success_tx_weight(funding.get_channel_type()) / 1000, - context.feerate_per_kw as u64 * htlc_timeout_tx_weight(funding.get_channel_type()) / 1000) - }; - let real_dust_limit_success_sat = htlc_success_dust_limit + context.holder_dust_limit_satoshis; - let real_dust_limit_timeout_sat = htlc_timeout_dust_limit + context.holder_dust_limit_satoshis; + if funding.get_channel_type().supports_anchor_zero_fee_commitments() { + debug_assert_eq!(context.feerate_per_kw, 0); + debug_assert!(fee_spike_buffer_htlc.is_none()); + return 0; + } + + let (htlc_success_tx_fee_sat, htlc_timeout_tx_fee_sat) = second_stage_tx_fees_sat( + funding.get_channel_type(), context.feerate_per_kw, + ); + let real_dust_limit_success_sat = htlc_success_tx_fee_sat + context.holder_dust_limit_satoshis; + let real_dust_limit_timeout_sat = htlc_timeout_tx_fee_sat + context.holder_dust_limit_satoshis; let mut addl_htlcs = 0; if fee_spike_buffer_htlc.is_some() { addl_htlcs += 1; } @@ -5058,19 +5090,22 @@ where fn next_remote_commit_tx_fee_msat( &self, funding: &FundingScope, htlc: Option, fee_spike_buffer_htlc: Option<()>, ) -> u64 { + if funding.get_channel_type().supports_anchor_zero_fee_commitments() { + debug_assert_eq!(self.feerate_per_kw, 0); + debug_assert!(fee_spike_buffer_htlc.is_none()); + return 0 + } + debug_assert!(htlc.is_some() || fee_spike_buffer_htlc.is_some(), "At least one of the options must be set"); let context = &self; assert!(!funding.is_outbound()); - let (htlc_success_dust_limit, htlc_timeout_dust_limit) = if funding.get_channel_type().supports_anchors_zero_fee_htlc_tx() { - (0, 0) - } else { - (context.feerate_per_kw as u64 * htlc_success_tx_weight(funding.get_channel_type()) / 1000, - context.feerate_per_kw as u64 * htlc_timeout_tx_weight(funding.get_channel_type()) / 1000) - }; - let real_dust_limit_success_sat = htlc_success_dust_limit + context.counterparty_dust_limit_satoshis; - let real_dust_limit_timeout_sat = htlc_timeout_dust_limit + context.counterparty_dust_limit_satoshis; + let (htlc_success_tx_fee_sat, htlc_timeout_tx_fee_sat) = second_stage_tx_fees_sat( + funding.get_channel_type(), self.feerate_per_kw, + ); + let real_dust_limit_success_sat = htlc_success_tx_fee_sat + context.counterparty_dust_limit_satoshis; + let real_dust_limit_timeout_sat = htlc_timeout_tx_fee_sat + context.counterparty_dust_limit_satoshis; let mut addl_htlcs = 0; if fee_spike_buffer_htlc.is_some() { addl_htlcs += 1; } @@ -5335,20 +5370,7 @@ where let next_channel_type = get_initial_channel_type(user_config, &eligible_features); - // Note that we can't get `anchor_zero_fee_commitments` type here, which requires zero - // fees, because we downgrade from this channel type first. If there were a superior - // channel type that downgrades to `anchor_zero_fee_commitments`, we'd need to handle - // fee setting differently here. If we proceeded to open a `anchor_zero_fee_commitments` - // channel with non-zero fees, we could produce a non-standard commitment transaction that - // puts us at risk of losing funds. We would expect our peer to reject such a channel - // open, but we don't want to rely on their validation. - assert!(!next_channel_type.supports_anchor_zero_fee_commitments()); - let conf_target = if next_channel_type.supports_anchors_zero_fee_htlc_tx() { - ConfirmationTarget::AnchorChannelFee - } else { - ConfirmationTarget::NonAnchorChannelFee - }; - self.feerate_per_kw = fee_estimator.bounded_sat_per_1000_weight(conf_target); + self.feerate_per_kw = commitment_sat_per_1000_weight_for_type(&fee_estimator, channel_type); funding.channel_transaction_parameters.channel_type_features = next_channel_type; Ok(()) @@ -7421,6 +7443,12 @@ where // unable to increase the fee, we don't try to force-close directly here. return Ok(()); } + + if self.funding.get_channel_type().supports_anchor_zero_fee_commitments() { + debug_assert_eq!(self.context.feerate_per_kw, 0); + return Ok(()); + } + if self.context.feerate_per_kw < min_feerate { log_info!(logger, "Closing channel as feerate of {} is below required {} (the minimum required rate over the past day)", @@ -7448,6 +7476,9 @@ 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())); } + if self.funding.get_channel_type().supports_anchor_zero_fee_commitments() { + return Err(ChannelError::WarnAndDisconnect("Update fee message received for zero fee commitment channel".to_owned())); + } core::iter::once(&self.funding) .chain(self.pending_funding.iter()) @@ -8524,7 +8555,9 @@ where return Err(LocalHTLCFailureReason::ChannelClosed) } - let dust_exposure_limiting_feerate = self.context.get_dust_exposure_limiting_feerate(&fee_estimator); + let dust_exposure_limiting_feerate = self.context.get_dust_exposure_limiting_feerate( + &fee_estimator, self.funding.get_channel_type(), + ); core::iter::once(&self.funding) .chain(self.pending_funding.iter()) @@ -12247,11 +12280,12 @@ mod tests { use crate::chain::chaininterface::LowerBoundedFeeEstimator; use crate::chain::transaction::OutPoint; use crate::chain::BestBlock; - use crate::ln::chan_utils::{self, htlc_success_tx_weight, htlc_timeout_tx_weight}; + use crate::ln::chan_utils; use crate::ln::channel::{ - commit_tx_fee_sat, AwaitingChannelReadyFlags, ChannelState, FundedChannel, HTLCCandidate, - HTLCInitiator, HTLCUpdateAwaitingACK, InboundHTLCOutput, InboundHTLCState, - InboundV1Channel, OutboundHTLCOutput, OutboundHTLCState, OutboundV1Channel, + commit_tx_fee_sat, second_stage_tx_fees_sat, AwaitingChannelReadyFlags, ChannelState, + FundedChannel, HTLCCandidate, HTLCInitiator, HTLCUpdateAwaitingACK, InboundHTLCOutput, + InboundHTLCState, InboundV1Channel, OutboundHTLCOutput, OutboundHTLCState, + OutboundV1Channel, }; use crate::ln::channel::{ MAX_FUNDING_SATOSHIS_NO_WUMBO, MIN_THEIR_CHAN_RESERVE_SATOSHIS, @@ -12529,16 +12563,19 @@ mod tests { let commitment_tx_fee_0_htlcs = commit_tx_fee_sat(chan.context.feerate_per_kw, 0, chan.funding.get_channel_type()) * 1000; let commitment_tx_fee_1_htlc = commit_tx_fee_sat(chan.context.feerate_per_kw, 1, chan.funding.get_channel_type()) * 1000; + let (htlc_success_tx_fee_sat, htlc_timeout_tx_fee_sat) = second_stage_tx_fees_sat( + &chan.funding.get_channel_type(), 253 + ); // If HTLC_SUCCESS_TX_WEIGHT and HTLC_TIMEOUT_TX_WEIGHT were swapped: then this HTLC would be // counted as dust when it shouldn't be. - let htlc_amt_above_timeout = ((253 * htlc_timeout_tx_weight(chan.funding.get_channel_type()) / 1000) + chan.context.holder_dust_limit_satoshis + 1) * 1000; + let htlc_amt_above_timeout = (htlc_timeout_tx_fee_sat + chan.context.holder_dust_limit_satoshis + 1) * 1000; let htlc_candidate = HTLCCandidate::new(htlc_amt_above_timeout, HTLCInitiator::LocalOffered); let commitment_tx_fee = chan.context.next_local_commit_tx_fee_msat(&chan.funding, htlc_candidate, None); assert_eq!(commitment_tx_fee, commitment_tx_fee_1_htlc); // If swapped: this HTLC would be counted as non-dust when it shouldn't be. - let dust_htlc_amt_below_success = ((253 * htlc_success_tx_weight(chan.funding.get_channel_type()) / 1000) + chan.context.holder_dust_limit_satoshis - 1) * 1000; + let dust_htlc_amt_below_success = (htlc_success_tx_fee_sat + chan.context.holder_dust_limit_satoshis - 1) * 1000; let htlc_candidate = HTLCCandidate::new(dust_htlc_amt_below_success, HTLCInitiator::RemoteOffered); let commitment_tx_fee = chan.context.next_local_commit_tx_fee_msat(&chan.funding, htlc_candidate, None); assert_eq!(commitment_tx_fee, commitment_tx_fee_0_htlcs); @@ -12546,13 +12583,13 @@ mod tests { chan.funding.channel_transaction_parameters.is_outbound_from_holder = false; // If swapped: this HTLC would be counted as non-dust when it shouldn't be. - let dust_htlc_amt_above_timeout = ((253 * htlc_timeout_tx_weight(chan.funding.get_channel_type()) / 1000) + chan.context.counterparty_dust_limit_satoshis + 1) * 1000; + let dust_htlc_amt_above_timeout = (htlc_timeout_tx_fee_sat + chan.context.counterparty_dust_limit_satoshis + 1) * 1000; let htlc_candidate = HTLCCandidate::new(dust_htlc_amt_above_timeout, HTLCInitiator::LocalOffered); let commitment_tx_fee = chan.context.next_remote_commit_tx_fee_msat(&chan.funding, Some(htlc_candidate), None); assert_eq!(commitment_tx_fee, commitment_tx_fee_0_htlcs); // If swapped: this HTLC would be counted as dust when it shouldn't be. - let htlc_amt_below_success = ((253 * htlc_success_tx_weight(chan.funding.get_channel_type()) / 1000) + chan.context.counterparty_dust_limit_satoshis - 1) * 1000; + let htlc_amt_below_success = (htlc_success_tx_fee_sat + chan.context.counterparty_dust_limit_satoshis - 1) * 1000; let htlc_candidate = HTLCCandidate::new(htlc_amt_below_success, HTLCInitiator::RemoteOffered); let commitment_tx_fee = chan.context.next_remote_commit_tx_fee_msat(&chan.funding, Some(htlc_candidate), None); assert_eq!(commitment_tx_fee, commitment_tx_fee_1_htlc); diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index c98ae74c001..677305a15eb 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -56,6 +56,7 @@ use crate::events::{ InboundChannelFunds, PaymentFailureReason, ReplayEvent, }; use crate::events::{FundingInfo, PaidBolt12Invoice}; +use crate::ln::chan_utils::commitment_sat_per_1000_weight_for_type; // Since this struct is returned in `list_channels` methods, expose it here in case users want to // construct one themselves. use crate::ln::channel::PendingV2Channel; @@ -6862,9 +6863,6 @@ where PersistenceNotifierGuard::optionally_notify(self, || { let mut should_persist = NotifyOption::SkipPersistNoEvents; - let non_anchor_feerate = self.fee_estimator.bounded_sat_per_1000_weight(ConfirmationTarget::NonAnchorChannelFee); - let anchor_feerate = self.fee_estimator.bounded_sat_per_1000_weight(ConfirmationTarget::AnchorChannelFee); - let per_peer_state = self.per_peer_state.read().unwrap(); for (_cp_id, peer_state_mutex) in per_peer_state.iter() { let mut peer_state_lock = peer_state_mutex.lock().unwrap(); @@ -6872,11 +6870,7 @@ where for (chan_id, chan) in peer_state.channel_by_id.iter_mut() .filter_map(|(chan_id, chan)| chan.as_funded_mut().map(|chan| (chan_id, chan))) { - let new_feerate = if chan.funding.get_channel_type().supports_anchors_zero_fee_htlc_tx() { - anchor_feerate - } else { - non_anchor_feerate - }; + let new_feerate = commitment_sat_per_1000_weight_for_type(&self.fee_estimator, chan.funding.get_channel_type()); let chan_needs_persist = self.update_channel_fee(chan_id, chan, new_feerate); if chan_needs_persist == NotifyOption::DoPersist { should_persist = NotifyOption::DoPersist; } } @@ -6912,9 +6906,6 @@ where PersistenceNotifierGuard::optionally_notify(self, || { let mut should_persist = NotifyOption::SkipPersistNoEvents; - let non_anchor_feerate = self.fee_estimator.bounded_sat_per_1000_weight(ConfirmationTarget::NonAnchorChannelFee); - let anchor_feerate = self.fee_estimator.bounded_sat_per_1000_weight(ConfirmationTarget::AnchorChannelFee); - let mut handle_errors: Vec<(Result<(), _>, _)> = Vec::new(); let mut timed_out_mpp_htlcs = Vec::new(); let mut pending_peers_awaiting_removal = Vec::new(); @@ -6930,11 +6921,7 @@ where peer_state.channel_by_id.retain(|chan_id, chan| { match chan.as_funded_mut() { Some(funded_chan) => { - let new_feerate = if funded_chan.funding.get_channel_type().supports_anchors_zero_fee_htlc_tx() { - anchor_feerate - } else { - non_anchor_feerate - }; + let new_feerate = commitment_sat_per_1000_weight_for_type(&self.fee_estimator, funded_chan.funding.get_channel_type()); let chan_needs_persist = self.update_channel_fee(chan_id, funded_chan, new_feerate); if chan_needs_persist == NotifyOption::DoPersist { should_persist = NotifyOption::DoPersist; } diff --git a/lightning/src/ln/functional_tests.rs b/lightning/src/ln/functional_tests.rs index 60aa21bc44e..8fbc7f9323b 100644 --- a/lightning/src/ln/functional_tests.rs +++ b/lightning/src/ln/functional_tests.rs @@ -25,13 +25,13 @@ use crate::events::{ PaymentPurpose, }; use crate::ln::chan_utils::{ - commitment_tx_base_weight, htlc_success_tx_weight, htlc_timeout_tx_weight, - COMMITMENT_TX_WEIGHT_PER_HTLC, OFFERED_HTLC_SCRIPT_WEIGHT, + commitment_tx_base_weight, htlc_success_tx_weight, COMMITMENT_TX_WEIGHT_PER_HTLC, + OFFERED_HTLC_SCRIPT_WEIGHT, }; use crate::ln::channel::{ - get_holder_selected_channel_reserve_satoshis, Channel, ChannelError, InboundV1Channel, - OutboundV1Channel, COINBASE_MATURITY, DISCONNECT_PEER_AWAITING_RESPONSE_TICKS, - MIN_CHAN_DUST_LIMIT_SATOSHIS, + get_holder_selected_channel_reserve_satoshis, second_stage_tx_fees_sat, Channel, ChannelError, + InboundV1Channel, OutboundV1Channel, COINBASE_MATURITY, + DISCONNECT_PEER_AWAITING_RESPONSE_TICKS, MIN_CHAN_DUST_LIMIT_SATOSHIS, }; use crate::ln::channelmanager::{ self, PaymentId, RAACommitmentOrder, RecipientOnionFields, BREAKDOWN_TIMEOUT, @@ -9865,12 +9865,12 @@ fn do_test_max_dust_htlc_exposure( nondust_htlc_count_in_limit, &ChannelTypeFeatures::empty(), ); - commitment_tx_cost_msat += if on_holder_tx { - htlc_success_tx_weight(&ChannelTypeFeatures::empty()) - } else { - htlc_timeout_tx_weight(&ChannelTypeFeatures::empty()) - } * (initial_feerate as u64 - 253) - * nondust_htlc_count_in_limit; + let (htlc_success_tx_fee_sat, htlc_timeout_tx_fee_sat) = + second_stage_tx_fees_sat(&ChannelTypeFeatures::empty(), initial_feerate as u32 - 253); + let per_htlc_cost = + if on_holder_tx { htlc_success_tx_fee_sat } else { htlc_timeout_tx_fee_sat }; + + commitment_tx_cost_msat += per_htlc_cost * nondust_htlc_count_in_limit; { let mut feerate_lock = chanmon_cfgs[0].fee_estimator.sat_per_kw.lock().unwrap(); *feerate_lock = initial_feerate; @@ -9902,8 +9902,6 @@ fn do_test_max_dust_htlc_exposure( get_event_msg!(nodes[1], MessageSendEvent::SendAcceptChannel, node_a_id); nodes[0].node.handle_accept_channel(node_b_id, &accept_channel); - let channel_type_features = ChannelTypeFeatures::only_static_remote_key(); - let (chan_id, tx, _) = create_funding_transaction(&nodes[0], &node_b_id, 1_000_000, 42); if on_holder_tx { @@ -9947,31 +9945,40 @@ fn do_test_max_dust_htlc_exposure( let (mut route, payment_hash, _, payment_secret) = get_route_and_payment_hash!(nodes[0], nodes[1], 1000); - let (dust_buffer_feerate, max_dust_htlc_exposure_msat) = { + let ( + dust_buffer_feerate, + max_dust_htlc_exposure_msat, + htlc_success_tx_fee_sat, + htlc_timeout_tx_fee_sat, + ) = { let per_peer_state = nodes[0].node.per_peer_state.read().unwrap(); let chan_lock = per_peer_state.get(&node_b_id).unwrap().lock().unwrap(); let chan = chan_lock.channel_by_id.get(&channel_id).unwrap(); + let dust_buffer_feerate = chan.context().get_dust_buffer_feerate(None) as u64; + let (htlc_success_tx_fee_sat, htlc_timeout_tx_fee_sat) = second_stage_tx_fees_sat( + &chan.as_funded().unwrap().funding.get_channel_type(), + dust_buffer_feerate as u32, + ); ( - chan.context().get_dust_buffer_feerate(None) as u64, - chan.context().get_max_dust_htlc_exposure_msat(253), + dust_buffer_feerate, + chan.context().get_max_dust_htlc_exposure_msat(Some(253)), + htlc_success_tx_fee_sat, + htlc_timeout_tx_fee_sat, ) }; assert_eq!(dust_buffer_feerate, expected_dust_buffer_feerate as u64); let dust_outbound_htlc_on_holder_tx_msat: u64 = - (dust_buffer_feerate * htlc_timeout_tx_weight(&channel_type_features) / 1000 - + open_channel.common_fields.dust_limit_satoshis - - 1) * 1000; + (htlc_timeout_tx_fee_sat + open_channel.common_fields.dust_limit_satoshis - 1) * 1000; let dust_outbound_htlc_on_holder_tx: u64 = max_dust_htlc_exposure_msat / dust_outbound_htlc_on_holder_tx_msat; // Substract 3 sats for multiplier and 2 sats for fixed limit to make sure we are 50% below the dust limit. // This is to make sure we fully use the dust limit. If we don't, we could end up with `dust_ibd_htlc_on_holder_tx` being 1 // while `max_dust_htlc_exposure_msat` is not equal to `dust_outbound_htlc_on_holder_tx_msat`. - let dust_inbound_htlc_on_holder_tx_msat: u64 = - (dust_buffer_feerate * htlc_success_tx_weight(&channel_type_features) / 1000 - + open_channel.common_fields.dust_limit_satoshis - - if multiplier_dust_limit { 3 } else { 2 }) - * 1000; + let dust_inbound_htlc_on_holder_tx_msat: u64 = (htlc_success_tx_fee_sat + + open_channel.common_fields.dust_limit_satoshis + - if multiplier_dust_limit { 3 } else { 2 }) + * 1000; let dust_inbound_htlc_on_holder_tx: u64 = max_dust_htlc_exposure_msat / dust_inbound_htlc_on_holder_tx_msat; @@ -9979,11 +9986,10 @@ fn do_test_max_dust_htlc_exposure( // indeed, dust on both transactions. let dust_htlc_on_counterparty_tx: u64 = 4; let dust_htlc_on_counterparty_tx_msat: u64 = 1_250_000; - let calcd_dust_htlc_on_counterparty_tx_msat: u64 = - (dust_buffer_feerate * htlc_timeout_tx_weight(&channel_type_features) / 1000 - + open_channel.common_fields.dust_limit_satoshis - - if multiplier_dust_limit { 3 } else { 2 }) - * 1000; + let calcd_dust_htlc_on_counterparty_tx_msat: u64 = (htlc_timeout_tx_fee_sat + + open_channel.common_fields.dust_limit_satoshis + - if multiplier_dust_limit { 3 } else { 2 }) + * 1000; assert!(dust_htlc_on_counterparty_tx_msat < dust_inbound_htlc_on_holder_tx_msat); assert!(dust_htlc_on_counterparty_tx_msat < calcd_dust_htlc_on_counterparty_tx_msat); @@ -10330,7 +10336,7 @@ pub fn test_nondust_htlc_excess_fees_are_dust() { ); nodes[0].logger.assert_log("lightning::ln::channel", format!("Cannot accept value that would put our total dust exposure at {} over the limit {} on counterparty commitment tx", - 2535000, 2530000), 1); + 2531000, 2530000), 1); check_added_monitors(&nodes[0], 1); // Clear the failed htlc @@ -10432,9 +10438,10 @@ fn do_test_nondust_htlc_fees_dust_exposure_delta(features: ChannelTypeFeatures) let mut expected_dust_exposure_msat = BASE_DUST_EXPOSURE_MSAT + EXCESS_FEERATE * (commitment_tx_base_weight(&features) + COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000 * 1000; + + let (_, htlc_timeout_tx_fee_sat) = second_stage_tx_fees_sat(&features, EXCESS_FEERATE as u32); if features == ChannelTypeFeatures::only_static_remote_key() { - expected_dust_exposure_msat += - EXCESS_FEERATE * htlc_timeout_tx_weight(&features) / 1000 * 1000; + expected_dust_exposure_msat += htlc_timeout_tx_fee_sat * 1000; assert_eq!(expected_dust_exposure_msat, 533_492); } else { assert_eq!(expected_dust_exposure_msat, 528_492); @@ -10549,12 +10556,13 @@ fn do_test_nondust_htlc_fees_dust_exposure_delta(features: ChannelTypeFeatures) // The `expected_dust_exposure_msat` for the outbound htlc changes in the non-anchor case, as the htlc success and timeout transactions have different weights // only_static_remote_key: 500_492 + 22 * (724 + 172) / 1000 * 1000 + 22 * 703 / 1000 * 1000 = 534_492 + let (htlc_success_tx_fee_sat, _) = second_stage_tx_fees_sat(&features, EXCESS_FEERATE as u32); if features == ChannelTypeFeatures::only_static_remote_key() { expected_dust_exposure_msat = BASE_DUST_EXPOSURE_MSAT + EXCESS_FEERATE * (commitment_tx_base_weight(&features) + COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000 * 1000 - + EXCESS_FEERATE * htlc_success_tx_weight(&features) / 1000 * 1000; + + htlc_success_tx_fee_sat * 1000; assert_eq!(expected_dust_exposure_msat, 534_492); } else { assert_eq!(expected_dust_exposure_msat, 528_492); @@ -10575,9 +10583,10 @@ fn do_test_nondust_htlc_fees_dust_exposure_delta(features: ChannelTypeFeatures) let res = nodes[1].node.send_payment_with_route(route_1_0, payment_hash_1_0, onion, id); unwrap_send_err!(nodes[1], res, true, APIError::ChannelUnavailable { .. }, {}); + let (htlc_success_tx_fee_sat, _) = + second_stage_tx_fees_sat(&features, node_1_dust_buffer_feerate as u32); let dust_limit = if features == ChannelTypeFeatures::only_static_remote_key() { - MIN_CHAN_DUST_LIMIT_SATOSHIS * 1000 - + htlc_success_tx_weight(&features) * node_1_dust_buffer_feerate / 1000 * 1000 + MIN_CHAN_DUST_LIMIT_SATOSHIS * 1000 + htlc_success_tx_fee_sat * 1000 } else { MIN_CHAN_DUST_LIMIT_SATOSHIS * 1000 }; diff --git a/lightning/src/ln/htlc_reserve_unit_tests.rs b/lightning/src/ln/htlc_reserve_unit_tests.rs index 111e0b404de..f374c3d5824 100644 --- a/lightning/src/ln/htlc_reserve_unit_tests.rs +++ b/lightning/src/ln/htlc_reserve_unit_tests.rs @@ -2,12 +2,11 @@ use crate::events::{ClosureReason, Event, HTLCHandlingFailureType, PaymentPurpose}; use crate::ln::chan_utils::{ - self, commitment_tx_base_weight, htlc_success_tx_weight, CommitmentTransaction, - COMMITMENT_TX_WEIGHT_PER_HTLC, + self, commitment_tx_base_weight, CommitmentTransaction, COMMITMENT_TX_WEIGHT_PER_HTLC, }; use crate::ln::channel::{ - get_holder_selected_channel_reserve_satoshis, Channel, FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE, - MIN_AFFORDABLE_HTLC_COUNT, + get_holder_selected_channel_reserve_satoshis, second_stage_tx_fees_sat, Channel, + FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE, MIN_AFFORDABLE_HTLC_COUNT, }; use crate::ln::channelmanager::{PaymentId, RAACommitmentOrder}; use crate::ln::functional_test_utils::*; @@ -1084,8 +1083,10 @@ pub fn test_chan_reserve_dust_inbound_htlcs_outbound_chan() { push_amt -= get_holder_selected_channel_reserve_satoshis(100_000, &default_config) * 1000; create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 100000, push_amt); + let (htlc_success_tx_fee_sat, _) = + second_stage_tx_fees_sat(&channel_type_features, feerate_per_kw); let dust_amt = crate::ln::channel::MIN_CHAN_DUST_LIMIT_SATOSHIS * 1000 - + feerate_per_kw as u64 * htlc_success_tx_weight(&channel_type_features) / 1000 * 1000 + + htlc_success_tx_fee_sat * 1000 - 1; // In the previous code, routing this dust payment would cause nodes[0] to perceive a channel // reserve violation even though it's a dust HTLC and therefore shouldn't count towards the diff --git a/lightning/src/ln/update_fee_tests.rs b/lightning/src/ln/update_fee_tests.rs index 8fa508ecf93..3ba55abdac3 100644 --- a/lightning/src/ln/update_fee_tests.rs +++ b/lightning/src/ln/update_fee_tests.rs @@ -1193,3 +1193,59 @@ pub fn do_cannot_afford_on_holding_cell_release( nodes[0].logger.assert_log("lightning::ln::channel", err, 1); } } + +#[test] +pub fn test_zero_fee_commiments_no_update_fee() { + // Tests that option_zero_fee_commitment channels do not sent update_fee messages, and that + // they'll disconnect and warn if they receive them. + let mut cfg = test_default_channel_config(); + cfg.channel_handshake_config.negotiate_anchor_zero_fee_commitments = true; + cfg.manually_accept_inbound_channels = true; + + let chanmon_cfgs = create_chanmon_cfgs(2); + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[Some(cfg.clone()), Some(cfg)]); + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + + let channel = create_chan_between_nodes(&nodes[0], &nodes[1]); + + let assert_zero_fee = || { + for node in nodes.iter() { + let channels = node.node.list_channels(); + assert_eq!(channels.len(), 1); + assert!(channels[0] + .channel_type + .as_ref() + .unwrap() + .supports_anchor_zero_fee_commitments()); + assert_eq!(channels[0].feerate_sat_per_1000_weight.unwrap(), 0); + } + }; + assert_zero_fee(); + + // Sender should not queue an update_fee message. + nodes[0].node.timer_tick_occurred(); + let events_0 = nodes[0].node.get_and_clear_pending_msg_events(); + assert_eq!(events_0.len(), 0); + + // Receiver should ignore and warn if sent update_fee. + let channel_id = channel.3; + let update_fee_msg = msgs::UpdateFee { channel_id, feerate_per_kw: 5000 }; + nodes[1].node.handle_update_fee(nodes[0].node.get_our_node_id(), &update_fee_msg); + + let events_1 = nodes[1].node.get_and_clear_pending_msg_events(); + assert_eq!(events_1.len(), 1); + match events_1[0] { + MessageSendEvent::HandleError { ref action, .. } => match action { + ErrorAction::DisconnectPeerWithWarning { ref msg, .. } => { + assert_eq!(msg.channel_id, channel_id); + assert!(msg + .data + .contains("Update fee message received for zero fee commitment channel")); + }, + _ => panic!("Expected DisconnectPeerWithWarning, got {:?}", action), + }, + _ => panic!("Expected HandleError event, got {:?}", events_1[0]), + } + assert_zero_fee(); +} diff --git a/lightning/src/util/config.rs b/lightning/src/util/config.rs index e98b237691c..343177d3d14 100644 --- a/lightning/src/util/config.rs +++ b/lightning/src/util/config.rs @@ -438,12 +438,20 @@ pub enum MaxDustHTLCExposure { /// on HTLC outputs means your channel may be subject to more dust exposure in the event of /// increases in fee rate. /// + /// Note that because zero-commitment-fee anchor channels do not allow for feerate updates (and + /// thus never experience dust exposure changes due to feerate shifts, resulting in no + /// force-closes due to dust exposure limits), such channels will calculate their maximum + /// dust exposure using a constant feerate of 250 sat/KW when using this variant. + /// /// # Backwards Compatibility /// This variant only became available in LDK 0.0.116, so if you downgrade to a prior version /// by default this will be set to a [`Self::FixedLimitMsat`] of 5,000,000 msat. /// /// [`FeeEstimator`]: crate::chain::chaininterface::FeeEstimator /// [`ConfirmationTarget::MaximumFeeEstimate`]: crate::chain::chaininterface::ConfirmationTarget::MaximumFeeEstimate + // + // TODO: link ChannelHandshakeConfig::negotiate_anchor_zero_fee_commitment in zero fee + // commitment doc once field is no longer behind cfg[test] flag. FeeRateMultiplier(u64), }