diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index e9614bcb35a..2e89740028b 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -78,7 +78,9 @@ use crate::ln::types::ChannelId; use crate::ln::LN_MAX_MSG_LEN; use crate::routing::gossip::NodeId; use crate::sign::ecdsa::EcdsaChannelSigner; -use crate::sign::tx_builder::{HTLCAmountDirection, NextCommitmentStats, SpecTxBuilder, TxBuilder}; +use crate::sign::tx_builder::{ + ChannelConstraints, HTLCAmountDirection, NextCommitmentStats, SpecTxBuilder, TxBuilder, +}; use crate::sign::{ChannelSigner, EntropySource, NodeSigner, Recipient, SignerProvider}; use crate::types::features::{ChannelTypeFeatures, InitFeatures}; use crate::types::payment::{PaymentHash, PaymentPreimage}; @@ -290,24 +292,6 @@ struct InboundHTLCOutput { state: InboundHTLCState, } -impl InboundHTLCOutput { - fn is_dust( - &self, local: bool, feerate_per_kw: u32, broadcaster_dust_limit_sat: u64, - features: &ChannelTypeFeatures, - ) -> bool { - 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 { - htlc_success_tx_fee_sat - }; - self.amount_msat / 1000 < broadcaster_dust_limit_sat + htlc_tx_fee_sat - } -} - #[cfg_attr(test, derive(Clone, Debug, PartialEq))] enum OutboundHTLCState { /// Added by us and included in a commitment_signed (if we were AwaitingRemoteRevoke when we @@ -433,24 +417,6 @@ struct OutboundHTLCOutput { send_timestamp: Option, } -impl OutboundHTLCOutput { - fn is_dust( - &self, local: bool, feerate_per_kw: u32, broadcaster_dust_limit_sat: u64, - features: &ChannelTypeFeatures, - ) -> bool { - 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 { - htlc_success_tx_fee_sat - }; - self.amount_msat / 1000 < broadcaster_dust_limit_sat + htlc_tx_fee_sat - } -} - /// See AwaitingRemoteRevoke ChannelState for more info #[cfg_attr(test, derive(Clone, Debug, PartialEq))] enum HTLCUpdateAwaitingACK { @@ -1100,26 +1066,6 @@ pub enum AnnouncementSigsState { PeerReceived, } -/// An enum indicating whether the local or remote side offered a given HTLC. -enum HTLCInitiator { - LocalOffered, - #[allow(dead_code)] - RemoteOffered, -} - -/// Current counts of various HTLCs, useful for calculating current balances available exactly. -struct HTLCStats { - pending_outbound_htlcs: usize, - pending_inbound_htlcs_value_msat: u64, - pending_outbound_htlcs_value_msat: u64, - on_counterparty_tx_dust_exposure_msat: u64, - // If the counterparty sets a feerate on the channel in excess of our dust_exposure_limiting_feerate, - // this will be set to the dust exposure that would result from us adding an additional nondust outbound - // htlc on the counterparty's commitment transaction. - extra_nondust_htlc_on_counterparty_tx_dust_exposure_msat: Option, - on_holder_tx_dust_exposure_msat: u64, -} - /// A struct gathering data on a commitment, either local or remote. struct CommitmentData<'a> { tx: CommitmentTransaction, @@ -1140,18 +1086,6 @@ pub(crate) struct CommitmentStats { pub remote_balance_before_fee_msat: u64, } -/// Used when calculating whether we or the remote can afford an additional HTLC. -struct HTLCCandidate { - amount_msat: u64, - origin: HTLCInitiator, -} - -impl HTLCCandidate { - fn new(amount_msat: u64, origin: HTLCInitiator) -> Self { - Self { amount_msat, origin } - } -} - /// A return value enum for get_update_fulfill_htlc. See UpdateFulfillCommitFetch variants for /// description enum UpdateFulfillFetch { @@ -1364,7 +1298,7 @@ impl HolderCommitmentPoint { #[cfg(any(fuzzing, test, feature = "_test_utils"))] pub const FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE: u64 = 2; #[cfg(not(any(fuzzing, test, feature = "_test_utils")))] -const FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE: u64 = 2; +pub(crate) const FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE: u64 = 2; /// If we fail to see a funding transaction confirmed on-chain within this many blocks after the /// channel creation on an inbound channel, we simply force-close and move on. @@ -3122,14 +3056,21 @@ where // check if the funder's amount for the initial commitment tx is sufficient // for full fee payment plus a few HTLCs to ensure the channel will be useful. let funders_amount_msat = open_channel_fields.funding_satoshis * 1000 - msg_push_msat; - let commit_tx_fee_sat = SpecTxBuilder {}.commit_tx_fee_sat(open_channel_fields.commitment_feerate_sat_per_1000_weight, MIN_AFFORDABLE_HTLC_COUNT, &channel_type); - // Subtract any non-HTLC outputs from the remote balance - let (_, remote_balance_before_fee_msat) = SpecTxBuilder {}.subtract_non_htlc_outputs(false, value_to_self_msat, funders_amount_msat, &channel_type); - if remote_balance_before_fee_msat / 1000 < commit_tx_fee_sat { - return Err(ChannelError::close(format!("Funding amount ({} sats) can't even pay fee for initial commitment transaction fee of {} sats.", funders_amount_msat / 1000, commit_tx_fee_sat))); + let local = false; + let is_outbound_from_holder = false; + let dust_exposure_limiting_feerate = if channel_type.supports_anchor_zero_fee_commitments() { + None + } else { + Some(fee_estimator.bounded_sat_per_1000_weight(ConfirmationTarget::MaximumFeeEstimate)) + }; + let remote_stats = SpecTxBuilder {}.get_next_commitment_stats(local, is_outbound_from_holder, open_channel_fields.funding_satoshis, msg_push_msat, &[], MIN_AFFORDABLE_HTLC_COUNT, + open_channel_fields.commitment_feerate_sat_per_1000_weight, dust_exposure_limiting_feerate, open_channel_fields.dust_limit_satoshis, &channel_type); + let remote_balance_before_fee_msat = remote_stats.counterparty_balance_before_fee_msat.ok_or(ChannelError::close(format!("Funding amount ({} sats) can't even pay for non-HTLC outputs ie anchors.", funders_amount_msat / 1000)))?; + if remote_balance_before_fee_msat / 1000 < remote_stats.commit_tx_fee_sat { + return Err(ChannelError::close(format!("Funding amount ({} sats) can't even pay fee for initial commitment transaction fee of {} sats.", funders_amount_msat / 1000, remote_stats.commit_tx_fee_sat))); } - let to_remote_satoshis = remote_balance_before_fee_msat / 1000 - commit_tx_fee_sat; + let to_remote_satoshis = remote_balance_before_fee_msat / 1000 - remote_stats.commit_tx_fee_sat; // While it's reasonable for us to not meet the channel reserve initially (if they don't // want to push much to us), our counterparty should always have more than our reserve. if to_remote_satoshis < holder_selected_channel_reserve_satoshis { @@ -3205,7 +3146,7 @@ where channel_transaction_parameters: ChannelTransactionParameters { holder_pubkeys: pubkeys, holder_selected_contest_delay: config.channel_handshake_config.our_to_self_delay, - is_outbound_from_holder: false, + is_outbound_from_holder, counterparty_parameters: Some(CounterpartyChannelTransactionParameters { selected_contest_delay: open_channel_fields.to_self_delay, pubkeys: counterparty_pubkeys, @@ -3388,16 +3329,19 @@ where ); let value_to_self_msat = channel_value_satoshis * 1000 - push_msat; - let commit_tx_fee_sat = SpecTxBuilder {}.commit_tx_fee_sat(commitment_feerate, MIN_AFFORDABLE_HTLC_COUNT, &channel_type); - // Subtract any non-HTLC outputs from the local balance - let (local_balance_before_fee_msat, _) = SpecTxBuilder {}.subtract_non_htlc_outputs( - true, - value_to_self_msat, - push_msat, - &channel_type, - ); - if local_balance_before_fee_msat / 1000 < commit_tx_fee_sat { - return Err(APIError::APIMisuseError{ err: format!("Funding amount ({}) can't even pay fee for initial commitment transaction fee of {}.", value_to_self_msat / 1000, commit_tx_fee_sat) }); + let local = true; + let is_outbound_from_holder = true; + let value_to_holder_msat = channel_value_msat - push_msat; + let dust_exposure_limiting_feerate = if channel_type.supports_anchor_zero_fee_commitments() { + None + } else { + Some(fee_estimator.bounded_sat_per_1000_weight(ConfirmationTarget::MaximumFeeEstimate)) + }; + let local_stats = SpecTxBuilder {}.get_next_commitment_stats(local, is_outbound_from_holder, channel_value_satoshis, value_to_holder_msat, &[], MIN_AFFORDABLE_HTLC_COUNT, + commitment_feerate, dust_exposure_limiting_feerate, MIN_CHAN_DUST_LIMIT_SATOSHIS, &channel_type); + let local_balance_before_fee_msat = local_stats.holder_balance_before_fee_msat.ok_or(APIError::APIMisuseError { err: format!("Funding amount ({} sats) can't even pay for non-HTLC outputs ie anchors.", value_to_self_msat / 1000) })?; + if local_balance_before_fee_msat / 1000 < local_stats.commit_tx_fee_sat { + return Err(APIError::APIMisuseError{ err: format!("Funding amount ({}) can't even pay fee for initial commitment transaction fee of {}.", value_to_self_msat / 1000, local_stats.commit_tx_fee_sat) }); } let mut secp_ctx = Secp256k1::new(); @@ -3445,7 +3389,7 @@ where channel_transaction_parameters: ChannelTransactionParameters { holder_pubkeys: pubkeys, holder_selected_contest_delay: config.channel_handshake_config.our_to_self_delay, - is_outbound_from_holder: true, + is_outbound_from_holder, counterparty_parameters: None, funding_outpoint: None, splice_parent_funding_txid: None, @@ -4618,83 +4562,6 @@ where feerate_per_kw } - /// Builds stats on a potential commitment transaction build, without actually building the - /// commitment transaction. See `build_commitment_transaction` for further docs. - #[inline] - #[rustfmt::skip] - fn build_commitment_stats(&self, funding: &FundingScope, local: bool, generated_by_local: bool, feerate_per_kw: Option, fee_buffer_nondust_htlcs: Option) -> CommitmentStats { - let broadcaster_dust_limit_sat = if local { self.holder_dust_limit_satoshis } else { self.counterparty_dust_limit_satoshis }; - let mut nondust_htlc_count = 0; - let mut remote_htlc_total_msat = 0; - let mut local_htlc_total_msat = 0; - let mut value_to_self_claimed_msat = 0; - let mut value_to_remote_claimed_msat = 0; - - let feerate_per_kw = feerate_per_kw.unwrap_or_else(|| self.get_commitment_feerate(funding, generated_by_local)); - - for htlc in self.pending_inbound_htlcs.iter() { - if htlc.state.included_in_commitment(generated_by_local) { - if !htlc.is_dust(local, feerate_per_kw, broadcaster_dust_limit_sat, funding.get_channel_type()) { - nondust_htlc_count += 1; - } - remote_htlc_total_msat += htlc.amount_msat; - } else { - if htlc.state.preimage().is_some() { - value_to_self_claimed_msat += htlc.amount_msat; - } - } - }; - - for htlc in self.pending_outbound_htlcs.iter() { - if htlc.state.included_in_commitment(generated_by_local) { - if !htlc.is_dust(local, feerate_per_kw, broadcaster_dust_limit_sat, funding.get_channel_type()) { - nondust_htlc_count += 1; - } - local_htlc_total_msat += htlc.amount_msat; - } else { - if htlc.state.preimage().is_some() { - value_to_remote_claimed_msat += htlc.amount_msat; - } - } - }; - - // # Panics - // - // After all HTLC claims have been accounted for, the local balance MUST remain greater than or equal to 0. - - let mut value_to_self_msat = (funding.value_to_self_msat + value_to_self_claimed_msat).checked_sub(value_to_remote_claimed_msat).unwrap(); - - let mut value_to_remote_msat = (funding.get_value_satoshis() * 1000).checked_sub(value_to_self_msat).unwrap(); - value_to_self_msat = value_to_self_msat.checked_sub(local_htlc_total_msat).unwrap(); - value_to_remote_msat = value_to_remote_msat.checked_sub(remote_htlc_total_msat).unwrap(); - - #[cfg(debug_assertions)] - { - // Make sure that the to_self/to_remote is always either past the appropriate - // channel_reserve *or* it is making progress towards it. - let mut broadcaster_max_commitment_tx_output = if generated_by_local { - funding.holder_max_commitment_tx_output.lock().unwrap() - } else { - funding.counterparty_max_commitment_tx_output.lock().unwrap() - }; - debug_assert!(broadcaster_max_commitment_tx_output.0 <= value_to_self_msat || value_to_self_msat / 1000 >= funding.counterparty_selected_channel_reserve_satoshis.unwrap()); - broadcaster_max_commitment_tx_output.0 = cmp::max(broadcaster_max_commitment_tx_output.0, value_to_self_msat); - debug_assert!(broadcaster_max_commitment_tx_output.1 <= value_to_remote_msat || value_to_remote_msat / 1000 >= funding.holder_selected_channel_reserve_satoshis); - broadcaster_max_commitment_tx_output.1 = cmp::max(broadcaster_max_commitment_tx_output.1, value_to_remote_msat); - } - - let commit_tx_fee_sat = SpecTxBuilder {}.commit_tx_fee_sat(feerate_per_kw, nondust_htlc_count + fee_buffer_nondust_htlcs.unwrap_or(0), funding.get_channel_type()); - // Subtract any non-HTLC outputs from the local and remote balances - let (local_balance_before_fee_msat, remote_balance_before_fee_msat) = SpecTxBuilder {}.subtract_non_htlc_outputs( - funding.is_outbound(), - value_to_self_msat, - value_to_remote_msat, - funding.get_channel_type(), - ); - - CommitmentStats { commit_tx_fee_sat, local_balance_before_fee_msat, remote_balance_before_fee_msat } - } - /// Transaction nomenclature is somewhat confusing here as there are many different cases - a /// transaction is referred to as "a's transaction" implying that a will be able to broadcast /// the transaction. Thus, b will generally be sending a signature over such a transaction to @@ -4795,7 +4662,21 @@ where broadcaster_dust_limit_sat, logger, ); - debug_assert_eq!(stats, self.build_commitment_stats(funding, local, generated_by_local, None, None), "Caught an inconsistency between `TxBuilder::build_commitment_transaction` and the rest of the `TxBuilder` methods"); + + #[cfg(debug_assertions)] + { + // Make sure that the to_self/to_remote is always either past the appropriate + // channel_reserve *or* it is making progress towards it. + let mut broadcaster_max_commitment_tx_output = if generated_by_local { + funding.holder_max_commitment_tx_output.lock().unwrap() + } else { + funding.counterparty_max_commitment_tx_output.lock().unwrap() + }; + debug_assert!(broadcaster_max_commitment_tx_output.0 <= stats.local_balance_before_fee_msat || stats.local_balance_before_fee_msat / 1000 >= funding.counterparty_selected_channel_reserve_satoshis.unwrap()); + broadcaster_max_commitment_tx_output.0 = cmp::max(broadcaster_max_commitment_tx_output.0, stats.local_balance_before_fee_msat); + debug_assert!(broadcaster_max_commitment_tx_output.1 <= stats.remote_balance_before_fee_msat || stats.remote_balance_before_fee_msat / 1000 >= funding.holder_selected_channel_reserve_satoshis); + broadcaster_max_commitment_tx_output.1 = cmp::max(broadcaster_max_commitment_tx_output.1, stats.remote_balance_before_fee_msat); + } // This populates the HTLC-source table with the indices from the HTLCs in the commitment // transaction. @@ -4865,111 +4746,6 @@ where self.counterparty_forwarding_info.clone() } - /// Returns a HTLCStats about pending htlcs - #[rustfmt::skip] - fn get_pending_htlc_stats( - &self, funding: &FundingScope, outbound_feerate_update: Option, - dust_exposure_limiting_feerate: Option, - ) -> HTLCStats { - let context = self; - - 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; - - let mut on_counterparty_tx_offered_nondust_htlcs = 0; - let mut on_counterparty_tx_accepted_nondust_htlcs = 0; - - let mut pending_inbound_htlcs_value_msat = 0; - - { - 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 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 { - on_counterparty_tx_dust_exposure_msat += htlc.amount_msat; - } else { - on_counterparty_tx_offered_nondust_htlcs += 1; - } - if htlc.amount_msat / 1000 < holder_dust_limit_success_sat { - on_holder_tx_dust_exposure_msat += htlc.amount_msat; - } - } - } - - let mut pending_outbound_htlcs_value_msat = 0; - let mut pending_outbound_htlcs = self.pending_outbound_htlcs.len(); - { - 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 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 { - on_counterparty_tx_dust_exposure_msat += htlc.amount_msat; - } else { - on_counterparty_tx_accepted_nondust_htlcs += 1; - } - if htlc.amount_msat / 1000 < holder_dust_limit_timeout_sat { - on_holder_tx_dust_exposure_msat += htlc.amount_msat; - } - } - - for update in context.holding_cell_htlc_updates.iter() { - if let &HTLCUpdateAwaitingACK::AddHTLC { ref amount_msat, .. } = update { - pending_outbound_htlcs += 1; - pending_outbound_htlcs_value_msat += amount_msat; - if *amount_msat / 1000 < counterparty_dust_limit_success_sat { - on_counterparty_tx_dust_exposure_msat += amount_msat; - } else { - on_counterparty_tx_accepted_nondust_htlcs += 1; - } - if *amount_msat / 1000 < holder_dust_limit_timeout_sat { - on_holder_tx_dust_exposure_msat += amount_msat; - } - } - } - } - - // Include any mining "excess" fees in the dust calculation - 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.unwrap_or(0)); - - // Dust exposure is only decoupled from feerate for zero fee commitment channels. - let is_zero_fee_comm = funding.get_channel_type().supports_anchor_zero_fee_commitments(); - debug_assert_eq!(is_zero_fee_comm, dust_exposure_limiting_feerate.is_none()); - if is_zero_fee_comm { - debug_assert_eq!(excess_feerate_opt, Some(0)); - } - - let extra_nondust_htlc_on_counterparty_tx_dust_exposure_msat = excess_feerate_opt.map(|excess_feerate| { - let extra_htlc_commit_tx_fee_sat = SpecTxBuilder {}.commit_tx_fee_sat(excess_feerate, on_counterparty_tx_accepted_nondust_htlcs + 1 + on_counterparty_tx_offered_nondust_htlcs, funding.get_channel_type()); - let extra_htlc_htlc_tx_fees_sat = chan_utils::htlc_tx_fees_sat(excess_feerate, on_counterparty_tx_accepted_nondust_htlcs + 1, on_counterparty_tx_offered_nondust_htlcs, funding.get_channel_type()); - - let commit_tx_fee_sat = SpecTxBuilder {}.commit_tx_fee_sat(excess_feerate, on_counterparty_tx_accepted_nondust_htlcs + on_counterparty_tx_offered_nondust_htlcs, funding.get_channel_type()); - let htlc_tx_fees_sat = chan_utils::htlc_tx_fees_sat(excess_feerate, on_counterparty_tx_accepted_nondust_htlcs, on_counterparty_tx_offered_nondust_htlcs, funding.get_channel_type()); - - let extra_htlc_dust_exposure = on_counterparty_tx_dust_exposure_msat + (extra_htlc_commit_tx_fee_sat + extra_htlc_htlc_tx_fees_sat) * 1000; - on_counterparty_tx_dust_exposure_msat += (commit_tx_fee_sat + htlc_tx_fees_sat) * 1000; - extra_htlc_dust_exposure - }); - - HTLCStats { - pending_outbound_htlcs, - pending_inbound_htlcs_value_msat, - pending_outbound_htlcs_value_msat, - on_counterparty_tx_dust_exposure_msat, - extra_nondust_htlc_on_counterparty_tx_dust_exposure_msat, - on_holder_tx_dust_exposure_msat, - } - } - /// Returns information on all pending inbound HTLCs. #[rustfmt::skip] pub fn get_pending_inbound_htlc_details(&self, funding: &FundingScope) -> Vec { @@ -5063,6 +4839,28 @@ where outbound_details } + fn get_holder_channel_constraints(&self, funding: &FundingScope) -> ChannelConstraints { + ChannelConstraints { + dust_limit_satoshis: self.holder_dust_limit_satoshis, + channel_reserve_satoshis: funding + .counterparty_selected_channel_reserve_satoshis + .unwrap_or(0), + htlc_minimum_msat: self.holder_htlc_minimum_msat, + max_accepted_htlcs: self.holder_max_accepted_htlcs as u64, + max_htlc_value_in_flight_msat: self.holder_max_htlc_value_in_flight_msat, + } + } + + fn get_counterparty_channel_constraints(&self, funding: &FundingScope) -> ChannelConstraints { + ChannelConstraints { + dust_limit_satoshis: self.counterparty_dust_limit_satoshis, + channel_reserve_satoshis: funding.holder_selected_channel_reserve_satoshis, + htlc_minimum_msat: self.counterparty_htlc_minimum_msat, + max_accepted_htlcs: self.counterparty_max_accepted_htlcs as u64, + max_htlc_value_in_flight_msat: self.counterparty_max_htlc_value_in_flight_msat, + } + } + #[rustfmt::skip] fn get_available_balances_for_scope( &self, funding: &FundingScope, fee_estimator: &LowerBoundedFeeEstimator, @@ -5070,311 +4868,36 @@ where where F::Target: FeeEstimator, { - let context = &self; - // Note that we have to handle overflow due to the case mentioned in the docs in general - // here. + let pending_outbound_htlcs = self.pending_outbound_htlcs.iter().map(|htlc| HTLCAmountDirection { amount_msat: htlc.amount_msat, outbound: true }); + let pending_inbound_htlcs = self.pending_inbound_htlcs.iter().map(|htlc| HTLCAmountDirection { amount_msat: htlc.amount_msat, outbound: false }); + let holding_cell_htlcs = self.holding_cell_htlc_updates.iter().filter_map(|htlc| { + if let &HTLCUpdateAwaitingACK::AddHTLC { amount_msat, .. } = htlc { + Some(HTLCAmountDirection { outbound: true, amount_msat }) + } else { + None + } + }); + + let mut pending_htlcs: Vec = Vec::with_capacity(self.pending_outbound_htlcs.len() + self.pending_inbound_htlcs.len() + self.holding_cell_htlc_updates.len()); + pending_htlcs.extend(pending_outbound_htlcs.chain(pending_inbound_htlcs).chain(holding_cell_htlcs)); 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 max_dust_htlc_exposure_msat = self.get_max_dust_htlc_exposure_msat(dust_exposure_limiting_feerate); - // Subtract any non-HTLC outputs from the local and remote balances - let (local_balance_before_fee_msat, remote_balance_before_fee_msat) = SpecTxBuilder {}.subtract_non_htlc_outputs( + SpecTxBuilder {}.get_available_balances( funding.is_outbound(), - funding.value_to_self_msat.saturating_sub(htlc_stats.pending_outbound_htlcs_value_msat), - (funding.get_value_satoshis() * 1000).checked_sub(funding.value_to_self_msat).unwrap().saturating_sub(htlc_stats.pending_inbound_htlcs_value_msat), + funding.get_value_satoshis(), + funding.get_value_to_self_msat(), + &pending_htlcs, + self.feerate_per_kw, + dust_exposure_limiting_feerate, + max_dust_htlc_exposure_msat, + self.get_holder_channel_constraints(funding), + self.get_counterparty_channel_constraints(funding), funding.get_channel_type(), - ); - - let outbound_capacity_msat = local_balance_before_fee_msat - .saturating_sub( - funding.counterparty_selected_channel_reserve_satoshis.unwrap_or(0) * 1000); - - let mut available_capacity_msat = outbound_capacity_msat; - let (real_htlc_success_tx_fee_sat, real_htlc_timeout_tx_fee_sat) = second_stage_tx_fees_sat( - funding.get_channel_type(), context.feerate_per_kw, - ); - - 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. - // - // The fee depends on whether the amount we will be sending is above dust or not, - // 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 fee_spike_buffer_htlc = if funding.get_channel_type().supports_anchor_zero_fee_commitments() { - None - } else { - Some(()) - }; - - let real_dust_limit_timeout_sat = real_htlc_timeout_tx_fee_sat + context.holder_dust_limit_satoshis; - 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, fee_spike_buffer_htlc); - 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, 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; - } - - // We will first subtract the fee as if we were above-dust. Then, if the resulting - // value ends up being below dust, we have this fee available again. In that case, - // 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; - if capacity_minus_commitment_fee_msat < (real_dust_limit_timeout_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); - 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; - } - } 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 real_dust_limit_success_sat = real_htlc_success_tx_fee_sat + context.counterparty_dust_limit_satoshis; - let htlc_above_dust = HTLCCandidate::new(real_dust_limit_success_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; - if remote_balance_before_fee_msat < max_reserved_commit_tx_fee_msat + holder_selected_chan_reserve_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); - } - } - - let mut next_outbound_htlc_minimum_msat = context.counterparty_htlc_minimum_msat; - - // If we get close to our maximum dust exposure, we end up in a situation where we can send - // between zero and the remaining dust exposure limit remaining OR above the dust limit. - // Because we cannot express this as a simple min/max, we prefer to tell the user they can - // send above the dust limit (as the router can always overpay to meet the dust limit). - let mut remaining_msat_below_dust_exposure_limit = None; - 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 dust_buffer_feerate = self.get_dust_buffer_feerate(None); - let (buffer_htlc_success_tx_fee_sat, buffer_htlc_timeout_tx_fee_sat) = second_stage_tx_fees_sat( - funding.get_channel_type(), dust_buffer_feerate, - ); - let buffer_dust_limit_success_sat = buffer_htlc_success_tx_fee_sat + context.counterparty_dust_limit_satoshis; - let buffer_dust_limit_timeout_sat = 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, buffer_dust_limit_success_sat * 1000); - } - } - - if htlc_stats.on_counterparty_tx_dust_exposure_msat.saturating_add(buffer_dust_limit_success_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, buffer_dust_limit_success_sat * 1000); - } - - if htlc_stats.on_holder_tx_dust_exposure_msat as i64 + buffer_dust_limit_timeout_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, buffer_dust_limit_timeout_sat * 1000); - } - - if let Some(remaining_limit_msat) = remaining_msat_below_dust_exposure_limit { - if available_capacity_msat < dust_exposure_dust_limit_msat { - available_capacity_msat = cmp::min(available_capacity_msat, remaining_limit_msat); - } else { - next_outbound_htlc_minimum_msat = cmp::max(next_outbound_htlc_minimum_msat, dust_exposure_dust_limit_msat); - } - } - - available_capacity_msat = cmp::min(available_capacity_msat, - context.counterparty_max_htlc_value_in_flight_msat - htlc_stats.pending_outbound_htlcs_value_msat); - - if htlc_stats.pending_outbound_htlcs + 1 > context.counterparty_max_accepted_htlcs as usize { - available_capacity_msat = 0; - } - - #[allow(deprecated)] // TODO: Remove once balance_msat is removed. - AvailableBalances { - inbound_capacity_msat: remote_balance_before_fee_msat.saturating_sub(funding.holder_selected_channel_reserve_satoshis * 1000), - outbound_capacity_msat, - next_outbound_htlc_limit_msat: available_capacity_msat, - next_outbound_htlc_minimum_msat, - } - } - - /// Get the commitment tx fee for the local's (i.e. our) next commitment transaction based on the - /// number of pending HTLCs that are on track to be in our next commitment tx. - /// - /// Includes the `HTLCCandidate` given by `htlc` and an additional non-dust HTLC if - /// `fee_spike_buffer_htlc` is `Some`. - /// - /// The first extra HTLC is useful for determining whether we can accept a further HTLC, the - /// second allows for creating a buffer to ensure a further HTLC can always be accepted/added. - /// - /// Dust HTLCs are excluded. - #[rustfmt::skip] - fn next_local_commit_tx_fee_msat( - &self, funding: &FundingScope, htlc: HTLCCandidate, fee_spike_buffer_htlc: Option<()>, - ) -> u64 { - let context = self; - assert!(funding.is_outbound()); - - 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; } - match htlc.origin { - HTLCInitiator::LocalOffered => { - if htlc.amount_msat / 1000 >= real_dust_limit_timeout_sat { - addl_htlcs += 1; - } - }, - HTLCInitiator::RemoteOffered => { - if htlc.amount_msat / 1000 >= real_dust_limit_success_sat { - addl_htlcs += 1; - } - } - } - - let mut included_htlcs = 0; - for ref htlc in context.pending_inbound_htlcs.iter() { - if htlc.amount_msat / 1000 < real_dust_limit_success_sat { - continue - } - // We include LocalRemoved HTLCs here because we may still need to broadcast a commitment - // transaction including this HTLC if it times out before they RAA. - included_htlcs += 1; - } - - for ref htlc in context.pending_outbound_htlcs.iter() { - if htlc.amount_msat / 1000 < real_dust_limit_timeout_sat { - continue - } - match htlc.state { - OutboundHTLCState::LocalAnnounced {..} => included_htlcs += 1, - OutboundHTLCState::Committed => included_htlcs += 1, - OutboundHTLCState::RemoteRemoved {..} => included_htlcs += 1, - // We don't include AwaitingRemoteRevokeToRemove HTLCs because our next commitment - // transaction won't be generated until they send us their next RAA, which will mean - // dropping any HTLCs in this state. - _ => {}, - } - } - - for htlc in context.holding_cell_htlc_updates.iter() { - match htlc { - &HTLCUpdateAwaitingACK::AddHTLC { amount_msat, .. } => { - if amount_msat / 1000 < real_dust_limit_timeout_sat { - continue - } - included_htlcs += 1 - }, - _ => {}, // Don't include claims/fails that are awaiting ack, because once we get the - // ack we're guaranteed to never include them in commitment txs anymore. - } - } - - let num_htlcs = included_htlcs + addl_htlcs; - SpecTxBuilder {}.commit_tx_fee_sat(context.feerate_per_kw, num_htlcs, funding.get_channel_type()) * 1000 - } - - /// Get the commitment tx fee for the remote's next commitment transaction based on the number of - /// pending HTLCs that are on track to be in their next commitment tx - /// - /// Optionally includes the `HTLCCandidate` given by `htlc` and an additional non-dust HTLC if - /// `fee_spike_buffer_htlc` is `Some`. - /// - /// The first extra HTLC is useful for determining whether we can accept a further HTLC, the - /// second allows for creating a buffer to ensure a further HTLC can always be accepted/added. - /// - /// Dust HTLCs are excluded. - #[rustfmt::skip] - fn next_remote_commit_tx_fee_msat( - &self, funding: &FundingScope, htlc: Option, fee_spike_buffer_htlc: Option<()>, - ) -> u64 { - let context = self; - assert!(!funding.is_outbound()); - - 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 - } - - debug_assert!(htlc.is_some() || fee_spike_buffer_htlc.is_some(), "At least one of the options must be set"); - - 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.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; } - if let Some(htlc) = &htlc { - match htlc.origin { - HTLCInitiator::LocalOffered => { - if htlc.amount_msat / 1000 >= real_dust_limit_success_sat { - addl_htlcs += 1; - } - }, - HTLCInitiator::RemoteOffered => { - if htlc.amount_msat / 1000 >= real_dust_limit_timeout_sat { - addl_htlcs += 1; - } - } - } - } - - // When calculating the set of HTLCs which will be included in their next commitment_signed, all - // non-dust inbound HTLCs are included (as all states imply it will be included) and only - // committed outbound HTLCs, see below. - let mut included_htlcs = 0; - for ref htlc in context.pending_inbound_htlcs.iter() { - if htlc.amount_msat / 1000 < real_dust_limit_timeout_sat { - continue - } - included_htlcs += 1; - } - - for ref htlc in context.pending_outbound_htlcs.iter() { - if htlc.amount_msat / 1000 < real_dust_limit_success_sat { - continue - } - // We only include outbound HTLCs if it will not be included in their next commitment_signed, - // i.e. if they've responded to us with an RAA after announcement. - match htlc.state { - OutboundHTLCState::Committed => included_htlcs += 1, - OutboundHTLCState::RemoteRemoved {..} => included_htlcs += 1, - OutboundHTLCState::LocalAnnounced { .. } => included_htlcs += 1, - _ => {}, - } - } - - let num_htlcs = included_htlcs + addl_htlcs; - SpecTxBuilder {}.commit_tx_fee_sat(context.feerate_per_kw, num_htlcs, funding.get_channel_type()) * 1000 + ) } #[rustfmt::skip] @@ -14362,9 +13885,9 @@ mod tests { use crate::ln::chan_utils::ChannelTransactionParameters; use crate::ln::chan_utils::{self, commit_tx_fee_sat}; use crate::ln::channel::{ - AwaitingChannelReadyFlags, ChannelState, FundedChannel, HTLCCandidate, HTLCInitiator, - HTLCUpdateAwaitingACK, InboundHTLCOutput, InboundHTLCState, InboundV1Channel, - OutboundHTLCOutput, OutboundHTLCState, OutboundV1Channel, + AwaitingChannelReadyFlags, ChannelState, FundedChannel, HTLCUpdateAwaitingACK, + InboundHTLCOutput, InboundHTLCState, InboundV1Channel, OutboundHTLCOutput, + OutboundHTLCState, OutboundV1Channel, }; use crate::ln::channel::{ MAX_FUNDING_SATOSHIS_NO_WUMBO, MIN_THEIR_CHAN_RESERVE_SATOSHIS, @@ -14379,6 +13902,7 @@ mod tests { use crate::ln::script::ShutdownScript; use crate::prelude::*; use crate::routing::router::{Path, RouteHop}; + use crate::sign::tx_builder::HTLCAmountDirection; #[cfg(ldk_test_vectors)] use crate::sign::{ChannelSigner, EntropySource, InMemorySigner, SignerProvider}; #[cfg(splicing)] @@ -14642,8 +14166,9 @@ mod tests { // Make sure when Node A calculates their local commitment transaction, none of the HTLCs pass // the dust limit check. - let htlc_candidate = HTLCCandidate::new(htlc_amount_msat, HTLCInitiator::LocalOffered); - let local_commit_tx_fee = node_a_chan.context.next_local_commit_tx_fee_msat(&node_a_chan.funding, htlc_candidate, None); + let htlc_candidate = HTLCAmountDirection { amount_msat: htlc_amount_msat, outbound: true }; + // We set dust_exposure_to_limiting_feerate to Some as the channel type is not zero fee, but we don't care for the value as it has no impact on the commitment transaction fee + let local_commit_tx_fee = node_a_chan.context.get_next_local_commitment_stats(&node_a_chan.funding, Some(htlc_candidate), true, 0, node_a_chan.context.feerate_per_kw, Some(0)).commit_tx_fee_sat * 1000; let local_commit_fee_0_htlcs = commit_tx_fee_sat(node_a_chan.context.feerate_per_kw, 0, node_a_chan.funding.get_channel_type()) * 1000; assert_eq!(local_commit_tx_fee, local_commit_fee_0_htlcs); @@ -14651,15 +14176,16 @@ mod tests { // of the HTLCs are seen to be above the dust limit. node_a_chan.funding.channel_transaction_parameters.is_outbound_from_holder = false; let remote_commit_fee_3_htlcs = commit_tx_fee_sat(node_a_chan.context.feerate_per_kw, 3, node_a_chan.funding.get_channel_type()) * 1000; - let htlc_candidate = HTLCCandidate::new(htlc_amount_msat, HTLCInitiator::LocalOffered); - let remote_commit_tx_fee = node_a_chan.context.next_remote_commit_tx_fee_msat(&node_a_chan.funding, Some(htlc_candidate), None); + let htlc_candidate = HTLCAmountDirection { amount_msat: htlc_amount_msat, outbound: true }; + // We set dust_exposure_to_limiting_feerate to Some as the channel type is not zero fee, but we don't care for the value as it has no impact on the commitment transaction fee + let remote_commit_tx_fee = node_a_chan.context.get_next_remote_commitment_stats(&node_a_chan.funding, Some(htlc_candidate), true, 0, node_a_chan.context.feerate_per_kw, Some(0)).commit_tx_fee_sat * 1000; assert_eq!(remote_commit_tx_fee, remote_commit_fee_3_htlcs); } #[test] #[rustfmt::skip] fn test_timeout_vs_success_htlc_dust_limit() { - // Make sure that when `next_remote_commit_tx_fee_msat` and `next_local_commit_tx_fee_msat` + // Make sure that when `get_next_local/remote_commitment_stats` // calculate the real dust limits for HTLCs (i.e. the dust limit given by the counterparty // *plus* the fees paid for the HTLC) they don't swap `HTLC_SUCCESS_TX_WEIGHT` for // `HTLC_TIMEOUT_TX_WEIGHT`, and vice versa. @@ -14684,28 +14210,32 @@ mod tests { // 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 = (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); + let htlc_candidate = HTLCAmountDirection { amount_msat: htlc_amt_above_timeout, outbound: true }; + // We set dust_exposure_to_limiting_feerate to Some as the channel type is not zero fee, but we don't care for the value as it has no impact on the commitment transaction fee + let commitment_tx_fee = chan.context.get_next_local_commitment_stats(&chan.funding, Some(htlc_candidate), true, 0, chan.context.feerate_per_kw, Some(0)).commit_tx_fee_sat * 1000; 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 = (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); + let htlc_candidate = HTLCAmountDirection { amount_msat: dust_htlc_amt_below_success, outbound: false }; + // We set dust_exposure_to_limiting_feerate to Some as the channel type is not zero fee, but we don't care for the value as it has no impact on the commitment transaction fee + let commitment_tx_fee = chan.context.get_next_local_commitment_stats(&chan.funding, Some(htlc_candidate), true, 0, chan.context.feerate_per_kw, Some(0)).commit_tx_fee_sat * 1000; assert_eq!(commitment_tx_fee, commitment_tx_fee_0_htlcs); 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 = (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); + let htlc_candidate = HTLCAmountDirection { amount_msat: dust_htlc_amt_above_timeout, outbound: true }; + // We set dust_exposure_to_limiting_feerate to Some as the channel type is not zero fee, but we don't care for the value as it has no impact on the commitment transaction fee + let commitment_tx_fee = chan.context.get_next_remote_commitment_stats(&chan.funding, Some(htlc_candidate), false, 0, chan.context.feerate_per_kw, Some(0)).commit_tx_fee_sat * 1000; 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 = (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); + let htlc_candidate = HTLCAmountDirection { amount_msat: htlc_amt_below_success, outbound: false }; + // We set dust_exposure_to_limiting_feerate to Some as the channel type is not zero fee, but we don't care for the value as it has no impact on the commitment transaction fee + let commitment_tx_fee = chan.context.get_next_remote_commitment_stats(&chan.funding, Some(htlc_candidate), false, 0, chan.context.feerate_per_kw, Some(0)).commit_tx_fee_sat * 1000; assert_eq!(commitment_tx_fee, commitment_tx_fee_1_htlc); } diff --git a/lightning/src/sign/tx_builder.rs b/lightning/src/sign/tx_builder.rs index cd16b543f4b..44431433bf1 100644 --- a/lightning/src/sign/tx_builder.rs +++ b/lightning/src/sign/tx_builder.rs @@ -116,7 +116,22 @@ fn get_dust_buffer_feerate(feerate_per_kw: u32) -> u32 { cmp::max(feerate_per_kw.saturating_add(2530), feerate_plus_quarter.unwrap_or(u32::MAX)) } +pub(crate) struct ChannelConstraints { + pub dust_limit_satoshis: u64, + pub channel_reserve_satoshis: u64, + pub htlc_minimum_msat: u64, + pub max_htlc_value_in_flight_msat: u64, + pub max_accepted_htlcs: u64, +} + pub(crate) trait TxBuilder { + fn get_available_balances( + &self, is_outbound_from_holder: bool, channel_value_satoshis: u64, + value_to_holder_msat: u64, pending_htlcs: &[HTLCAmountDirection], feerate_per_kw: u32, + dust_exposure_limiting_feerate: Option, max_dust_htlc_exposure_msat: u64, + holder_channel_constraints: ChannelConstraints, + counterparty_channel_constraints: ChannelConstraints, channel_type: &ChannelTypeFeatures, + ) -> crate::ln::channel::AvailableBalances; fn get_next_commitment_stats( &self, local: bool, is_outbound_from_holder: bool, channel_value_satoshis: u64, value_to_holder_msat: u64, next_commitment_htlcs: &[HTLCAmountDirection], @@ -124,13 +139,6 @@ pub(crate) trait TxBuilder { dust_exposure_limiting_feerate: Option, broadcaster_dust_limit_satoshis: u64, channel_type: &ChannelTypeFeatures, ) -> NextCommitmentStats; - fn commit_tx_fee_sat( - &self, feerate_per_kw: u32, nondust_htlc_count: usize, channel_type: &ChannelTypeFeatures, - ) -> u64; - fn subtract_non_htlc_outputs( - &self, is_outbound_from_holder: bool, value_to_self_after_htlcs: u64, - value_to_remote_after_htlcs: u64, channel_type: &ChannelTypeFeatures, - ) -> (u64, u64); fn build_commitment_transaction( &self, local: bool, commitment_number: u64, per_commitment_point: &PublicKey, channel_parameters: &ChannelTransactionParameters, secp_ctx: &Secp256k1, @@ -144,6 +152,204 @@ pub(crate) trait TxBuilder { pub(crate) struct SpecTxBuilder {} impl TxBuilder for SpecTxBuilder { + fn get_available_balances( + &self, is_outbound_from_holder: bool, channel_value_satoshis: u64, + value_to_holder_msat: u64, pending_htlcs: &[HTLCAmountDirection], feerate_per_kw: u32, + dust_exposure_limiting_feerate: Option, max_dust_htlc_exposure_msat: u64, + holder_channel_constraints: ChannelConstraints, + counterparty_channel_constraints: ChannelConstraints, channel_type: &ChannelTypeFeatures, + ) -> crate::ln::channel::AvailableBalances { + let fee_spike_buffer_htlc = + if channel_type.supports_anchor_zero_fee_commitments() { 0 } else { 1 }; + + let local_stats_max_fee = SpecTxBuilder {}.get_next_commitment_stats( + true, + is_outbound_from_holder, + channel_value_satoshis, + value_to_holder_msat, + pending_htlcs, + fee_spike_buffer_htlc + 1, + feerate_per_kw, + dust_exposure_limiting_feerate, + holder_channel_constraints.dust_limit_satoshis, + channel_type, + ); + let local_stats_min_fee = SpecTxBuilder {}.get_next_commitment_stats( + true, + is_outbound_from_holder, + channel_value_satoshis, + value_to_holder_msat, + pending_htlcs, + fee_spike_buffer_htlc, + feerate_per_kw, + dust_exposure_limiting_feerate, + holder_channel_constraints.dust_limit_satoshis, + channel_type, + ); + let remote_stats = SpecTxBuilder {}.get_next_commitment_stats( + false, + is_outbound_from_holder, + channel_value_satoshis, + value_to_holder_msat, + pending_htlcs, + 1, + feerate_per_kw, + dust_exposure_limiting_feerate, + counterparty_channel_constraints.dust_limit_satoshis, + channel_type, + ); + + let outbound_capacity_msat = local_stats_max_fee + .holder_balance_before_fee_msat + .unwrap_or(0) + .saturating_sub(holder_channel_constraints.channel_reserve_satoshis * 1000); + + let mut available_capacity_msat = outbound_capacity_msat; + let (real_htlc_success_tx_fee_sat, real_htlc_timeout_tx_fee_sat) = + second_stage_tx_fees_sat(channel_type, feerate_per_kw); + + if is_outbound_from_holder { + // 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. + // + // The fee depends on whether the amount we will be sending is above dust or not, + // 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 real_dust_limit_timeout_sat = + real_htlc_timeout_tx_fee_sat + holder_channel_constraints.dust_limit_satoshis; + let mut max_reserved_commit_tx_fee_msat = local_stats_max_fee.commit_tx_fee_sat * 1000; + let mut min_reserved_commit_tx_fee_msat = local_stats_min_fee.commit_tx_fee_sat * 1000; + + if !channel_type.supports_anchors_zero_fee_htlc_tx() { + max_reserved_commit_tx_fee_msat *= + crate::ln::channel::FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE; + min_reserved_commit_tx_fee_msat *= + crate::ln::channel::FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE; + } + + // We will first subtract the fee as if we were above-dust. Then, if the resulting + // value ends up being below dust, we have this fee available again. In that case, + // match the value to right-below-dust. + let capacity_minus_max_commitment_fee_msat = + available_capacity_msat.saturating_sub(max_reserved_commit_tx_fee_msat); + if capacity_minus_max_commitment_fee_msat < real_dust_limit_timeout_sat * 1000 { + let capacity_minus_min_commitment_fee_msat = + available_capacity_msat.saturating_sub(min_reserved_commit_tx_fee_msat); + available_capacity_msat = cmp::min( + real_dust_limit_timeout_sat * 1000 - 1, + capacity_minus_min_commitment_fee_msat, + ); + } else { + available_capacity_msat = capacity_minus_max_commitment_fee_msat; + } + } 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 real_dust_limit_success_sat = + real_htlc_success_tx_fee_sat + counterparty_channel_constraints.dust_limit_satoshis; + let max_reserved_commit_tx_fee_msat = remote_stats.commit_tx_fee_sat * 1000; + + let holder_selected_chan_reserve_msat = + counterparty_channel_constraints.channel_reserve_satoshis * 1000; + if remote_stats.counterparty_balance_before_fee_msat.unwrap_or(0) + < max_reserved_commit_tx_fee_msat + holder_selected_chan_reserve_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); + } + } + + let mut next_outbound_htlc_minimum_msat = + counterparty_channel_constraints.htlc_minimum_msat; + + // If we get close to our maximum dust exposure, we end up in a situation where we can send + // between zero and the remaining dust exposure limit remaining OR above the dust limit. + // Because we cannot express this as a simple min/max, we prefer to tell the user they can + // send above the dust limit (as the router can always overpay to meet the dust limit). + let mut remaining_msat_below_dust_exposure_limit = None; + let mut dust_exposure_dust_limit_msat = 0; + + let dust_buffer_feerate = get_dust_buffer_feerate(feerate_per_kw); + let (buffer_htlc_success_tx_fee_sat, buffer_htlc_timeout_tx_fee_sat) = + second_stage_tx_fees_sat(channel_type, dust_buffer_feerate); + let buffer_dust_limit_success_sat = + buffer_htlc_success_tx_fee_sat + counterparty_channel_constraints.dust_limit_satoshis; + let buffer_dust_limit_timeout_sat = + buffer_htlc_timeout_tx_fee_sat + holder_channel_constraints.dust_limit_satoshis; + + if let Some(extra_htlc_dust_exposure) = + remote_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, buffer_dust_limit_success_sat * 1000); + } + } + + if remote_stats.dust_exposure_msat.saturating_add(buffer_dust_limit_success_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(remote_stats.dust_exposure_msat)); + dust_exposure_dust_limit_msat = + cmp::max(dust_exposure_dust_limit_msat, buffer_dust_limit_success_sat * 1000); + } + + if local_stats_max_fee.dust_exposure_msat as i64 + + buffer_dust_limit_timeout_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(local_stats_max_fee.dust_exposure_msat), + )); + dust_exposure_dust_limit_msat = + cmp::max(dust_exposure_dust_limit_msat, buffer_dust_limit_timeout_sat * 1000); + } + + if let Some(remaining_limit_msat) = remaining_msat_below_dust_exposure_limit { + if available_capacity_msat < dust_exposure_dust_limit_msat { + available_capacity_msat = cmp::min(available_capacity_msat, remaining_limit_msat); + } else { + next_outbound_htlc_minimum_msat = + cmp::max(next_outbound_htlc_minimum_msat, dust_exposure_dust_limit_msat); + } + } + + available_capacity_msat = cmp::min( + available_capacity_msat, + counterparty_channel_constraints.max_htlc_value_in_flight_msat + - pending_htlcs + .iter() + .filter(|htlc| htlc.outbound) + .map(|htlc| htlc.amount_msat) + .sum::(), + ); + + if pending_htlcs.iter().filter(|htlc| htlc.outbound).count() + 1 + > counterparty_channel_constraints.max_accepted_htlcs as usize + { + available_capacity_msat = 0; + } + + crate::ln::channel::AvailableBalances { + inbound_capacity_msat: remote_stats + .counterparty_balance_before_fee_msat + .unwrap_or(0) + .saturating_sub(counterparty_channel_constraints.channel_reserve_satoshis * 1000), + outbound_capacity_msat, + next_outbound_htlc_limit_msat: available_capacity_msat, + next_outbound_htlc_minimum_msat, + } + } fn get_next_commitment_stats( &self, local: bool, is_outbound_from_holder: bool, channel_value_satoshis: u64, value_to_holder_msat: u64, next_commitment_htlcs: &[HTLCAmountDirection], @@ -159,7 +365,6 @@ impl TxBuilder for SpecTxBuilder { if is_zero_fee_comm { debug_assert_eq!(feerate_per_kw, 0); debug_assert_eq!(excess_feerate_opt, Some(0)); - debug_assert_eq!(addl_nondust_htlc_count, 0); } // Calculate inbound htlc count @@ -253,41 +458,6 @@ impl TxBuilder for SpecTxBuilder { extra_nondust_htlc_on_counterparty_tx_dust_exposure_msat, } } - fn commit_tx_fee_sat( - &self, feerate_per_kw: u32, nondust_htlc_count: usize, channel_type: &ChannelTypeFeatures, - ) -> u64 { - commit_tx_fee_sat(feerate_per_kw, nondust_htlc_count, channel_type) - } - fn subtract_non_htlc_outputs( - &self, is_outbound_from_holder: bool, value_to_self_after_htlcs: u64, - value_to_remote_after_htlcs: u64, channel_type: &ChannelTypeFeatures, - ) -> (u64, u64) { - let total_anchors_sat = if channel_type.supports_anchors_zero_fee_htlc_tx() { - ANCHOR_OUTPUT_VALUE_SATOSHI * 2 - } else { - 0 - }; - - let mut local_balance_before_fee_msat = value_to_self_after_htlcs; - let mut remote_balance_before_fee_msat = value_to_remote_after_htlcs; - - // We MUST use saturating subs here, as the funder's balance is not guaranteed to be greater - // than or equal to `total_anchors_sat`. - // - // This is because when the remote party sends an `update_fee` message, we build the new - // commitment transaction *before* checking whether the remote party's balance is enough to - // cover the total anchor sum. - - if is_outbound_from_holder { - local_balance_before_fee_msat = - local_balance_before_fee_msat.saturating_sub(total_anchors_sat * 1000); - } else { - remote_balance_before_fee_msat = - remote_balance_before_fee_msat.saturating_sub(total_anchors_sat * 1000); - } - - (local_balance_before_fee_msat, remote_balance_before_fee_msat) - } #[rustfmt::skip] fn build_commitment_transaction( &self, local: bool, commitment_number: u64, per_commitment_point: &PublicKey, @@ -338,12 +508,14 @@ impl TxBuilder for SpecTxBuilder { // The value going to each party MUST be 0 or positive, even if all HTLCs pending in the // commitment clear by failure. - let commit_tx_fee_sat = self.commit_tx_fee_sat(feerate_per_kw, htlcs_in_tx.len(), &channel_parameters.channel_type_features); + let commit_tx_fee_sat = commit_tx_fee_sat(feerate_per_kw, htlcs_in_tx.len(), &channel_parameters.channel_type_features); let value_to_self_after_htlcs_msat = value_to_self_msat.checked_sub(local_htlc_total_msat).unwrap(); let value_to_remote_after_htlcs_msat = (channel_parameters.channel_value_satoshis * 1000).checked_sub(value_to_self_msat).unwrap().checked_sub(remote_htlc_total_msat).unwrap(); - let (local_balance_before_fee_msat, remote_balance_before_fee_msat) = - self.subtract_non_htlc_outputs(channel_parameters.is_outbound_from_holder, value_to_self_after_htlcs_msat, value_to_remote_after_htlcs_msat, &channel_parameters.channel_type_features); + let (local_balance_before_fee_msat, remote_balance_before_fee_msat) = subtract_addl_outputs( + channel_parameters.is_outbound_from_holder, Some(value_to_self_after_htlcs_msat), Some(value_to_remote_after_htlcs_msat), &channel_parameters.channel_type_features); + let local_balance_before_fee_msat = local_balance_before_fee_msat.unwrap_or(0); + let remote_balance_before_fee_msat = remote_balance_before_fee_msat.unwrap_or(0); // We MUST use saturating subs here, as the funder's balance is not guaranteed to be greater // than or equal to `commit_tx_fee_sat`.