diff --git a/lightning-liquidity/tests/lsps2_integration_tests.rs b/lightning-liquidity/tests/lsps2_integration_tests.rs index 312199e19ec..cc192ef5806 100644 --- a/lightning-liquidity/tests/lsps2_integration_tests.rs +++ b/lightning-liquidity/tests/lsps2_integration_tests.rs @@ -1514,10 +1514,12 @@ fn create_channel_with_manual_broadcast( Event::OpenChannelRequest { temporary_channel_id, .. } => { client_node .node - .accept_inbound_channel_from_trusted_peer_0conf( + .accept_inbound_channel_from_trusted_peer( &temporary_channel_id, &service_node_id, user_channel_id, + true, + false, None, ) .unwrap(); diff --git a/lightning-types/src/features.rs b/lightning-types/src/features.rs index 05a504ab8ca..e808a65dc6c 100644 --- a/lightning-types/src/features.rs +++ b/lightning-types/src/features.rs @@ -168,7 +168,8 @@ mod sealed { // Byte 7 Trampoline | SimpleClose | SpliceProduction, // Byte 8 - 16 - ,,,,,,,,, + ZeroReserve, + ,,,,,,,, // Byte 17 AnchorZeroFeeCommitmentsStaging, // Byte 18 @@ -197,7 +198,8 @@ mod sealed { // Byte 7 Trampoline | SimpleClose | SpliceProduction, // Byte 8 - 16 - ,,,,,,,,, + ZeroReserve, + ,,,,,,,, // Byte 17 AnchorZeroFeeCommitmentsStaging, // Byte 18 @@ -696,6 +698,17 @@ mod sealed { supports_splicing_production, requires_splicing_production ); + define_feature!( + 65, + ZeroReserve, + [InitContext, NodeContext], + "Feature flags for zero reserve.", + set_zero_reserve_optional, + set_zero_reserve_required, + clear_zero_reserve, + supports_zero_reserve, + requires_zero_reserve + ); // By default, allocate enough bytes to cover up to Splice. Update this as new features are // added which we expect to appear commonly across contexts. pub(super) const MIN_FEATURES_ALLOCATION_BYTES: usize = 63_usize.div_ceil(8); diff --git a/lightning/src/events/mod.rs b/lightning/src/events/mod.rs index 3d860e9f363..e266fb50cc7 100644 --- a/lightning/src/events/mod.rs +++ b/lightning/src/events/mod.rs @@ -1657,7 +1657,7 @@ pub enum Event { /// Furthermore, note that if [`ChannelTypeFeatures::supports_zero_conf`] returns true on this type, /// the resulting [`ChannelManager`] will not be readable by versions of LDK prior to /// 0.0.107. Channels setting this type also need to get manually accepted via - /// [`crate::ln::channelmanager::ChannelManager::accept_inbound_channel_from_trusted_peer_0conf`], + /// [`crate::ln::channelmanager::ChannelManager::accept_inbound_channel_from_trusted_peer`], /// or will be rejected otherwise. /// /// [`ChannelManager`]: crate::ln::channelmanager::ChannelManager diff --git a/lightning/src/ln/async_signer_tests.rs b/lightning/src/ln/async_signer_tests.rs index 53187c14168..de3d188a542 100644 --- a/lightning/src/ln/async_signer_tests.rs +++ b/lightning/src/ln/async_signer_tests.rs @@ -78,10 +78,12 @@ fn do_test_open_channel(zero_conf: bool) { Event::OpenChannelRequest { temporary_channel_id, .. } => { nodes[1] .node - .accept_inbound_channel_from_trusted_peer_0conf( + .accept_inbound_channel_from_trusted_peer( temporary_channel_id, &node_a_id, 0, + true, + false, None, ) .expect("Unable to accept inbound zero-conf channel"); @@ -387,10 +389,12 @@ fn do_test_funding_signed_0conf(signer_ops: Vec) { Event::OpenChannelRequest { temporary_channel_id, .. } => { nodes[1] .node - .accept_inbound_channel_from_trusted_peer_0conf( + .accept_inbound_channel_from_trusted_peer( temporary_channel_id, &node_a_id, 0, + true, + false, None, ) .expect("Unable to accept inbound zero-conf channel"); diff --git a/lightning/src/ln/chanmon_update_fail_tests.rs b/lightning/src/ln/chanmon_update_fail_tests.rs index 3fa2073d5ba..843d2cfd647 100644 --- a/lightning/src/ln/chanmon_update_fail_tests.rs +++ b/lightning/src/ln/chanmon_update_fail_tests.rs @@ -3243,7 +3243,9 @@ fn do_test_outbound_reload_without_init_mon(use_0conf: bool) { if use_0conf { nodes[1] .node - .accept_inbound_channel_from_trusted_peer_0conf(&chan_id, &node_a_id, 0, None) + .accept_inbound_channel_from_trusted_peer( + &chan_id, &node_a_id, 0, true, false, None, + ) .unwrap(); } else { nodes[1].node.accept_inbound_channel(&chan_id, &node_a_id, 0, None).unwrap(); @@ -3353,7 +3355,9 @@ fn do_test_inbound_reload_without_init_mon(use_0conf: bool, lock_commitment: boo if use_0conf { nodes[1] .node - .accept_inbound_channel_from_trusted_peer_0conf(&chan_id, &node_a_id, 0, None) + .accept_inbound_channel_from_trusted_peer( + &chan_id, &node_a_id, 0, true, false, None, + ) .unwrap(); } else { nodes[1].node.accept_inbound_channel(&chan_id, &node_a_id, 0, None).unwrap(); diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index a50365c1148..a5bf0bb481c 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -73,7 +73,9 @@ use crate::ln::LN_MAX_MSG_LEN; use crate::offers::static_invoice::StaticInvoice; 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, OnchainStats, SpecTxBuilder, TxBuilder, +}; use crate::sign::{ChannelSigner, EntropySource, NodeSigner, Recipient, SignerProvider}; use crate::types::features::{ChannelTypeFeatures, InitFeatures}; use crate::types::payment::{PaymentHash, PaymentPreimage}; @@ -1060,26 +1062,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, @@ -1099,18 +1081,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 { @@ -1347,7 +1317,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. @@ -2355,24 +2325,56 @@ where /// Doesn't bother handling the /// if-we-removed-it-already-but-haven't-fully-resolved-they-can-still-send-an-inbound-HTLC /// corner case properly. - pub fn get_available_balances( + fn get_available_balances_internal( &self, fee_estimator: &LowerBoundedFeeEstimator, - ) -> AvailableBalances { + include_counterparty_unknown_htlcs: bool, + ) -> Result { match &self.phase { ChannelPhase::Undefined => unreachable!(), - ChannelPhase::Funded(chan) => chan.get_available_balances(fee_estimator), + ChannelPhase::Funded(chan) => chan + .get_available_balances_internal(fee_estimator, include_counterparty_unknown_htlcs), ChannelPhase::UnfundedOutboundV1(chan) => { - chan.context.get_available_balances_for_scope(&chan.funding, fee_estimator) - }, - ChannelPhase::UnfundedInboundV1(chan) => { - chan.context.get_available_balances_for_scope(&chan.funding, fee_estimator) - }, - ChannelPhase::UnfundedV2(chan) => { - chan.context.get_available_balances_for_scope(&chan.funding, fee_estimator) + chan.context.get_available_balances_for_scope( + &chan.funding, + fee_estimator, + include_counterparty_unknown_htlcs, + ) }, + ChannelPhase::UnfundedInboundV1(chan) => chan.context.get_available_balances_for_scope( + &chan.funding, + fee_estimator, + include_counterparty_unknown_htlcs, + ), + ChannelPhase::UnfundedV2(chan) => chan.context.get_available_balances_for_scope( + &chan.funding, + fee_estimator, + include_counterparty_unknown_htlcs, + ), } } + #[cfg(test)] + pub fn get_available_balances_with_counterparty_unknown_htlcs( + &self, fee_estimator: &LowerBoundedFeeEstimator, + ) -> Result { + self.get_available_balances_internal(fee_estimator, true) + } + + pub fn get_available_balances( + &self, fee_estimator: &LowerBoundedFeeEstimator, + ) -> AvailableBalances { + let balances_result = self.get_available_balances_internal(fee_estimator, false); + balances_result.unwrap_or_else(|()| { + debug_assert!(false, "some channel balance has been overdrawn"); + AvailableBalances { + inbound_capacity_msat: 0, + outbound_capacity_msat: 0, + next_outbound_htlc_limit_msat: 0, + next_outbound_htlc_minimum_msat: u64::MAX, + } + }) + } + pub fn minimum_depth(&self) -> Option { self.context().minimum_depth(self.funding()) } @@ -2698,12 +2700,24 @@ impl FundingScope { .funding_pubkey = counterparty_funding_pubkey; // New reserve values are based on the new channel value and are v2-specific - let counterparty_selected_channel_reserve_satoshis = - Some(get_v2_channel_reserve_satoshis(post_channel_value, MIN_CHAN_DUST_LIMIT_SATOSHIS)); - let holder_selected_channel_reserve_satoshis = get_v2_channel_reserve_satoshis( - post_channel_value, - context.counterparty_dust_limit_satoshis, - ); + let counterparty_selected_channel_reserve_satoshis = if prev_funding + .counterparty_selected_channel_reserve_satoshis + .expect("counterparty reserve is set") + == 0 + { + Some(0) + } else { + Some(get_v2_channel_reserve_satoshis(post_channel_value, MIN_CHAN_DUST_LIMIT_SATOSHIS)) + }; + let holder_selected_channel_reserve_satoshis = + if prev_funding.holder_selected_channel_reserve_satoshis == 0 { + 0 + } else { + get_v2_channel_reserve_satoshis( + post_channel_value, + context.counterparty_dust_limit_satoshis, + ) + }; Self { channel_transaction_parameters: post_channel_transaction_parameters, @@ -3562,7 +3576,7 @@ impl ChannelContext { } } - if holder_selected_channel_reserve_satoshis < MIN_CHAN_DUST_LIMIT_SATOSHIS { + if holder_selected_channel_reserve_satoshis < MIN_CHAN_DUST_LIMIT_SATOSHIS && holder_selected_channel_reserve_satoshis != 0 { // Protocol level safety check in place, although it should never happen because // of `MIN_THEIR_CHAN_RESERVE_SATOSHIS` return Err(ChannelError::close(format!("Suitable channel reserve not found. remote_channel_reserve was ({}). dust_limit_satoshis is ({}).", holder_selected_channel_reserve_satoshis, MIN_CHAN_DUST_LIMIT_SATOSHIS))); @@ -3574,7 +3588,7 @@ impl ChannelContext { log_debug!(logger, "channel_reserve_satoshis ({}) is smaller than our dust limit ({}). We can broadcast stale states without any risk, implying this channel is very insecure for our counterparty.", msg_channel_reserve_satoshis, MIN_CHAN_DUST_LIMIT_SATOSHIS); } - if holder_selected_channel_reserve_satoshis < open_channel_fields.dust_limit_satoshis { + if holder_selected_channel_reserve_satoshis < open_channel_fields.dust_limit_satoshis && holder_selected_channel_reserve_satoshis != 0 { return Err(ChannelError::close(format!("Dust limit ({}) too high for the channel reserve we require the remote to keep ({})", open_channel_fields.dust_limit_satoshis, holder_selected_channel_reserve_satoshis))); } @@ -3582,23 +3596,6 @@ impl ChannelContext { debug_assert!(our_funding_satoshis == 0 || msg_push_msat == 0); let value_to_self_msat = our_funding_satoshis * 1000 + msg_push_msat; - // 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 to_remote_satoshis = remote_balance_before_fee_msat / 1000 - 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 { - return Err(ChannelError::close("Insufficient funding amount for initial reserve".to_owned())); - } - let counterparty_shutdown_scriptpubkey = if their_features.supports_upfront_shutdown_script() { match &open_channel_fields.shutdown_scriptpubkey { &Some(ref script) => { @@ -3799,6 +3796,28 @@ impl ChannelContext { interactive_tx_signing_session: None, }; + // 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 = funding.get_value_satoshis() * 1000 - funding.get_value_to_self_msat(); + let htlc_candidate = None; + let include_counterparty_unknown_htlcs = false; + let addl_nondust_htlc_count = MIN_AFFORDABLE_HTLC_COUNT; + let dust_exposure_limiting_feerate = channel_context.get_dust_exposure_limiting_feerate(&fee_estimator, funding.get_channel_type()); + let (remote_stats, _remote_htlcs) = channel_context.get_next_remote_commitment_stats( + &funding, + htlc_candidate, + include_counterparty_unknown_htlcs, + addl_nondust_htlc_count, + channel_context.feerate_per_kw, + dust_exposure_limiting_feerate + ).map_err(|()| ChannelError::close(format!("Funding amount ({} sats) can't even pay fee for initial commitment transaction.", funders_amount_msat / 1000)))?; + + // 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 remote_stats.commitment_stats.counterparty_balance_msat / 1000 < funding.holder_selected_channel_reserve_satoshis { + return Err(ChannelError::close("Insufficient funding amount for initial reserve".to_owned())); + } + Ok((funding, channel_context)) } @@ -3849,17 +3868,6 @@ impl ChannelContext { ); 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 mut secp_ctx = Secp256k1::new(); secp_ctx.seeded_randomize(&entropy_source.get_secure_random_bytes()); @@ -4033,6 +4041,19 @@ impl ChannelContext { interactive_tx_signing_session: None, }; + let htlc_candidate = None; + let include_counterparty_unknown_htlcs = false; + let addl_nondust_htlc_count = MIN_AFFORDABLE_HTLC_COUNT; + let dust_exposure_limiting_feerate = channel_context.get_dust_exposure_limiting_feerate(&fee_estimator, funding.get_channel_type()); + let _local_stats = channel_context.get_next_local_commitment_stats( + &funding, + htlc_candidate, + include_counterparty_unknown_htlcs, + addl_nondust_htlc_count, + channel_context.feerate_per_kw, + dust_exposure_limiting_feerate, + ).map_err(|()| APIError::APIMisuseError { err: format!("Funding amount ({}) can't even pay fee for initial commitment transaction.", funding.get_value_to_self_msat() / 1000)})?; + Ok((funding, channel_context)) } @@ -4285,7 +4306,7 @@ impl ChannelContext { if channel_reserve_satoshis > funding.get_value_satoshis() { return Err(ChannelError::close(format!("Bogus channel_reserve_satoshis ({}). Must not be greater than ({})", channel_reserve_satoshis, funding.get_value_satoshis()))); } - if common_fields.dust_limit_satoshis > funding.holder_selected_channel_reserve_satoshis { + if common_fields.dust_limit_satoshis > funding.holder_selected_channel_reserve_satoshis && funding.holder_selected_channel_reserve_satoshis != 0 { return Err(ChannelError::close(format!("Dust limit ({}) is bigger than our channel reserve ({})", common_fields.dust_limit_satoshis, funding.holder_selected_channel_reserve_satoshis))); } if channel_reserve_satoshis > funding.get_value_satoshis() - funding.holder_selected_channel_reserve_satoshis { @@ -4713,7 +4734,7 @@ impl ChannelContext { &self, funding: &FundingScope, htlc_candidate: Option, include_counterparty_unknown_htlcs: bool, addl_nondust_htlc_count: usize, feerate_per_kw: u32, dust_exposure_limiting_feerate: Option, - ) -> Result { + ) -> Result<(OnchainStats, Vec), ()> { let next_commitment_htlcs = self.get_next_commitment_htlcs( true, htlc_candidate, @@ -4721,7 +4742,13 @@ impl ChannelContext { ); let next_value_to_self_msat = self.get_next_commitment_value_to_self_msat(true, funding); - let ret = SpecTxBuilder {}.get_next_commitment_stats( + let max_dust_htlc_exposure_msat = + self.get_max_dust_htlc_exposure_msat(dust_exposure_limiting_feerate); + + let holder_channel_constraints = self.get_holder_channel_constraints(funding); + let counterparty_channel_constraints = self.get_counterparty_channel_constraints(funding); + + let local_stats = SpecTxBuilder {}.get_onchain_stats( true, funding.is_outbound(), funding.get_value_satoshis(), @@ -4730,7 +4757,9 @@ impl ChannelContext { addl_nondust_htlc_count, feerate_per_kw, dust_exposure_limiting_feerate, - self.holder_dust_limit_satoshis, + max_dust_htlc_exposure_msat, + holder_channel_constraints, + counterparty_channel_constraints, funding.get_channel_type(), )?; @@ -4739,12 +4768,12 @@ impl ChannelContext { if addl_nondust_htlc_count == 0 { *funding.next_local_fee.lock().unwrap() = PredictedNextFee { predicted_feerate: feerate_per_kw, - predicted_nondust_htlc_count: ret.nondust_htlc_count, - predicted_fee_sat: ret.commit_tx_fee_sat, + predicted_nondust_htlc_count: local_stats.commitment_stats.nondust_htlc_count, + predicted_fee_sat: local_stats.commitment_stats.commit_tx_fee_sat, }; } else { let predicted_stats = SpecTxBuilder {} - .get_next_commitment_stats( + .get_onchain_stats( true, funding.is_outbound(), funding.get_value_satoshis(), @@ -4753,10 +4782,13 @@ impl ChannelContext { 0, feerate_per_kw, dust_exposure_limiting_feerate, - self.holder_dust_limit_satoshis, + max_dust_htlc_exposure_msat, + holder_channel_constraints, + counterparty_channel_constraints, funding.get_channel_type(), ) - .expect("Balance after HTLCs and anchors exhausted on local commitment"); + .expect("Balance exhausted on local commitment") + .commitment_stats; *funding.next_local_fee.lock().unwrap() = PredictedNextFee { predicted_feerate: feerate_per_kw, predicted_nondust_htlc_count: predicted_stats.nondust_htlc_count, @@ -4765,14 +4797,14 @@ impl ChannelContext { } } - Ok(ret) + Ok((local_stats, next_commitment_htlcs)) } fn get_next_remote_commitment_stats( &self, funding: &FundingScope, htlc_candidate: Option, include_counterparty_unknown_htlcs: bool, addl_nondust_htlc_count: usize, feerate_per_kw: u32, dust_exposure_limiting_feerate: Option, - ) -> Result { + ) -> Result<(OnchainStats, Vec), ()> { let next_commitment_htlcs = self.get_next_commitment_htlcs( false, htlc_candidate, @@ -4780,7 +4812,13 @@ impl ChannelContext { ); let next_value_to_self_msat = self.get_next_commitment_value_to_self_msat(false, funding); - let ret = SpecTxBuilder {}.get_next_commitment_stats( + let max_dust_htlc_exposure_msat = + self.get_max_dust_htlc_exposure_msat(dust_exposure_limiting_feerate); + + let holder_channel_constraints = self.get_holder_channel_constraints(funding); + let counterparty_channel_constraints = self.get_counterparty_channel_constraints(funding); + + let remote_stats = SpecTxBuilder {}.get_onchain_stats( false, funding.is_outbound(), funding.get_value_satoshis(), @@ -4789,7 +4827,9 @@ impl ChannelContext { addl_nondust_htlc_count, feerate_per_kw, dust_exposure_limiting_feerate, - self.counterparty_dust_limit_satoshis, + max_dust_htlc_exposure_msat, + holder_channel_constraints, + counterparty_channel_constraints, funding.get_channel_type(), )?; @@ -4798,12 +4838,12 @@ impl ChannelContext { if addl_nondust_htlc_count == 0 { *funding.next_remote_fee.lock().unwrap() = PredictedNextFee { predicted_feerate: feerate_per_kw, - predicted_nondust_htlc_count: ret.nondust_htlc_count, - predicted_fee_sat: ret.commit_tx_fee_sat, + predicted_nondust_htlc_count: remote_stats.commitment_stats.nondust_htlc_count, + predicted_fee_sat: remote_stats.commitment_stats.commit_tx_fee_sat, }; } else { let predicted_stats = SpecTxBuilder {} - .get_next_commitment_stats( + .get_onchain_stats( false, funding.is_outbound(), funding.get_value_satoshis(), @@ -4812,10 +4852,13 @@ impl ChannelContext { 0, feerate_per_kw, dust_exposure_limiting_feerate, - self.counterparty_dust_limit_satoshis, + max_dust_htlc_exposure_msat, + holder_channel_constraints, + counterparty_channel_constraints, funding.get_channel_type(), ) - .expect("Balance after HTLCs and anchors exhausted on remote commitment"); + .expect("Balance exhausted on remote commitment") + .commitment_stats; *funding.next_remote_fee.lock().unwrap() = PredictedNextFee { predicted_feerate: feerate_per_kw, predicted_nondust_htlc_count: predicted_stats.nondust_htlc_count, @@ -4824,7 +4867,7 @@ impl ChannelContext { } } - Ok(ret) + Ok((remote_stats, next_commitment_htlcs)) } fn validate_update_add_htlc( @@ -4844,7 +4887,7 @@ impl ChannelContext { let include_counterparty_unknown_htlcs = false; // Don't include the extra fee spike buffer HTLC in calculations let fee_spike_buffer_htlc = 0; - let next_remote_commitment_stats = self + let (remote_stats, remote_htlcs) = self .get_next_remote_commitment_stats( funding, Some(HTLCAmountDirection { outbound: false, amount_msat: msg.amount_msat }), @@ -4857,17 +4900,19 @@ impl ChannelContext { ChannelError::close(String::from("Remote HTLC add would overdraw remaining funds")) })?; - if next_remote_commitment_stats.inbound_htlcs_count - > self.holder_max_accepted_htlcs as usize - { + let inbound_htlcs_count = remote_htlcs.iter().filter(|htlc| !htlc.outbound).count(); + let inbound_htlcs_value_msat: u64 = remote_htlcs + .iter() + .filter_map(|htlc| (!htlc.outbound).then_some(htlc.amount_msat)) + .sum(); + + if inbound_htlcs_count > self.holder_max_accepted_htlcs as usize { return Err(ChannelError::close(format!( "Remote tried to push more than our max accepted HTLCs ({})", self.holder_max_accepted_htlcs, ))); } - if next_remote_commitment_stats.inbound_htlcs_value_msat - > self.holder_max_htlc_value_in_flight_msat - { + if inbound_htlcs_value_msat > self.holder_max_htlc_value_in_flight_msat { return Err(ChannelError::close(format!( "Remote HTLC add would put them over our max HTLC value ({})", self.holder_max_htlc_value_in_flight_msat, @@ -4889,54 +4934,35 @@ impl ChannelContext { // violate the reserve value if we do not do this (as we forget inbound HTLCs from the // Channel state once they will not be present in the next received commitment // transaction). + if remote_stats.commitment_stats.counterparty_balance_msat + < funding.holder_selected_channel_reserve_satoshis * 1000 { - let remote_commit_tx_fee_msat = if funding.is_outbound() { - 0 - } else { - next_remote_commitment_stats.commit_tx_fee_sat * 1000 - }; - if next_remote_commitment_stats.counterparty_balance_before_fee_msat - < remote_commit_tx_fee_msat - { - return Err(ChannelError::close( - "Remote HTLC add would not leave enough to pay for fees".to_owned(), - )); - }; - if next_remote_commitment_stats - .counterparty_balance_before_fee_msat - .saturating_sub(remote_commit_tx_fee_msat) - < funding.holder_selected_channel_reserve_satoshis * 1000 - { - return Err(ChannelError::close( - "Remote HTLC add would put them under remote reserve value".to_owned(), - )); - } + return Err(ChannelError::close( + "Remote HTLC add would put them under remote reserve value".to_owned(), + )); } - if funding.is_outbound() { - let next_local_commitment_stats = self - .get_next_local_commitment_stats( - funding, - Some(HTLCAmountDirection { outbound: false, amount_msat: msg.amount_msat }), - include_counterparty_unknown_htlcs, - fee_spike_buffer_htlc, - self.feerate_per_kw, - dust_exposure_limiting_feerate, - ) - .map_err(|()| { - ChannelError::close(String::from( - "Balance after HTLCs and anchors exhausted on local commitment", - )) - })?; - // Check that they won't violate our local required channel reserve by adding this HTLC. - if next_local_commitment_stats.holder_balance_before_fee_msat + let (local_stats, _local_htlcs) = self + .get_next_local_commitment_stats( + funding, + Some(HTLCAmountDirection { outbound: false, amount_msat: msg.amount_msat }), + include_counterparty_unknown_htlcs, + fee_spike_buffer_htlc, + self.feerate_per_kw, + dust_exposure_limiting_feerate, + ) + .map_err(|()| { + ChannelError::close(String::from("Balance exhausted on local commitment")) + })?; + + // Check that they won't violate our local required channel reserve by adding this HTLC. + if funding.is_outbound() + && local_stats.commitment_stats.holder_balance_msat < funding.counterparty_selected_channel_reserve_satoshis.unwrap() * 1000 - + next_local_commitment_stats.commit_tx_fee_sat * 1000 - { - return Err(ChannelError::close( - "Cannot accept HTLC that would put our balance under counterparty-announced channel reserve value".to_owned() - )); - } + { + return Err(ChannelError::close( + "Cannot accept HTLC that would put our balance under counterparty-announced channel reserve value".to_owned() + )); } Ok(()) @@ -4952,7 +4978,7 @@ impl ChannelContext { // Do not include outbound update_add_htlc's in the holding cell, or those which haven't yet been ACK'ed // by the counterparty (ie. LocalAnnounced HTLCs) let include_counterparty_unknown_htlcs = false; - let next_local_commitment_stats = self + let (local_stats, _local_htlcs) = self .get_next_local_commitment_stats( funding, None, @@ -4962,23 +4988,18 @@ impl ChannelContext { dust_exposure_limiting_feerate, ) .map_err(|()| { - ChannelError::close(String::from( - "Balance after HTLCs and anchors exhausted on local commitment", - )) + ChannelError::close(String::from("Funding remote cannot afford proposed new fee")) })?; - next_local_commitment_stats - .get_holder_counterparty_balances_incl_fee_msat() - .and_then(|(_, counterparty_balance_incl_fee_msat)| { - counterparty_balance_incl_fee_msat - .checked_sub(funding.holder_selected_channel_reserve_satoshis * 1000) - .ok_or(()) - }) - .map_err(|()| { - ChannelError::close("Funding remote cannot afford proposed new fee".to_owned()) - })?; + local_stats + .commitment_stats + .counterparty_balance_msat + .checked_sub(funding.holder_selected_channel_reserve_satoshis * 1000) + .ok_or(ChannelError::close( + "Funding remote cannot afford proposed new fee".to_owned(), + ))?; - let next_remote_commitment_stats = self + let (remote_stats, _remote_htlcs) = self .get_next_remote_commitment_stats( funding, None, @@ -4988,28 +5009,26 @@ impl ChannelContext { dust_exposure_limiting_feerate, ) .map_err(|()| { - ChannelError::close(String::from( - "Balance after HTLCs and anchors exhausted on remote commitment", - )) + ChannelError::close(String::from("Balance exhausted on remote commitment")) })?; let max_dust_htlc_exposure_msat = self.get_max_dust_htlc_exposure_msat(dust_exposure_limiting_feerate); - if next_local_commitment_stats.dust_exposure_msat > max_dust_htlc_exposure_msat { + if local_stats.commitment_stats.dust_exposure_msat > max_dust_htlc_exposure_msat { return Err(ChannelError::close( format!( "Peer sent update_fee with a feerate ({}) which may over-expose us to dust-in-flight on our own transactions (totaling {} msat)", new_feerate_per_kw, - next_local_commitment_stats.dust_exposure_msat, + local_stats.commitment_stats.dust_exposure_msat, ) )); } - if next_remote_commitment_stats.dust_exposure_msat > max_dust_htlc_exposure_msat { + if remote_stats.commitment_stats.dust_exposure_msat > max_dust_htlc_exposure_msat { return Err(ChannelError::close( format!( "Peer sent update_fee with a feerate ({}) which may over-expose us to dust-in-flight on our counterparty's transactions (totaling {} msat)", new_feerate_per_kw, - next_remote_commitment_stats.dust_exposure_msat, + remote_stats.commitment_stats.dust_exposure_msat, ) )); } @@ -5037,6 +5056,12 @@ impl ChannelContext { let commitment_txid = { let trusted_tx = commitment_data.tx.trust(); let bitcoin_tx = trusted_tx.built_transaction(); + if bitcoin_tx.transaction.output.is_empty() { + return Err(ChannelError::close( + "Commitment tx from peer has 0 outputs".to_owned(), + )); + } + let sighash = bitcoin_tx.get_sighash_all(&funding_script, funding.get_value_satoshis()); log_trace!(logger, "Checking commitment tx signature {} by key {} against tx {} (sighash {}) with redeemscript {} in channel {}", @@ -5155,27 +5180,27 @@ impl ChannelContext { // Include outbound update_add_htlc's in the holding cell, and those which haven't yet been ACK'ed by // the counterparty (ie. LocalAnnounced HTLCs) let include_counterparty_unknown_htlcs = true; - let next_remote_commitment_stats = if let Ok(stats) = self.get_next_remote_commitment_stats( - funding, - None, - include_counterparty_unknown_htlcs, - CONCURRENT_INBOUND_HTLC_FEE_BUFFER as usize, - feerate_per_kw, - dust_exposure_limiting_feerate, - ) { + let (remote_stats, _remote_htlcs) = if let Ok(stats) = self + .get_next_remote_commitment_stats( + funding, + None, + include_counterparty_unknown_htlcs, + CONCURRENT_INBOUND_HTLC_FEE_BUFFER as usize, + feerate_per_kw, + dust_exposure_limiting_feerate, + ) { stats } else { log_debug!( logger, - "Cannot afford to send new feerate due to balance after HTLCs and anchors exhausted on remote commitment", + "Cannot afford to send new feerate due to balance exhausted on remote commitment", ); return false; }; // Note that `stats.commit_tx_fee_sat` accounts for any HTLCs that transition from non-dust to dust // under a higher feerate (in the case where HTLC-transactions pay endogenous fees). - if next_remote_commitment_stats.holder_balance_before_fee_msat - < next_remote_commitment_stats.commit_tx_fee_sat * 1000 - + funding.counterparty_selected_channel_reserve_satoshis.unwrap() * 1000 + if remote_stats.commitment_stats.holder_balance_msat + < funding.counterparty_selected_channel_reserve_satoshis.unwrap() * 1000 { //TODO: auto-close after a number of failures? log_debug!(logger, "Cannot afford to send new feerate at {}", feerate_per_kw); @@ -5186,7 +5211,7 @@ impl ChannelContext { // `feerate_per_kw`. let max_dust_htlc_exposure_msat = self.get_max_dust_htlc_exposure_msat(dust_exposure_limiting_feerate); - if next_remote_commitment_stats.dust_exposure_msat > max_dust_htlc_exposure_msat { + if remote_stats.commitment_stats.dust_exposure_msat > max_dust_htlc_exposure_msat { log_debug!( logger, "Cannot afford to send new feerate at {} without infringing max dust htlc exposure", @@ -5195,7 +5220,7 @@ impl ChannelContext { return false; } - let next_local_commitment_stats = if let Ok(stats) = self.get_next_local_commitment_stats( + let (local_stats, _local_htlcs) = if let Ok(stats) = self.get_next_local_commitment_stats( funding, None, include_counterparty_unknown_htlcs, @@ -5207,11 +5232,11 @@ impl ChannelContext { } else { log_debug!( logger, - "Cannot afford to send new feerate due to balance after HTLCs and anchors exhausted on local commitment", + "Cannot afford to send new feerate due to balance exhausted on local commitment", ); return false; }; - if next_local_commitment_stats.dust_exposure_msat > max_dust_htlc_exposure_msat { + if local_stats.commitment_stats.dust_exposure_msat > max_dust_htlc_exposure_msat { log_debug!( logger, "Cannot afford to send new feerate at {} without infringing max dust htlc exposure", @@ -5241,7 +5266,7 @@ impl ChannelContext { cmp::max(self.feerate_per_kw, self.pending_update_fee.map(|(fee, _)| fee).unwrap_or(0)); // 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 next_local_commitment_stats = self + let (local_stats, _local_htlcs) = self .get_next_local_commitment_stats( funding, None, @@ -5251,10 +5276,13 @@ impl ChannelContext { dust_exposure_limiting_feerate, ) .map_err(|()| { - log_trace!(logger, "Attempting to fail HTLC due to balance after HTLCs and anchors exhausted on local commitment"); + log_trace!( + logger, + "Attempting to fail HTLC due to balance exhausted on local commitment" + ); LocalHTLCFailureReason::ChannelBalanceOverdrawn })?; - let next_remote_commitment_stats = self + let (remote_stats, _remote_htlcs) = self .get_next_remote_commitment_stats( funding, None, @@ -5264,46 +5292,64 @@ impl ChannelContext { dust_exposure_limiting_feerate, ) .map_err(|()| { - log_trace!(logger, "Attempting to fail HTLC due to balance after HTLCs and anchors exhausted on remote commitment"); + log_trace!( + logger, + "Attempting to fail HTLC due to balance exhausted on remote commitment" + ); LocalHTLCFailureReason::ChannelBalanceOverdrawn })?; let max_dust_htlc_exposure_msat = self.get_max_dust_htlc_exposure_msat(dust_exposure_limiting_feerate); - if next_remote_commitment_stats.dust_exposure_msat > max_dust_htlc_exposure_msat { + if remote_stats.commitment_stats.dust_exposure_msat > max_dust_htlc_exposure_msat { // Note that the total dust exposure includes both the dust HTLCs and the excess mining fees of // the counterparty commitment transaction log_info!( logger, "Cannot accept value that would put our total dust exposure at {} over the limit {} on counterparty commitment tx", - next_remote_commitment_stats.dust_exposure_msat, + remote_stats.commitment_stats.dust_exposure_msat, max_dust_htlc_exposure_msat, ); return Err(LocalHTLCFailureReason::DustLimitCounterparty); } - if next_local_commitment_stats.dust_exposure_msat > max_dust_htlc_exposure_msat { + if local_stats.commitment_stats.dust_exposure_msat > max_dust_htlc_exposure_msat { log_info!( logger, "Cannot accept value that would put our exposure to dust HTLCs at {} over the limit {} on holder commitment tx", - next_local_commitment_stats.dust_exposure_msat, + local_stats.commitment_stats.dust_exposure_msat, max_dust_htlc_exposure_msat, ); return Err(LocalHTLCFailureReason::DustLimitHolder); } if !funding.is_outbound() { - let mut remote_fee_incl_fee_spike_buffer_htlc_msat = - next_remote_commitment_stats.commit_tx_fee_sat * 1000; // Note that with anchor outputs we are no longer as sensitive to fee spikes, so we don't need // to account for them. - if !funding.get_channel_type().supports_anchors_zero_fee_htlc_tx() { - remote_fee_incl_fee_spike_buffer_htlc_msat *= - FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE; - } - if next_remote_commitment_stats - .counterparty_balance_before_fee_msat - .saturating_sub(funding.holder_selected_channel_reserve_satoshis * 1000) - < remote_fee_incl_fee_spike_buffer_htlc_msat + let fee_spike_multiple = + if !funding.get_channel_type().supports_anchors_zero_fee_htlc_tx() { + FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE as u32 + } else { + 1 + }; + let spiked_feerate = feerate * fee_spike_multiple; + let (remote_stats, _remote_htlcs) = self + .get_next_remote_commitment_stats( + funding, + None, + include_counterparty_unknown_htlcs, + fee_spike_buffer_htlc, + spiked_feerate, + dust_exposure_limiting_feerate, + ) + .map_err(|()| { + log_trace!( + logger, + "Attempting to fail HTLC due to balance exhausted on remote commitment" + ); + LocalHTLCFailureReason::FeeSpikeBuffer + })?; + if remote_stats.commitment_stats.counterparty_balance_msat + < funding.holder_selected_channel_reserve_satoshis * 1000 { log_info!( logger, @@ -5422,7 +5468,7 @@ impl ChannelContext { let value_to_self_msat = (funding.value_to_self_msat + value_to_self_claimed_msat).checked_sub(value_to_remote_claimed_msat).unwrap(); - let (tx, stats) = SpecTxBuilder {}.build_commitment_transaction( + let (tx, _stats) = SpecTxBuilder {}.build_commitment_transaction( local, commitment_number, per_commitment_point, @@ -5438,7 +5484,7 @@ impl ChannelContext { { let PredictedNextFee { predicted_feerate, predicted_nondust_htlc_count, predicted_fee_sat } = if local { *funding.next_local_fee.lock().unwrap() } else { *funding.next_remote_fee.lock().unwrap() }; if predicted_feerate == tx.negotiated_feerate_per_kw() && predicted_nondust_htlc_count == tx.nondust_htlcs().len() { - assert_eq!(predicted_fee_sat, stats.commit_tx_fee_sat); + assert_eq!(predicted_fee_sat, _stats.commit_tx_fee_sat); } } #[cfg(debug_assertions)] @@ -5450,10 +5496,10 @@ impl ChannelContext { } 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); + 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); } @@ -5524,111 +5570,6 @@ impl ChannelContext { 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 { @@ -5722,315 +5663,69 @@ impl ChannelContext { outbound_details } - #[rustfmt::skip] - fn get_available_balances_for_scope( - &self, funding: &FundingScope, fee_estimator: &LowerBoundedFeeEstimator, - ) -> AvailableBalances { - let context = &self; - // 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, funding.get_channel_type(), - ); - let htlc_stats = context.get_pending_htlc_stats(funding, None, 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( - 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_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, + 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, } } - /// 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. - _ => {}, - } + 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, } - - 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, + fn get_available_balances_for_scope( + &self, funding: &FundingScope, fee_estimator: &LowerBoundedFeeEstimator, + include_counterparty_unknown_htlcs: bool, + ) -> Result { + let dust_exposure_limiting_feerate = self.get_dust_exposure_limiting_feerate( + &fee_estimator, funding.get_channel_type(), ); - 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; - } + let balances = self.get_next_remote_commitment_stats( + funding, + None, + include_counterparty_unknown_htlcs, + 0, + self.feerate_per_kw, + dust_exposure_limiting_feerate + ).map(|(remote_stats, _)| remote_stats.available_balances)?; - 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, - _ => {}, - } + #[cfg(debug_assertions)] + if balances.next_outbound_htlc_limit_msat >= balances.next_outbound_htlc_minimum_msat + && balances.next_outbound_htlc_limit_msat != 0 + { + let (remote_stats, _remote_htlcs) = self.get_next_remote_commitment_stats( + funding, + Some(HTLCAmountDirection { + outbound: true, + // Note that this likely creates a non-dust HTLC, we could add a check for the + // biggest dust HTLC to make sure we still have a broadcastable commitment in + // that case. + amount_msat: balances.next_outbound_htlc_limit_msat, + }), + include_counterparty_unknown_htlcs, + 0, + self.feerate_per_kw, + dust_exposure_limiting_feerate + ).unwrap(); + assert!(remote_stats.commitment_stats.holder_balance_msat + >= funding.counterparty_selected_channel_reserve_satoshis.unwrap_or(0) * 1000); } - let num_htlcs = included_htlcs + addl_htlcs; - SpecTxBuilder {}.commit_tx_fee_sat(context.feerate_per_kw, num_htlcs, funding.get_channel_type()) * 1000 + Ok(balances) } #[rustfmt::skip] @@ -12411,7 +12106,7 @@ where // We are not interested in dust exposure let dust_exposure_limiting_feerate = None; - let local_commitment_stats = self + let (local_stats, _local_htlcs) = self .context .get_next_local_commitment_stats( funding, @@ -12421,13 +12116,9 @@ where self.context.feerate_per_kw, dust_exposure_limiting_feerate, ) - .map_err(|()| "Balance after HTLCs and anchors exhausted on local commitment")?; - let (holder_balance_on_local_msat, counterparty_balance_on_local_msat) = - local_commitment_stats - .get_holder_counterparty_balances_incl_fee_msat() - .map_err(|()| "Channel funder cannot afford the fee on local commitment")?; + .map_err(|()| "Balance exhausted on local commitment")?; - let remote_commitment_stats = self + let (remote_stats, _remote_htlcs) = self .context .get_next_remote_commitment_stats( funding, @@ -12437,18 +12128,19 @@ where self.context.feerate_per_kw, dust_exposure_limiting_feerate, ) - .map_err(|()| "Balance after HTLCs and anchors exhausted on remote commitment")?; - let (holder_balance_on_remote_msat, counterparty_balance_on_remote_msat) = - remote_commitment_stats - .get_holder_counterparty_balances_incl_fee_msat() - .map_err(|()| "Channel funder cannot afford the fee on remote commitment")?; + .map_err(|()| "Balance exhausted on remote commitment")?; let holder_balance_floor = Amount::from_sat( - cmp::min(holder_balance_on_local_msat, holder_balance_on_remote_msat) / 1000, + cmp::min( + local_stats.commitment_stats.holder_balance_msat, + remote_stats.commitment_stats.holder_balance_msat, + ) / 1000, ); let counterparty_balance_floor = Amount::from_sat( - cmp::min(counterparty_balance_on_local_msat, counterparty_balance_on_remote_msat) - / 1000, + cmp::min( + local_stats.commitment_stats.counterparty_balance_msat, + remote_stats.commitment_stats.counterparty_balance_msat, + ) / 1000, ); Ok((holder_balance_floor, counterparty_balance_floor)) @@ -12561,7 +12253,13 @@ where return Err((LocalHTLCFailureReason::ZeroAmount, "Cannot send 0-msat HTLC".to_owned())); } - let available_balances = self.get_available_balances(fee_estimator); + let available_balances = + self.get_available_balances_internal(fee_estimator, true).map_err(|()| { + ( + LocalHTLCFailureReason::ChannelBalanceOverdrawn, + "Channel balance overdrawn".to_owned(), + ) + })?; if amount_msat < available_balances.next_outbound_htlc_minimum_msat { return Err(( LocalHTLCFailureReason::HTLCMinimum, @@ -12656,21 +12354,37 @@ where } #[rustfmt::skip] - pub(super) fn get_available_balances( - &self, fee_estimator: &LowerBoundedFeeEstimator, - ) -> AvailableBalances { - core::iter::once(&self.funding) - .chain(self.pending_funding().iter()) - .map(|funding| self.context.get_available_balances_for_scope(funding, fee_estimator)) - .reduce(|acc, e| { - AvailableBalances { + pub(super) fn get_available_balances_internal( + &self, fee_estimator: &LowerBoundedFeeEstimator, include_counterparty_unknown_htlcs: bool, + ) -> Result { + let init = self.context.get_available_balances_for_scope(&self.funding, fee_estimator, include_counterparty_unknown_htlcs)?; + self.pending_funding().iter().try_fold( + init, + |acc, funding| { + let e = self.context.get_available_balances_for_scope(funding, fee_estimator, include_counterparty_unknown_htlcs)?; + Ok(AvailableBalances { inbound_capacity_msat: acc.inbound_capacity_msat.min(e.inbound_capacity_msat), outbound_capacity_msat: acc.outbound_capacity_msat.min(e.outbound_capacity_msat), next_outbound_htlc_limit_msat: acc.next_outbound_htlc_limit_msat.min(e.next_outbound_htlc_limit_msat), next_outbound_htlc_minimum_msat: acc.next_outbound_htlc_minimum_msat.max(e.next_outbound_htlc_minimum_msat), - } + }) }) - .expect("At least one FundingScope is always provided") + } + + #[rustfmt::skip] + pub(super) fn get_available_balances( + &self, fee_estimator: &LowerBoundedFeeEstimator, + ) -> AvailableBalances { + let balances_result = self.get_available_balances_internal(fee_estimator, false); + balances_result.unwrap_or_else(|()| { + debug_assert!(false, "some channel balance has been overdrawn"); + AvailableBalances { + inbound_capacity_msat: 0, + outbound_capacity_msat: 0, + next_outbound_htlc_limit_msat: 0, + next_outbound_htlc_minimum_msat: u64::MAX, + } + }) } fn build_commitment_no_status_check(&mut self, logger: &L) -> ChannelMonitorUpdate { @@ -13366,15 +13080,20 @@ impl OutboundV1Channel { pub fn new( fee_estimator: &LowerBoundedFeeEstimator, entropy_source: &ES, signer_provider: &SP, counterparty_node_id: PublicKey, their_features: &InitFeatures, channel_value_satoshis: u64, push_msat: u64, user_id: u128, config: &UserConfig, current_chain_height: u32, - outbound_scid_alias: u64, temporary_channel_id: Option, logger: L + outbound_scid_alias: u64, temporary_channel_id: Option, logger: L, is_0reserve: bool, ) -> Result, APIError> { - let holder_selected_channel_reserve_satoshis = get_holder_selected_channel_reserve_satoshis(channel_value_satoshis, config); - if holder_selected_channel_reserve_satoshis < MIN_CHAN_DUST_LIMIT_SATOSHIS { - // Protocol level safety check in place, although it should never happen because - // of `MIN_THEIR_CHAN_RESERVE_SATOSHIS` - return Err(APIError::APIMisuseError { err: format!("Holder selected channel reserve below \ - implemention limit dust_limit_satoshis {}", holder_selected_channel_reserve_satoshis) }); - } + let holder_selected_channel_reserve_satoshis = if is_0reserve { + 0 + } else { + let reserve_satoshis = get_holder_selected_channel_reserve_satoshis(channel_value_satoshis, config); + if reserve_satoshis < MIN_CHAN_DUST_LIMIT_SATOSHIS { + // Protocol level safety check in place, although it should never happen because + // of `MIN_THEIR_CHAN_RESERVE_SATOSHIS` + return Err(APIError::APIMisuseError { err: format!("Holder selected channel reserve below \ + implemention limit dust_limit_satoshis {}", reserve_satoshis) }); + } + reserve_satoshis + }; let channel_keys_id = signer_provider.generate_channel_keys_id(false, user_id); let holder_signer = signer_provider.derive_channel_signer(channel_keys_id); @@ -13742,7 +13461,7 @@ impl InboundV1Channel { fee_estimator: &LowerBoundedFeeEstimator, entropy_source: &ES, signer_provider: &SP, counterparty_node_id: PublicKey, our_supported_features: &ChannelTypeFeatures, their_features: &InitFeatures, msg: &msgs::OpenChannel, user_id: u128, config: &UserConfig, - current_chain_height: u32, logger: &L, is_0conf: bool, + current_chain_height: u32, logger: &L, is_0conf: bool, is_0reserve: bool, ) -> Result, ChannelError> { let logger = WithContext::from(logger, Some(counterparty_node_id), Some(msg.common_fields.temporary_channel_id), None); @@ -13750,7 +13469,11 @@ impl InboundV1Channel { // support this channel type. let channel_type = channel_type_from_open_channel(&msg.common_fields, our_supported_features)?; - let holder_selected_channel_reserve_satoshis = get_holder_selected_channel_reserve_satoshis(msg.common_fields.funding_satoshis, config); + let holder_selected_channel_reserve_satoshis = if is_0reserve { + 0 + } else { + get_holder_selected_channel_reserve_satoshis(msg.common_fields.funding_satoshis, config) + }; let counterparty_pubkeys = ChannelPublicKeys { funding_pubkey: msg.common_fields.funding_pubkey, revocation_basepoint: RevocationBasepoint::from(msg.common_fields.revocation_basepoint), @@ -15814,9 +15537,9 @@ mod tests { use crate::chain::BestBlock; use crate::ln::chan_utils::{self, commit_tx_fee_sat, ChannelTransactionParameters}; 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, @@ -15831,6 +15554,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}; use crate::sync::Mutex; @@ -15972,6 +15696,7 @@ mod tests { 42, None, &logger, + false, ); match res { Err(APIError::IncompatibleShutdownScript { script }) => { @@ -15998,7 +15723,7 @@ mod tests { let node_a_node_id = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap()); let config = UserConfig::default(); - let mut node_a_chan = OutboundV1Channel::<&TestKeysInterface>::new(&bounded_fee_estimator, &&keys_provider, &&keys_provider, node_a_node_id, &channelmanager::provided_init_features(&config), 10000000, 100000, 42, &config, 0, 42, None, &logger).unwrap(); + let mut node_a_chan = OutboundV1Channel::<&TestKeysInterface>::new(&bounded_fee_estimator, &&keys_provider, &&keys_provider, node_a_node_id, &channelmanager::provided_init_features(&config), 10000000, 100000, 42, &config, 0, 42, None, &logger, false).unwrap(); // Now change the fee so we can check that the fee in the open_channel message is the // same as the old fee. @@ -16027,13 +15752,13 @@ mod tests { // Create Node A's channel pointing to Node B's pubkey let node_b_node_id = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap()); let config = UserConfig::default(); - let mut node_a_chan = OutboundV1Channel::<&TestKeysInterface>::new(&feeest, &&keys_provider, &&keys_provider, node_b_node_id, &channelmanager::provided_init_features(&config), 10000000, 100000, 42, &config, 0, 42, None, &logger).unwrap(); + let mut node_a_chan = OutboundV1Channel::<&TestKeysInterface>::new(&feeest, &&keys_provider, &&keys_provider, node_b_node_id, &channelmanager::provided_init_features(&config), 10_000_000, 100_000_000, 42, &config, 0, 42, None, &logger, false).unwrap(); // Create Node B's channel by receiving Node A's open_channel message // Make sure A's dust limit is as we expect. let open_channel_msg = node_a_chan.get_open_channel(ChainHash::using_genesis_block(network), &&logger).unwrap(); let node_b_node_id = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[7; 32]).unwrap()); - let mut node_b_chan = InboundV1Channel::<&TestKeysInterface>::new(&feeest, &&keys_provider, &&keys_provider, node_b_node_id, &channelmanager::provided_channel_type_features(&config), &channelmanager::provided_init_features(&config), &open_channel_msg, 7, &config, 0, &&logger, /*is_0conf=*/false).unwrap(); + let mut node_b_chan = InboundV1Channel::<&TestKeysInterface>::new(&feeest, &&keys_provider, &&keys_provider, node_b_node_id, &channelmanager::provided_channel_type_features(&config), &channelmanager::provided_init_features(&config), &open_channel_msg, 7, &config, 0, &&logger, /*is_0conf=*/false, /*is_0reserve=*/false).unwrap(); // Node B --> Node A: accept channel, explicitly setting B's dust limit. let mut accept_channel_msg = node_b_chan.accept_inbound_channel(&&logger).unwrap(); @@ -16086,8 +15811,8 @@ 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 }; + let local_commit_tx_fee = node_a_chan.context.get_next_local_commitment_stats(&node_a_chan.funding, Some(htlc_candidate), false, 0, node_a_chan.context.feerate_per_kw, None).unwrap().0.commitment_stats.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); @@ -16095,15 +15820,15 @@ 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 }; + let remote_commit_tx_fee = node_a_chan.context.get_next_remote_commitment_stats(&node_a_chan.funding, Some(htlc_candidate), false, 0, node_a_chan.context.feerate_per_kw, None).unwrap().0.commitment_stats.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. @@ -16117,7 +15842,8 @@ mod tests { let node_id = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap()); let config = UserConfig::default(); - let mut chan = OutboundV1Channel::<&TestKeysInterface>::new(&fee_est, &&keys_provider, &&keys_provider, node_id, &channelmanager::provided_init_features(&config), 10000000, 100000, 42, &config, 0, 42, None, &logger).unwrap(); + let mut chan = OutboundV1Channel::<&TestKeysInterface>::new(&fee_est, &&keys_provider, &&keys_provider, node_id, &channelmanager::provided_init_features(&config), 10_000_000, 100_000_000, 42, &config, 0, 42, None, &logger, false).unwrap(); + chan.context.counterparty_max_htlc_value_in_flight_msat = 1_000_000_000; 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; @@ -16128,28 +15854,28 @@ 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 }; + let commitment_tx_fee = chan.context.get_next_local_commitment_stats(&chan.funding, Some(htlc_candidate), false, 0, chan.context.feerate_per_kw, None).unwrap().0.commitment_stats.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 }; + let commitment_tx_fee = chan.context.get_next_local_commitment_stats(&chan.funding, Some(htlc_candidate), false, 0, chan.context.feerate_per_kw, None).unwrap().0.commitment_stats.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 }; + let commitment_tx_fee = chan.context.get_next_remote_commitment_stats(&chan.funding, Some(htlc_candidate), false, 0, chan.context.feerate_per_kw, None).unwrap().0.commitment_stats.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 }; + let commitment_tx_fee = chan.context.get_next_remote_commitment_stats(&chan.funding, Some(htlc_candidate), false, 0, chan.context.feerate_per_kw, None).unwrap().0.commitment_stats.commit_tx_fee_sat * 1000; assert_eq!(commitment_tx_fee, commitment_tx_fee_1_htlc); } @@ -16171,12 +15897,12 @@ mod tests { // Create Node A's channel pointing to Node B's pubkey let node_b_node_id = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap()); let config = UserConfig::default(); - let mut node_a_chan = OutboundV1Channel::<&TestKeysInterface>::new(&feeest, &&keys_provider, &&keys_provider, node_b_node_id, &channelmanager::provided_init_features(&config), 10000000, 100000, 42, &config, 0, 42, None, &logger).unwrap(); + let mut node_a_chan = OutboundV1Channel::<&TestKeysInterface>::new(&feeest, &&keys_provider, &&keys_provider, node_b_node_id, &channelmanager::provided_init_features(&config), 10000000, 100000, 42, &config, 0, 42, None, &logger, false).unwrap(); // Create Node B's channel by receiving Node A's open_channel message let open_channel_msg = node_a_chan.get_open_channel(chain_hash, &&logger).unwrap(); let node_b_node_id = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[7; 32]).unwrap()); - let mut node_b_chan = InboundV1Channel::<&TestKeysInterface>::new(&feeest, &&keys_provider, &&keys_provider, node_b_node_id, &channelmanager::provided_channel_type_features(&config), &channelmanager::provided_init_features(&config), &open_channel_msg, 7, &config, 0, &&logger, /*is_0conf=*/false).unwrap(); + let mut node_b_chan = InboundV1Channel::<&TestKeysInterface>::new(&feeest, &&keys_provider, &&keys_provider, node_b_node_id, &channelmanager::provided_channel_type_features(&config), &channelmanager::provided_init_features(&config), &open_channel_msg, 7, &config, 0, &&logger, /*is_0conf=*/false, /*is_0reserve=*/false).unwrap(); // Node B --> Node A: accept channel let accept_channel_msg = node_b_chan.accept_inbound_channel(&&logger).unwrap(); @@ -16237,12 +15963,12 @@ mod tests { // Test that `OutboundV1Channel::new` creates a channel with the correct value for // `holder_max_htlc_value_in_flight_msat`, when configured with a valid percentage value, // which is set to the lower bound + 1 (2%) of the `channel_value`. - let mut chan_1 = OutboundV1Channel::<&TestKeysInterface>::new(&feeest, &&keys_provider, &&keys_provider, outbound_node_id, &channelmanager::provided_init_features(&config_2_percent), 10000000, 100000, 42, &config_2_percent, 0, 42, None, &logger).unwrap(); + let mut chan_1 = OutboundV1Channel::<&TestKeysInterface>::new(&feeest, &&keys_provider, &&keys_provider, outbound_node_id, &channelmanager::provided_init_features(&config_2_percent), 10000000, 100000, 42, &config_2_percent, 0, 42, None, &logger, false).unwrap(); let chan_1_value_msat = chan_1.funding.get_value_satoshis() * 1000; assert_eq!(chan_1.context.holder_max_htlc_value_in_flight_msat, (chan_1_value_msat as f64 * 0.02) as u64); // Test with the upper bound - 1 of valid values (99%). - let chan_2 = OutboundV1Channel::<&TestKeysInterface>::new(&feeest, &&keys_provider, &&keys_provider, outbound_node_id, &channelmanager::provided_init_features(&config_99_percent), 10000000, 100000, 42, &config_99_percent, 0, 42, None, &logger).unwrap(); + let chan_2 = OutboundV1Channel::<&TestKeysInterface>::new(&feeest, &&keys_provider, &&keys_provider, outbound_node_id, &channelmanager::provided_init_features(&config_99_percent), 10000000, 100000, 42, &config_99_percent, 0, 42, None, &logger, false).unwrap(); let chan_2_value_msat = chan_2.funding.get_value_satoshis() * 1000; assert_eq!(chan_2.context.holder_max_htlc_value_in_flight_msat, (chan_2_value_msat as f64 * 0.99) as u64); @@ -16251,38 +15977,38 @@ mod tests { // Test that `InboundV1Channel::new` creates a channel with the correct value for // `holder_max_htlc_value_in_flight_msat`, when configured with a valid percentage value, // which is set to the lower bound - 1 (2%) of the `channel_value`. - let chan_3 = InboundV1Channel::<&TestKeysInterface>::new(&feeest, &&keys_provider, &&keys_provider, inbound_node_id, &channelmanager::provided_channel_type_features(&config_2_percent), &channelmanager::provided_init_features(&config_2_percent), &chan_1_open_channel_msg, 7, &config_2_percent, 0, &&logger, /*is_0conf=*/false).unwrap(); + let chan_3 = InboundV1Channel::<&TestKeysInterface>::new(&feeest, &&keys_provider, &&keys_provider, inbound_node_id, &channelmanager::provided_channel_type_features(&config_2_percent), &channelmanager::provided_init_features(&config_2_percent), &chan_1_open_channel_msg, 7, &config_2_percent, 0, &&logger, /*is_0conf=*/false, /*is_0reserve=*/false).unwrap(); let chan_3_value_msat = chan_3.funding.get_value_satoshis() * 1000; assert_eq!(chan_3.context.holder_max_htlc_value_in_flight_msat, (chan_3_value_msat as f64 * 0.02) as u64); // Test with the upper bound - 1 of valid values (99%). - let chan_4 = InboundV1Channel::<&TestKeysInterface>::new(&feeest, &&keys_provider, &&keys_provider, inbound_node_id, &channelmanager::provided_channel_type_features(&config_99_percent), &channelmanager::provided_init_features(&config_99_percent), &chan_1_open_channel_msg, 7, &config_99_percent, 0, &&logger, /*is_0conf=*/false).unwrap(); + let chan_4 = InboundV1Channel::<&TestKeysInterface>::new(&feeest, &&keys_provider, &&keys_provider, inbound_node_id, &channelmanager::provided_channel_type_features(&config_99_percent), &channelmanager::provided_init_features(&config_99_percent), &chan_1_open_channel_msg, 7, &config_99_percent, 0, &&logger, /*is_0conf=*/false, /*is_0reserve=*/false).unwrap(); let chan_4_value_msat = chan_4.funding.get_value_satoshis() * 1000; assert_eq!(chan_4.context.holder_max_htlc_value_in_flight_msat, (chan_4_value_msat as f64 * 0.99) as u64); // Test that `OutboundV1Channel::new` uses the lower bound of the configurable percentage values (1%) // if `max_inbound_htlc_value_in_flight_percent_of_channel` is set to a value less than 1. - let chan_5 = OutboundV1Channel::<&TestKeysInterface>::new(&feeest, &&keys_provider, &&keys_provider, outbound_node_id, &channelmanager::provided_init_features(&config_0_percent), 10000000, 100000, 42, &config_0_percent, 0, 42, None, &logger).unwrap(); + let chan_5 = OutboundV1Channel::<&TestKeysInterface>::new(&feeest, &&keys_provider, &&keys_provider, outbound_node_id, &channelmanager::provided_init_features(&config_0_percent), 10000000, 100000, 42, &config_0_percent, 0, 42, None, &logger, false).unwrap(); let chan_5_value_msat = chan_5.funding.get_value_satoshis() * 1000; assert_eq!(chan_5.context.holder_max_htlc_value_in_flight_msat, (chan_5_value_msat as f64 * 0.01) as u64); // Test that `OutboundV1Channel::new` uses the upper bound of the configurable percentage values // (100%) if `max_inbound_htlc_value_in_flight_percent_of_channel` is set to a larger value // than 100. - let chan_6 = OutboundV1Channel::<&TestKeysInterface>::new(&feeest, &&keys_provider, &&keys_provider, outbound_node_id, &channelmanager::provided_init_features(&config_101_percent), 10000000, 100000, 42, &config_101_percent, 0, 42, None, &logger).unwrap(); + let chan_6 = OutboundV1Channel::<&TestKeysInterface>::new(&feeest, &&keys_provider, &&keys_provider, outbound_node_id, &channelmanager::provided_init_features(&config_101_percent), 10000000, 100000, 42, &config_101_percent, 0, 42, None, &logger, false).unwrap(); let chan_6_value_msat = chan_6.funding.get_value_satoshis() * 1000; assert_eq!(chan_6.context.holder_max_htlc_value_in_flight_msat, chan_6_value_msat); // Test that `InboundV1Channel::new` uses the lower bound of the configurable percentage values (1%) // if `max_inbound_htlc_value_in_flight_percent_of_channel` is set to a value less than 1. - let chan_7 = InboundV1Channel::<&TestKeysInterface>::new(&feeest, &&keys_provider, &&keys_provider, inbound_node_id, &channelmanager::provided_channel_type_features(&config_0_percent), &channelmanager::provided_init_features(&config_0_percent), &chan_1_open_channel_msg, 7, &config_0_percent, 0, &&logger, /*is_0conf=*/false).unwrap(); + let chan_7 = InboundV1Channel::<&TestKeysInterface>::new(&feeest, &&keys_provider, &&keys_provider, inbound_node_id, &channelmanager::provided_channel_type_features(&config_0_percent), &channelmanager::provided_init_features(&config_0_percent), &chan_1_open_channel_msg, 7, &config_0_percent, 0, &&logger, /*is_0conf=*/false, /*is_0reserve=*/false).unwrap(); let chan_7_value_msat = chan_7.funding.get_value_satoshis() * 1000; assert_eq!(chan_7.context.holder_max_htlc_value_in_flight_msat, (chan_7_value_msat as f64 * 0.01) as u64); // Test that `InboundV1Channel::new` uses the upper bound of the configurable percentage values // (100%) if `max_inbound_htlc_value_in_flight_percent_of_channel` is set to a larger value // than 100. - let chan_8 = InboundV1Channel::<&TestKeysInterface>::new(&feeest, &&keys_provider, &&keys_provider, inbound_node_id, &channelmanager::provided_channel_type_features(&config_101_percent), &channelmanager::provided_init_features(&config_101_percent), &chan_1_open_channel_msg, 7, &config_101_percent, 0, &&logger, /*is_0conf=*/false).unwrap(); + let chan_8 = InboundV1Channel::<&TestKeysInterface>::new(&feeest, &&keys_provider, &&keys_provider, inbound_node_id, &channelmanager::provided_channel_type_features(&config_101_percent), &channelmanager::provided_init_features(&config_101_percent), &chan_1_open_channel_msg, 7, &config_101_percent, 0, &&logger, /*is_0conf=*/false, /*is_0reserve=*/false).unwrap(); let chan_8_value_msat = chan_8.funding.get_value_satoshis() * 1000; assert_eq!(chan_8.context.holder_max_htlc_value_in_flight_msat, chan_8_value_msat); } @@ -16325,7 +16051,7 @@ mod tests { let mut outbound_node_config = UserConfig::default(); outbound_node_config.channel_handshake_config.their_channel_reserve_proportional_millionths = (outbound_selected_channel_reserve_perc * 1_000_000.0) as u32; - let mut chan = OutboundV1Channel::<&TestKeysInterface>::new(&&fee_est, &&keys_provider, &&keys_provider, outbound_node_id, &channelmanager::provided_init_features(&outbound_node_config), channel_value_satoshis, 100_000, 42, &outbound_node_config, 0, 42, None, &logger).unwrap(); + let mut chan = OutboundV1Channel::<&TestKeysInterface>::new(&&fee_est, &&keys_provider, &&keys_provider, outbound_node_id, &channelmanager::provided_init_features(&outbound_node_config), channel_value_satoshis, 100_000, 42, &outbound_node_config, 0, 42, None, &logger, false).unwrap(); let expected_outbound_selected_chan_reserve = cmp::max(MIN_THEIR_CHAN_RESERVE_SATOSHIS, (chan.funding.get_value_satoshis() as f64 * outbound_selected_channel_reserve_perc) as u64); assert_eq!(chan.funding.holder_selected_channel_reserve_satoshis, expected_outbound_selected_chan_reserve); @@ -16335,7 +16061,7 @@ mod tests { inbound_node_config.channel_handshake_config.their_channel_reserve_proportional_millionths = (inbound_selected_channel_reserve_perc * 1_000_000.0) as u32; if outbound_selected_channel_reserve_perc + inbound_selected_channel_reserve_perc < 1.0 { - let chan_inbound_node = InboundV1Channel::<&TestKeysInterface>::new(&&fee_est, &&keys_provider, &&keys_provider, inbound_node_id, &channelmanager::provided_channel_type_features(&inbound_node_config), &channelmanager::provided_init_features(&outbound_node_config), &chan_open_channel_msg, 7, &inbound_node_config, 0, &&logger, /*is_0conf=*/false).unwrap(); + let chan_inbound_node = InboundV1Channel::<&TestKeysInterface>::new(&&fee_est, &&keys_provider, &&keys_provider, inbound_node_id, &channelmanager::provided_channel_type_features(&inbound_node_config), &channelmanager::provided_init_features(&outbound_node_config), &chan_open_channel_msg, 7, &inbound_node_config, 0, &&logger, /*is_0conf=*/false, /*is_0reserve=*/false).unwrap(); let expected_inbound_selected_chan_reserve = cmp::max(MIN_THEIR_CHAN_RESERVE_SATOSHIS, (chan.funding.get_value_satoshis() as f64 * inbound_selected_channel_reserve_perc) as u64); @@ -16343,7 +16069,7 @@ mod tests { assert_eq!(chan_inbound_node.funding.counterparty_selected_channel_reserve_satoshis.unwrap(), expected_outbound_selected_chan_reserve); } else { // Channel Negotiations failed - let result = InboundV1Channel::<&TestKeysInterface>::new(&&fee_est, &&keys_provider, &&keys_provider, inbound_node_id, &channelmanager::provided_channel_type_features(&inbound_node_config), &channelmanager::provided_init_features(&outbound_node_config), &chan_open_channel_msg, 7, &inbound_node_config, 0, &&logger, /*is_0conf=*/false); + let result = InboundV1Channel::<&TestKeysInterface>::new(&&fee_est, &&keys_provider, &&keys_provider, inbound_node_id, &channelmanager::provided_channel_type_features(&inbound_node_config), &channelmanager::provided_init_features(&outbound_node_config), &chan_open_channel_msg, 7, &inbound_node_config, 0, &&logger, /*is_0conf=*/false, /*is_0reserve=*/false); assert!(result.is_err()); } } @@ -16364,13 +16090,13 @@ mod tests { // Create Node A's channel pointing to Node B's pubkey let node_b_node_id = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap()); let config = UserConfig::default(); - let mut node_a_chan = OutboundV1Channel::<&TestKeysInterface>::new(&feeest, &&keys_provider, &&keys_provider, node_b_node_id, &channelmanager::provided_init_features(&config), 10000000, 100000, 42, &config, 0, 42, None, &logger).unwrap(); + let mut node_a_chan = OutboundV1Channel::<&TestKeysInterface>::new(&feeest, &&keys_provider, &&keys_provider, node_b_node_id, &channelmanager::provided_init_features(&config), 10000000, 100000, 42, &config, 0, 42, None, &logger, false).unwrap(); // Create Node B's channel by receiving Node A's open_channel message // Make sure A's dust limit is as we expect. let open_channel_msg = node_a_chan.get_open_channel(ChainHash::using_genesis_block(network), &&logger).unwrap(); let node_b_node_id = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[7; 32]).unwrap()); - let mut node_b_chan = InboundV1Channel::<&TestKeysInterface>::new(&feeest, &&keys_provider, &&keys_provider, node_b_node_id, &channelmanager::provided_channel_type_features(&config), &channelmanager::provided_init_features(&config), &open_channel_msg, 7, &config, 0, &&logger, /*is_0conf=*/false).unwrap(); + let mut node_b_chan = InboundV1Channel::<&TestKeysInterface>::new(&feeest, &&keys_provider, &&keys_provider, node_b_node_id, &channelmanager::provided_channel_type_features(&config), &channelmanager::provided_init_features(&config), &open_channel_msg, 7, &config, 0, &&logger, /*is_0conf=*/false, /*is_0reserve=*/false).unwrap(); // Node B --> Node A: accept channel, explicitly setting B's dust limit. let mut accept_channel_msg = node_b_chan.accept_inbound_channel(&&logger).unwrap(); @@ -16456,6 +16182,7 @@ mod tests { 42, None, &logger, + false, ) .unwrap(); let open_channel_msg = &outbound_chan @@ -16474,6 +16201,7 @@ mod tests { 0, &&logger, false, + false, ) .unwrap(); outbound_chan @@ -16746,7 +16474,7 @@ mod tests { ChannelPublicKeys, CounterpartyChannelTransactionParameters, HolderCommitmentTransaction, }; - use crate::ln::channel::HTLCOutputInCommitment; + use crate::ln::channel::{HTLCOutputInCommitment, PredictedNextFee}; use crate::ln::channel_keys::{DelayedPaymentBasepoint, HtlcBasepoint}; use crate::sign::{ecdsa::EcdsaChannelSigner, ChannelDerivationParameters, HTLCDescriptor}; use crate::sync::Arc; @@ -16812,6 +16540,7 @@ mod tests { 42, None, &*logger, + false, ) .unwrap(); // Nothing uses their network key in this test chan.context.holder_dust_limit_satoshis = 546; @@ -16875,6 +16604,8 @@ mod tests { macro_rules! test_commitment { ( $counterparty_sig_hex: expr, $sig_hex: expr, $tx_hex: expr, $($remain:tt)* ) => { chan.funding.channel_transaction_parameters.channel_type_features = ChannelTypeFeatures::only_static_remote_key(); + chan.funding.next_local_fee = Mutex::new(PredictedNextFee::default()); + chan.funding.next_remote_fee = Mutex::new(PredictedNextFee::default()); test_commitment_common!(chan, logger, secp_ctx, signer, holder_pubkeys, per_commitment_point, $counterparty_sig_hex, $sig_hex, $tx_hex, &ChannelTypeFeatures::only_static_remote_key(), $($remain)*); }; } @@ -16882,6 +16613,8 @@ mod tests { macro_rules! test_commitment_with_anchors { ( $counterparty_sig_hex: expr, $sig_hex: expr, $tx_hex: expr, $($remain:tt)* ) => { chan.funding.channel_transaction_parameters.channel_type_features = ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies(); + chan.funding.next_local_fee = Mutex::new(PredictedNextFee::default()); + chan.funding.next_remote_fee = Mutex::new(PredictedNextFee::default()); test_commitment_common!(chan, logger, secp_ctx, signer, holder_pubkeys, per_commitment_point, $counterparty_sig_hex, $sig_hex, $tx_hex, &ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies(), $($remain)*); }; } @@ -17533,6 +17266,7 @@ mod tests { 0, None, &*logger, + false, ) .unwrap(); @@ -18108,7 +17842,8 @@ mod tests { 0, 42, None, - &logger + &logger, + false, ).unwrap(); let open_channel_msg = node_a_chan.get_open_channel(ChainHash::using_genesis_block(network), &&logger).unwrap(); @@ -18126,6 +17861,7 @@ mod tests { 0, &&logger, true, // Allow node b to send a 0conf channel_ready. + false, ).unwrap(); let accept_channel_msg = node_b_chan.accept_inbound_channel(&&logger).unwrap(); diff --git a/lightning/src/ln/channel_open_tests.rs b/lightning/src/ln/channel_open_tests.rs index 3a9c266aacd..b1918c8ecc6 100644 --- a/lightning/src/ln/channel_open_tests.rs +++ b/lightning/src/ln/channel_open_tests.rs @@ -170,10 +170,12 @@ fn test_0conf_limiting() { Event::OpenChannelRequest { temporary_channel_id, .. } => { nodes[1] .node - .accept_inbound_channel_from_trusted_peer_0conf( + .accept_inbound_channel_from_trusted_peer( &temporary_channel_id, &last_random_pk, 23, + true, + false, None, ) .unwrap(); @@ -972,6 +974,7 @@ pub fn test_user_configurable_csv_delay() { 42, None, &logger, + false, ) { match error { APIError::APIMisuseError { err } => { @@ -1004,6 +1007,7 @@ pub fn test_user_configurable_csv_delay() { 0, &nodes[0].logger, /*is_0conf=*/ false, + /*is_0reserve=*/ false, ) { match error { ChannelError::Close((err, _)) => { @@ -1064,6 +1068,7 @@ pub fn test_user_configurable_csv_delay() { 0, &nodes[0].logger, /*is_0conf=*/ false, + /*is_0reserve=*/ false, ) { match error { ChannelError::Close((err, _)) => { diff --git a/lightning/src/ln/channel_type_tests.rs b/lightning/src/ln/channel_type_tests.rs index 13470d50614..17fb8e3586f 100644 --- a/lightning/src/ln/channel_type_tests.rs +++ b/lightning/src/ln/channel_type_tests.rs @@ -142,6 +142,7 @@ fn test_zero_conf_channel_type_support() { 42, None, &logger, + false, ) .unwrap(); @@ -166,6 +167,7 @@ fn test_zero_conf_channel_type_support() { 0, &&logger, /*is_0conf=*/ false, + /*is_0reserve=*/ false, ); assert!(res.is_ok()); } @@ -240,6 +242,7 @@ fn do_test_supports_channel_type(config: UserConfig, expected_channel_type: Chan 42, None, &logger, + false, ) .unwrap(); assert_eq!( @@ -261,6 +264,7 @@ fn do_test_supports_channel_type(config: UserConfig, expected_channel_type: Chan 42, None, &logger, + false, ) .unwrap(); @@ -279,6 +283,7 @@ fn do_test_supports_channel_type(config: UserConfig, expected_channel_type: Chan 0, &&logger, /*is_0conf=*/ false, + /*is_0reserve=*/ false, ) .unwrap(); @@ -326,6 +331,7 @@ fn test_rejects_if_channel_type_not_set() { 42, None, &logger, + false, ) .unwrap(); @@ -347,6 +353,7 @@ fn test_rejects_if_channel_type_not_set() { 0, &&logger, /*is_0conf=*/ false, + /*is_0reserve=*/ false, ); assert!(channel_b.is_err()); @@ -365,6 +372,7 @@ fn test_rejects_if_channel_type_not_set() { 0, &&logger, /*is_0conf=*/ false, + /*is_0reserve=*/ false, ) .unwrap(); @@ -412,6 +420,7 @@ fn test_rejects_if_channel_type_differ() { 42, None, &logger, + false, ) .unwrap(); @@ -431,6 +440,7 @@ fn test_rejects_if_channel_type_differ() { 0, &&logger, /*is_0conf=*/ false, + /*is_0reserve=*/ false, ) .unwrap(); @@ -495,6 +505,7 @@ fn test_rejects_simple_anchors_channel_type() { 42, None, &logger, + false, ) .unwrap(); @@ -515,6 +526,7 @@ fn test_rejects_simple_anchors_channel_type() { 0, &&logger, /*is_0conf=*/ false, + /*is_0reserve=*/ false, ); assert!(res.is_err()); @@ -536,6 +548,7 @@ fn test_rejects_simple_anchors_channel_type() { 42, None, &logger, + false, ) .unwrap(); @@ -555,6 +568,7 @@ fn test_rejects_simple_anchors_channel_type() { 0, &&logger, /*is_0conf=*/ false, + /*is_0reserve=*/ false, ) .unwrap(); diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 532514a3ae9..7201ddadde6 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -3599,6 +3599,28 @@ impl< /// [`Event::ChannelClosed::channel_id`]: events::Event::ChannelClosed::channel_id #[rustfmt::skip] pub fn create_channel(&self, their_network_key: PublicKey, channel_value_satoshis: u64, push_msat: u64, user_channel_id: u128, temporary_channel_id: Option, override_config: Option) -> Result { + self.create_channel_internal(their_network_key, channel_value_satoshis, push_msat, user_channel_id, temporary_channel_id, override_config, false) + } + + /// Document this please + pub fn create_channel_to_trusted_peer( + &self, their_network_key: PublicKey, channel_value_satoshis: u64, push_msat: u64, + user_channel_id: u128, temporary_channel_id: Option, + override_config: Option, + ) -> Result { + self.create_channel_internal( + their_network_key, + channel_value_satoshis, + push_msat, + user_channel_id, + temporary_channel_id, + override_config, + true, + ) + } + + #[rustfmt::skip] + fn create_channel_internal(&self, their_network_key: PublicKey, channel_value_satoshis: u64, push_msat: u64, user_channel_id: u128, temporary_channel_id: Option, override_config: Option, is_0reserve: bool) -> Result { if channel_value_satoshis < 1000 { return Err(APIError::APIMisuseError { err: format!("Channel value must be at least 1000 satoshis. It was {}", channel_value_satoshis) }); } @@ -3634,7 +3656,7 @@ impl< }; match OutboundV1Channel::new(&self.fee_estimator, &self.entropy_source, &self.signer_provider, their_network_key, their_features, channel_value_satoshis, push_msat, user_channel_id, config, - self.best_block.read().unwrap().height, outbound_scid_alias, temporary_channel_id, &self.logger) + self.best_block.read().unwrap().height, outbound_scid_alias, temporary_channel_id, &self.logger, is_0reserve) { Ok(res) => res, Err(e) => { @@ -10357,10 +10379,10 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ /// /// The `user_channel_id` parameter will be provided back in /// [`Event::ChannelClosed::user_channel_id`] to allow tracking of which events correspond - /// with which `accept_inbound_channel`/`accept_inbound_channel_from_trusted_peer_0conf` call. + /// with which `accept_inbound_channel`/`accept_inbound_channel_from_trusted_peer` call. /// /// Note that this method will return an error and reject the channel, if it requires support - /// for zero confirmations. Instead, `accept_inbound_channel_from_trusted_peer_0conf` must be + /// for zero confirmations. Instead, `accept_inbound_channel_from_trusted_peer` must be /// used to accept such channels. /// /// NOTE: LDK makes no attempt to prevent the counterparty from using non-standard inputs which @@ -10377,6 +10399,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ temporary_channel_id, counterparty_node_id, false, + false, user_channel_id, config_overrides, ) @@ -10387,7 +10410,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ /// /// The `user_channel_id` parameter will be provided back in /// [`Event::ChannelClosed::user_channel_id`] to allow tracking of which events correspond - /// with which `accept_inbound_channel`/`accept_inbound_channel_from_trusted_peer_0conf` call. + /// with which `accept_inbound_channel`/`accept_inbound_channel_from_trusted_peer` call. /// /// Unlike [`ChannelManager::accept_inbound_channel`], this method accepts the incoming channel /// and (if the counterparty agrees), enables forwarding of payments immediately. @@ -10400,14 +10423,16 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ /// /// [`Event::OpenChannelRequest`]: events::Event::OpenChannelRequest /// [`Event::ChannelClosed::user_channel_id`]: events::Event::ChannelClosed::user_channel_id - pub fn accept_inbound_channel_from_trusted_peer_0conf( + pub fn accept_inbound_channel_from_trusted_peer( &self, temporary_channel_id: &ChannelId, counterparty_node_id: &PublicKey, - user_channel_id: u128, config_overrides: Option, + user_channel_id: u128, accept_0conf: bool, accept_0reserve: bool, + config_overrides: Option, ) -> Result<(), APIError> { self.do_accept_inbound_channel( temporary_channel_id, counterparty_node_id, - true, + accept_0conf, + accept_0reserve, user_channel_id, config_overrides, ) @@ -10416,7 +10441,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ /// TODO(dual_funding): Allow contributions, pass intended amount and inputs fn do_accept_inbound_channel( &self, temporary_channel_id: &ChannelId, counterparty_node_id: &PublicKey, - accept_0conf: bool, user_channel_id: u128, + accept_0conf: bool, accept_0reserve: bool, user_channel_id: u128, config_overrides: Option, ) -> Result<(), APIError> { let mut config = self.config.read().unwrap().clone(); @@ -10470,6 +10495,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ best_block_height, &self.logger, accept_0conf, + accept_0reserve, ) .map_err(|err| { MsgHandleErrInternal::from_chan_no_close(err, *temporary_channel_id) @@ -10561,7 +10587,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ }; debug_assert!(peer_state.is_connected); peer_state.pending_msg_events.push(send_msg_err_event); - let err_str = "Please use accept_inbound_channel_from_trusted_peer_0conf to accept channels with zero confirmations.".to_owned(); + let err_str = "Please use accept_inbound_channel_from_trusted_peer to accept channels with zero confirmations.".to_owned(); log_error!(logger, "{}", err_str); return Err(APIError::APIMisuseError { err: err_str }); @@ -10786,7 +10812,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ let mut channel = InboundV1Channel::new( &self.fee_estimator, &self.entropy_source, &self.signer_provider, *counterparty_node_id, &self.channel_type_features(), &peer_state.latest_features, msg, user_channel_id, - &self.config.read().unwrap(), best_block_height, &self.logger, /*is_0conf=*/false + &self.config.read().unwrap(), best_block_height, &self.logger, /*is_0conf=*/false, /*is_0reserve=*/false, ).map_err(|e| MsgHandleErrInternal::from_chan_no_close(e, msg.common_fields.temporary_channel_id))?; let logger = WithChannelContext::from(&self.logger, &channel.context, None); let message_send_event = channel.accept_inbound_channel(&&logger).map(|msg| { @@ -16472,6 +16498,7 @@ pub fn provided_init_features(config: &UserConfig) -> InitFeatures { features.set_simple_close_optional(); features.set_quiescence_optional(); features.set_splicing_optional(); + features.set_zero_reserve_optional(); if config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx { features.set_anchors_zero_fee_htlc_tx_optional(); diff --git a/lightning/src/ln/functional_test_utils.rs b/lightning/src/ln/functional_test_utils.rs index e8965752331..bd4fc1fbaa0 100644 --- a/lightning/src/ln/functional_test_utils.rs +++ b/lightning/src/ln/functional_test_utils.rs @@ -1581,10 +1581,12 @@ pub fn exchange_open_accept_zero_conf_chan<'a, 'b, 'c, 'd>( Event::OpenChannelRequest { temporary_channel_id, .. } => { receiver .node - .accept_inbound_channel_from_trusted_peer_0conf( + .accept_inbound_channel_from_trusted_peer( &temporary_channel_id, &initiator_node_id, 0, + true, + false, None, ) .unwrap(); @@ -1600,6 +1602,109 @@ pub fn exchange_open_accept_zero_conf_chan<'a, 'b, 'c, 'd>( accept_channel.common_fields.temporary_channel_id } +pub fn exchange_open_accept_zero_reserve_chan<'a, 'b, 'c, 'd>( + initiator: &'a Node<'b, 'c, 'd>, receiver: &'a Node<'b, 'c, 'd>, channel_value_sat: u64, + push_msat: u64, +) -> ChannelId { + let receiver_node_id = receiver.node.get_our_node_id(); + let initiator_node_id = initiator.node.get_our_node_id(); + + initiator + .node + .create_channel(receiver_node_id, channel_value_sat, push_msat, 42, None, None) + .unwrap(); + let open_channel = + get_event_msg!(initiator, MessageSendEvent::SendOpenChannel, receiver_node_id); + + receiver.node.handle_open_channel(initiator_node_id, &open_channel); + let events = receiver.node.get_and_clear_pending_events(); + assert_eq!(events.len(), 1); + match events[0] { + Event::OpenChannelRequest { temporary_channel_id, .. } => { + receiver + .node + .accept_inbound_channel_from_trusted_peer( + &temporary_channel_id, + &initiator_node_id, + 0, + false, + true, + None, + ) + .unwrap(); + }, + _ => panic!("Unexpected event"), + }; + + let accept_channel = + get_event_msg!(receiver, MessageSendEvent::SendAcceptChannel, initiator_node_id); + assert_eq!(accept_channel.channel_reserve_satoshis, 0); + initiator.node.handle_accept_channel(receiver_node_id, &accept_channel); + + accept_channel.common_fields.temporary_channel_id +} + +pub fn create_zero_reserve_chan_between_nodes_with_value_init<'a, 'b, 'c>( + node_a: &Node<'a, 'b, 'c>, node_b: &Node<'a, 'b, 'c>, channel_value: u64, push_msat: u64, +) -> Transaction { + let create_chan_id = + exchange_open_accept_zero_reserve_chan(node_a, node_b, channel_value, push_msat); + sign_funding_transaction(node_a, node_b, channel_value, create_chan_id) +} + +pub fn create_zero_reserve_chan_between_nodes_with_value_a<'a, 'b, 'c: 'd, 'd>( + node_a: &'a Node<'b, 'c, 'd>, node_b: &'a Node<'b, 'c, 'd>, channel_value: u64, push_msat: u64, +) -> ((msgs::ChannelReady, msgs::AnnouncementSignatures), ChannelId, Transaction) { + let tx = create_zero_reserve_chan_between_nodes_with_value_init( + node_a, + node_b, + channel_value, + push_msat, + ); + let (msgs, chan_id) = create_chan_between_nodes_with_value_confirm(node_a, node_b, &tx); + (msgs, chan_id, tx) +} + +pub fn create_zero_reserve_chan_between_nodes_with_value<'a, 'b, 'c: 'd, 'd>( + node_a: &'a Node<'b, 'c, 'd>, node_b: &'a Node<'b, 'c, 'd>, channel_value: u64, push_msat: u64, +) -> (msgs::ChannelAnnouncement, msgs::ChannelUpdate, msgs::ChannelUpdate, ChannelId, Transaction) { + let (channel_ready, channel_id, tx) = create_zero_reserve_chan_between_nodes_with_value_a( + node_a, + node_b, + channel_value, + push_msat, + ); + let (announcement, as_update, bs_update) = + create_chan_between_nodes_with_value_b(node_a, node_b, &channel_ready); + (announcement, as_update, bs_update, channel_id, tx) +} + +pub fn create_announced_zero_reserve_chan_between_nodes_with_value<'a, 'b, 'c: 'd, 'd>( + nodes: &'a Vec>, a: usize, b: usize, channel_value: u64, push_msat: u64, +) -> (msgs::ChannelUpdate, msgs::ChannelUpdate, ChannelId, Transaction) { + let chan_announcement = create_zero_reserve_chan_between_nodes_with_value( + &nodes[a], + &nodes[b], + channel_value, + push_msat, + ); + update_nodes_with_chan_announce( + nodes, + a, + b, + &chan_announcement.0, + &chan_announcement.1, + &chan_announcement.2, + ); + (chan_announcement.1, chan_announcement.2, chan_announcement.3, chan_announcement.4) +} + +pub fn create_announced_zero_reserve_chan_between_nodes<'a, 'b, 'c: 'd, 'd>( + nodes: &'a Vec>, a: usize, b: usize, +) -> (msgs::ChannelUpdate, msgs::ChannelUpdate, ChannelId, Transaction) { + create_announced_zero_reserve_chan_between_nodes_with_value(nodes, a, b, 100000, 10001) +} + // Receiver must have been initialized with manually_accept_inbound_channels set to true. pub fn open_zero_conf_channel_with_value<'a, 'b, 'c, 'd>( initiator: &'a Node<'b, 'c, 'd>, receiver: &'a Node<'b, 'c, 'd>, diff --git a/lightning/src/ln/functional_tests.rs b/lightning/src/ln/functional_tests.rs index 8e854b31150..182f04dfda0 100644 --- a/lightning/src/ln/functional_tests.rs +++ b/lightning/src/ln/functional_tests.rs @@ -9991,9 +9991,19 @@ pub fn test_dust_exposure_holding_cell_assertion() { expect_and_process_pending_htlcs(&nodes[1], false); assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty()); - let bs_chans = nodes[1].node.list_channels(); - let bc_chan = bs_chans.iter().find(|chan| chan.counterparty.node_id == node_c_id).unwrap(); - assert!(bc_chan.next_outbound_htlc_minimum_msat > DUST_HTLC_VALUE_MSAT); + { + let per_peer_state_lock; + let mut peer_state_lock; + let chan = + get_channel_ref!(nodes[1], nodes[2], per_peer_state_lock, peer_state_lock, bc_chan_id); + // This how `chan.send_htlc` determines whether it can send another HTLC + let balances = chan + .get_available_balances_with_counterparty_unknown_htlcs(&LowerBoundedFeeEstimator::new( + nodes[1].fee_estimator, + )) + .unwrap(); + assert!(balances.next_outbound_htlc_minimum_msat > DUST_HTLC_VALUE_MSAT); + } // Send an additional HTLC from C to B. This will make B unable to forward the HTLC already in // its holding cell as it would be over-exposed to dust. diff --git a/lightning/src/ln/htlc_reserve_unit_tests.rs b/lightning/src/ln/htlc_reserve_unit_tests.rs index 4c4fbada7dd..112ee048b37 100644 --- a/lightning/src/ln/htlc_reserve_unit_tests.rs +++ b/lightning/src/ln/htlc_reserve_unit_tests.rs @@ -14,7 +14,7 @@ use crate::ln::functional_test_utils::*; use crate::ln::msgs::{self, BaseMessageHandler, ChannelMessageHandler, MessageSendEvent}; use crate::ln::onion_utils::{self, AttributionData}; use crate::ln::outbound_payment::RecipientOnionFields; -use crate::routing::router::PaymentParameters; +use crate::routing::router::{PaymentParameters, RouteParameters}; use crate::sign::ecdsa::EcdsaChannelSigner; use crate::sign::tx_builder::{SpecTxBuilder, TxBuilder}; use crate::types::features::ChannelTypeFeatures; @@ -2431,3 +2431,170 @@ pub fn do_test_dust_limit_fee_accounting(can_afford: bool) { check_added_monitors(&nodes[1], 3); } } + +#[test] +fn test_create_channel_to_trusted_peer() { + let chanmon_cfgs = create_chanmon_cfgs(2); + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let mut config = test_default_channel_config(); + config.channel_handshake_config.max_inbound_htlc_value_in_flight_percent_of_channel = 100; + let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[Some(config), None]); + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + + let node_a_id = nodes[0].node.get_our_node_id(); + let node_b_id = nodes[1].node.get_our_node_id(); + + let feerate_per_kw = 253; + let channel_type_features = ChannelTypeFeatures::only_static_remote_key(); + + let default_config = UserConfig::default(); + + let mut push_amt = 100_000_000; + push_amt -= chan_utils::commit_tx_fee_sat( + feerate_per_kw, + MIN_AFFORDABLE_HTLC_COUNT, + &channel_type_features, + ) * 1000; + push_amt -= get_holder_selected_channel_reserve_satoshis(100_000, &default_config) * 1000; + + let temp_channel_id = nodes[0] + .node + .create_channel_to_trusted_peer(node_b_id, 100_000, push_amt, 42, None, None) + .unwrap(); + let mut open_channel_message = + get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, node_b_id); + + nodes[1].node.handle_open_channel(node_a_id, &open_channel_message); + + let mut accept_channel_message = + get_event_msg!(nodes[1], MessageSendEvent::SendAcceptChannel, node_a_id); + + nodes[0].node.handle_accept_channel(node_b_id, &accept_channel_message); + + let funding_tx = sign_funding_transaction(&nodes[0], &nodes[1], 100_000, temp_channel_id); + let funding_msgs = + create_chan_between_nodes_with_value_confirm(&nodes[0], &nodes[1], &funding_tx); + create_chan_between_nodes_with_value_b(&nodes[0], &nodes[1], &funding_msgs.0); + + send_payment(&nodes[1], &[&nodes[0]], push_amt); +} + +#[test] +fn test_accept_inbound_channel_from_trusted_peer_0reserve() { + let chanmon_cfgs = create_chanmon_cfgs(2); + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let mut config = test_default_channel_config(); + config.channel_handshake_config.max_inbound_htlc_value_in_flight_percent_of_channel = 100; + config.manually_accept_inbound_channels = true; + let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, Some(config)]); + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + + let initial_channel_value_sat = 100_000; + let (_, _, chan_id, _) = create_announced_zero_reserve_chan_between_nodes_with_value( + &nodes, + 0, + 1, + initial_channel_value_sat, + 0, + ); + + let channel_type = ChannelTypeFeatures::only_static_remote_key(); + + let delta = chan_utils::commit_tx_fee_sat( + 253 * FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE as u32, + 2, + &channel_type, + ); + + let _ = send_payment(&nodes[0], &[&nodes[1]], (100_000 - delta) * 1000); + + { + let per_peer_state_lock; + let mut peer_state_lock; + let chan = + get_channel_ref!(nodes[1], nodes[0], per_peer_state_lock, peer_state_lock, chan_id); + assert_eq!(chan.funding().holder_selected_channel_reserve_satoshis, 0); + } +} + +#[test] +fn test_zero_reserve_no_outputs() { + let chanmon_cfgs = create_chanmon_cfgs(2); + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let mut config = test_default_channel_config(); + config.channel_handshake_config.max_inbound_htlc_value_in_flight_percent_of_channel = 100; + config.manually_accept_inbound_channels = true; + let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, Some(config)]); + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + let channel_type = ChannelTypeFeatures::only_static_remote_key(); + + let node_a_id = nodes[0].node.get_our_node_id(); + let node_b_id = nodes[1].node.get_our_node_id(); + + nodes[0].node.create_channel(node_b_id, 1000 + 2, 0, 42, None, None).unwrap(); + let mut open_channel = get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, node_b_id); + open_channel.common_fields.max_htlc_value_in_flight_msat = 1_000_000; + open_channel.common_fields.dust_limit_satoshis = 546; + nodes[1].node.handle_open_channel(node_a_id, &open_channel); + let events = nodes[1].node.get_and_clear_pending_events(); + assert_eq!(events.len(), 1); + match events[0] { + Event::OpenChannelRequest { temporary_channel_id: chan_id, .. } => { + nodes[1] + .node + .accept_inbound_channel_from_trusted_peer( + &chan_id, &node_a_id, 0, false, true, None, + ) + .unwrap(); + }, + _ => panic!("Unexpected event"), + }; + + nodes[0].node.handle_accept_channel( + node_b_id, + &get_event_msg!(nodes[1], MessageSendEvent::SendAcceptChannel, node_a_id), + ); + + let (chan_id, tx, _) = create_funding_transaction(&nodes[0], &node_b_id, 1000 + 2, 42); + + { + let mut per_peer_lock; + let mut peer_state_lock; + let channel = get_channel_ref!(nodes[0], nodes[1], per_peer_lock, peer_state_lock, chan_id); + if let Some(mut chan) = channel.as_unfunded_outbound_v1_mut() { + chan.context.holder_dust_limit_satoshis = 546; + } else { + panic!("Unexpected Channel phase"); + } + } + + nodes[0].node.funding_transaction_generated(chan_id, node_b_id, tx.clone()).unwrap(); + nodes[1].node.handle_funding_created( + node_a_id, + &get_event_msg!(nodes[0], MessageSendEvent::SendFundingCreated, node_b_id), + ); + check_added_monitors(&nodes[1], 1); + expect_channel_pending_event(&nodes[1], &node_a_id); + + nodes[0].node.handle_funding_signed( + node_b_id, + &get_event_msg!(nodes[1], MessageSendEvent::SendFundingSigned, node_a_id), + ); + check_added_monitors(&nodes[0], 1); + expect_channel_pending_event(&nodes[0], &node_b_id); + + let (channel_ready, _channel_id) = + create_chan_between_nodes_with_value_confirm(&nodes[0], &nodes[1], &tx); + let (announcement, as_update, bs_update) = + create_chan_between_nodes_with_value_b(&nodes[0], &nodes[1], &channel_ready); + update_nodes_with_chan_announce(&nodes, 0, 1, &announcement, &as_update, &bs_update); + + let delta = chan_utils::commit_tx_fee_sat(253 * 2, 2, &channel_type); + //let _ = send_payment(&nodes[0], &[&nodes[1]], delta * 1000); + + let payment_params = + PaymentParameters::from_node_id(nodes[1].node.get_our_node_id(), TEST_FINAL_CLTV); + + let route_params = RouteParameters::from_payment_params_and_value(payment_params, delta * 1000); + assert!(get_route(&nodes[0], &route_params).is_err()); +} diff --git a/lightning/src/ln/peer_handler.rs b/lightning/src/ln/peer_handler.rs index 759a1e7d887..64ba3e9c561 100644 --- a/lightning/src/ln/peer_handler.rs +++ b/lightning/src/ln/peer_handler.rs @@ -413,6 +413,7 @@ impl BaseMessageHandler for ErroringMessageHandler { features.set_scid_privacy_optional(); features.set_zero_conf_optional(); features.set_route_blinding_optional(); + features.set_zero_reserve_optional(); #[cfg(simple_close)] features.set_simple_close_optional(); features diff --git a/lightning/src/ln/priv_short_conf_tests.rs b/lightning/src/ln/priv_short_conf_tests.rs index 2035af15046..42b6fbd1703 100644 --- a/lightning/src/ln/priv_short_conf_tests.rs +++ b/lightning/src/ln/priv_short_conf_tests.rs @@ -774,7 +774,7 @@ fn test_simple_0conf_channel() { // If our peer tells us they will accept our channel with 0 confs, and we funded the channel, // we should trust the funding won't be double-spent (assuming `trust_own_funding_0conf` is // set)! - // Further, if we `accept_inbound_channel_from_trusted_peer_0conf`, `channel_ready` messages + // Further, if we `accept_inbound_channel_from_trusted_peer`, `channel_ready` messages // should fly immediately and the channel should be available for use as soon as they are // received. @@ -820,10 +820,12 @@ fn test_0conf_channel_with_async_monitor() { Event::OpenChannelRequest { temporary_channel_id, .. } => { nodes[1] .node - .accept_inbound_channel_from_trusted_peer_0conf( + .accept_inbound_channel_from_trusted_peer( &temporary_channel_id, &node_a_id, 0, + true, + false, None, ) .unwrap(); @@ -1411,10 +1413,12 @@ fn test_zero_conf_accept_reject() { // Assert we can accept via the 0conf method assert!(nodes[1] .node - .accept_inbound_channel_from_trusted_peer_0conf( + .accept_inbound_channel_from_trusted_peer( &temporary_channel_id, &node_a_id, 0, + true, + false, None ) .is_ok()); @@ -1456,10 +1460,12 @@ fn test_connect_before_funding() { Event::OpenChannelRequest { temporary_channel_id, .. } => { nodes[1] .node - .accept_inbound_channel_from_trusted_peer_0conf( + .accept_inbound_channel_from_trusted_peer( &temporary_channel_id, &node_a_id, 0, + true, + false, None, ) .unwrap(); diff --git a/lightning/src/ln/update_fee_tests.rs b/lightning/src/ln/update_fee_tests.rs index 67a07325ad6..3a5e4c64c85 100644 --- a/lightning/src/ln/update_fee_tests.rs +++ b/lightning/src/ln/update_fee_tests.rs @@ -887,8 +887,13 @@ pub fn test_chan_init_feerate_unaffordability() { MIN_AFFORDABLE_HTLC_COUNT as u64, &channel_type_features, ); - assert_eq!(nodes[0].node.create_channel(node_b_id, 100_000, push_amt + 1, 42, None, None).unwrap_err(), - APIError::APIMisuseError { err: "Funding amount (356) can't even pay fee for initial commitment transaction fee of 357.".to_string() }); + assert_eq!( + nodes[0].node.create_channel(node_b_id, 100_000, push_amt + 1, 42, None, None).unwrap_err(), + APIError::APIMisuseError { + err: "Funding amount (356) can't even pay fee for initial commitment transaction." + .to_string() + } + ); // During open, we don't have a "counterparty channel reserve" to check against, so that // requirement only comes into play on the open_channel handling side. diff --git a/lightning/src/sign/tx_builder.rs b/lightning/src/sign/tx_builder.rs index 27b8b1a9a2b..4447d98c116 100644 --- a/lightning/src/sign/tx_builder.rs +++ b/lightning/src/sign/tx_builder.rs @@ -1,5 +1,4 @@ //! Defines the `TxBuilder` trait, and the `SpecTxBuilder` type -#![allow(dead_code)] use core::cmp; @@ -34,35 +33,18 @@ impl HTLCAmountDirection { } pub(crate) struct NextCommitmentStats { - pub is_outbound_from_holder: bool, - pub inbound_htlcs_count: usize, - pub inbound_htlcs_value_msat: u64, - pub holder_balance_before_fee_msat: u64, - pub counterparty_balance_before_fee_msat: u64, + pub holder_balance_msat: u64, + pub counterparty_balance_msat: u64, + pub dust_exposure_msat: u64, + #[cfg(any(test, fuzzing))] pub nondust_htlc_count: usize, + #[cfg(any(test, fuzzing))] pub commit_tx_fee_sat: u64, - pub dust_exposure_msat: u64, - pub extra_accepted_htlc_dust_exposure_msat: u64, } -impl NextCommitmentStats { - pub(crate) fn get_holder_counterparty_balances_incl_fee_msat(&self) -> Result<(u64, u64), ()> { - if self.is_outbound_from_holder { - Ok(( - self.holder_balance_before_fee_msat - .checked_sub(self.commit_tx_fee_sat * 1000) - .ok_or(())?, - self.counterparty_balance_before_fee_msat, - )) - } else { - Ok(( - self.holder_balance_before_fee_msat, - self.counterparty_balance_before_fee_msat - .checked_sub(self.commit_tx_fee_sat * 1000) - .ok_or(())?, - )) - } - } +pub(crate) struct OnchainStats { + pub commitment_stats: NextCommitmentStats, + pub available_balances: crate::ln::channel::AvailableBalances, } fn commit_plus_htlc_tx_fees_msat( @@ -113,7 +95,7 @@ fn commit_plus_htlc_tx_fees_msat( (total_fees_msat, extra_accepted_htlc_total_fees_msat) } -fn subtract_addl_outputs( +fn checked_sub_anchor_outputs( is_outbound_from_holder: bool, value_to_self_after_htlcs_msat: u64, value_to_remote_after_htlcs_msat: u64, channel_type: &ChannelTypeFeatures, ) -> Result<(u64, u64), ()> { @@ -123,13 +105,6 @@ fn subtract_addl_outputs( 0 }; - // We MUST use checked 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 { Ok(( value_to_self_after_htlcs_msat.checked_sub(total_anchors_sat * 1000).ok_or(())?, @@ -143,6 +118,29 @@ fn subtract_addl_outputs( } } +fn saturating_sub_anchor_outputs( + 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 + }; + + if is_outbound_from_holder { + ( + value_to_self_after_htlcs.saturating_sub(total_anchors_sat * 1000), + value_to_remote_after_htlcs, + ) + } else { + ( + value_to_self_after_htlcs, + value_to_remote_after_htlcs.saturating_sub(total_anchors_sat * 1000), + ) + } +} + fn get_dust_buffer_feerate(feerate_per_kw: u32) -> u32 { // When calculating our exposure to dust HTLCs, we assume that the channel feerate // may, at any point, increase by at least 10 sat/vB (i.e 2530 sat/kWU) or 25%, @@ -153,114 +151,48 @@ 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) trait TxBuilder { - 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], - addl_nondust_htlc_count: usize, feerate_per_kw: u32, - dust_exposure_limiting_feerate: Option, broadcaster_dust_limit_satoshis: u64, - channel_type: &ChannelTypeFeatures, - ) -> Result; - 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, - value_to_self_msat: u64, htlcs_in_tx: Vec, feerate_per_kw: u32, - broadcaster_dust_limit_satoshis: u64, logger: &L, - ) -> (CommitmentTransaction, CommitmentStats); +#[derive(Clone, Copy, Debug)] +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) struct SpecTxBuilder {} - -impl TxBuilder for SpecTxBuilder { - 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], - addl_nondust_htlc_count: usize, feerate_per_kw: u32, - dust_exposure_limiting_feerate: Option, broadcaster_dust_limit_satoshis: u64, - channel_type: &ChannelTypeFeatures, - ) -> Result { - let excess_feerate = - feerate_per_kw.saturating_sub(dust_exposure_limiting_feerate.unwrap_or(feerate_per_kw)); - if channel_type.supports_anchor_zero_fee_commitments() { - debug_assert_eq!(feerate_per_kw, 0); - debug_assert_eq!(excess_feerate, 0); - debug_assert_eq!(addl_nondust_htlc_count, 0); - } +fn get_dust_exposure_stats( + local: bool, commitment_htlcs: &[HTLCAmountDirection], feerate_per_kw: u32, + dust_exposure_limiting_feerate: Option, broadcaster_dust_limit_satoshis: u64, + channel_type: &ChannelTypeFeatures, +) -> (u64, Option) { + let excess_feerate = + feerate_per_kw.saturating_sub(dust_exposure_limiting_feerate.unwrap_or(feerate_per_kw)); + if channel_type.supports_anchor_zero_fee_commitments() { + debug_assert_eq!(feerate_per_kw, 0); + debug_assert_eq!(excess_feerate, 0); + } - // Calculate inbound htlc count - let inbound_htlcs_count = - next_commitment_htlcs.iter().filter(|htlc| !htlc.outbound).count(); - - // Calculate balances after htlcs - let value_to_counterparty_msat = - (channel_value_satoshis * 1000).checked_sub(value_to_holder_msat).ok_or(())?; - let outbound_htlcs_value_msat: u64 = next_commitment_htlcs - .iter() - .filter_map(|htlc| htlc.outbound.then_some(htlc.amount_msat)) - .sum(); - let inbound_htlcs_value_msat: u64 = next_commitment_htlcs - .iter() - .filter_map(|htlc| (!htlc.outbound).then_some(htlc.amount_msat)) - .sum(); - let value_to_holder_after_htlcs_msat = - value_to_holder_msat.checked_sub(outbound_htlcs_value_msat).ok_or(())?; - let value_to_counterparty_after_htlcs_msat = - value_to_counterparty_msat.checked_sub(inbound_htlcs_value_msat).ok_or(())?; - - // Subtract the anchors from the channel funder - let (holder_balance_before_fee_msat, counterparty_balance_before_fee_msat) = - subtract_addl_outputs( - is_outbound_from_holder, - value_to_holder_after_htlcs_msat, - value_to_counterparty_after_htlcs_msat, - channel_type, - )?; - - // Increment the feerate by a buffer to calculate dust exposure - let dust_buffer_feerate = get_dust_buffer_feerate(feerate_per_kw); - - // Calculate fees on commitment transaction - let nondust_htlc_count = next_commitment_htlcs - .iter() - .filter(|htlc| { - !htlc.is_dust(local, feerate_per_kw, broadcaster_dust_limit_satoshis, channel_type) - }) - .count(); - let commit_tx_fee_sat = commit_tx_fee_sat( - feerate_per_kw, - nondust_htlc_count + addl_nondust_htlc_count, - channel_type, - ); + // Increment the feerate by a buffer to calculate dust exposure + let dust_buffer_feerate = get_dust_buffer_feerate(feerate_per_kw); - // Calculate dust exposure on commitment transaction - let dust_exposure_msat = next_commitment_htlcs - .iter() - .filter_map(|htlc| { - htlc.is_dust( - local, - dust_buffer_feerate, - broadcaster_dust_limit_satoshis, - channel_type, - ) + // Calculate dust exposure on commitment transaction + let dust_exposure_msat = commitment_htlcs + .iter() + .filter_map(|htlc| { + htlc.is_dust(local, dust_buffer_feerate, broadcaster_dust_limit_satoshis, channel_type) .then_some(htlc.amount_msat) - }) - .sum(); + }) + .sum(); - // Add any excess fees to dust exposure on counterparty transactions - let (dust_exposure_msat, extra_accepted_htlc_dust_exposure_msat) = if local { - (dust_exposure_msat, dust_exposure_msat) - } else { + // Add any excess fees to dust exposure on counterparty transactions + if local { + (dust_exposure_msat, None) + } else { + if excess_feerate != 0 { let (excess_fees_msat, extra_accepted_htlc_excess_fees_msat) = commit_plus_htlc_tx_fees_msat( local, - &next_commitment_htlcs, + &commitment_htlcs, dust_buffer_feerate, excess_feerate, broadcaster_dust_limit_satoshis, @@ -268,56 +200,481 @@ impl TxBuilder for SpecTxBuilder { ); ( dust_exposure_msat + excess_fees_msat, - dust_exposure_msat + extra_accepted_htlc_excess_fees_msat, + Some(dust_exposure_msat + extra_accepted_htlc_excess_fees_msat), ) - }; + } else { + (dust_exposure_msat, None) + } + } +} + +fn get_next_commitment_stats( + local: bool, is_outbound_from_holder: bool, channel_value_satoshis: u64, + value_to_holder_msat: u64, next_commitment_htlcs: &[HTLCAmountDirection], + addl_nondust_htlc_count: usize, feerate_per_kw: u32, + dust_exposure_limiting_feerate: Option, broadcaster_dust_limit_satoshis: u64, + channel_type: &ChannelTypeFeatures, +) -> Result { + if channel_type.supports_anchor_zero_fee_commitments() { + debug_assert_eq!(feerate_per_kw, 0); + } + + // Calculate balances after htlcs + let value_to_counterparty_msat = + (channel_value_satoshis * 1000).checked_sub(value_to_holder_msat).ok_or(())?; + let outbound_htlcs_value_msat: u64 = next_commitment_htlcs + .iter() + .filter_map(|htlc| htlc.outbound.then_some(htlc.amount_msat)) + .sum(); + let inbound_htlcs_value_msat: u64 = next_commitment_htlcs + .iter() + .filter_map(|htlc| (!htlc.outbound).then_some(htlc.amount_msat)) + .sum(); + let value_to_holder_after_htlcs_msat = + value_to_holder_msat.checked_sub(outbound_htlcs_value_msat).ok_or(())?; + let value_to_counterparty_after_htlcs_msat = + value_to_counterparty_msat.checked_sub(inbound_htlcs_value_msat).ok_or(())?; + + // Subtract the anchors from the channel funder + + // We MUST use checked 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. - Ok(NextCommitmentStats { + let (holder_balance_before_fee_msat, counterparty_balance_before_fee_msat) = + checked_sub_anchor_outputs( is_outbound_from_holder, - inbound_htlcs_count, - inbound_htlcs_value_msat, - holder_balance_before_fee_msat, - counterparty_balance_before_fee_msat, - nondust_htlc_count: nondust_htlc_count + addl_nondust_htlc_count, - commit_tx_fee_sat, - dust_exposure_msat, - extra_accepted_htlc_dust_exposure_msat, + value_to_holder_after_htlcs_msat, + value_to_counterparty_after_htlcs_msat, + channel_type, + )?; + + // Calculate fees on commitment transaction + let nondust_htlc_count = next_commitment_htlcs + .iter() + .filter(|htlc| { + !htlc.is_dust(local, feerate_per_kw, broadcaster_dust_limit_satoshis, channel_type) }) + .count(); + let commit_tx_fee_sat = commit_tx_fee_sat( + feerate_per_kw, + nondust_htlc_count + addl_nondust_htlc_count, + channel_type, + ); + + let (dust_exposure_msat, _extra_accepted_htlc_dust_exposure_msat) = get_dust_exposure_stats( + local, + next_commitment_htlcs, + feerate_per_kw, + dust_exposure_limiting_feerate, + broadcaster_dust_limit_satoshis, + channel_type, + ); + + let (holder_balance_msat, counterparty_balance_msat) = if is_outbound_from_holder { + ( + holder_balance_before_fee_msat.checked_sub(commit_tx_fee_sat * 1000).ok_or(())?, + counterparty_balance_before_fee_msat, + ) + } else { + ( + holder_balance_before_fee_msat, + counterparty_balance_before_fee_msat.checked_sub(commit_tx_fee_sat * 1000).ok_or(())?, + ) + }; + + let dust_limit_msat = broadcaster_dust_limit_satoshis * 1000; + if holder_balance_msat < dust_limit_msat + && counterparty_balance_msat < dust_limit_msat + && nondust_htlc_count == 0 + { + return Err(()); } - 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 + + Ok(NextCommitmentStats { + holder_balance_msat, + counterparty_balance_msat, + dust_exposure_msat, + #[cfg(any(test, fuzzing))] + nondust_htlc_count: nondust_htlc_count + addl_nondust_htlc_count, + #[cfg(any(test, fuzzing))] + commit_tx_fee_sat, + }) +} + +fn get_available_balances( + 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 { + // When sizing the next HTLC add, we take the remote's view of the set of pending HTLCs in + // `ChannelContext::get_next_commitment_htlcs`, set this view to `pending_htlcs` here, and use this set of + // pending HTLCs to calculate stats on our own commitment below. + // + // This means we do *not* include `LocalRemoved` HTLCs. `LocalRemoved` and `LocalAnnounced` HTLCs are applied + // atomically to our own commitment upon the counterparty's next ack. + // + // `RemoteRemoved` HTLCs *are* included. While we don't expect these HTLCs to be present in our next + // commitment, we have not ack'ed these removals yet, so we expect the counterparty to count them when + // validating our own HTLC add. These HTLCs would also revert to `Committed` upon a disconnection. + + let fee_spike_buffer_htlc = + if channel_type.supports_anchor_zero_fee_commitments() { 0 } else { 1 }; + + let local_feerate = feerate_per_kw + * if is_outbound_from_holder && !channel_type.supports_anchors_zero_fee_htlc_tx() { + crate::ln::channel::FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE as u32 } else { - 0 + 1 }; - let mut local_balance_before_fee_msat = value_to_self_after_htlcs; - let mut remote_balance_before_fee_msat = value_to_remote_after_htlcs; + let local_nondust_htlc_count = pending_htlcs + .iter() + .filter(|htlc| { + !htlc.is_dust( + true, + local_feerate, + holder_channel_constraints.dust_limit_satoshis, + channel_type, + ) + }) + .count(); + let local_max_commit_tx_fee_sat = commit_tx_fee_sat( + local_feerate, + local_nondust_htlc_count + fee_spike_buffer_htlc + 1, + channel_type, + ); + let local_min_commit_tx_fee_sat = commit_tx_fee_sat( + local_feerate, + local_nondust_htlc_count + fee_spike_buffer_htlc, + channel_type, + ); + let (local_dust_exposure_msat, _) = get_dust_exposure_stats( + true, + pending_htlcs, + feerate_per_kw, + dust_exposure_limiting_feerate, + holder_channel_constraints.dust_limit_satoshis, + channel_type, + ); + let remote_nondust_htlc_count = pending_htlcs + .iter() + .filter(|htlc| { + !htlc.is_dust( + false, + feerate_per_kw, + counterparty_channel_constraints.dust_limit_satoshis, + channel_type, + ) + }) + .count(); + let remote_commit_tx_fee_sat = + commit_tx_fee_sat(feerate_per_kw, remote_nondust_htlc_count + 1, channel_type); + let (remote_dust_exposure_msat, extra_htlc_remote_dust_exposure_msat) = get_dust_exposure_stats( + false, + pending_htlcs, + feerate_per_kw, + dust_exposure_limiting_feerate, + counterparty_channel_constraints.dust_limit_satoshis, + channel_type, + ); + + let outbound_htlcs_value_msat: u64 = + pending_htlcs.iter().filter_map(|htlc| htlc.outbound.then_some(htlc.amount_msat)).sum(); + let inbound_htlcs_value_msat: u64 = + pending_htlcs.iter().filter_map(|htlc| (!htlc.outbound).then_some(htlc.amount_msat)).sum(); + let (local_balance_before_fee_msat, remote_balance_before_fee_msat) = + saturating_sub_anchor_outputs( + is_outbound_from_holder, + value_to_holder_msat.saturating_sub(outbound_htlcs_value_msat), + (channel_value_satoshis * 1000) + .checked_sub(value_to_holder_msat) + .unwrap() + .saturating_sub(inbound_htlcs_value_msat), + &channel_type, + ); + + let outbound_capacity_msat = local_balance_before_fee_msat + .saturating_sub(holder_channel_constraints.channel_reserve_satoshis * 1000); - // We MUST use saturating subs here, as the funder's balance is not guaranteed to be greater - // than or equal to `total_anchors_sat`. + 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. // - // 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. + // 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 max_reserved_commit_tx_fee_msat = local_max_commit_tx_fee_sat * 1000; + let min_reserved_commit_tx_fee_msat = local_min_commit_tx_fee_sat * 1000; + + // 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_commit_tx_fee_sat * 1000; + + let holder_selected_chan_reserve_msat = + counterparty_channel_constraints.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 = 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_remote_dust_exposure) = extra_htlc_remote_dust_exposure_msat { + if extra_htlc_remote_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_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_dust_exposure_msat)); + dust_exposure_dust_limit_msat = + cmp::max(dust_exposure_dust_limit_msat, buffer_dust_limit_success_sat * 1000); + } + + if local_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_dust_exposure_msat), + )); + dust_exposure_dust_limit_msat = + cmp::max(dust_exposure_dust_limit_msat, buffer_dust_limit_timeout_sat * 1000); + } - if is_outbound_from_holder { - local_balance_before_fee_msat = - local_balance_before_fee_msat.saturating_sub(total_anchors_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 { - remote_balance_before_fee_msat = - remote_balance_before_fee_msat.saturating_sub(total_anchors_sat * 1000); + 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 - outbound_htlcs_value_msat, + ); - (local_balance_before_fee_msat, remote_balance_before_fee_msat) + if pending_htlcs.iter().filter(|htlc| htlc.outbound).count() + 1 + > counterparty_channel_constraints.max_accepted_htlcs as usize + { + available_capacity_msat = 0; + } + + let current_local_nondust_htlc_count = pending_htlcs + .iter() + .filter(|htlc| { + !htlc.is_dust( + true, + feerate_per_kw, + holder_channel_constraints.dust_limit_satoshis, + channel_type, + ) + }) + .count(); + let current_remote_nondust_htlc_count = remote_nondust_htlc_count; + + let maybe_new_local_min_msat = bump_min_if_max_dust_produces_no_output( + true, + is_outbound_from_holder, + local_balance_before_fee_msat, + remote_balance_before_fee_msat, + current_local_nondust_htlc_count, + holder_channel_constraints.dust_limit_satoshis, + feerate_per_kw, + channel_type, + ); + next_outbound_htlc_minimum_msat = + cmp::max(next_outbound_htlc_minimum_msat, maybe_new_local_min_msat); + + let maybe_new_remote_min_msat = bump_min_if_max_dust_produces_no_output( + false, + is_outbound_from_holder, + local_balance_before_fee_msat, + remote_balance_before_fee_msat, + current_remote_nondust_htlc_count, + counterparty_channel_constraints.dust_limit_satoshis, + feerate_per_kw, + channel_type, + ); + next_outbound_htlc_minimum_msat = + cmp::max(next_outbound_htlc_minimum_msat, maybe_new_remote_min_msat); + + crate::ln::channel::AvailableBalances { + inbound_capacity_msat: remote_balance_before_fee_msat + .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 bump_min_if_max_dust_produces_no_output( + local: bool, is_outbound_from_holder: bool, holder_balance_before_fee_msat: u64, + counterparty_balance_before_fee_msat: u64, nondust_htlc_count: usize, dust_limit_satoshis: u64, + feerate_per_kw: u32, channel_type: &ChannelTypeFeatures, +) -> u64 { + let commit_tx_fee_sat = commit_tx_fee_sat(feerate_per_kw, nondust_htlc_count, channel_type); + let (htlc_success_tx_fee_sat, htlc_timeout_tx_fee_sat) = + second_stage_tx_fees_sat(channel_type, feerate_per_kw); + let min_nondust_htlc_sat = + dust_limit_satoshis + if local { htlc_timeout_tx_fee_sat } else { htlc_success_tx_fee_sat }; + + let (holder_balance_msat, counterparty_balance_msat) = if is_outbound_from_holder { + ( + holder_balance_before_fee_msat.saturating_sub(commit_tx_fee_sat * 1000), + counterparty_balance_before_fee_msat, + ) + } else { + ( + holder_balance_before_fee_msat, + counterparty_balance_before_fee_msat.saturating_sub(commit_tx_fee_sat * 1000), + ) + }; + + let dust_limit_msat = dust_limit_satoshis * 1000; + if holder_balance_msat.saturating_sub((min_nondust_htlc_sat * 1000).saturating_sub(1)) + < dust_limit_msat + && counterparty_balance_msat < dust_limit_msat + && nondust_htlc_count == 0 + { + min_nondust_htlc_sat * 1000 + } else { + 0 + } +} + +pub(crate) trait TxBuilder { + fn get_onchain_stats( + &self, local: bool, is_outbound_from_holder: bool, channel_value_satoshis: u64, + value_to_holder_msat: u64, pending_htlcs: &[HTLCAmountDirection], + addl_nondust_htlc_count: usize, 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, + ) -> Result; + fn build_commitment_transaction( + &self, local: bool, commitment_number: u64, per_commitment_point: &PublicKey, + channel_parameters: &ChannelTransactionParameters, secp_ctx: &Secp256k1, + value_to_self_msat: u64, htlcs_in_tx: Vec, feerate_per_kw: u32, + broadcaster_dust_limit_satoshis: u64, logger: &L, + ) -> (CommitmentTransaction, CommitmentStats); +} + +pub(crate) struct SpecTxBuilder {} + +impl TxBuilder for SpecTxBuilder { + fn get_onchain_stats( + &self, local: bool, is_outbound_from_holder: bool, channel_value_satoshis: u64, + value_to_holder_msat: u64, pending_htlcs: &[HTLCAmountDirection], + addl_nondust_htlc_count: usize, 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, + ) -> Result { + let commitment_stats = if local { + get_next_commitment_stats( + true, + is_outbound_from_holder, + channel_value_satoshis, + value_to_holder_msat, + pending_htlcs, + addl_nondust_htlc_count, + feerate_per_kw, + dust_exposure_limiting_feerate, + holder_channel_constraints.dust_limit_satoshis, + channel_type, + )? + } else { + get_next_commitment_stats( + false, + is_outbound_from_holder, + channel_value_satoshis, + value_to_holder_msat, + pending_htlcs, + addl_nondust_htlc_count, + feerate_per_kw, + dust_exposure_limiting_feerate, + counterparty_channel_constraints.dust_limit_satoshis, + channel_type, + )? + }; + + let available_balances = get_available_balances( + is_outbound_from_holder, + channel_value_satoshis, + value_to_holder_msat, + pending_htlcs, + feerate_per_kw, + dust_exposure_limiting_feerate, + max_dust_htlc_exposure_msat, + holder_channel_constraints, + counterparty_channel_constraints, + channel_type, + ); + + Ok(OnchainStats { commitment_stats, available_balances }) } fn build_commitment_transaction( &self, local: bool, commitment_number: u64, per_commitment_point: &PublicKey, @@ -372,7 +729,7 @@ 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( + let commit_tx_fee_sat = commit_tx_fee_sat( feerate_per_kw, htlcs_in_tx.len(), &channel_parameters.channel_type_features, @@ -384,8 +741,16 @@ impl TxBuilder for SpecTxBuilder { .unwrap() .checked_sub(remote_htlc_total_msat) .unwrap(); - let (local_balance_before_fee_msat, remote_balance_before_fee_msat) = self - .subtract_non_htlc_outputs( + + // 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. + + let (local_balance_before_fee_msat, remote_balance_before_fee_msat) = + saturating_sub_anchor_outputs( channel_parameters.is_outbound_from_holder, value_to_self_after_htlcs_msat, value_to_remote_after_htlcs_msat, diff --git a/lightning/src/util/config.rs b/lightning/src/util/config.rs index feb326cfad6..63ffe663fca 100644 --- a/lightning/src/util/config.rs +++ b/lightning/src/util/config.rs @@ -32,12 +32,12 @@ pub struct ChannelHandshakeConfig { /// A lower-bound of `1` is applied, requiring all channels to have a confirmed commitment /// transaction before operation. If you wish to accept channels with zero confirmations, see /// [`UserConfig::manually_accept_inbound_channels`] and - /// [`ChannelManager::accept_inbound_channel_from_trusted_peer_0conf`]. + /// [`ChannelManager::accept_inbound_channel_from_trusted_peer`]. /// /// Default value: `6` /// /// [`ChannelManager::accept_inbound_channel`]: crate::ln::channelmanager::ChannelManager::accept_inbound_channel - /// [`ChannelManager::accept_inbound_channel_from_trusted_peer_0conf`]: crate::ln::channelmanager::ChannelManager::accept_inbound_channel_from_trusted_peer_0conf + /// [`ChannelManager::accept_inbound_channel_from_trusted_peer`]: crate::ln::channelmanager::ChannelManager::accept_inbound_channel_from_trusted_peer pub minimum_depth: u32, /// Set to the number of blocks we require our counterparty to wait to claim their money (ie /// the number of blocks we have to punish our counterparty if they broadcast a revoked