From c7aa6b770e105be7f95e1f36b033e01c65c5a16e Mon Sep 17 00:00:00 2001 From: Jeffrey Czyz Date: Wed, 5 Feb 2025 16:27:20 -0600 Subject: [PATCH 1/6] Introduce a channel FundingScope When establishing a channel, the funding transaction may be replaced either: - after the funding transaction has confirmed using splicing, - before the funding transaction has confirmed for v2 channel establishment using tx_init_rbf, or - before the splice's funding transaction has confirmed using tx_init_rbf. In each of these cases, fields in ChannelContext will need to be updated once the funding transaction confirms. Additionally, the same fields for a pending attempt may need to be considered instead of a previously confirmed funding. This commit introduces a FundingScope to hold the aforementioned fields. It lives next to ChannelContext and will be needed whenever these fields are accessed. The next few commits will move the relevant fields to FundingScope and provide access to them whenever needed, allowing to swap in another FundingScope when necessary. --- lightning/src/ln/channel.rs | 45 ++++++++++++++++++++++++++++--------- 1 file changed, 34 insertions(+), 11 deletions(-) diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index 9a0c0a0c67f..e82132794b9 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -1522,6 +1522,12 @@ impl UnfundedChannelContext { } } +/// Information pertaining to an attempt at funding the channel. This is typically constructed +/// during channel establishment and may be replaced during channel splicing or if the attempted +/// funding transaction is replaced using tx_init_rbf. +pub(super) struct FundingScope { +} + /// Contains everything about the channel including state, and various flags. pub(super) struct ChannelContext where SP::Target: SignerProvider { config: LegacyChannelConfig, @@ -2166,6 +2172,7 @@ impl PendingV2Channel where SP::Target: SignerProvider { match self.unfunded_context.holder_commitment_point { Some(holder_commitment_point) => { let funded_chan = FundedChannel { + funding: self.funding, context: self.context, interactive_tx_signing_session: Some(signing_session), holder_commitment_point, @@ -2203,7 +2210,7 @@ impl ChannelContext where SP::Target: SignerProvider { msg_channel_reserve_satoshis: u64, msg_push_msat: u64, open_channel_fields: msgs::CommonOpenChannelFields, - ) -> Result, ChannelError> + ) -> Result<(FundingScope, ChannelContext), ChannelError> where ES::Target: EntropySource, F::Target: FeeEstimator, @@ -2377,6 +2384,8 @@ impl ChannelContext where SP::Target: SignerProvider { // TODO(dual_funding): Checks for `funding_feerate_sat_per_1000_weight`? + let funding = FundingScope { + }; let channel_context = ChannelContext { user_id, @@ -2521,7 +2530,7 @@ impl ChannelContext where SP::Target: SignerProvider { next_funding_txid: None, }; - Ok(channel_context) + Ok((funding, channel_context)) } fn new_for_outbound_channel<'a, ES: Deref, F: Deref, L: Deref>( @@ -2542,7 +2551,7 @@ impl ChannelContext where SP::Target: SignerProvider { holder_signer: ::EcdsaSigner, pubkeys: ChannelPublicKeys, _logger: L, - ) -> Result, APIError> + ) -> Result<(FundingScope, ChannelContext), APIError> where ES::Target: EntropySource, F::Target: FeeEstimator, @@ -2608,7 +2617,9 @@ impl ChannelContext where SP::Target: SignerProvider { let temporary_channel_id = temporary_channel_id.unwrap_or_else(|| ChannelId::temporary_from_entropy_source(entropy_source)); - Ok(Self { + let funding = FundingScope { + }; + let channel_context = Self { user_id, config: LegacyChannelConfig { @@ -2746,7 +2757,9 @@ impl ChannelContext where SP::Target: SignerProvider { local_initiated_shutdown: None, is_manual_broadcast: false, next_funding_txid: None, - }) + }; + + Ok((funding, channel_context)) } pub(crate) fn get_value_to_self_msat(&self) -> u64 {self.value_to_self_msat} @@ -4502,6 +4515,7 @@ pub(super) struct DualFundingChannelContext { // Holder designates channel data owned for the benefit of the user client. // Counterparty designates channel data owned by the another channel participant entity. pub(super) struct FundedChannel where SP::Target: SignerProvider { + pub funding: FundingScope, pub context: ChannelContext, pub interactive_tx_signing_session: Option, holder_commitment_point: HolderCommitmentPoint, @@ -8518,6 +8532,7 @@ impl FundedChannel where /// A not-yet-funded outbound (from holder) channel using V1 channel establishment. pub(super) struct OutboundV1Channel where SP::Target: SignerProvider { + pub funding: FundingScope, pub context: ChannelContext, pub unfunded_context: UnfundedChannelContext, /// We tried to send an `open_channel` message but our commitment point wasn't ready. @@ -8549,7 +8564,7 @@ impl OutboundV1Channel where SP::Target: SignerProvider { let holder_signer = signer_provider.derive_channel_signer(channel_value_satoshis, channel_keys_id); let pubkeys = holder_signer.pubkeys().clone(); - let context = ChannelContext::new_for_outbound_channel( + let (funding, context) = ChannelContext::new_for_outbound_channel( fee_estimator, entropy_source, signer_provider, @@ -8575,7 +8590,7 @@ impl OutboundV1Channel where SP::Target: SignerProvider { // We initialize `signer_pending_open_channel` to false, and leave setting the flag // for when we try to generate the open_channel message. - let chan = Self { context, unfunded_context, signer_pending_open_channel: false }; + let chan = Self { funding, context, unfunded_context, signer_pending_open_channel: false }; Ok(chan) } @@ -8775,6 +8790,7 @@ impl OutboundV1Channel where SP::Target: SignerProvider { log_info!(logger, "Received funding_signed from peer for channel {}", &self.context.channel_id()); let mut channel = FundedChannel { + funding: self.funding, context: self.context, interactive_tx_signing_session: None, holder_commitment_point, @@ -8815,6 +8831,7 @@ impl OutboundV1Channel where SP::Target: SignerProvider { /// A not-yet-funded inbound (from counterparty) channel using V1 channel establishment. pub(super) struct InboundV1Channel where SP::Target: SignerProvider { + pub funding: FundingScope, pub context: ChannelContext, pub unfunded_context: UnfundedChannelContext, pub signer_pending_accept_channel: bool, @@ -8883,7 +8900,7 @@ impl InboundV1Channel where SP::Target: SignerProvider { htlc_basepoint: HtlcBasepoint::from(msg.common_fields.htlc_basepoint) }; - let context = ChannelContext::new_for_inbound_channel( + let (funding, context) = ChannelContext::new_for_inbound_channel( fee_estimator, entropy_source, signer_provider, @@ -8907,7 +8924,7 @@ impl InboundV1Channel where SP::Target: SignerProvider { unfunded_channel_age_ticks: 0, holder_commitment_point: HolderCommitmentPoint::new(&context.holder_signer, &context.secp_ctx), }; - let chan = Self { context, unfunded_context, signer_pending_accept_channel: false }; + let chan = Self { funding, context, unfunded_context, signer_pending_accept_channel: false }; Ok(chan) } @@ -9040,6 +9057,7 @@ impl InboundV1Channel where SP::Target: SignerProvider { // Promote the channel to a full-fledged one now that we have updated the state and have a // `ChannelMonitor`. let mut channel = FundedChannel { + funding: self.funding, context: self.context, interactive_tx_signing_session: None, holder_commitment_point, @@ -9073,6 +9091,7 @@ impl InboundV1Channel where SP::Target: SignerProvider { // A not-yet-funded channel using V2 channel establishment. pub(super) struct PendingV2Channel where SP::Target: SignerProvider { + pub funding: FundingScope, pub context: ChannelContext, pub unfunded_context: UnfundedChannelContext, pub dual_funding_context: DualFundingChannelContext, @@ -9109,7 +9128,7 @@ impl PendingV2Channel where SP::Target: SignerProvider { "Provided current chain height of {} doesn't make sense for a height-based timelock for the funding transaction", current_chain_height) })?; - let context = ChannelContext::new_for_outbound_channel( + let (funding, context) = ChannelContext::new_for_outbound_channel( fee_estimator, entropy_source, signer_provider, @@ -9133,6 +9152,7 @@ impl PendingV2Channel where SP::Target: SignerProvider { holder_commitment_point: HolderCommitmentPoint::new(&context.holder_signer, &context.secp_ctx), }; let chan = Self { + funding, context, unfunded_context, dual_funding_context: DualFundingChannelContext { @@ -9255,7 +9275,7 @@ impl PendingV2Channel where SP::Target: SignerProvider { htlc_basepoint: HtlcBasepoint(msg.common_fields.htlc_basepoint) }; - let mut context = ChannelContext::new_for_inbound_channel( + let (funding, mut context) = ChannelContext::new_for_inbound_channel( fee_estimator, entropy_source, signer_provider, @@ -9311,6 +9331,7 @@ impl PendingV2Channel where SP::Target: SignerProvider { holder_commitment_point: HolderCommitmentPoint::new(&context.holder_signer, &context.secp_ctx), }; Ok(Self { + funding, context, dual_funding_context, interactive_tx_constructor, @@ -10284,6 +10305,8 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, u32, &'c Ch }; Ok(FundedChannel { + funding: FundingScope { + }, context: ChannelContext { user_id, From 770f90ee3ef8ba84502cb5a9c9a8d5149dcc1406 Mon Sep 17 00:00:00 2001 From: Jeffrey Czyz Date: Mon, 10 Feb 2025 13:48:57 -0600 Subject: [PATCH 2/6] Move channel_value_satoshis to FundingScope --- lightning/src/ln/channel.rs | 253 +++++++++++++++---------- lightning/src/ln/channel_state.rs | 14 +- lightning/src/ln/channelmanager.rs | 75 ++++---- lightning/src/ln/dual_funding_tests.rs | 1 + 4 files changed, 197 insertions(+), 146 deletions(-) diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index e82132794b9..10760a34446 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -1176,6 +1176,26 @@ impl Channel where } } + pub fn funding(&self) -> &FundingScope { + match &self.phase { + ChannelPhase::Undefined => unreachable!(), + ChannelPhase::Funded(chan) => &chan.funding, + ChannelPhase::UnfundedOutboundV1(chan) => &chan.funding, + ChannelPhase::UnfundedInboundV1(chan) => &chan.funding, + ChannelPhase::UnfundedV2(chan) => &chan.funding, + } + } + + pub fn funding_and_context_mut(&mut self) -> (&FundingScope, &mut ChannelContext) { + match &mut self.phase { + ChannelPhase::Undefined => unreachable!(), + ChannelPhase::Funded(chan) => (&chan.funding, &mut chan.context), + ChannelPhase::UnfundedOutboundV1(chan) => (&chan.funding, &mut chan.context), + ChannelPhase::UnfundedInboundV1(chan) => (&chan.funding, &mut chan.context), + ChannelPhase::UnfundedV2(chan) => (&chan.funding, &mut chan.context), + } + } + pub fn unfunded_context_mut(&mut self) -> Option<&mut UnfundedChannelContext> { match &mut self.phase { ChannelPhase::Undefined => unreachable!(), @@ -1443,6 +1463,11 @@ impl Channel where debug_assert!(!matches!(self.phase, ChannelPhase::Undefined)); result } + + pub fn force_shutdown(&mut self, should_broadcast: bool, closure_reason: ClosureReason) -> ShutdownResult { + let (funding, context) = self.funding_and_context_mut(); + context.force_shutdown(funding, should_broadcast, closure_reason) + } } impl From> for Channel @@ -1526,6 +1551,13 @@ impl UnfundedChannelContext { /// during channel establishment and may be replaced during channel splicing or if the attempted /// funding transaction is replaced using tx_init_rbf. pub(super) struct FundingScope { + channel_value_satoshis: u64, +} + +impl FundingScope { + pub fn get_value_satoshis(&self) -> u64 { + self.channel_value_satoshis + } } /// Contains everything about the channel including state, and various flags. @@ -1561,7 +1593,6 @@ pub(super) struct ChannelContext where SP::Target: SignerProvider { announcement_sigs_state: AnnouncementSigsState, secp_ctx: Secp256k1, - channel_value_satoshis: u64, latest_monitor_update_id: u64, @@ -1861,6 +1892,8 @@ trait InitialRemoteCommitmentReceiver where SP::Target: SignerProvide fn context_mut(&mut self) -> &mut ChannelContext; + fn funding(&self) -> &FundingScope; + fn received_msg(&self) -> &'static str; fn check_counterparty_commitment_signature( @@ -1869,10 +1902,10 @@ trait InitialRemoteCommitmentReceiver where SP::Target: SignerProvide let funding_script = self.context().get_funding_redeemscript(); let keys = self.context().build_holder_transaction_keys(holder_commitment_point.current_point()); - let initial_commitment_tx = self.context().build_commitment_transaction(holder_commitment_point.transaction_number(), &keys, true, false, logger).tx; + let initial_commitment_tx = self.context().build_commitment_transaction(self.funding(), holder_commitment_point.transaction_number(), &keys, true, false, logger).tx; let trusted_tx = initial_commitment_tx.trust(); let initial_commitment_bitcoin_tx = trusted_tx.built_transaction(); - let sighash = initial_commitment_bitcoin_tx.get_sighash_all(&funding_script, self.context().channel_value_satoshis); + let sighash = initial_commitment_bitcoin_tx.get_sighash_all(&funding_script, self.funding().channel_value_satoshis); // They sign the holder commitment transaction... log_trace!(logger, "Checking {} tx signature {} by key {} against tx {} (sighash {}) with redeemscript {} for channel {}.", self.received_msg(), log_bytes!(sig.serialize_compact()[..]), log_bytes!(self.context().counterparty_funding_pubkey().serialize()), @@ -1905,9 +1938,9 @@ trait InitialRemoteCommitmentReceiver where SP::Target: SignerProvide panic!("unexpected error type from check_counterparty_commitment_signature {:?}", e); } }; - let context = self.context_mut(); + let context = self.context(); let counterparty_keys = context.build_remote_transaction_keys(); - let counterparty_initial_commitment_tx = context.build_commitment_transaction(context.cur_counterparty_commitment_transaction_number, &counterparty_keys, false, false, logger).tx; + let counterparty_initial_commitment_tx = context.build_commitment_transaction(self.funding(), context.cur_counterparty_commitment_transaction_number, &counterparty_keys, false, false, logger).tx; let counterparty_trusted_tx = counterparty_initial_commitment_tx.trust(); let counterparty_initial_bitcoin_tx = counterparty_trusted_tx.built_transaction(); @@ -1928,6 +1961,7 @@ trait InitialRemoteCommitmentReceiver where SP::Target: SignerProvide // Now that we're past error-generating stuff, update our local state: + let context = self.context_mut(); context.channel_id = channel_id; assert!(!context.channel_state.is_monitor_update_in_progress()); // We have not had any monitor(s) yet to fail update! @@ -1945,12 +1979,13 @@ trait InitialRemoteCommitmentReceiver where SP::Target: SignerProvide return Err(ChannelError::close("Failed to advance holder commitment point".to_owned())); } + let context = self.context(); let funding_redeemscript = context.get_funding_redeemscript(); let funding_txo = context.get_funding_txo().unwrap(); let funding_txo_script = funding_redeemscript.to_p2wsh(); let obscure_factor = get_commitment_transaction_number_obscure_factor(&context.get_holder_pubkeys().payment_point, &context.get_counterparty_pubkeys().payment_point, context.is_outbound()); let shutdown_script = context.shutdown_scriptpubkey.clone().map(|script| script.into_inner()); - let mut monitor_signer = signer_provider.derive_channel_signer(context.channel_value_satoshis, context.channel_keys_id); + let mut monitor_signer = signer_provider.derive_channel_signer(self.funding().channel_value_satoshis, context.channel_keys_id); monitor_signer.provide_channel_parameters(&context.channel_transaction_parameters); // TODO(RBF): When implementing RBF, the funding_txo passed here must only update // ChannelMonitorImp::first_confirmed_funding_txo during channel establishment, not splicing @@ -1958,7 +1993,7 @@ trait InitialRemoteCommitmentReceiver where SP::Target: SignerProvide shutdown_script, context.get_holder_selected_contest_delay(), &context.destination_script, (funding_txo, funding_txo_script), &context.channel_transaction_parameters, context.is_outbound(), - funding_redeemscript.clone(), context.channel_value_satoshis, + funding_redeemscript.clone(), self.funding().channel_value_satoshis, obscure_factor, holder_commitment_tx, best_block, context.counterparty_node_id, context.channel_id()); channel_monitor.provide_initial_counterparty_commitment_tx( @@ -1970,7 +2005,7 @@ trait InitialRemoteCommitmentReceiver where SP::Target: SignerProvide counterparty_initial_commitment_tx.to_countersignatory_value_sat(), logger); - context.cur_counterparty_commitment_transaction_number -= 1; + self.context_mut().cur_counterparty_commitment_transaction_number -= 1; Ok((channel_monitor, counterparty_initial_commitment_tx)) } @@ -1985,6 +2020,10 @@ impl InitialRemoteCommitmentReceiver for OutboundV1Channel wh &mut self.context } + fn funding(&self) -> &FundingScope { + &self.funding + } + fn received_msg(&self) -> &'static str { "funding_signed" } @@ -1999,6 +2038,10 @@ impl InitialRemoteCommitmentReceiver for InboundV1Channel whe &mut self.context } + fn funding(&self) -> &FundingScope { + &self.funding + } + fn received_msg(&self) -> &'static str { "funding_created" } @@ -2013,6 +2056,10 @@ impl InitialRemoteCommitmentReceiver for FundedChannel where &mut self.context } + fn funding(&self) -> &FundingScope { + &self.funding + } + fn received_msg(&self) -> &'static str { "commitment_signed" } @@ -2101,7 +2148,7 @@ impl PendingV2Channel where SP::Target: SignerProvider { let mut output_index = None; let expected_spk = self.context.get_funding_redeemscript().to_p2wsh(); for (idx, outp) in signing_session.unsigned_tx.outputs().enumerate() { - if outp.script_pubkey() == &expected_spk && outp.value() == self.context.get_value_satoshis() { + if outp.script_pubkey() == &expected_spk && outp.value() == self.funding.get_value_satoshis() { if output_index.is_some() { return Err(ChannelError::Close( ( @@ -2125,7 +2172,7 @@ impl PendingV2Channel where SP::Target: SignerProvider { self.context.holder_signer.as_mut().provide_channel_parameters(&self.context.channel_transaction_parameters); self.context.assert_no_commitment_advancement(transaction_number, "initial commitment_signed"); - let commitment_signed = self.context.get_initial_commitment_signed(logger); + let commitment_signed = self.context.get_initial_commitment_signed(&self.funding, logger); let commitment_signed = match commitment_signed { Ok(commitment_signed) => { self.context.funding_transaction = Some(signing_session.unsigned_tx.build_unsigned_tx()); @@ -2385,6 +2432,7 @@ impl ChannelContext where SP::Target: SignerProvider { // TODO(dual_funding): Checks for `funding_feerate_sat_per_1000_weight`? let funding = FundingScope { + channel_value_satoshis, }; let channel_context = ChannelContext { user_id, @@ -2461,7 +2509,6 @@ impl ChannelContext where SP::Target: SignerProvider { channel_creation_height: current_chain_height, feerate_per_kw: open_channel_fields.commitment_feerate_sat_per_1000_weight, - channel_value_satoshis, counterparty_dust_limit_satoshis: open_channel_fields.dust_limit_satoshis, holder_dust_limit_satoshis: MIN_CHAN_DUST_LIMIT_SATOSHIS, counterparty_max_htlc_value_in_flight_msat: cmp::min(open_channel_fields.max_htlc_value_in_flight_msat, channel_value_satoshis * 1000), @@ -2618,6 +2665,8 @@ impl ChannelContext where SP::Target: SignerProvider { let temporary_channel_id = temporary_channel_id.unwrap_or_else(|| ChannelId::temporary_from_entropy_source(entropy_source)); let funding = FundingScope { + // We'll add our counterparty's `funding_satoshis` when we receive `accept_channel2`. + channel_value_satoshis, }; let channel_context = Self { user_id, @@ -2637,8 +2686,6 @@ impl ChannelContext where SP::Target: SignerProvider { channel_state: ChannelState::NegotiatingFunding(NegotiatingFundingFlags::OUR_INIT_SENT), announcement_sigs_state: AnnouncementSigsState::NotSent, secp_ctx, - // We'll add our counterparty's `funding_satoshis` when we receive `accept_channel2`. - channel_value_satoshis, latest_monitor_update_id: 0, @@ -2926,8 +2973,9 @@ impl ChannelContext where SP::Target: SignerProvider { /// Performs checks against necessary constraints after receiving either an `accept_channel` or /// `accept_channel2` message. pub fn do_accept_channel_checks( - &mut self, default_limits: &ChannelHandshakeLimits, their_features: &InitFeatures, - common_fields: &msgs::CommonAcceptChannelFields, channel_reserve_satoshis: u64, + &mut self, funding: &FundingScope, default_limits: &ChannelHandshakeLimits, + their_features: &InitFeatures, common_fields: &msgs::CommonAcceptChannelFields, + channel_reserve_satoshis: u64, ) -> Result<(), ChannelError> { let peer_limits = if let Some(ref limits) = self.inbound_handshake_limits_override { limits } else { default_limits }; @@ -2941,17 +2989,17 @@ impl ChannelContext where SP::Target: SignerProvider { if common_fields.dust_limit_satoshis > 21000000 * 100000000 { return Err(ChannelError::close(format!("Peer never wants payout outputs? dust_limit_satoshis was {}", common_fields.dust_limit_satoshis))); } - if channel_reserve_satoshis > self.channel_value_satoshis { - return Err(ChannelError::close(format!("Bogus channel_reserve_satoshis ({}). Must not be greater than ({})", channel_reserve_satoshis, self.channel_value_satoshis))); + if channel_reserve_satoshis > funding.channel_value_satoshis { + return Err(ChannelError::close(format!("Bogus channel_reserve_satoshis ({}). Must not be greater than ({})", channel_reserve_satoshis, funding.channel_value_satoshis))); } if common_fields.dust_limit_satoshis > self.holder_selected_channel_reserve_satoshis { return Err(ChannelError::close(format!("Dust limit ({}) is bigger than our channel reserve ({})", common_fields.dust_limit_satoshis, self.holder_selected_channel_reserve_satoshis))); } - if channel_reserve_satoshis > self.channel_value_satoshis - self.holder_selected_channel_reserve_satoshis { + if channel_reserve_satoshis > funding.channel_value_satoshis - self.holder_selected_channel_reserve_satoshis { return Err(ChannelError::close(format!("Bogus channel_reserve_satoshis ({}). Must not be greater than channel value minus our reserve ({})", - channel_reserve_satoshis, self.channel_value_satoshis - self.holder_selected_channel_reserve_satoshis))); + channel_reserve_satoshis, funding.channel_value_satoshis - self.holder_selected_channel_reserve_satoshis))); } - let full_channel_value_msat = (self.channel_value_satoshis - channel_reserve_satoshis) * 1000; + let full_channel_value_msat = (funding.channel_value_satoshis - channel_reserve_satoshis) * 1000; if common_fields.htlc_minimum_msat >= full_channel_value_msat { return Err(ChannelError::close(format!("Minimum htlc value ({}) is full channel value ({})", common_fields.htlc_minimum_msat, full_channel_value_msat))); } @@ -3025,7 +3073,7 @@ impl ChannelContext where SP::Target: SignerProvider { } else { None }; self.counterparty_dust_limit_satoshis = common_fields.dust_limit_satoshis; - self.counterparty_max_htlc_value_in_flight_msat = cmp::min(common_fields.max_htlc_value_in_flight_msat, self.channel_value_satoshis * 1000); + self.counterparty_max_htlc_value_in_flight_msat = cmp::min(common_fields.max_htlc_value_in_flight_msat, funding.channel_value_satoshis * 1000); self.counterparty_selected_channel_reserve_satoshis = Some(channel_reserve_satoshis); self.counterparty_htlc_minimum_msat = common_fields.htlc_minimum_msat; self.counterparty_max_accepted_htlcs = common_fields.max_accepted_htlcs; @@ -3103,20 +3151,8 @@ impl ChannelContext where SP::Target: SignerProvider { } /// Allowed in any state (including after shutdown), but will return none before TheirInitSent - pub fn get_holder_htlc_maximum_msat(&self) -> Option { - self.get_htlc_maximum_msat(self.holder_max_htlc_value_in_flight_msat) - } - - /// Allowed in any state (including after shutdown) - pub fn get_announced_htlc_max_msat(&self) -> u64 { - return cmp::min( - // Upper bound by capacity. We make it a bit less than full capacity to prevent attempts - // to use full capacity. This is an effort to reduce routing failures, because in many cases - // channel might have been used to route very small values (either by honest users or as DoS). - self.channel_value_satoshis * 1000 * 9 / 10, - - self.counterparty_max_htlc_value_in_flight_msat - ); + pub fn get_holder_htlc_maximum_msat(&self, funding: &FundingScope) -> Option { + self.get_htlc_maximum_msat(funding, self.holder_max_htlc_value_in_flight_msat) } /// Allowed in any state (including after shutdown) @@ -3125,24 +3161,20 @@ impl ChannelContext where SP::Target: SignerProvider { } /// Allowed in any state (including after shutdown), but will return none before TheirInitSent - pub fn get_counterparty_htlc_maximum_msat(&self) -> Option { - self.get_htlc_maximum_msat(self.counterparty_max_htlc_value_in_flight_msat) + pub fn get_counterparty_htlc_maximum_msat(&self, funding: &FundingScope) -> Option { + self.get_htlc_maximum_msat(funding, self.counterparty_max_htlc_value_in_flight_msat) } - fn get_htlc_maximum_msat(&self, party_max_htlc_value_in_flight_msat: u64) -> Option { + fn get_htlc_maximum_msat(&self, funding: &FundingScope, party_max_htlc_value_in_flight_msat: u64) -> Option { self.counterparty_selected_channel_reserve_satoshis.map(|counterparty_reserve| { let holder_reserve = self.holder_selected_channel_reserve_satoshis; cmp::min( - (self.channel_value_satoshis - counterparty_reserve - holder_reserve) * 1000, + (funding.channel_value_satoshis - counterparty_reserve - holder_reserve) * 1000, party_max_htlc_value_in_flight_msat ) }) } - pub fn get_value_satoshis(&self) -> u64 { - self.channel_value_satoshis - } - pub fn get_fee_proportional_millionths(&self) -> u32 { self.config.options.forwarding_fee_proportional_millionths } @@ -3278,7 +3310,7 @@ impl ChannelContext where SP::Target: SignerProvider { /// generated by the peer which proposed adding the HTLCs, and thus we need to understand both /// which peer generated this transaction and "to whom" this transaction flows. #[inline] - fn build_commitment_transaction(&self, commitment_number: u64, keys: &TxCreationKeys, local: bool, generated_by_local: bool, logger: &L) -> CommitmentStats + fn build_commitment_transaction(&self, funding: &FundingScope, commitment_number: u64, keys: &TxCreationKeys, local: bool, generated_by_local: bool, logger: &L) -> CommitmentStats where L::Target: Logger { let mut included_dust_htlcs: Vec<(HTLCOutputInCommitment, Option<&HTLCSource>)> = Vec::new(); @@ -3433,7 +3465,7 @@ impl ChannelContext where SP::Target: SignerProvider { // AwaitingRemoteRevokeToRemove or AwaitingRemovedRemoteRevoke) we may have allowed them to // "violate" their reserve value by couting those against it. Thus, we have to convert // everything to i64 before subtracting as otherwise we can overflow. - let value_to_remote_msat: i64 = (self.channel_value_satoshis * 1000) as i64 - (self.value_to_self_msat as i64) - (remote_htlc_total_msat as i64) - value_to_self_msat_offset; + let value_to_remote_msat: i64 = (funding.channel_value_satoshis * 1000) as i64 - (self.value_to_self_msat as i64) - (remote_htlc_total_msat as i64) - value_to_self_msat_offset; assert!(value_to_remote_msat >= 0); #[cfg(debug_assertions)] @@ -3781,9 +3813,11 @@ impl ChannelContext where SP::Target: SignerProvider { /// 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(&self, fee_estimator: &LowerBoundedFeeEstimator) - -> AvailableBalances - where F::Target: FeeEstimator + pub fn get_available_balances( + &self, funding: &FundingScope, fee_estimator: &LowerBoundedFeeEstimator, + ) -> AvailableBalances + where + F::Target: FeeEstimator, { let context = &self; // Note that we have to handle overflow due to the case mentioned in the docs in general @@ -3852,7 +3886,7 @@ impl ChannelContext where SP::Target: SignerProvider { let max_reserved_commit_tx_fee_msat = context.next_remote_commit_tx_fee_msat(Some(htlc_above_dust), None); let holder_selected_chan_reserve_msat = context.holder_selected_channel_reserve_satoshis * 1000; - let remote_balance_msat = (context.channel_value_satoshis * 1000 - context.value_to_self_msat) + let remote_balance_msat = (funding.channel_value_satoshis * 1000 - context.value_to_self_msat) .saturating_sub(htlc_stats.pending_inbound_htlcs_value_msat); if remote_balance_msat < max_reserved_commit_tx_fee_msat + holder_selected_chan_reserve_msat + anchor_outputs_value_msat { @@ -3925,7 +3959,7 @@ impl ChannelContext where SP::Target: SignerProvider { #[allow(deprecated)] // TODO: Remove once balance_msat is removed. AvailableBalances { - inbound_capacity_msat: cmp::max(context.channel_value_satoshis as i64 * 1000 + inbound_capacity_msat: cmp::max(funding.channel_value_satoshis as i64 * 1000 - context.value_to_self_msat as i64 - htlc_stats.pending_inbound_htlcs_value_msat as i64 - context.holder_selected_channel_reserve_satoshis as i64 * 1000, @@ -4187,7 +4221,7 @@ impl ChannelContext where SP::Target: SignerProvider { /// those explicitly stated to be allowed after shutdown completes, eg some simple getters). /// Also returns the list of payment_hashes for channels which we can safely fail backwards /// immediately (others we will have to allow to time out). - pub fn force_shutdown(&mut self, should_broadcast: bool, closure_reason: ClosureReason) -> ShutdownResult { + pub fn force_shutdown(&mut self, funding: &FundingScope, should_broadcast: bool, closure_reason: ClosureReason) -> ShutdownResult { // Note that we MUST only generate a monitor update that indicates force-closure - we're // called during initialization prior to the chain_monitor in the encompassing ChannelManager // being fully configured in some cases. Thus, its likely any monitor events we generate will @@ -4236,7 +4270,7 @@ impl ChannelContext where SP::Target: SignerProvider { unbroadcasted_batch_funding_txid, channel_id: self.channel_id, user_channel_id: self.user_id, - channel_capacity_satoshis: self.channel_value_satoshis, + channel_capacity_satoshis: funding.channel_value_satoshis, counterparty_node_id: self.counterparty_node_id, unbroadcasted_funding_tx, is_manual_broadcast: self.is_manual_broadcast, @@ -4335,7 +4369,7 @@ impl ChannelContext where SP::Target: SignerProvider { } fn get_initial_counterparty_commitment_signature( - &self, logger: &L + &self, funding: &FundingScope, logger: &L ) -> Result where SP::Target: SignerProvider, @@ -4343,7 +4377,7 @@ impl ChannelContext where SP::Target: SignerProvider { { let counterparty_keys = self.build_remote_transaction_keys(); let counterparty_initial_commitment_tx = self.build_commitment_transaction( - self.cur_counterparty_commitment_transaction_number, &counterparty_keys, false, false, logger).tx; + funding, self.cur_counterparty_commitment_transaction_number, &counterparty_keys, false, false, logger).tx; match self.holder_signer { // TODO (taproot|arik): move match into calling method for Taproot ChannelSignerType::Ecdsa(ref ecdsa) => { @@ -4362,7 +4396,7 @@ impl ChannelContext where SP::Target: SignerProvider { } fn get_initial_commitment_signed( - &mut self, logger: &L + &mut self, funding: &FundingScope, logger: &L ) -> Result where SP::Target: SignerProvider, @@ -4374,7 +4408,7 @@ impl ChannelContext where SP::Target: SignerProvider { panic!("Tried to get an initial commitment_signed messsage at a time other than immediately after initial handshake completion (or tried to get funding_created twice)"); } - let signature = match self.get_initial_counterparty_commitment_signature(logger) { + let signature = match self.get_initial_counterparty_commitment_signature(funding, logger) { Ok(res) => res, Err(e) => { log_error!(logger, "Got bad signatures: {:?}!", e); @@ -4396,7 +4430,7 @@ impl ChannelContext where SP::Target: SignerProvider { #[cfg(all(test, dual_funding))] pub fn get_initial_counterparty_commitment_signature_for_test( - &mut self, logger: &L, channel_transaction_parameters: ChannelTransactionParameters, + &mut self, funding: &FundingScope, logger: &L, channel_transaction_parameters: ChannelTransactionParameters, counterparty_cur_commitment_point_override: PublicKey, ) -> Result where @@ -4405,7 +4439,7 @@ impl ChannelContext where SP::Target: SignerProvider { { self.counterparty_cur_commitment_point = Some(counterparty_cur_commitment_point_override); self.channel_transaction_parameters = channel_transaction_parameters; - self.get_initial_counterparty_commitment_signature(logger) + self.get_initial_counterparty_commitment_signature(funding, logger) } } @@ -4663,7 +4697,7 @@ impl FundedChannel where let mut total_fee_satoshis = proposed_total_fee_satoshis; let mut value_to_holder: i64 = (self.context.value_to_self_msat as i64) / 1000 - if self.context.is_outbound() { total_fee_satoshis as i64 } else { 0 }; - let mut value_to_counterparty: i64 = ((self.context.channel_value_satoshis * 1000 - self.context.value_to_self_msat) as i64 / 1000) - if self.context.is_outbound() { 0 } else { total_fee_satoshis as i64 }; + let mut value_to_counterparty: i64 = ((self.funding.channel_value_satoshis * 1000 - self.context.value_to_self_msat) as i64 / 1000) - if self.context.is_outbound() { 0 } else { total_fee_satoshis as i64 }; if value_to_holder < 0 { assert!(self.context.is_outbound()); @@ -5101,7 +5135,7 @@ impl FundedChannel where if self.context.channel_state.is_peer_disconnected() { return Err(ChannelError::close("Peer sent update_add_htlc when we needed a channel_reestablish".to_owned())); } - if msg.amount_msat > self.context.channel_value_satoshis * 1000 { + if msg.amount_msat > self.funding.channel_value_satoshis * 1000 { return Err(ChannelError::close("Remote side tried to send more than the total value of the channel".to_owned())); } if msg.amount_msat == 0 { @@ -5144,7 +5178,7 @@ impl FundedChannel where let pending_value_to_self_msat = self.context.value_to_self_msat + htlc_stats.pending_inbound_htlcs_value_msat - removed_outbound_total_msat; let pending_remote_value_msat = - self.context.channel_value_satoshis * 1000 - pending_value_to_self_msat; + self.funding.channel_value_satoshis * 1000 - pending_value_to_self_msat; if pending_remote_value_msat < msg.amount_msat { return Err(ChannelError::close("Remote HTLC add would overdraw remaining funds".to_owned())); } @@ -5323,11 +5357,11 @@ impl FundedChannel where let keys = self.context.build_holder_transaction_keys(self.holder_commitment_point.current_point()); - let commitment_stats = self.context.build_commitment_transaction(self.holder_commitment_point.transaction_number(), &keys, true, false, logger); + let commitment_stats = self.context.build_commitment_transaction(&self.funding, self.holder_commitment_point.transaction_number(), &keys, true, false, logger); let commitment_txid = { let trusted_tx = commitment_stats.tx.trust(); let bitcoin_tx = trusted_tx.built_transaction(); - let sighash = bitcoin_tx.get_sighash_all(&funding_script, self.context.channel_value_satoshis); + let sighash = bitcoin_tx.get_sighash_all(&funding_script, self.funding.channel_value_satoshis); log_trace!(logger, "Checking commitment tx signature {} by key {} against tx {} (sighash {}) with redeemscript {} in channel {}", log_bytes!(msg.signature.serialize_compact()[..]), @@ -6075,7 +6109,7 @@ impl FundedChannel where let dust_exposure_limiting_feerate = self.context.get_dust_exposure_limiting_feerate(&fee_estimator); let htlc_stats = self.context.get_pending_htlc_stats(Some(feerate_per_kw), dust_exposure_limiting_feerate); let keys = self.context.build_holder_transaction_keys(self.holder_commitment_point.current_point()); - let commitment_stats = self.context.build_commitment_transaction(self.holder_commitment_point.transaction_number(), &keys, true, true, logger); + let commitment_stats = self.context.build_commitment_transaction(&self.funding, self.holder_commitment_point.transaction_number(), &keys, true, true, logger); let buffer_fee_msat = commit_tx_fee_sat(feerate_per_kw, commitment_stats.num_nondust_htlcs + htlc_stats.on_holder_tx_outbound_holding_cell_htlcs_count as usize + CONCURRENT_INBOUND_HTLC_FEE_BUFFER as usize, self.context.get_channel_type()) * 1000; let holder_balance_msat = commitment_stats.local_balance_msat - htlc_stats.outbound_holding_cell_msat; if holder_balance_msat < buffer_fee_msat + self.context.counterparty_selected_channel_reserve_satoshis.unwrap() * 1000 { @@ -6376,7 +6410,7 @@ impl FundedChannel where } let funding_signed = if self.context.signer_pending_funding && !self.context.is_outbound() { let counterparty_keys = self.context.build_remote_transaction_keys(); - let counterparty_initial_commitment_tx = self.context.build_commitment_transaction(self.context.cur_counterparty_commitment_transaction_number + 1, &counterparty_keys, false, false, logger).tx; + let counterparty_initial_commitment_tx = self.context.build_commitment_transaction(&self.funding, self.context.cur_counterparty_commitment_transaction_number + 1, &counterparty_keys, false, false, logger).tx; self.context.get_funding_signed_msg(logger, counterparty_initial_commitment_tx) } else { None }; // Provide a `channel_ready` message if we need to, but only if we're _not_ still pending @@ -6421,7 +6455,7 @@ impl FundedChannel where let signed_tx = if let (Some(ClosingSigned { signature, .. }), Some(counterparty_sig)) = (closing_signed.as_ref(), self.context.last_received_closing_sig) { let funding_redeemscript = self.context.get_funding_redeemscript(); - let sighash = closing_tx.trust().get_sighash_all(&funding_redeemscript, self.context.channel_value_satoshis); + let sighash = closing_tx.trust().get_sighash_all(&funding_redeemscript, self.funding.channel_value_satoshis); debug_assert!(self.context.secp_ctx.verify_ecdsa(&sighash, &counterparty_sig, &self.context.get_counterparty_pubkeys().funding_pubkey).is_ok()); Some(self.build_signed_closing_transaction(&closing_tx, &counterparty_sig, signature)) @@ -6430,7 +6464,7 @@ impl FundedChannel where (closing_signed, signed_tx, shutdown_result) } Err(err) => { - let shutdown = self.context.force_shutdown(true, ClosureReason::ProcessingError {err: err.to_string()}); + let shutdown = self.context.force_shutdown(&self.funding, true, ClosureReason::ProcessingError {err: err.to_string()}); (None, None, Some(shutdown)) } } @@ -6836,7 +6870,7 @@ impl FundedChannel where cmp::max(normal_feerate as u64 * tx_weight / 1000 + self.context.config.options.force_close_avoidance_max_fee_satoshis, proposed_max_feerate as u64 * tx_weight / 1000) } else { - self.context.channel_value_satoshis - (self.context.value_to_self_msat + 999) / 1000 + self.funding.channel_value_satoshis - (self.context.value_to_self_msat + 999) / 1000 }; self.context.closing_fee_limits = Some((proposed_total_fee_satoshis, proposed_max_total_fee_satoshis)); @@ -7087,7 +7121,7 @@ impl FundedChannel where unbroadcasted_batch_funding_txid: self.context.unbroadcasted_batch_funding_txid(), channel_id: self.context.channel_id, user_channel_id: self.context.user_id, - channel_capacity_satoshis: self.context.channel_value_satoshis, + channel_capacity_satoshis: self.funding.channel_value_satoshis, counterparty_node_id: self.context.counterparty_node_id, unbroadcasted_funding_tx: self.context.unbroadcasted_funding(), is_manual_broadcast: self.context.is_manual_broadcast, @@ -7132,7 +7166,7 @@ impl FundedChannel where if used_total_fee != msg.fee_satoshis { return Err(ChannelError::close(format!("Remote sent us a closing_signed with a fee other than the value they can claim. Fee in message: {}. Actual closing tx fee: {}", msg.fee_satoshis, used_total_fee))); } - let sighash = closing_tx.trust().get_sighash_all(&funding_redeemscript, self.context.channel_value_satoshis); + let sighash = closing_tx.trust().get_sighash_all(&funding_redeemscript, self.funding.channel_value_satoshis); match self.context.secp_ctx.verify_ecdsa(&sighash, &msg.signature, &self.context.get_counterparty_pubkeys().funding_pubkey) { Ok(_) => {}, @@ -7141,7 +7175,7 @@ impl FundedChannel where // limits, so check for that case by re-checking the signature here. skip_remote_output = true; closing_tx = self.build_closing_transaction(msg.fee_satoshis, skip_remote_output)?.0; - let sighash = closing_tx.trust().get_sighash_all(&funding_redeemscript, self.context.channel_value_satoshis); + let sighash = closing_tx.trust().get_sighash_all(&funding_redeemscript, self.funding.channel_value_satoshis); secp_check!(self.context.secp_ctx.verify_ecdsa(&sighash, &msg.signature, self.context.counterparty_funding_pubkey()), "Invalid closing tx signature from peer".to_owned()); }, }; @@ -7207,7 +7241,7 @@ impl FundedChannel where if !self.context.is_outbound() { // They have to pay, so pick the highest fee in the overlapping range. // We should never set an upper bound aside from their full balance - debug_assert_eq!(our_max_fee, self.context.channel_value_satoshis - (self.context.value_to_self_msat + 999) / 1000); + debug_assert_eq!(our_max_fee, self.funding.channel_value_satoshis - (self.context.value_to_self_msat + 999) / 1000); propose_fee!(cmp::min(max_fee_satoshis, our_max_fee)); } else { if msg.fee_satoshis < our_min_fee || msg.fee_satoshis > our_max_fee { @@ -7356,7 +7390,7 @@ impl FundedChannel where let pending_value_to_self_msat = self.context.value_to_self_msat + htlc_stats.pending_inbound_htlcs_value_msat - removed_outbound_total_msat; let pending_remote_value_msat = - self.context.channel_value_satoshis * 1000 - pending_value_to_self_msat; + self.funding.channel_value_satoshis * 1000 - pending_value_to_self_msat; if !self.context.is_outbound() { // `Some(())` is for the fee spike buffer we keep for the remote. This deviates from @@ -7400,7 +7434,7 @@ impl FundedChannel where pub fn get_value_stat(&self) -> ChannelValueStat { ChannelValueStat { value_to_self_msat: self.context.value_to_self_msat, - channel_value_msat: self.context.channel_value_satoshis * 1000, + channel_value_msat: self.funding.channel_value_satoshis * 1000, channel_reserve_msat: self.context.counterparty_selected_channel_reserve_satoshis.unwrap() * 1000, pending_outbound_htlcs_amount_msat: self.context.pending_outbound_htlcs.iter().map(|ref h| h.amount_msat).sum::(), pending_inbound_htlcs_amount_msat: self.context.pending_inbound_htlcs.iter().map(|ref h| h.amount_msat).sum::(), @@ -7664,7 +7698,7 @@ impl FundedChannel where if tx.compute_txid() == funding_txo.txid { let txo_idx = funding_txo.index as usize; if txo_idx >= tx.output.len() || tx.output[txo_idx].script_pubkey != self.context.get_funding_redeemscript().to_p2wsh() || - tx.output[txo_idx].value.to_sat() != self.context.channel_value_satoshis { + tx.output[txo_idx].value.to_sat() != self.funding.channel_value_satoshis { if self.context.is_outbound() { // If we generated the funding transaction and it doesn't match what it // should, the client is really broken and we should just panic and @@ -8154,7 +8188,7 @@ impl FundedChannel where { return Err(ChannelError::Ignore("Cannot send HTLC until channel is fully established and we haven't started shutting down".to_owned())); } - let channel_total_msat = self.context.channel_value_satoshis * 1000; + let channel_total_msat = self.funding.channel_value_satoshis * 1000; if amount_msat > channel_total_msat { return Err(ChannelError::Ignore(format!("Cannot send amount {}, because it is more than the total value of the channel {}", amount_msat, channel_total_msat))); } @@ -8163,7 +8197,7 @@ impl FundedChannel where return Err(ChannelError::Ignore("Cannot send 0-msat HTLC".to_owned())); } - let available_balances = self.context.get_available_balances(fee_estimator); + let available_balances = self.context.get_available_balances(&self.funding, fee_estimator); if amount_msat < available_balances.next_outbound_htlc_minimum_msat { return Err(ChannelError::Ignore(format!("Cannot send less than our next-HTLC minimum - {} msat", available_balances.next_outbound_htlc_minimum_msat))); @@ -8302,7 +8336,7 @@ impl FundedChannel where where L::Target: Logger { let counterparty_keys = self.context.build_remote_transaction_keys(); - let commitment_stats = self.context.build_commitment_transaction(self.context.cur_counterparty_commitment_transaction_number, &counterparty_keys, false, true, logger); + let commitment_stats = self.context.build_commitment_transaction(&self.funding, self.context.cur_counterparty_commitment_transaction_number, &counterparty_keys, false, true, logger); let counterparty_commitment_tx = commitment_stats.tx; #[cfg(any(test, fuzzing))] @@ -8334,7 +8368,7 @@ impl FundedChannel where self.build_commitment_no_state_update(logger); let counterparty_keys = self.context.build_remote_transaction_keys(); - let commitment_stats = self.context.build_commitment_transaction(self.context.cur_counterparty_commitment_transaction_number, &counterparty_keys, false, true, logger); + let commitment_stats = self.context.build_commitment_transaction(&self.funding, self.context.cur_counterparty_commitment_transaction_number, &counterparty_keys, false, true, logger); let counterparty_commitment_txid = commitment_stats.tx.trust().txid(); match &self.context.holder_signer { @@ -8517,6 +8551,8 @@ impl FundedChannel where Ok((shutdown, monitor_update, dropped_outbound_htlcs)) } + // Miscellaneous utilities + pub fn inflight_htlc_sources(&self) -> impl Iterator { self.context.holding_cell_htlc_updates.iter() .flat_map(|htlc_update| { @@ -8528,6 +8564,17 @@ impl FundedChannel where }) .chain(self.context.pending_outbound_htlcs.iter().map(|htlc| (&htlc.source, &htlc.payment_hash))) } + + pub fn get_announced_htlc_max_msat(&self) -> u64 { + return cmp::min( + // Upper bound by capacity. We make it a bit less than full capacity to prevent attempts + // to use full capacity. This is an effort to reduce routing failures, because in many cases + // channel might have been used to route very small values (either by honest users or as DoS). + self.funding.channel_value_satoshis * 1000 * 9 / 10, + + self.context.counterparty_max_htlc_value_in_flight_msat + ); + } } /// A not-yet-funded outbound (from holder) channel using V1 channel establishment. @@ -8597,7 +8644,7 @@ impl OutboundV1Channel where SP::Target: SignerProvider { /// Only allowed after [`ChannelContext::channel_transaction_parameters`] is set. fn get_funding_created_msg(&mut self, logger: &L) -> Option where L::Target: Logger { let counterparty_keys = self.context.build_remote_transaction_keys(); - let counterparty_initial_commitment_tx = self.context.build_commitment_transaction(self.context.cur_counterparty_commitment_transaction_number, &counterparty_keys, false, false, logger).tx; + let counterparty_initial_commitment_tx = self.context.build_commitment_transaction(&self.funding, self.context.cur_counterparty_commitment_transaction_number, &counterparty_keys, false, false, logger).tx; let signature = match &self.context.holder_signer { // TODO (taproot|arik): move match into calling method for Taproot ChannelSignerType::Ecdsa(ecdsa) => { @@ -8724,7 +8771,7 @@ impl OutboundV1Channel where SP::Target: SignerProvider { common_fields: msgs::CommonOpenChannelFields { chain_hash, temporary_channel_id: self.context.channel_id, - funding_satoshis: self.context.channel_value_satoshis, + funding_satoshis: self.funding.channel_value_satoshis, dust_limit_satoshis: self.context.holder_dust_limit_satoshis, max_htlc_value_in_flight_msat: self.context.holder_max_htlc_value_in_flight_msat, htlc_minimum_msat: self.context.holder_htlc_minimum_msat, @@ -8744,7 +8791,7 @@ impl OutboundV1Channel where SP::Target: SignerProvider { }), channel_type: Some(self.context.channel_type.clone()), }, - push_msat: self.context.channel_value_satoshis * 1000 - self.context.value_to_self_msat, + push_msat: self.funding.channel_value_satoshis * 1000 - self.context.value_to_self_msat, channel_reserve_satoshis: self.context.holder_selected_channel_reserve_satoshis, }) } @@ -8755,7 +8802,9 @@ impl OutboundV1Channel where SP::Target: SignerProvider { their_features: &InitFeatures ) -> Result<(), ChannelError> { self.context.do_accept_channel_checks( - default_limits, their_features, &msg.common_fields, msg.channel_reserve_satoshis) + &self.funding, default_limits, their_features, &msg.common_fields, + msg.channel_reserve_satoshis, + ) } /// Handles a funding_signed message from the remote end. @@ -9208,7 +9257,7 @@ impl PendingV2Channel where SP::Target: SignerProvider { common_fields: msgs::CommonOpenChannelFields { chain_hash, temporary_channel_id: self.context.temporary_channel_id.unwrap(), - funding_satoshis: self.context.channel_value_satoshis, + funding_satoshis: self.funding.channel_value_satoshis, dust_limit_satoshis: self.context.holder_dust_limit_satoshis, max_htlc_value_in_flight_msat: self.context.holder_max_htlc_value_in_flight_msat, htlc_minimum_msat: self.context.holder_htlc_minimum_msat, @@ -9319,7 +9368,7 @@ impl PendingV2Channel where SP::Target: SignerProvider { is_initiator: false, inputs_to_contribute: our_funding_inputs, outputs_to_contribute: Vec::new(), - expected_remote_shared_funding_output: Some((context.get_funding_redeemscript().to_p2wsh(), context.channel_value_satoshis)), + expected_remote_shared_funding_output: Some((context.get_funding_redeemscript().to_p2wsh(), funding.channel_value_satoshis)), } ).map_err(|_| ChannelError::Close(( "V2 channel rejected due to sender error".into(), @@ -9524,7 +9573,7 @@ impl Writeable for FundedChannel where SP::Target: SignerProvider } channel_state.to_u32().write(writer)?; } - self.context.channel_value_satoshis.write(writer)?; + self.funding.channel_value_satoshis.write(writer)?; self.context.latest_monitor_update_id.write(writer)?; @@ -9763,13 +9812,13 @@ impl Writeable for FundedChannel where SP::Target: SignerProvider // a different percentage of the channel value then 10%, which older versions of LDK used // to set it to before the percentage was made configurable. let serialized_holder_selected_reserve = - if self.context.holder_selected_channel_reserve_satoshis != get_legacy_default_holder_selected_channel_reserve_satoshis(self.context.channel_value_satoshis) + if self.context.holder_selected_channel_reserve_satoshis != get_legacy_default_holder_selected_channel_reserve_satoshis(self.funding.channel_value_satoshis) { Some(self.context.holder_selected_channel_reserve_satoshis) } else { None }; let mut old_max_in_flight_percent_config = UserConfig::default().channel_handshake_config; old_max_in_flight_percent_config.max_inbound_htlc_value_in_flight_percent_of_channel = MAX_IN_FLIGHT_PERCENT_LEGACY; let serialized_holder_htlc_max_in_flight = - if self.context.holder_max_htlc_value_in_flight_msat != get_holder_max_htlc_value_in_flight_msat(self.context.channel_value_satoshis, &old_max_in_flight_percent_config) + if self.context.holder_max_htlc_value_in_flight_msat != get_holder_max_htlc_value_in_flight_msat(self.funding.channel_value_satoshis, &old_max_in_flight_percent_config) { Some(self.context.holder_max_htlc_value_in_flight_msat) } else { None }; let channel_pending_event_emitted = Some(self.context.channel_pending_event_emitted); @@ -10306,6 +10355,7 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, u32, &'c Ch Ok(FundedChannel { funding: FundingScope { + channel_value_satoshis, }, context: ChannelContext { user_id, @@ -10323,7 +10373,6 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, u32, &'c Ch channel_state, announcement_sigs_state: announcement_sigs_state.unwrap(), secp_ctx, - channel_value_satoshis, latest_monitor_update_id, @@ -10826,12 +10875,12 @@ mod tests { // `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 chan_1_value_msat = chan_1.context.channel_value_satoshis * 1000; + let chan_1_value_msat = chan_1.funding.channel_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_value_msat = chan_2.context.channel_value_satoshis * 1000; + let chan_2_value_msat = chan_2.funding.channel_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); let chan_1_open_channel_msg = chan_1.get_open_channel(ChainHash::using_genesis_block(network), &&logger).unwrap(); @@ -10840,38 +10889,38 @@ mod tests { // `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_value_msat = chan_3.context.channel_value_satoshis * 1000; + let chan_3_value_msat = chan_3.funding.channel_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_value_msat = chan_4.context.channel_value_satoshis * 1000; + let chan_4_value_msat = chan_4.funding.channel_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_value_msat = chan_5.context.channel_value_satoshis * 1000; + let chan_5_value_msat = chan_5.funding.channel_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_value_msat = chan_6.context.channel_value_satoshis * 1000; + let chan_6_value_msat = chan_6.funding.channel_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_value_msat = chan_7.context.channel_value_satoshis * 1000; + let chan_7_value_msat = chan_7.funding.channel_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_value_msat = chan_8.context.channel_value_satoshis * 1000; + let chan_8_value_msat = chan_8.funding.channel_value_satoshis * 1000; assert_eq!(chan_8.context.holder_max_htlc_value_in_flight_msat, chan_8_value_msat); } @@ -10912,7 +10961,7 @@ mod tests { 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 expected_outbound_selected_chan_reserve = cmp::max(MIN_THEIR_CHAN_RESERVE_SATOSHIS, (chan.context.channel_value_satoshis as f64 * outbound_selected_channel_reserve_perc) as u64); + let expected_outbound_selected_chan_reserve = cmp::max(MIN_THEIR_CHAN_RESERVE_SATOSHIS, (chan.funding.channel_value_satoshis as f64 * outbound_selected_channel_reserve_perc) as u64); assert_eq!(chan.context.holder_selected_channel_reserve_satoshis, expected_outbound_selected_chan_reserve); let chan_open_channel_msg = chan.get_open_channel(ChainHash::using_genesis_block(network), &&logger).unwrap(); @@ -10922,7 +10971,7 @@ mod tests { 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 expected_inbound_selected_chan_reserve = cmp::max(MIN_THEIR_CHAN_RESERVE_SATOSHIS, (chan.context.channel_value_satoshis as f64 * inbound_selected_channel_reserve_perc) as u64); + let expected_inbound_selected_chan_reserve = cmp::max(MIN_THEIR_CHAN_RESERVE_SATOSHIS, (chan.funding.channel_value_satoshis as f64 * inbound_selected_channel_reserve_perc) as u64); assert_eq!(chan_inbound_node.context.holder_selected_channel_reserve_satoshis, expected_inbound_selected_chan_reserve); assert_eq!(chan_inbound_node.context.counterparty_selected_channel_reserve_satoshis.unwrap(), expected_outbound_selected_chan_reserve); @@ -11238,7 +11287,7 @@ mod tests { $( { $htlc_idx: expr, $counterparty_htlc_sig_hex: expr, $htlc_sig_hex: expr, $htlc_tx_hex: expr } ), * } ) => { { let (commitment_tx, htlcs): (_, Vec) = { - let mut commitment_stats = chan.context.build_commitment_transaction(0xffffffffffff - 42, &keys, true, false, &logger); + let mut commitment_stats = chan.context.build_commitment_transaction(&chan.funding, 0xffffffffffff - 42, &keys, true, false, &logger); let htlcs = commitment_stats.htlcs_included.drain(..) .filter_map(|(htlc, _)| if htlc.transaction_output_index.is_some() { Some(htlc) } else { None }) @@ -11249,7 +11298,7 @@ mod tests { let unsigned_tx = trusted_tx.built_transaction(); let redeemscript = chan.context.get_funding_redeemscript(); let counterparty_signature = Signature::from_der(&>::from_hex($counterparty_sig_hex).unwrap()[..]).unwrap(); - let sighash = unsigned_tx.get_sighash_all(&redeemscript, chan.context.channel_value_satoshis); + let sighash = unsigned_tx.get_sighash_all(&redeemscript, chan.funding.channel_value_satoshis); log_trace!(logger, "unsigned_tx = {}", serialize(&unsigned_tx.transaction).as_hex()); assert!(secp_ctx.verify_ecdsa(&sighash, &counterparty_signature, chan.context.counterparty_funding_pubkey()).is_ok(), "verify counterparty commitment sig"); @@ -11309,7 +11358,7 @@ mod tests { let htlc_counterparty_sig = htlc_counterparty_sig_iter.next().unwrap(); let htlc_holder_sig = signer.sign_holder_htlc_transaction(&htlc_tx, 0, &HTLCDescriptor { channel_derivation_parameters: ChannelDerivationParameters { - value_satoshis: chan.context.channel_value_satoshis, + value_satoshis: chan.funding.channel_value_satoshis, keys_id: chan.context.channel_keys_id, transaction_parameters: chan.context.channel_transaction_parameters.clone(), }, diff --git a/lightning/src/ln/channel_state.rs b/lightning/src/ln/channel_state.rs index cbea8d54776..c0ea8b3f31c 100644 --- a/lightning/src/ln/channel_state.rs +++ b/lightning/src/ln/channel_state.rs @@ -15,7 +15,7 @@ use bitcoin::secp256k1::PublicKey; use crate::chain::chaininterface::{FeeEstimator, LowerBoundedFeeEstimator}; use crate::chain::transaction::OutPoint; -use crate::ln::channel::ChannelContext; +use crate::ln::channel::{ChannelContext, FundingScope}; use crate::ln::types::ChannelId; use crate::sign::SignerProvider; use crate::types::features::{ChannelTypeFeatures, InitFeatures}; @@ -476,14 +476,14 @@ impl ChannelDetails { } pub(super) fn from_channel_context( - context: &ChannelContext, best_block_height: u32, latest_features: InitFeatures, - fee_estimator: &LowerBoundedFeeEstimator, + context: &ChannelContext, funding: &FundingScope, best_block_height: u32, + latest_features: InitFeatures, fee_estimator: &LowerBoundedFeeEstimator, ) -> Self where SP::Target: SignerProvider, F::Target: FeeEstimator, { - let balance = context.get_available_balances(fee_estimator); + let balance = context.get_available_balances(funding, fee_estimator); let (to_remote_reserve_satoshis, to_self_reserve_satoshis) = context.get_holder_counterparty_selected_channel_reserve_satoshis(); #[allow(deprecated)] // TODO: Remove once balance_msat is removed. @@ -504,7 +504,7 @@ impl ChannelDetails { } else { None }, - outbound_htlc_maximum_msat: context.get_counterparty_htlc_maximum_msat(), + outbound_htlc_maximum_msat: context.get_counterparty_htlc_maximum_msat(funding), }, funding_txo: context.get_funding_txo(), // Note that accept_channel (or open_channel) is always the first message, so @@ -521,7 +521,7 @@ impl ChannelDetails { None }, inbound_scid_alias: context.latest_inbound_scid_alias(), - channel_value_satoshis: context.get_value_satoshis(), + channel_value_satoshis: funding.get_value_satoshis(), feerate_sat_per_1000_weight: Some(context.get_feerate_sat_per_1000_weight()), unspendable_punishment_reserve: to_self_reserve_satoshis, inbound_capacity_msat: balance.inbound_capacity_msat, @@ -537,7 +537,7 @@ impl ChannelDetails { is_usable: context.is_live(), is_announced: context.should_announce(), inbound_htlc_minimum_msat: Some(context.get_holder_htlc_minimum_msat()), - inbound_htlc_maximum_msat: context.get_holder_htlc_maximum_msat(), + inbound_htlc_maximum_msat: context.get_holder_htlc_maximum_msat(funding), config: Some(context.config()), channel_shutdown_state: Some(context.shutdown_state()), pending_inbound_htlcs: context.get_pending_inbound_htlc_details(), diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 5342cc649d3..6840ecc373b 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -3064,7 +3064,7 @@ macro_rules! locked_close_channel { /// Returns (boolean indicating if we should remove the Channel object from memory, a mapped error) macro_rules! convert_channel_err { - ($self: ident, $peer_state: expr, $err: expr, $context: expr, $channel_id: expr, MANUAL_CHANNEL_UPDATE, $channel_update: expr) => { + ($self: ident, $peer_state: expr, $err: expr, $context: expr, $funding: expr, $channel_id: expr, MANUAL_CHANNEL_UPDATE, $channel_update: expr) => { match $err { ChannelError::Warn(msg) => { (false, MsgHandleErrInternal::from_chan_no_close(ChannelError::Warn(msg), *$channel_id)) @@ -3075,7 +3075,7 @@ macro_rules! convert_channel_err { ChannelError::Close((msg, reason)) => { let logger = WithChannelContext::from(&$self.logger, &$context, None); log_error!(logger, "Closing channel {} due to close-required error: {}", $channel_id, msg); - let mut shutdown_res = $context.force_shutdown(true, reason); + let mut shutdown_res = $context.force_shutdown($funding, true, reason); locked_close_channel!($self, $peer_state, $context, &mut shutdown_res); let err = MsgHandleErrInternal::from_finish_shutdown(msg, *$channel_id, shutdown_res, $channel_update); @@ -3087,10 +3087,10 @@ macro_rules! convert_channel_err { } }; ($self: ident, $peer_state: expr, $err: expr, $funded_channel: expr, $channel_id: expr, FUNDED_CHANNEL) => { - convert_channel_err!($self, $peer_state, $err, $funded_channel.context, $channel_id, MANUAL_CHANNEL_UPDATE, { $self.get_channel_update_for_broadcast(&$funded_channel).ok() }) + convert_channel_err!($self, $peer_state, $err, $funded_channel.context, &$funded_channel.funding, $channel_id, MANUAL_CHANNEL_UPDATE, { $self.get_channel_update_for_broadcast(&$funded_channel).ok() }) }; - ($self: ident, $peer_state: expr, $err: expr, $context: expr, $channel_id: expr, UNFUNDED_CHANNEL) => { - convert_channel_err!($self, $peer_state, $err, $context, $channel_id, MANUAL_CHANNEL_UPDATE, None) + ($self: ident, $peer_state: expr, $err: expr, $context: expr, $funding: expr, $channel_id: expr, UNFUNDED_CHANNEL) => { + convert_channel_err!($self, $peer_state, $err, $context, $funding, $channel_id, MANUAL_CHANNEL_UPDATE, None) }; ($self: ident, $peer_state: expr, $err: expr, $channel: expr, $channel_id: expr) => { match $channel.as_funded_mut() { @@ -3098,7 +3098,8 @@ macro_rules! convert_channel_err { convert_channel_err!($self, $peer_state, $err, funded_channel, $channel_id, FUNDED_CHANNEL) }, None => { - convert_channel_err!($self, $peer_state, $err, $channel.context_mut(), $channel_id, UNFUNDED_CHANNEL) + let (funding, context) = $channel.funding_and_context_mut(); + convert_channel_err!($self, $peer_state, $err, context, funding, $channel_id, UNFUNDED_CHANNEL) }, } }; @@ -3761,7 +3762,7 @@ where .filter_map(|(chan_id, chan)| chan.as_funded().map(|chan| (chan_id, chan))) .filter(f) .map(|(_channel_id, channel)| { - ChannelDetails::from_channel_context(&channel.context, best_block_height, + ChannelDetails::from_channel_context(&channel.context, &channel.funding, best_block_height, peer_state.latest_features.clone(), &self.fee_estimator) }) ); @@ -3786,8 +3787,8 @@ where for (_cp_id, peer_state_mutex) in per_peer_state.iter() { let mut peer_state_lock = peer_state_mutex.lock().unwrap(); let peer_state = &mut *peer_state_lock; - for context in peer_state.channel_by_id.iter().map(|(_, chan)| chan.context()) { - let details = ChannelDetails::from_channel_context(context, best_block_height, + for (context, funding) in peer_state.channel_by_id.iter().map(|(_, chan)| (chan.context(), chan.funding())) { + let details = ChannelDetails::from_channel_context(context, funding, best_block_height, peer_state.latest_features.clone(), &self.fee_estimator); res.push(details); } @@ -3818,12 +3819,12 @@ where let mut peer_state_lock = peer_state_mutex.lock().unwrap(); let peer_state = &mut *peer_state_lock; let features = &peer_state.latest_features; - let context_to_details = |context| { - ChannelDetails::from_channel_context(context, best_block_height, features.clone(), &self.fee_estimator) + let context_to_details = |(context, funding)| { + ChannelDetails::from_channel_context(context, funding, best_block_height, features.clone(), &self.fee_estimator) }; return peer_state.channel_by_id .iter() - .map(|(_, chan)| chan.context()) + .map(|(_, chan)| (chan.context(), chan.funding())) .map(context_to_details) .collect(); } @@ -3910,7 +3911,7 @@ where peer_state_lock, peer_state, per_peer_state, chan); } } else { - let mut shutdown_res = chan_entry.get_mut().context_mut() + let mut shutdown_res = chan_entry.get_mut() .force_shutdown(false, ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(false) }); remove_channel_entry!(self, peer_state, chan_entry, shutdown_res); shutdown_result = Some(shutdown_res); @@ -4091,7 +4092,7 @@ where if let Some(peer_state_mutex) = per_peer_state.get(&counterparty_node_id) { let mut peer_state = peer_state_mutex.lock().unwrap(); if let Some(mut chan) = peer_state.channel_by_id.remove(&channel_id) { - let mut close_res = chan.context_mut().force_shutdown(false, ClosureReason::FundingBatchClosure); + let mut close_res = chan.force_shutdown(false, ClosureReason::FundingBatchClosure); locked_close_channel!(self, &mut *peer_state, chan.context(), close_res); shutdown_results.push(close_res); } @@ -4155,13 +4156,13 @@ where let (mut shutdown_res, update_opt) = match chan_entry.get_mut().as_funded_mut() { Some(chan) => { ( - chan.context.force_shutdown(broadcast, closure_reason), + chan.context.force_shutdown(&chan.funding, broadcast, closure_reason), self.get_channel_update_for_broadcast(&chan).ok(), ) }, None => { // Unfunded channel has no update - (chan_entry.get_mut().context_mut().force_shutdown(false, closure_reason), None) + (chan_entry.get_mut().force_shutdown(false, closure_reason), None) }, }; let chan = remove_channel_entry!(self, peer_state, chan_entry, shutdown_res); @@ -4544,7 +4545,7 @@ where channel_flags: (!were_node_one) as u8 | ((!enabled as u8) << 1), cltv_expiry_delta: chan.context.get_cltv_expiry_delta(), htlc_minimum_msat: chan.context.get_counterparty_htlc_minimum_msat(), - htlc_maximum_msat: chan.context.get_announced_htlc_max_msat(), + htlc_maximum_msat: chan.get_announced_htlc_max_msat(), fee_base_msat: chan.context.get_outbound_forwarding_fee_base_msat(), fee_proportional_millionths: chan.context.get_fee_proportional_millionths(), excess_data: Vec::new(), @@ -5124,7 +5125,7 @@ where let err = if let ChannelError::Close((msg, reason)) = $err { let channel_id = $chan.context.channel_id(); counterparty = $chan.context.get_counterparty_node_id(); - let shutdown_res = $chan.context.force_shutdown(false, reason); + let shutdown_res = $chan.context.force_shutdown(&$chan.funding, false, reason); MsgHandleErrInternal::from_finish_shutdown(msg, channel_id, shutdown_res, None) } else { unreachable!(); }; @@ -5192,7 +5193,7 @@ where mem::drop(peer_state_lock); mem::drop(per_peer_state); let reason = ClosureReason::ProcessingError { err: err.clone() }; - self.finish_close_channel(chan.context.force_shutdown(true, reason)); + self.finish_close_channel(chan.context.force_shutdown(&chan.funding, true, reason)); return Err(APIError::ChannelUnavailable { err }); } } @@ -5359,7 +5360,7 @@ where let outpoint = match &funding { FundingType::Checked(tx) => { for (idx, outp) in tx.output.iter().enumerate() { - if outp.script_pubkey == expected_spk && outp.value.to_sat() == chan.context.get_value_satoshis() { + if outp.script_pubkey == expected_spk && outp.value.to_sat() == chan.funding.get_value_satoshis() { if output_index.is_some() { return Err("Multiple outputs matched the expected script and value"); } @@ -5405,7 +5406,7 @@ where .and_then(|mut peer_state| peer_state.channel_by_id.remove(&channel_id).map(|chan| (chan, peer_state))) .map(|(mut chan, mut peer_state)| { let closure_reason = ClosureReason::ProcessingError { err: e.clone() }; - let mut close_res = chan.context_mut().force_shutdown(false, closure_reason); + let mut close_res = chan.force_shutdown(false, closure_reason); locked_close_channel!(self, peer_state, chan.context(), close_res); shutdown_results.push(close_res); peer_state.pending_msg_events.push(events::MessageSendEvent::HandleError { @@ -5987,7 +5988,7 @@ where let maybe_optimal_channel = peer_state.channel_by_id.values_mut() .filter_map(Channel::as_funded_mut) .filter_map(|chan| { - let balances = chan.context.get_available_balances(&self.fee_estimator); + let balances = chan.context.get_available_balances(&chan.funding, &self.fee_estimator); if outgoing_amt_msat <= balances.next_outbound_htlc_limit_msat && outgoing_amt_msat >= balances.next_outbound_htlc_minimum_msat && chan.context.is_usable() { @@ -6649,12 +6650,13 @@ where chan.context_mut().maybe_expire_prev_config(); let unfunded_context = chan.unfunded_context_mut().expect("channel should be unfunded"); if unfunded_context.should_expire_unfunded_channel() { - let context = chan.context_mut(); + let context = chan.context(); let logger = WithChannelContext::from(&self.logger, context, None); log_error!(logger, "Force-closing pending channel with ID {} for not establishing in a timely manner", context.channel_id()); - let mut close_res = context.force_shutdown(false, ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(false) }); + let mut close_res = chan.force_shutdown(false, ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(false) }); + let context = chan.context_mut(); locked_close_channel!(self, peer_state, context, close_res); shutdown_channels.push(close_res); pending_msg_events.push(MessageSendEvent::HandleError { @@ -8159,7 +8161,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ match chan.get_mut().as_unfunded_outbound_v1_mut() { Some(unfunded_chan) => { try_channel_entry!(self, peer_state, unfunded_chan.accept_channel(msg, &self.default_configuration.channel_handshake_limits, &peer_state.latest_features), chan); - (unfunded_chan.context.get_value_satoshis(), unfunded_chan.context.get_funding_redeemscript().to_p2wsh(), unfunded_chan.context.get_user_id()) + (unfunded_chan.funding.get_value_satoshis(), unfunded_chan.context.get_funding_redeemscript().to_p2wsh(), unfunded_chan.context.get_user_id()) }, None => { return Err(MsgHandleErrInternal::send_err_msg_no_close(format!("Got an unexpected accept_channel message from peer with counterparty_node_id {}", counterparty_node_id), msg.common_fields.temporary_channel_id)); @@ -8208,7 +8210,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ // Really we should be returning the channel_id the peer expects based // on their funding info here, but they're horribly confused anyway, so // there's not a lot we can do to save them. - return Err(convert_channel_err!(self, peer_state, err, inbound_chan.context, &msg.temporary_channel_id, UNFUNDED_CHANNEL).1); + return Err(convert_channel_err!(self, peer_state, err, inbound_chan.context, &inbound_chan.funding, &msg.temporary_channel_id, UNFUNDED_CHANNEL).1); }, } }, @@ -8230,7 +8232,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ // Thus, we must first unset the funding outpoint on the channel. let err = ChannelError::close($err.to_owned()); chan.unset_funding_info(); - return Err(convert_channel_err!(self, peer_state, err, chan.context, &funded_channel_id, UNFUNDED_CHANNEL).1); + return Err(convert_channel_err!(self, peer_state, err, chan.context, &chan.funding, &funded_channel_id, UNFUNDED_CHANNEL).1); } } } match peer_state.channel_by_id.entry(funded_channel_id) { @@ -8703,10 +8705,9 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ } }, None => { - let context = chan_entry.get_mut().context_mut(); - let logger = WithChannelContext::from(&self.logger, context, None); + let logger = WithChannelContext::from(&self.logger, chan_entry.get().context(), None); log_error!(logger, "Immediately closing unfunded channel {} as peer asked to cooperatively shut it down (which is unnecessary)", &msg.channel_id); - let mut close_res = context.force_shutdown(false, ClosureReason::CounterpartyCoopClosedUnfundedChannel); + let mut close_res = chan_entry.get_mut().force_shutdown(false, ClosureReason::CounterpartyCoopClosedUnfundedChannel); remove_channel_entry!(self, peer_state, chan_entry, close_res); finish_shutdown = Some(close_res); }, @@ -9446,7 +9447,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ } else { ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(true) } }; - let mut shutdown_res = chan_entry.get_mut().context_mut().force_shutdown(false, reason.clone()); + let mut shutdown_res = chan_entry.get_mut().force_shutdown(false, reason.clone()); let chan = remove_channel_entry!(self, peer_state, chan_entry, shutdown_res); failed_channels.push(shutdown_res); if let Some(funded_chan) = chan.as_funded() { @@ -11280,7 +11281,7 @@ where // It looks like our counterparty went on-chain or funding transaction was // reorged out of the main chain. Close the channel. let reason_message = format!("{}", reason); - let mut close_res = funded_channel.context.force_shutdown(true, reason); + let mut close_res = funded_channel.context.force_shutdown(&funded_channel.funding, true, reason); locked_close_channel!(self, peer_state, &funded_channel.context, close_res); failed_channels.push(close_res); if let Ok(update) = self.get_channel_update_for_broadcast(&funded_channel) { @@ -11711,8 +11712,8 @@ where return true; } // Clean up for removal. + let mut close_res = chan.force_shutdown(false, ClosureReason::DisconnectedPeer); let context = chan.context_mut(); - let mut close_res = context.force_shutdown(false, ClosureReason::DisconnectedPeer); locked_close_channel!(self, peer_state, &context, close_res); failed_channels.push(close_res); false @@ -13486,7 +13487,7 @@ where log_error!(logger, " The ChannelMonitor for channel {} is at counterparty commitment transaction number {} but the ChannelManager is at counterparty commitment transaction number {}.", &channel.context.channel_id(), monitor.get_cur_counterparty_commitment_number(), channel.get_cur_counterparty_commitment_transaction_number()); } - let mut shutdown_result = channel.context.force_shutdown(true, ClosureReason::OutdatedChannelManager); + let mut shutdown_result = channel.context.force_shutdown(&channel.funding, true, ClosureReason::OutdatedChannelManager); if shutdown_result.unbroadcasted_batch_funding_txid.is_some() { return Err(DecodeError::InvalidValue); } @@ -13513,7 +13514,7 @@ where user_channel_id: channel.context.get_user_id(), reason: ClosureReason::OutdatedChannelManager, counterparty_node_id: Some(channel.context.get_counterparty_node_id()), - channel_capacity_sats: Some(channel.context.get_value_satoshis()), + channel_capacity_sats: Some(channel.funding.get_value_satoshis()), channel_funding_txo: channel.context.get_funding_txo(), last_local_balance_msat: Some(channel.context.get_value_to_self_msat()), }, None)); @@ -13555,13 +13556,13 @@ where // If we were persisted and shut down while the initial ChannelMonitor persistence // was in-progress, we never broadcasted the funding transaction and can still // safely discard the channel. - let _ = channel.context.force_shutdown(false, ClosureReason::DisconnectedPeer); + let _ = channel.context.force_shutdown(&channel.funding, false, ClosureReason::DisconnectedPeer); channel_closures.push_back((events::Event::ChannelClosed { channel_id: channel.context.channel_id(), user_channel_id: channel.context.get_user_id(), reason: ClosureReason::DisconnectedPeer, counterparty_node_id: Some(channel.context.get_counterparty_node_id()), - channel_capacity_sats: Some(channel.context.get_value_satoshis()), + channel_capacity_sats: Some(channel.funding.get_value_satoshis()), channel_funding_txo: channel.context.get_funding_txo(), last_local_balance_msat: Some(channel.context.get_value_to_self_msat()), }, None)); diff --git a/lightning/src/ln/dual_funding_tests.rs b/lightning/src/ln/dual_funding_tests.rs index c6c1d503900..7f0f9c2023e 100644 --- a/lightning/src/ln/dual_funding_tests.rs +++ b/lightning/src/ln/dual_funding_tests.rs @@ -187,6 +187,7 @@ fn do_test_v2_channel_establishment( signature: channel .context .get_initial_counterparty_commitment_signature_for_test( + &channel.funding, &&logger_a, channel_transaction_parameters, accept_channel_v2_msg.common_fields.first_per_commitment_point, From 5c7cf11d34d29a8f688cdeaf5fea2b4734d3d35b Mon Sep 17 00:00:00 2001 From: Jeffrey Czyz Date: Mon, 10 Feb 2025 15:00:52 -0600 Subject: [PATCH 3/6] Move value_to_self_msat to FundingScope --- lightning/src/ln/channel.rs | 92 +++++++++++++++--------------- lightning/src/ln/channelmanager.rs | 4 +- 2 files changed, 49 insertions(+), 47 deletions(-) diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index 10760a34446..880d0763651 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -1552,12 +1552,17 @@ impl UnfundedChannelContext { /// funding transaction is replaced using tx_init_rbf. pub(super) struct FundingScope { channel_value_satoshis: u64, + value_to_self_msat: u64, // Excluding all pending_htlcs, fees, and anchor outputs } impl FundingScope { pub fn get_value_satoshis(&self) -> u64 { self.channel_value_satoshis } + + pub(crate) fn get_value_to_self_msat(&self) -> u64 { + self.value_to_self_msat + } } /// Contains everything about the channel including state, and various flags. @@ -1605,7 +1610,6 @@ pub(super) struct ChannelContext where SP::Target: SignerProvider { // cost of others, but should really just be changed. cur_counterparty_commitment_transaction_number: u64, - value_to_self_msat: u64, // Excluding all pending_htlcs, fees, and anchor outputs pending_inbound_htlcs: Vec, pending_outbound_htlcs: Vec, holding_cell_htlc_updates: Vec, @@ -2433,6 +2437,7 @@ impl ChannelContext where SP::Target: SignerProvider { let funding = FundingScope { channel_value_satoshis, + value_to_self_msat, }; let channel_context = ChannelContext { user_id, @@ -2462,7 +2467,6 @@ impl ChannelContext where SP::Target: SignerProvider { destination_script, cur_counterparty_commitment_transaction_number: INITIAL_COMMITMENT_NUMBER, - value_to_self_msat, pending_inbound_htlcs: Vec::new(), pending_outbound_htlcs: Vec::new(), @@ -2667,6 +2671,7 @@ impl ChannelContext where SP::Target: SignerProvider { let funding = FundingScope { // We'll add our counterparty's `funding_satoshis` when we receive `accept_channel2`. channel_value_satoshis, + value_to_self_msat, }; let channel_context = Self { user_id, @@ -2694,7 +2699,6 @@ impl ChannelContext where SP::Target: SignerProvider { destination_script, cur_counterparty_commitment_transaction_number: INITIAL_COMMITMENT_NUMBER, - value_to_self_msat, pending_inbound_htlcs: Vec::new(), pending_outbound_htlcs: Vec::new(), @@ -2809,8 +2813,6 @@ impl ChannelContext where SP::Target: SignerProvider { Ok((funding, channel_context)) } - pub(crate) fn get_value_to_self_msat(&self) -> u64 {self.value_to_self_msat} - /// Allowed in any state (including after shutdown) pub fn get_update_time_counter(&self) -> u32 { self.update_time_counter @@ -3459,13 +3461,13 @@ impl ChannelContext where SP::Target: SignerProvider { } } - let value_to_self_msat: i64 = (self.value_to_self_msat - local_htlc_total_msat) as i64 + value_to_self_msat_offset; + let value_to_self_msat: i64 = (funding.value_to_self_msat - local_htlc_total_msat) as i64 + value_to_self_msat_offset; assert!(value_to_self_msat >= 0); // Note that in case they have several just-awaiting-last-RAA fulfills in-progress (ie // AwaitingRemoteRevokeToRemove or AwaitingRemovedRemoteRevoke) we may have allowed them to // "violate" their reserve value by couting those against it. Thus, we have to convert // everything to i64 before subtracting as otherwise we can overflow. - let value_to_remote_msat: i64 = (funding.channel_value_satoshis * 1000) as i64 - (self.value_to_self_msat as i64) - (remote_htlc_total_msat as i64) - value_to_self_msat_offset; + let value_to_remote_msat: i64 = (funding.channel_value_satoshis * 1000) as i64 - (funding.value_to_self_msat as i64) - (remote_htlc_total_msat as i64) - value_to_self_msat_offset; assert!(value_to_remote_msat >= 0); #[cfg(debug_assertions)] @@ -3826,7 +3828,7 @@ impl ChannelContext where SP::Target: SignerProvider { let dust_exposure_limiting_feerate = self.get_dust_exposure_limiting_feerate(&fee_estimator); let htlc_stats = context.get_pending_htlc_stats(None, dust_exposure_limiting_feerate); - let outbound_capacity_msat = context.value_to_self_msat + let outbound_capacity_msat = funding.value_to_self_msat .saturating_sub(htlc_stats.pending_outbound_htlcs_value_msat) .saturating_sub( context.counterparty_selected_channel_reserve_satoshis.unwrap_or(0) * 1000); @@ -3886,7 +3888,7 @@ impl ChannelContext where SP::Target: SignerProvider { let max_reserved_commit_tx_fee_msat = context.next_remote_commit_tx_fee_msat(Some(htlc_above_dust), None); let holder_selected_chan_reserve_msat = context.holder_selected_channel_reserve_satoshis * 1000; - let remote_balance_msat = (funding.channel_value_satoshis * 1000 - context.value_to_self_msat) + let remote_balance_msat = (funding.channel_value_satoshis * 1000 - funding.value_to_self_msat) .saturating_sub(htlc_stats.pending_inbound_htlcs_value_msat); if remote_balance_msat < max_reserved_commit_tx_fee_msat + holder_selected_chan_reserve_msat + anchor_outputs_value_msat { @@ -3960,7 +3962,7 @@ impl ChannelContext where SP::Target: SignerProvider { #[allow(deprecated)] // TODO: Remove once balance_msat is removed. AvailableBalances { inbound_capacity_msat: cmp::max(funding.channel_value_satoshis as i64 * 1000 - - context.value_to_self_msat as i64 + - funding.value_to_self_msat as i64 - htlc_stats.pending_inbound_htlcs_value_msat as i64 - context.holder_selected_channel_reserve_satoshis as i64 * 1000, 0) as u64, @@ -4275,7 +4277,7 @@ impl ChannelContext where SP::Target: SignerProvider { unbroadcasted_funding_tx, is_manual_broadcast: self.is_manual_broadcast, channel_funding_txo: self.get_funding_txo(), - last_local_balance_msat: self.value_to_self_msat, + last_local_balance_msat: funding.value_to_self_msat, } } @@ -4696,8 +4698,8 @@ impl FundedChannel where assert!(self.context.pending_update_fee.is_none()); let mut total_fee_satoshis = proposed_total_fee_satoshis; - let mut value_to_holder: i64 = (self.context.value_to_self_msat as i64) / 1000 - if self.context.is_outbound() { total_fee_satoshis as i64 } else { 0 }; - let mut value_to_counterparty: i64 = ((self.funding.channel_value_satoshis * 1000 - self.context.value_to_self_msat) as i64 / 1000) - if self.context.is_outbound() { 0 } else { total_fee_satoshis as i64 }; + let mut value_to_holder: i64 = (self.funding.value_to_self_msat as i64) / 1000 - if self.context.is_outbound() { total_fee_satoshis as i64 } else { 0 }; + let mut value_to_counterparty: i64 = ((self.funding.channel_value_satoshis * 1000 - self.funding.value_to_self_msat) as i64 / 1000) - if self.context.is_outbound() { 0 } else { total_fee_satoshis as i64 }; if value_to_holder < 0 { assert!(self.context.is_outbound()); @@ -5176,7 +5178,7 @@ impl FundedChannel where } let pending_value_to_self_msat = - self.context.value_to_self_msat + htlc_stats.pending_inbound_htlcs_value_msat - removed_outbound_total_msat; + self.funding.value_to_self_msat + htlc_stats.pending_inbound_htlcs_value_msat - removed_outbound_total_msat; let pending_remote_value_msat = self.funding.channel_value_satoshis * 1000 - pending_value_to_self_msat; if pending_remote_value_msat < msg.amount_msat { @@ -5212,7 +5214,7 @@ impl FundedChannel where // Check that they won't violate our local required channel reserve by adding this HTLC. let htlc_candidate = HTLCCandidate::new(msg.amount_msat, HTLCInitiator::RemoteOffered); let local_commit_tx_fee_msat = self.context.next_local_commit_tx_fee_msat(htlc_candidate, None); - if self.context.value_to_self_msat < self.context.counterparty_selected_channel_reserve_satoshis.unwrap() * 1000 + local_commit_tx_fee_msat + anchor_outputs_value_msat { + if self.funding.value_to_self_msat < self.context.counterparty_selected_channel_reserve_satoshis.unwrap() * 1000 + local_commit_tx_fee_msat + anchor_outputs_value_msat { return Err(ChannelError::close("Cannot accept HTLC that would put our balance under counterparty-announced channel reserve value".to_owned())); } } @@ -5905,7 +5907,7 @@ impl FundedChannel where } } } - self.context.value_to_self_msat = (self.context.value_to_self_msat as i64 + value_to_self_msat_diff) as u64; + self.funding.value_to_self_msat = (self.funding.value_to_self_msat as i64 + value_to_self_msat_diff) as u64; if let Some((feerate, update_state)) = self.context.pending_update_fee { match update_state { @@ -6870,7 +6872,7 @@ impl FundedChannel where cmp::max(normal_feerate as u64 * tx_weight / 1000 + self.context.config.options.force_close_avoidance_max_fee_satoshis, proposed_max_feerate as u64 * tx_weight / 1000) } else { - self.funding.channel_value_satoshis - (self.context.value_to_self_msat + 999) / 1000 + self.funding.channel_value_satoshis - (self.funding.value_to_self_msat + 999) / 1000 }; self.context.closing_fee_limits = Some((proposed_total_fee_satoshis, proposed_max_total_fee_satoshis)); @@ -7126,7 +7128,7 @@ impl FundedChannel where unbroadcasted_funding_tx: self.context.unbroadcasted_funding(), is_manual_broadcast: self.context.is_manual_broadcast, channel_funding_txo: self.context.get_funding_txo(), - last_local_balance_msat: self.context.value_to_self_msat, + last_local_balance_msat: self.funding.value_to_self_msat, } } @@ -7241,7 +7243,7 @@ impl FundedChannel where if !self.context.is_outbound() { // They have to pay, so pick the highest fee in the overlapping range. // We should never set an upper bound aside from their full balance - debug_assert_eq!(our_max_fee, self.funding.channel_value_satoshis - (self.context.value_to_self_msat + 999) / 1000); + debug_assert_eq!(our_max_fee, self.funding.channel_value_satoshis - (self.funding.value_to_self_msat + 999) / 1000); propose_fee!(cmp::min(max_fee_satoshis, our_max_fee)); } else { if msg.fee_satoshis < our_min_fee || msg.fee_satoshis > our_max_fee { @@ -7388,7 +7390,7 @@ impl FundedChannel where } let pending_value_to_self_msat = - self.context.value_to_self_msat + htlc_stats.pending_inbound_htlcs_value_msat - removed_outbound_total_msat; + self.funding.value_to_self_msat + htlc_stats.pending_inbound_htlcs_value_msat - removed_outbound_total_msat; let pending_remote_value_msat = self.funding.channel_value_satoshis * 1000 - pending_value_to_self_msat; @@ -7433,7 +7435,7 @@ impl FundedChannel where #[cfg(test)] pub fn get_value_stat(&self) -> ChannelValueStat { ChannelValueStat { - value_to_self_msat: self.context.value_to_self_msat, + value_to_self_msat: self.funding.value_to_self_msat, channel_value_msat: self.funding.channel_value_satoshis * 1000, channel_reserve_msat: self.context.counterparty_selected_channel_reserve_satoshis.unwrap() * 1000, pending_outbound_htlcs_amount_msat: self.context.pending_outbound_htlcs.iter().map(|ref h| h.amount_msat).sum::(), @@ -8791,7 +8793,7 @@ impl OutboundV1Channel where SP::Target: SignerProvider { }), channel_type: Some(self.context.channel_type.clone()), }, - push_msat: self.funding.channel_value_satoshis * 1000 - self.context.value_to_self_msat, + push_msat: self.funding.channel_value_satoshis * 1000 - self.funding.value_to_self_msat, channel_reserve_satoshis: self.context.holder_selected_channel_reserve_satoshis, }) } @@ -9587,7 +9589,7 @@ impl Writeable for FundedChannel where SP::Target: SignerProvider self.holder_commitment_point.transaction_number().write(writer)?; self.context.cur_counterparty_commitment_transaction_number.write(writer)?; - self.context.value_to_self_msat.write(writer)?; + self.funding.value_to_self_msat.write(writer)?; let mut dropped_inbound_htlcs = 0; for htlc in self.context.pending_inbound_htlcs.iter() { @@ -10356,6 +10358,7 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, u32, &'c Ch Ok(FundedChannel { funding: FundingScope { channel_value_satoshis, + value_to_self_msat, }, context: ChannelContext { user_id, @@ -10381,7 +10384,6 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, u32, &'c Ch destination_script, cur_counterparty_commitment_transaction_number, - value_to_self_msat, holder_max_accepted_htlcs, pending_inbound_htlcs, @@ -11390,7 +11392,7 @@ mod tests { "02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b80024a010000000000002200202b1b5854183c12d3316565972c4668929d314d81c5dcdbb21cb45fe8a9a8114f10529800000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0400473044022007cf6b405e9c9b4f527b0ecad9d8bb661fabb8b12abf7d1c0b3ad1855db3ed490220616d5c1eeadccc63bd775a131149455d62d95a42c2a1b01cc7821fc42dce7778014730440220655bf909fb6fa81d086f1336ac72c97906dce29d1b166e305c99152d810e26e1022051f577faa46412c46707aaac46b65d50053550a66334e00a44af2706f27a865801475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220", {}); // simple commitment tx with no HTLCs - chan.context.value_to_self_msat = 7000000000; + chan.funding.value_to_self_msat = 7000000000; test_commitment!("3045022100c3127b33dcc741dd6b05b1e63cbd1a9a7d816f37af9b6756fa2376b056f032370220408b96279808fe57eb7e463710804cdf4f108388bc5cf722d8c848d2c7f9f3b0", "30440220616210b2cc4d3afb601013c373bbd8aac54febd9f15400379a8cb65ce7deca60022034236c010991beb7ff770510561ae8dc885b8d38d1947248c38f2ae055647142", @@ -11464,7 +11466,7 @@ mod tests { }); // commitment tx with all five HTLCs untrimmed (minimum feerate) - chan.context.value_to_self_msat = 6993000000; // 7000000000 - 7000000 + chan.funding.value_to_self_msat = 6993000000; // 7000000000 - 7000000 chan.context.feerate_per_kw = 0; test_commitment!("3044022009b048187705a8cbc9ad73adbe5af148c3d012e1f067961486c822c7af08158c022006d66f3704cfab3eb2dc49dae24e4aa22a6910fc9b424007583204e3621af2e5", @@ -11498,7 +11500,7 @@ mod tests { } ); // commitment tx with seven outputs untrimmed (maximum feerate) - chan.context.value_to_self_msat = 6993000000; // 7000000000 - 7000000 + chan.funding.value_to_self_msat = 6993000000; // 7000000000 - 7000000 chan.context.feerate_per_kw = 647; test_commitment!("3045022100a135f9e8a5ed25f7277446c67956b00ce6f610ead2bdec2c2f686155b7814772022059f1f6e1a8b336a68efcc1af3fe4d422d4827332b5b067501b099c47b7b5b5ee", @@ -11532,7 +11534,7 @@ mod tests { } ); // commitment tx with six outputs untrimmed (minimum feerate) - chan.context.value_to_self_msat = 6993000000; // 7000000000 - 7000000 + chan.funding.value_to_self_msat = 6993000000; // 7000000000 - 7000000 chan.context.feerate_per_kw = 648; test_commitment!("304402203948f900a5506b8de36a4d8502f94f21dd84fd9c2314ab427d52feaa7a0a19f2022059b6a37a4adaa2c5419dc8aea63c6e2a2ec4c4bde46207f6dc1fcd22152fc6e5", @@ -11561,7 +11563,7 @@ mod tests { } ); // anchors: commitment tx with six outputs untrimmed (minimum dust limit) - chan.context.value_to_self_msat = 6993000000; // 7000000000 - 7000000 + chan.funding.value_to_self_msat = 6993000000; // 7000000000 - 7000000 chan.context.feerate_per_kw = 645; chan.context.holder_dust_limit_satoshis = 1001; @@ -11591,7 +11593,7 @@ mod tests { } ); // commitment tx with six outputs untrimmed (maximum feerate) - chan.context.value_to_self_msat = 6993000000; // 7000000000 - 7000000 + chan.funding.value_to_self_msat = 6993000000; // 7000000000 - 7000000 chan.context.feerate_per_kw = 2069; chan.context.holder_dust_limit_satoshis = 546; @@ -11621,7 +11623,7 @@ mod tests { } ); // commitment tx with five outputs untrimmed (minimum feerate) - chan.context.value_to_self_msat = 6993000000; // 7000000000 - 7000000 + chan.funding.value_to_self_msat = 6993000000; // 7000000000 - 7000000 chan.context.feerate_per_kw = 2070; test_commitment!("304402204ca1ba260dee913d318271d86e10ca0f5883026fb5653155cff600fb40895223022037b145204b7054a40e08bb1fefbd826f827b40838d3e501423bcc57924bcb50c", @@ -11645,7 +11647,7 @@ mod tests { } ); // commitment tx with five outputs untrimmed (maximum feerate) - chan.context.value_to_self_msat = 6993000000; // 7000000000 - 7000000 + chan.funding.value_to_self_msat = 6993000000; // 7000000000 - 7000000 chan.context.feerate_per_kw = 2194; test_commitment!("304402204bb3d6e279d71d9da414c82de42f1f954267c762b2e2eb8b76bc3be4ea07d4b0022014febc009c5edc8c3fc5d94015de163200f780046f1c293bfed8568f08b70fb3", @@ -11669,7 +11671,7 @@ mod tests { } ); // commitment tx with four outputs untrimmed (minimum feerate) - chan.context.value_to_self_msat = 6993000000; // 7000000000 - 7000000 + chan.funding.value_to_self_msat = 6993000000; // 7000000000 - 7000000 chan.context.feerate_per_kw = 2195; test_commitment!("304402201a8c1b1f9671cd9e46c7323a104d7047cc48d3ee80d40d4512e0c72b8dc65666022066d7f9a2ce18c9eb22d2739ffcce05721c767f9b607622a31b6ea5793ddce403", @@ -11688,7 +11690,7 @@ mod tests { } ); // anchors: commitment tx with four outputs untrimmed (minimum dust limit) - chan.context.value_to_self_msat = 6993000000; // 7000000000 - 7000000 + chan.funding.value_to_self_msat = 6993000000; // 7000000000 - 7000000 chan.context.feerate_per_kw = 2185; chan.context.holder_dust_limit_satoshis = 2001; let cached_channel_type = chan.context.channel_type; @@ -11710,7 +11712,7 @@ mod tests { } ); // commitment tx with four outputs untrimmed (maximum feerate) - chan.context.value_to_self_msat = 6993000000; // 7000000000 - 7000000 + chan.funding.value_to_self_msat = 6993000000; // 7000000000 - 7000000 chan.context.feerate_per_kw = 3702; chan.context.holder_dust_limit_satoshis = 546; chan.context.channel_type = cached_channel_type.clone(); @@ -11731,7 +11733,7 @@ mod tests { } ); // commitment tx with three outputs untrimmed (minimum feerate) - chan.context.value_to_self_msat = 6993000000; // 7000000000 - 7000000 + chan.funding.value_to_self_msat = 6993000000; // 7000000000 - 7000000 chan.context.feerate_per_kw = 3703; test_commitment!("3045022100b495d239772a237ff2cf354b1b11be152fd852704cb184e7356d13f2fb1e5e430220723db5cdb9cbd6ead7bfd3deb419cf41053a932418cbb22a67b581f40bc1f13e", @@ -11745,7 +11747,7 @@ mod tests { } ); // anchors: commitment tx with three outputs untrimmed (minimum dust limit) - chan.context.value_to_self_msat = 6993000000; // 7000000000 - 7000000 + chan.funding.value_to_self_msat = 6993000000; // 7000000000 - 7000000 chan.context.feerate_per_kw = 3687; chan.context.holder_dust_limit_satoshis = 3001; chan.context.channel_type = ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies(); @@ -11761,7 +11763,7 @@ mod tests { } ); // commitment tx with three outputs untrimmed (maximum feerate) - chan.context.value_to_self_msat = 6993000000; // 7000000000 - 7000000 + chan.funding.value_to_self_msat = 6993000000; // 7000000000 - 7000000 chan.context.feerate_per_kw = 4914; chan.context.holder_dust_limit_satoshis = 546; chan.context.channel_type = cached_channel_type.clone(); @@ -11777,7 +11779,7 @@ mod tests { } ); // commitment tx with two outputs untrimmed (minimum feerate) - chan.context.value_to_self_msat = 6993000000; // 7000000000 - 7000000 + chan.funding.value_to_self_msat = 6993000000; // 7000000000 - 7000000 chan.context.feerate_per_kw = 4915; chan.context.holder_dust_limit_satoshis = 546; @@ -11786,7 +11788,7 @@ mod tests { "02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b8002c0c62d0000000000160014cc1b07838e387deacd0e5232e1e8b49f4c29e484fa926a00000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e04004830450221008a953551f4d67cb4df3037207fc082ddaf6be84d417b0bd14c80aab66f1b01a402207508796dc75034b2dee876fe01dc05a08b019f3e5d689ac8842ade2f1befccf50147304402203a286936e74870ca1459c700c71202af0381910a6bfab687ef494ef1bc3e02c902202506c362d0e3bee15e802aa729bf378e051644648253513f1c085b264cc2a72001475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220", {}); // anchors: commitment tx with two outputs untrimmed (minimum dust limit) - chan.context.value_to_self_msat = 6993000000; // 7000000000 - 7000000 + chan.funding.value_to_self_msat = 6993000000; // 7000000000 - 7000000 chan.context.feerate_per_kw = 4894; chan.context.holder_dust_limit_satoshis = 4001; chan.context.channel_type = ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies(); @@ -11796,7 +11798,7 @@ mod tests { "02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b80044a010000000000002200202b1b5854183c12d3316565972c4668929d314d81c5dcdbb21cb45fe8a9a8114f4a01000000000000220020e9e86e4823faa62e222ebc858a226636856158f07e69898da3b0d1af0ddb3994c0c62d0000000000220020f3394e1e619b0eca1f91be2fb5ab4dfc59ba5b84ebe014ad1d43a564d012994ad0886a00000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e04004830450221009f16ac85d232e4eddb3fcd750a68ebf0b58e3356eaada45d3513ede7e817bf4c02207c2b043b4e5f971261975406cb955219fa56bffe5d834a833694b5abc1ce4cfd01483045022100e784a66b1588575801e237d35e510fd92a81ae3a4a2a1b90c031ad803d07b3f3022021bc5f16501f167607d63b681442da193eb0a76b4b7fd25c2ed4f8b28fd35b9501475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220", {}); // commitment tx with two outputs untrimmed (maximum feerate) - chan.context.value_to_self_msat = 6993000000; // 7000000000 - 7000000 + chan.funding.value_to_self_msat = 6993000000; // 7000000000 - 7000000 chan.context.feerate_per_kw = 9651180; chan.context.holder_dust_limit_satoshis = 546; chan.context.channel_type = cached_channel_type.clone(); @@ -11806,7 +11808,7 @@ mod tests { "02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b800222020000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80ec0c62d0000000000160014cc1b07838e387deacd0e5232e1e8b49f4c29e4840400483045022100e11b638c05c650c2f63a421d36ef8756c5ce82f2184278643520311cdf50aa200220259565fb9c8e4a87ccaf17f27a3b9ca4f20625754a0920d9c6c239d8156a11de0147304402200a8544eba1d216f5c5e530597665fa9bec56943c0f66d98fc3d028df52d84f7002201e45fa5c6bc3a506cc2553e7d1c0043a9811313fc39c954692c0d47cfce2bbd301475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220", {}); // commitment tx with one output untrimmed (minimum feerate) - chan.context.value_to_self_msat = 6993000000; // 7000000000 - 7000000 + chan.funding.value_to_self_msat = 6993000000; // 7000000000 - 7000000 chan.context.feerate_per_kw = 9651181; test_commitment!("304402202ade0142008309eb376736575ad58d03e5b115499709c6db0b46e36ff394b492022037b63d78d66404d6504d4c4ac13be346f3d1802928a6d3ad95a6a944227161a2", @@ -11814,7 +11816,7 @@ mod tests { "02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b8001c0c62d0000000000160014cc1b07838e387deacd0e5232e1e8b49f4c29e484040047304402207e8d51e0c570a5868a78414f4e0cbfaed1106b171b9581542c30718ee4eb95ba02203af84194c97adf98898c9afe2f2ed4a7f8dba05a2dfab28ac9d9c604aa49a3790147304402202ade0142008309eb376736575ad58d03e5b115499709c6db0b46e36ff394b492022037b63d78d66404d6504d4c4ac13be346f3d1802928a6d3ad95a6a944227161a201475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220", {}); // anchors: commitment tx with one output untrimmed (minimum dust limit) - chan.context.value_to_self_msat = 6993000000; // 7000000000 - 7000000 + chan.funding.value_to_self_msat = 6993000000; // 7000000000 - 7000000 chan.context.feerate_per_kw = 6216010; chan.context.holder_dust_limit_satoshis = 4001; chan.context.channel_type = ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies(); @@ -11824,7 +11826,7 @@ mod tests { "02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b80024a01000000000000220020e9e86e4823faa62e222ebc858a226636856158f07e69898da3b0d1af0ddb3994c0c62d0000000000220020f3394e1e619b0eca1f91be2fb5ab4dfc59ba5b84ebe014ad1d43a564d012994a04004830450221009ad80792e3038fe6968d12ff23e6888a565c3ddd065037f357445f01675d63f3022018384915e5f1f4ae157e15debf4f49b61c8d9d2b073c7d6f97c4a68caa3ed4c1014830450221008fd5dbff02e4b59020d4cd23a3c30d3e287065fda75a0a09b402980adf68ccda022001e0b8b620cd915ddff11f1de32addf23d81d51b90e6841b2cb8dcaf3faa5ecf01475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220", {}); // commitment tx with fee greater than funder amount - chan.context.value_to_self_msat = 6993000000; // 7000000000 - 7000000 + chan.funding.value_to_self_msat = 6993000000; // 7000000000 - 7000000 chan.context.feerate_per_kw = 9651936; chan.context.holder_dust_limit_satoshis = 546; chan.context.channel_type = cached_channel_type; @@ -11834,7 +11836,7 @@ mod tests { "02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b8001c0c62d0000000000160014cc1b07838e387deacd0e5232e1e8b49f4c29e484040047304402207e8d51e0c570a5868a78414f4e0cbfaed1106b171b9581542c30718ee4eb95ba02203af84194c97adf98898c9afe2f2ed4a7f8dba05a2dfab28ac9d9c604aa49a3790147304402202ade0142008309eb376736575ad58d03e5b115499709c6db0b46e36ff394b492022037b63d78d66404d6504d4c4ac13be346f3d1802928a6d3ad95a6a944227161a201475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220", {}); // commitment tx with 3 htlc outputs, 2 offered having the same amount and preimage - chan.context.value_to_self_msat = 7_000_000_000 - 2_000_000; + chan.funding.value_to_self_msat = 7_000_000_000 - 2_000_000; chan.context.feerate_per_kw = 253; chan.context.pending_inbound_htlcs.clear(); chan.context.pending_inbound_htlcs.push({ diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 6840ecc373b..4c99c8a4b11 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -13516,7 +13516,7 @@ where counterparty_node_id: Some(channel.context.get_counterparty_node_id()), channel_capacity_sats: Some(channel.funding.get_value_satoshis()), channel_funding_txo: channel.context.get_funding_txo(), - last_local_balance_msat: Some(channel.context.get_value_to_self_msat()), + last_local_balance_msat: Some(channel.funding.get_value_to_self_msat()), }, None)); for (channel_htlc_source, payment_hash) in channel.inflight_htlc_sources() { let mut found_htlc = false; @@ -13564,7 +13564,7 @@ where counterparty_node_id: Some(channel.context.get_counterparty_node_id()), channel_capacity_sats: Some(channel.funding.get_value_satoshis()), channel_funding_txo: channel.context.get_funding_txo(), - last_local_balance_msat: Some(channel.context.get_value_to_self_msat()), + last_local_balance_msat: Some(channel.funding.get_value_to_self_msat()), }, None)); } else { log_error!(logger, "Missing ChannelMonitor for channel {} needed by ChannelManager.", &channel.context.channel_id()); From 6bfb5fb028fd9160df6d1f4ff07538c66f7fd591 Mon Sep 17 00:00:00 2001 From: Jeffrey Czyz Date: Mon, 10 Feb 2025 15:12:42 -0600 Subject: [PATCH 4/6] Move *_selected_channel_reserve_satoshis to FundedScope --- lightning/src/ln/channel.rs | 128 +++++++++++++++------------ lightning/src/ln/channel_state.rs | 2 +- lightning/src/ln/functional_tests.rs | 5 +- 3 files changed, 72 insertions(+), 63 deletions(-) diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index 880d0763651..216556fcc3e 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -1186,6 +1186,17 @@ impl Channel where } } + #[cfg(test)] + pub fn funding_mut(&mut self) -> &mut FundingScope { + match &mut self.phase { + ChannelPhase::Undefined => unreachable!(), + ChannelPhase::Funded(chan) => &mut chan.funding, + ChannelPhase::UnfundedOutboundV1(chan) => &mut chan.funding, + ChannelPhase::UnfundedInboundV1(chan) => &mut chan.funding, + ChannelPhase::UnfundedV2(chan) => &mut chan.funding, + } + } + pub fn funding_and_context_mut(&mut self) -> (&FundingScope, &mut ChannelContext) { match &mut self.phase { ChannelPhase::Undefined => unreachable!(), @@ -1553,6 +1564,14 @@ impl UnfundedChannelContext { pub(super) struct FundingScope { channel_value_satoshis: u64, value_to_self_msat: u64, // Excluding all pending_htlcs, fees, and anchor outputs + + /// minimum channel reserve for self to maintain - set by them. + counterparty_selected_channel_reserve_satoshis: Option, + + #[cfg(test)] + pub(super) holder_selected_channel_reserve_satoshis: u64, + #[cfg(not(test))] + holder_selected_channel_reserve_satoshis: u64, } impl FundingScope { @@ -1563,6 +1582,20 @@ impl FundingScope { pub(crate) fn get_value_to_self_msat(&self) -> u64 { self.value_to_self_msat } + + pub fn get_holder_counterparty_selected_channel_reserve_satoshis(&self) -> (u64, Option) { + (self.holder_selected_channel_reserve_satoshis, self.counterparty_selected_channel_reserve_satoshis) + } + + fn get_htlc_maximum_msat(&self, party_max_htlc_value_in_flight_msat: u64) -> Option { + self.counterparty_selected_channel_reserve_satoshis.map(|counterparty_reserve| { + let holder_reserve = self.holder_selected_channel_reserve_satoshis; + cmp::min( + (self.channel_value_satoshis - counterparty_reserve - holder_reserve) * 1000, + party_max_htlc_value_in_flight_msat + ) + }) + } } /// Contains everything about the channel including state, and various flags. @@ -1749,14 +1782,6 @@ pub(super) struct ChannelContext where SP::Target: SignerProvider { #[cfg(not(test))] holder_max_htlc_value_in_flight_msat: u64, - /// minimum channel reserve for self to maintain - set by them. - counterparty_selected_channel_reserve_satoshis: Option, - - #[cfg(test)] - pub(super) holder_selected_channel_reserve_satoshis: u64, - #[cfg(not(test))] - holder_selected_channel_reserve_satoshis: u64, - counterparty_htlc_minimum_msat: u64, holder_htlc_minimum_msat: u64, #[cfg(test)] @@ -2438,6 +2463,8 @@ impl ChannelContext where SP::Target: SignerProvider { let funding = FundingScope { channel_value_satoshis, value_to_self_msat, + counterparty_selected_channel_reserve_satoshis: Some(msg_channel_reserve_satoshis), + holder_selected_channel_reserve_satoshis, }; let channel_context = ChannelContext { user_id, @@ -2517,8 +2544,6 @@ impl ChannelContext where SP::Target: SignerProvider { holder_dust_limit_satoshis: MIN_CHAN_DUST_LIMIT_SATOSHIS, counterparty_max_htlc_value_in_flight_msat: cmp::min(open_channel_fields.max_htlc_value_in_flight_msat, channel_value_satoshis * 1000), holder_max_htlc_value_in_flight_msat: get_holder_max_htlc_value_in_flight_msat(channel_value_satoshis, &config.channel_handshake_config), - counterparty_selected_channel_reserve_satoshis: Some(msg_channel_reserve_satoshis), - holder_selected_channel_reserve_satoshis, counterparty_htlc_minimum_msat: open_channel_fields.htlc_minimum_msat, holder_htlc_minimum_msat: if config.channel_handshake_config.our_htlc_minimum_msat == 0 { 1 } else { config.channel_handshake_config.our_htlc_minimum_msat }, counterparty_max_accepted_htlcs: open_channel_fields.max_accepted_htlcs, @@ -2672,6 +2697,8 @@ impl ChannelContext where SP::Target: SignerProvider { // We'll add our counterparty's `funding_satoshis` when we receive `accept_channel2`. channel_value_satoshis, value_to_self_msat, + counterparty_selected_channel_reserve_satoshis: None, // Filled in in accept_channel + holder_selected_channel_reserve_satoshis, }; let channel_context = Self { user_id, @@ -2752,8 +2779,6 @@ impl ChannelContext where SP::Target: SignerProvider { // We'll adjust this to include our counterparty's `funding_satoshis` when we // receive `accept_channel2`. holder_max_htlc_value_in_flight_msat: get_holder_max_htlc_value_in_flight_msat(channel_value_satoshis, &config.channel_handshake_config), - counterparty_selected_channel_reserve_satoshis: None, // Filled in in accept_channel - holder_selected_channel_reserve_satoshis, counterparty_htlc_minimum_msat: 0, holder_htlc_minimum_msat: if config.channel_handshake_config.our_htlc_minimum_msat == 0 { 1 } else { config.channel_handshake_config.our_htlc_minimum_msat }, counterparty_max_accepted_htlcs: 0, @@ -2975,7 +3000,7 @@ impl ChannelContext where SP::Target: SignerProvider { /// Performs checks against necessary constraints after receiving either an `accept_channel` or /// `accept_channel2` message. pub fn do_accept_channel_checks( - &mut self, funding: &FundingScope, default_limits: &ChannelHandshakeLimits, + &mut self, funding: &mut FundingScope, default_limits: &ChannelHandshakeLimits, their_features: &InitFeatures, common_fields: &msgs::CommonAcceptChannelFields, channel_reserve_satoshis: u64, ) -> Result<(), ChannelError> { @@ -2994,12 +3019,12 @@ impl ChannelContext where SP::Target: SignerProvider { if channel_reserve_satoshis > funding.channel_value_satoshis { return Err(ChannelError::close(format!("Bogus channel_reserve_satoshis ({}). Must not be greater than ({})", channel_reserve_satoshis, funding.channel_value_satoshis))); } - if common_fields.dust_limit_satoshis > self.holder_selected_channel_reserve_satoshis { - return Err(ChannelError::close(format!("Dust limit ({}) is bigger than our channel reserve ({})", common_fields.dust_limit_satoshis, self.holder_selected_channel_reserve_satoshis))); + if common_fields.dust_limit_satoshis > funding.holder_selected_channel_reserve_satoshis { + 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.channel_value_satoshis - self.holder_selected_channel_reserve_satoshis { + if channel_reserve_satoshis > funding.channel_value_satoshis - funding.holder_selected_channel_reserve_satoshis { return Err(ChannelError::close(format!("Bogus channel_reserve_satoshis ({}). Must not be greater than channel value minus our reserve ({})", - channel_reserve_satoshis, funding.channel_value_satoshis - self.holder_selected_channel_reserve_satoshis))); + channel_reserve_satoshis, funding.channel_value_satoshis - funding.holder_selected_channel_reserve_satoshis))); } let full_channel_value_msat = (funding.channel_value_satoshis - channel_reserve_satoshis) * 1000; if common_fields.htlc_minimum_msat >= full_channel_value_msat { @@ -3076,7 +3101,7 @@ impl ChannelContext where SP::Target: SignerProvider { self.counterparty_dust_limit_satoshis = common_fields.dust_limit_satoshis; self.counterparty_max_htlc_value_in_flight_msat = cmp::min(common_fields.max_htlc_value_in_flight_msat, funding.channel_value_satoshis * 1000); - self.counterparty_selected_channel_reserve_satoshis = Some(channel_reserve_satoshis); + funding.counterparty_selected_channel_reserve_satoshis = Some(channel_reserve_satoshis); self.counterparty_htlc_minimum_msat = common_fields.htlc_minimum_msat; self.counterparty_max_accepted_htlcs = common_fields.max_accepted_htlcs; @@ -3154,7 +3179,7 @@ impl ChannelContext where SP::Target: SignerProvider { /// Allowed in any state (including after shutdown), but will return none before TheirInitSent pub fn get_holder_htlc_maximum_msat(&self, funding: &FundingScope) -> Option { - self.get_htlc_maximum_msat(funding, self.holder_max_htlc_value_in_flight_msat) + funding.get_htlc_maximum_msat(self.holder_max_htlc_value_in_flight_msat) } /// Allowed in any state (including after shutdown) @@ -3164,17 +3189,7 @@ impl ChannelContext where SP::Target: SignerProvider { /// Allowed in any state (including after shutdown), but will return none before TheirInitSent pub fn get_counterparty_htlc_maximum_msat(&self, funding: &FundingScope) -> Option { - self.get_htlc_maximum_msat(funding, self.counterparty_max_htlc_value_in_flight_msat) - } - - fn get_htlc_maximum_msat(&self, funding: &FundingScope, party_max_htlc_value_in_flight_msat: u64) -> Option { - self.counterparty_selected_channel_reserve_satoshis.map(|counterparty_reserve| { - let holder_reserve = self.holder_selected_channel_reserve_satoshis; - cmp::min( - (funding.channel_value_satoshis - counterparty_reserve - holder_reserve) * 1000, - party_max_htlc_value_in_flight_msat - ) - }) + funding.get_htlc_maximum_msat(self.counterparty_max_htlc_value_in_flight_msat) } pub fn get_fee_proportional_millionths(&self) -> u32 { @@ -3479,9 +3494,9 @@ impl ChannelContext where SP::Target: SignerProvider { } else { self.counterparty_max_commitment_tx_output.lock().unwrap() }; - debug_assert!(broadcaster_max_commitment_tx_output.0 <= value_to_self_msat as u64 || value_to_self_msat / 1000 >= self.counterparty_selected_channel_reserve_satoshis.unwrap() as i64); + debug_assert!(broadcaster_max_commitment_tx_output.0 <= value_to_self_msat as u64 || value_to_self_msat / 1000 >= funding.counterparty_selected_channel_reserve_satoshis.unwrap() as i64); broadcaster_max_commitment_tx_output.0 = cmp::max(broadcaster_max_commitment_tx_output.0, value_to_self_msat as u64); - debug_assert!(broadcaster_max_commitment_tx_output.1 <= value_to_remote_msat as u64 || value_to_remote_msat / 1000 >= self.holder_selected_channel_reserve_satoshis as i64); + debug_assert!(broadcaster_max_commitment_tx_output.1 <= value_to_remote_msat as u64 || value_to_remote_msat / 1000 >= funding.holder_selected_channel_reserve_satoshis as i64); broadcaster_max_commitment_tx_output.1 = cmp::max(broadcaster_max_commitment_tx_output.1, value_to_remote_msat as u64); } @@ -3831,7 +3846,7 @@ impl ChannelContext where SP::Target: SignerProvider { let outbound_capacity_msat = funding.value_to_self_msat .saturating_sub(htlc_stats.pending_outbound_htlcs_value_msat) .saturating_sub( - context.counterparty_selected_channel_reserve_satoshis.unwrap_or(0) * 1000); + funding.counterparty_selected_channel_reserve_satoshis.unwrap_or(0) * 1000); let mut available_capacity_msat = outbound_capacity_msat; @@ -3887,7 +3902,7 @@ impl ChannelContext where SP::Target: SignerProvider { 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(Some(htlc_above_dust), None); - let holder_selected_chan_reserve_msat = context.holder_selected_channel_reserve_satoshis * 1000; + let holder_selected_chan_reserve_msat = funding.holder_selected_channel_reserve_satoshis * 1000; let remote_balance_msat = (funding.channel_value_satoshis * 1000 - funding.value_to_self_msat) .saturating_sub(htlc_stats.pending_inbound_htlcs_value_msat); @@ -3964,7 +3979,7 @@ impl ChannelContext where SP::Target: SignerProvider { inbound_capacity_msat: cmp::max(funding.channel_value_satoshis as i64 * 1000 - funding.value_to_self_msat as i64 - htlc_stats.pending_inbound_htlcs_value_msat as i64 - - context.holder_selected_channel_reserve_satoshis as i64 * 1000, + - funding.holder_selected_channel_reserve_satoshis as i64 * 1000, 0) as u64, outbound_capacity_msat, next_outbound_htlc_limit_msat: available_capacity_msat, @@ -3972,11 +3987,6 @@ impl ChannelContext where SP::Target: SignerProvider { } } - pub fn get_holder_counterparty_selected_channel_reserve_satoshis(&self) -> (u64, Option) { - let context = &self; - (context.holder_selected_channel_reserve_satoshis, context.counterparty_selected_channel_reserve_satoshis) - } - /// 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. /// @@ -5200,7 +5210,7 @@ impl FundedChannel where if pending_remote_value_msat.saturating_sub(msg.amount_msat).saturating_sub(anchor_outputs_value_msat) < remote_commit_tx_fee_msat { return Err(ChannelError::close("Remote HTLC add would not leave enough to pay for fees".to_owned())); }; - if pending_remote_value_msat.saturating_sub(msg.amount_msat).saturating_sub(remote_commit_tx_fee_msat).saturating_sub(anchor_outputs_value_msat) < self.context.holder_selected_channel_reserve_satoshis * 1000 { + if pending_remote_value_msat.saturating_sub(msg.amount_msat).saturating_sub(remote_commit_tx_fee_msat).saturating_sub(anchor_outputs_value_msat) < self.funding.holder_selected_channel_reserve_satoshis * 1000 { return Err(ChannelError::close("Remote HTLC add would put them under remote reserve value".to_owned())); } } @@ -5214,7 +5224,7 @@ impl FundedChannel where // Check that they won't violate our local required channel reserve by adding this HTLC. let htlc_candidate = HTLCCandidate::new(msg.amount_msat, HTLCInitiator::RemoteOffered); let local_commit_tx_fee_msat = self.context.next_local_commit_tx_fee_msat(htlc_candidate, None); - if self.funding.value_to_self_msat < self.context.counterparty_selected_channel_reserve_satoshis.unwrap() * 1000 + local_commit_tx_fee_msat + anchor_outputs_value_msat { + if self.funding.value_to_self_msat < self.funding.counterparty_selected_channel_reserve_satoshis.unwrap() * 1000 + local_commit_tx_fee_msat + anchor_outputs_value_msat { return Err(ChannelError::close("Cannot accept HTLC that would put our balance under counterparty-announced channel reserve value".to_owned())); } } @@ -5383,7 +5393,7 @@ impl FundedChannel where } else { false }; if update_fee { debug_assert!(!self.context.is_outbound()); - let counterparty_reserve_we_require_msat = self.context.holder_selected_channel_reserve_satoshis * 1000; + let counterparty_reserve_we_require_msat = self.funding.holder_selected_channel_reserve_satoshis * 1000; if commitment_stats.remote_balance_msat < commitment_stats.total_fee_sat * 1000 + counterparty_reserve_we_require_msat { return Err(ChannelError::close("Funding remote cannot afford proposed new fee".to_owned())); } @@ -6114,7 +6124,7 @@ impl FundedChannel where let commitment_stats = self.context.build_commitment_transaction(&self.funding, self.holder_commitment_point.transaction_number(), &keys, true, true, logger); let buffer_fee_msat = commit_tx_fee_sat(feerate_per_kw, commitment_stats.num_nondust_htlcs + htlc_stats.on_holder_tx_outbound_holding_cell_htlcs_count as usize + CONCURRENT_INBOUND_HTLC_FEE_BUFFER as usize, self.context.get_channel_type()) * 1000; let holder_balance_msat = commitment_stats.local_balance_msat - htlc_stats.outbound_holding_cell_msat; - if holder_balance_msat < buffer_fee_msat + self.context.counterparty_selected_channel_reserve_satoshis.unwrap() * 1000 { + if holder_balance_msat < buffer_fee_msat + self.funding.counterparty_selected_channel_reserve_satoshis.unwrap() * 1000 { //TODO: auto-close after a number of failures? log_debug!(logger, "Cannot afford to send new feerate at {}", feerate_per_kw); return None; @@ -7406,7 +7416,7 @@ impl FundedChannel where if !self.context.get_channel_type().supports_anchors_zero_fee_htlc_tx() { remote_fee_cost_incl_stuck_buffer_msat *= FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE; } - if pending_remote_value_msat.saturating_sub(self.context.holder_selected_channel_reserve_satoshis * 1000).saturating_sub(anchor_outputs_value_msat) < remote_fee_cost_incl_stuck_buffer_msat { + if pending_remote_value_msat.saturating_sub(self.funding.holder_selected_channel_reserve_satoshis * 1000).saturating_sub(anchor_outputs_value_msat) < remote_fee_cost_incl_stuck_buffer_msat { log_info!(logger, "Attempting to fail HTLC due to fee spike buffer violation in channel {}. Rebalancing is required.", &self.context.channel_id()); return Err(("Fee spike buffer violation", 0x1000|7)); } @@ -7437,7 +7447,7 @@ impl FundedChannel where ChannelValueStat { value_to_self_msat: self.funding.value_to_self_msat, channel_value_msat: self.funding.channel_value_satoshis * 1000, - channel_reserve_msat: self.context.counterparty_selected_channel_reserve_satoshis.unwrap() * 1000, + channel_reserve_msat: self.funding.counterparty_selected_channel_reserve_satoshis.unwrap() * 1000, pending_outbound_htlcs_amount_msat: self.context.pending_outbound_htlcs.iter().map(|ref h| h.amount_msat).sum::(), pending_inbound_htlcs_amount_msat: self.context.pending_inbound_htlcs.iter().map(|ref h| h.amount_msat).sum::(), holding_cell_outbound_amount_msat: { @@ -8794,7 +8804,7 @@ impl OutboundV1Channel where SP::Target: SignerProvider { channel_type: Some(self.context.channel_type.clone()), }, push_msat: self.funding.channel_value_satoshis * 1000 - self.funding.value_to_self_msat, - channel_reserve_satoshis: self.context.holder_selected_channel_reserve_satoshis, + channel_reserve_satoshis: self.funding.holder_selected_channel_reserve_satoshis, }) } @@ -8804,7 +8814,7 @@ impl OutboundV1Channel where SP::Target: SignerProvider { their_features: &InitFeatures ) -> Result<(), ChannelError> { self.context.do_accept_channel_checks( - &self.funding, default_limits, their_features, &msg.common_fields, + &mut self.funding, default_limits, their_features, &msg.common_fields, msg.channel_reserve_satoshis, ) } @@ -9044,7 +9054,7 @@ impl InboundV1Channel where SP::Target: SignerProvider { }), channel_type: Some(self.context.channel_type.clone()), }, - channel_reserve_satoshis: self.context.holder_selected_channel_reserve_satoshis, + channel_reserve_satoshis: self.funding.holder_selected_channel_reserve_satoshis, #[cfg(taproot)] next_local_nonce: None, }) @@ -9770,7 +9780,7 @@ impl Writeable for FundedChannel where SP::Target: SignerProvider self.context.counterparty_max_htlc_value_in_flight_msat.write(writer)?; // Note that this field is ignored by 0.0.99+ as the TLV Optional variant is used instead. - self.context.counterparty_selected_channel_reserve_satoshis.unwrap_or(0).write(writer)?; + self.funding.counterparty_selected_channel_reserve_satoshis.unwrap_or(0).write(writer)?; self.context.counterparty_htlc_minimum_msat.write(writer)?; self.context.holder_htlc_minimum_msat.write(writer)?; @@ -9814,8 +9824,8 @@ impl Writeable for FundedChannel where SP::Target: SignerProvider // a different percentage of the channel value then 10%, which older versions of LDK used // to set it to before the percentage was made configurable. let serialized_holder_selected_reserve = - if self.context.holder_selected_channel_reserve_satoshis != get_legacy_default_holder_selected_channel_reserve_satoshis(self.funding.channel_value_satoshis) - { Some(self.context.holder_selected_channel_reserve_satoshis) } else { None }; + if self.funding.holder_selected_channel_reserve_satoshis != get_legacy_default_holder_selected_channel_reserve_satoshis(self.funding.channel_value_satoshis) + { Some(self.funding.holder_selected_channel_reserve_satoshis) } else { None }; let mut old_max_in_flight_percent_config = UserConfig::default().channel_handshake_config; old_max_in_flight_percent_config.max_inbound_htlc_value_in_flight_percent_of_channel = MAX_IN_FLIGHT_PERCENT_LEGACY; @@ -9854,7 +9864,7 @@ impl Writeable for FundedChannel where SP::Target: SignerProvider // override that. (1, self.context.minimum_depth, option), (2, chan_type, option), - (3, self.context.counterparty_selected_channel_reserve_satoshis, option), + (3, self.funding.counterparty_selected_channel_reserve_satoshis, option), (4, serialized_holder_selected_reserve, option), (5, self.context.config, required), (6, serialized_holder_htlc_max_in_flight, option), @@ -10359,6 +10369,8 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, u32, &'c Ch funding: FundingScope { channel_value_satoshis, value_to_self_msat, + counterparty_selected_channel_reserve_satoshis, + holder_selected_channel_reserve_satoshis: holder_selected_channel_reserve_satoshis.unwrap(), }, context: ChannelContext { user_id, @@ -10435,8 +10447,6 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, u32, &'c Ch holder_dust_limit_satoshis, counterparty_max_htlc_value_in_flight_msat, holder_max_htlc_value_in_flight_msat: holder_max_htlc_value_in_flight_msat.unwrap(), - counterparty_selected_channel_reserve_satoshis, - holder_selected_channel_reserve_satoshis: holder_selected_channel_reserve_satoshis.unwrap(), counterparty_htlc_minimum_msat, holder_htlc_minimum_msat, counterparty_max_accepted_htlcs, @@ -10964,7 +10974,7 @@ mod tests { 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 expected_outbound_selected_chan_reserve = cmp::max(MIN_THEIR_CHAN_RESERVE_SATOSHIS, (chan.funding.channel_value_satoshis as f64 * outbound_selected_channel_reserve_perc) as u64); - assert_eq!(chan.context.holder_selected_channel_reserve_satoshis, expected_outbound_selected_chan_reserve); + assert_eq!(chan.funding.holder_selected_channel_reserve_satoshis, expected_outbound_selected_chan_reserve); let chan_open_channel_msg = chan.get_open_channel(ChainHash::using_genesis_block(network), &&logger).unwrap(); let mut inbound_node_config = UserConfig::default(); @@ -10975,8 +10985,8 @@ mod tests { let expected_inbound_selected_chan_reserve = cmp::max(MIN_THEIR_CHAN_RESERVE_SATOSHIS, (chan.funding.channel_value_satoshis as f64 * inbound_selected_channel_reserve_perc) as u64); - assert_eq!(chan_inbound_node.context.holder_selected_channel_reserve_satoshis, expected_inbound_selected_chan_reserve); - assert_eq!(chan_inbound_node.context.counterparty_selected_channel_reserve_satoshis.unwrap(), expected_outbound_selected_chan_reserve); + assert_eq!(chan_inbound_node.funding.holder_selected_channel_reserve_satoshis, expected_inbound_selected_chan_reserve); + 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); @@ -11233,7 +11243,7 @@ mod tests { config.channel_handshake_config.announce_for_forwarding = false; let mut chan = OutboundV1Channel::<&Keys>::new(&LowerBoundedFeeEstimator::new(&feeest), &&keys_provider, &&keys_provider, counterparty_node_id, &channelmanager::provided_init_features(&config), 10_000_000, 0, 42, &config, 0, 42, None, &*logger).unwrap(); // Nothing uses their network key in this test chan.context.holder_dust_limit_satoshis = 546; - chan.context.counterparty_selected_channel_reserve_satoshis = Some(0); // Filled in in accept_channel + chan.funding.counterparty_selected_channel_reserve_satoshis = Some(0); // Filled in in accept_channel let funding_info = OutPoint{ txid: Txid::from_str("8984484a580b825b9972d7adb15050b3ab624ccd731946b3eeddb92f4e7ef6be").unwrap(), index: 0 }; diff --git a/lightning/src/ln/channel_state.rs b/lightning/src/ln/channel_state.rs index c0ea8b3f31c..614160b5a30 100644 --- a/lightning/src/ln/channel_state.rs +++ b/lightning/src/ln/channel_state.rs @@ -485,7 +485,7 @@ impl ChannelDetails { { let balance = context.get_available_balances(funding, fee_estimator); let (to_remote_reserve_satoshis, to_self_reserve_satoshis) = - context.get_holder_counterparty_selected_channel_reserve_satoshis(); + funding.get_holder_counterparty_selected_channel_reserve_satoshis(); #[allow(deprecated)] // TODO: Remove once balance_msat is removed. ChannelDetails { channel_id: context.channel_id(), diff --git a/lightning/src/ln/functional_tests.rs b/lightning/src/ln/functional_tests.rs index 66e21d64d54..f8787e1b657 100644 --- a/lightning/src/ln/functional_tests.rs +++ b/lightning/src/ln/functional_tests.rs @@ -217,9 +217,8 @@ fn do_test_counterparty_no_reserve(send_from_initiator: bool) { let channel = get_channel_ref!(sender_node, counterparty_node, sender_node_per_peer_lock, sender_node_peer_state_lock, temp_channel_id); assert!(channel.is_unfunded_v1()); - let chan_context = channel.context_mut(); - chan_context.holder_selected_channel_reserve_satoshis = 0; - chan_context.holder_max_htlc_value_in_flight_msat = 100_000_000; + channel.funding_mut().holder_selected_channel_reserve_satoshis = 0; + channel.context_mut().holder_max_htlc_value_in_flight_msat = 100_000_000; } let funding_tx = sign_funding_transaction(&nodes[0], &nodes[1], 100_000, temp_channel_id); From 2d452fba1c042584a3571830132b3a6c95bb3084 Mon Sep 17 00:00:00 2001 From: Jeffrey Czyz Date: Tue, 11 Feb 2025 11:42:04 -0600 Subject: [PATCH 5/6] Move *_max_commitment_tx_output to FundingScope --- lightning/src/ln/channel.rs | 53 ++++++++++++++++++------------------- 1 file changed, 26 insertions(+), 27 deletions(-) diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index 216556fcc3e..a9d15a062a8 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -1572,6 +1572,13 @@ pub(super) struct FundingScope { pub(super) holder_selected_channel_reserve_satoshis: u64, #[cfg(not(test))] holder_selected_channel_reserve_satoshis: u64, + + #[cfg(debug_assertions)] + /// Max to_local and to_remote outputs in a locally-generated commitment transaction + holder_max_commitment_tx_output: Mutex<(u64, u64)>, + #[cfg(debug_assertions)] + /// Max to_local and to_remote outputs in a remote-generated commitment transaction + counterparty_max_commitment_tx_output: Mutex<(u64, u64)>, } impl FundingScope { @@ -1719,13 +1726,6 @@ pub(super) struct ChannelContext where SP::Target: SignerProvider { /// time. update_time_counter: u32, - #[cfg(debug_assertions)] - /// Max to_local and to_remote outputs in a locally-generated commitment transaction - holder_max_commitment_tx_output: Mutex<(u64, u64)>, - #[cfg(debug_assertions)] - /// Max to_local and to_remote outputs in a remote-generated commitment transaction - counterparty_max_commitment_tx_output: Mutex<(u64, u64)>, - // (fee_sats, skip_remote_output, fee_range, holder_sig) last_sent_closing_fee: Option<(u64, bool, ClosingSignedFeeRange, Option)>, last_received_closing_sig: Option, @@ -2465,6 +2465,11 @@ impl ChannelContext where SP::Target: SignerProvider { value_to_self_msat, counterparty_selected_channel_reserve_satoshis: Some(msg_channel_reserve_satoshis), holder_selected_channel_reserve_satoshis, + + #[cfg(debug_assertions)] + holder_max_commitment_tx_output: Mutex::new((value_to_self_msat, (channel_value_satoshis * 1000 - msg_push_msat).saturating_sub(value_to_self_msat))), + #[cfg(debug_assertions)] + counterparty_max_commitment_tx_output: Mutex::new((value_to_self_msat, (channel_value_satoshis * 1000 - msg_push_msat).saturating_sub(value_to_self_msat))), }; let channel_context = ChannelContext { user_id, @@ -2521,12 +2526,6 @@ impl ChannelContext where SP::Target: SignerProvider { signer_pending_closing: false, signer_pending_channel_ready: false, - - #[cfg(debug_assertions)] - holder_max_commitment_tx_output: Mutex::new((value_to_self_msat, (channel_value_satoshis * 1000 - msg_push_msat).saturating_sub(value_to_self_msat))), - #[cfg(debug_assertions)] - counterparty_max_commitment_tx_output: Mutex::new((value_to_self_msat, (channel_value_satoshis * 1000 - msg_push_msat).saturating_sub(value_to_self_msat))), - last_sent_closing_fee: None, last_received_closing_sig: None, pending_counterparty_closing_signed: None, @@ -2699,6 +2698,13 @@ impl ChannelContext where SP::Target: SignerProvider { value_to_self_msat, counterparty_selected_channel_reserve_satoshis: None, // Filled in in accept_channel holder_selected_channel_reserve_satoshis, + + // We'll add our counterparty's `funding_satoshis` to these max commitment output assertions + // when we receive `accept_channel2`. + #[cfg(debug_assertions)] + holder_max_commitment_tx_output: Mutex::new((channel_value_satoshis * 1000 - push_msat, push_msat)), + #[cfg(debug_assertions)] + counterparty_max_commitment_tx_output: Mutex::new((channel_value_satoshis * 1000 - push_msat, push_msat)), }; let channel_context = Self { user_id, @@ -2753,13 +2759,6 @@ impl ChannelContext where SP::Target: SignerProvider { signer_pending_closing: false, signer_pending_channel_ready: false, - // We'll add our counterparty's `funding_satoshis` to these max commitment output assertions - // when we receive `accept_channel2`. - #[cfg(debug_assertions)] - holder_max_commitment_tx_output: Mutex::new((channel_value_satoshis * 1000 - push_msat, push_msat)), - #[cfg(debug_assertions)] - counterparty_max_commitment_tx_output: Mutex::new((channel_value_satoshis * 1000 - push_msat, push_msat)), - last_sent_closing_fee: None, last_received_closing_sig: None, pending_counterparty_closing_signed: None, @@ -3490,9 +3489,9 @@ impl ChannelContext where SP::Target: SignerProvider { // Make sure that the to_self/to_remote is always either past the appropriate // channel_reserve *or* it is making progress towards it. let mut broadcaster_max_commitment_tx_output = if generated_by_local { - self.holder_max_commitment_tx_output.lock().unwrap() + funding.holder_max_commitment_tx_output.lock().unwrap() } else { - self.counterparty_max_commitment_tx_output.lock().unwrap() + funding.counterparty_max_commitment_tx_output.lock().unwrap() }; debug_assert!(broadcaster_max_commitment_tx_output.0 <= value_to_self_msat as u64 || value_to_self_msat / 1000 >= funding.counterparty_selected_channel_reserve_satoshis.unwrap() as i64); broadcaster_max_commitment_tx_output.0 = cmp::max(broadcaster_max_commitment_tx_output.0, value_to_self_msat as u64); @@ -10371,6 +10370,11 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, u32, &'c Ch value_to_self_msat, counterparty_selected_channel_reserve_satoshis, holder_selected_channel_reserve_satoshis: holder_selected_channel_reserve_satoshis.unwrap(), + + #[cfg(debug_assertions)] + holder_max_commitment_tx_output: Mutex::new((0, 0)), + #[cfg(debug_assertions)] + counterparty_max_commitment_tx_output: Mutex::new((0, 0)), }, context: ChannelContext { user_id, @@ -10426,11 +10430,6 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, u32, &'c Ch update_time_counter, feerate_per_kw, - #[cfg(debug_assertions)] - holder_max_commitment_tx_output: Mutex::new((0, 0)), - #[cfg(debug_assertions)] - counterparty_max_commitment_tx_output: Mutex::new((0, 0)), - last_sent_closing_fee: None, last_received_closing_sig: None, pending_counterparty_closing_signed: None, From 79ce104e68b8bdcec585d1182e3a1c0a0286ad50 Mon Sep 17 00:00:00 2001 From: Jeffrey Czyz Date: Wed, 12 Feb 2025 15:16:52 -0600 Subject: [PATCH 6/6] Move next_*_commitment_tx_fee_info_cached to FundingScope --- lightning/src/ln/channel.rs | 96 +++++++++++++++++++------------------ 1 file changed, 50 insertions(+), 46 deletions(-) diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index a9d15a062a8..f3abb77a849 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -1579,6 +1579,15 @@ pub(super) struct FundingScope { #[cfg(debug_assertions)] /// Max to_local and to_remote outputs in a remote-generated commitment transaction counterparty_max_commitment_tx_output: Mutex<(u64, u64)>, + + // We save these values so we can make sure `next_local_commit_tx_fee_msat` and + // `next_remote_commit_tx_fee_msat` properly predict what the next commitment transaction fee will + // be, by comparing the cached values to the fee of the transaction generated by + // `build_commitment_transaction`. + #[cfg(any(test, fuzzing))] + next_local_commitment_tx_fee_info_cached: Mutex>, + #[cfg(any(test, fuzzing))] + next_remote_commitment_tx_fee_info_cached: Mutex>, } impl FundingScope { @@ -1823,15 +1832,6 @@ pub(super) struct ChannelContext where SP::Target: SignerProvider { /// This can be used to rebroadcast the channel_announcement message later. announcement_sigs: Option<(Signature, Signature)>, - // We save these values so we can make sure `next_local_commit_tx_fee_msat` and - // `next_remote_commit_tx_fee_msat` properly predict what the next commitment transaction fee will - // be, by comparing the cached values to the fee of the tranaction generated by - // `build_commitment_transaction`. - #[cfg(any(test, fuzzing))] - next_local_commitment_tx_fee_info_cached: Mutex>, - #[cfg(any(test, fuzzing))] - next_remote_commitment_tx_fee_info_cached: Mutex>, - /// lnd has a long-standing bug where, upon reconnection, if the channel is not yet confirmed /// they will not send a channel_reestablish until the channel locks in. Then, they will send a /// channel_ready *before* sending the channel_reestablish (which is clearly a violation of @@ -2470,6 +2470,11 @@ impl ChannelContext where SP::Target: SignerProvider { holder_max_commitment_tx_output: Mutex::new((value_to_self_msat, (channel_value_satoshis * 1000 - msg_push_msat).saturating_sub(value_to_self_msat))), #[cfg(debug_assertions)] counterparty_max_commitment_tx_output: Mutex::new((value_to_self_msat, (channel_value_satoshis * 1000 - msg_push_msat).saturating_sub(value_to_self_msat))), + + #[cfg(any(test, fuzzing))] + next_local_commitment_tx_fee_info_cached: Mutex::new(None), + #[cfg(any(test, fuzzing))] + next_remote_commitment_tx_fee_info_cached: Mutex::new(None), }; let channel_context = ChannelContext { user_id, @@ -2578,11 +2583,6 @@ impl ChannelContext where SP::Target: SignerProvider { announcement_sigs: None, - #[cfg(any(test, fuzzing))] - next_local_commitment_tx_fee_info_cached: Mutex::new(None), - #[cfg(any(test, fuzzing))] - next_remote_commitment_tx_fee_info_cached: Mutex::new(None), - workaround_lnd_bug_4006: None, sent_message_awaiting_response: None, @@ -2705,6 +2705,11 @@ impl ChannelContext where SP::Target: SignerProvider { holder_max_commitment_tx_output: Mutex::new((channel_value_satoshis * 1000 - push_msat, push_msat)), #[cfg(debug_assertions)] counterparty_max_commitment_tx_output: Mutex::new((channel_value_satoshis * 1000 - push_msat, push_msat)), + + #[cfg(any(test, fuzzing))] + next_local_commitment_tx_fee_info_cached: Mutex::new(None), + #[cfg(any(test, fuzzing))] + next_remote_commitment_tx_fee_info_cached: Mutex::new(None), }; let channel_context = Self { user_id, @@ -2810,11 +2815,6 @@ impl ChannelContext where SP::Target: SignerProvider { announcement_sigs: None, - #[cfg(any(test, fuzzing))] - next_local_commitment_tx_fee_info_cached: Mutex::new(None), - #[cfg(any(test, fuzzing))] - next_remote_commitment_tx_fee_info_cached: Mutex::new(None), - workaround_lnd_bug_4006: None, sent_message_awaiting_response: None, @@ -3868,9 +3868,9 @@ impl ChannelContext where SP::Target: SignerProvider { } 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(htlc_above_dust, Some(())); + let mut max_reserved_commit_tx_fee_msat = context.next_local_commit_tx_fee_msat(&funding, htlc_above_dust, Some(())); let htlc_dust = HTLCCandidate::new(real_dust_limit_timeout_sat * 1000 - 1, HTLCInitiator::LocalOffered); - let mut min_reserved_commit_tx_fee_msat = context.next_local_commit_tx_fee_msat(htlc_dust, Some(())); + let mut min_reserved_commit_tx_fee_msat = context.next_local_commit_tx_fee_msat(&funding, htlc_dust, Some(())); if !context.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; @@ -3899,7 +3899,7 @@ impl ChannelContext where SP::Target: SignerProvider { } 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(Some(htlc_above_dust), None); + 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; let remote_balance_msat = (funding.channel_value_satoshis * 1000 - funding.value_to_self_msat) @@ -3996,7 +3996,9 @@ impl ChannelContext where SP::Target: SignerProvider { /// second allows for creating a buffer to ensure a further HTLC can always be accepted/added. /// /// Dust HTLCs are excluded. - fn next_local_commit_tx_fee_msat(&self, htlc: HTLCCandidate, fee_spike_buffer_htlc: Option<()>) -> u64 { + fn next_local_commit_tx_fee_msat( + &self, _funding: &FundingScope, htlc: HTLCCandidate, fee_spike_buffer_htlc: Option<()>, + ) -> u64 { let context = &self; assert!(context.is_outbound()); @@ -4085,7 +4087,7 @@ impl ChannelContext where SP::Target: SignerProvider { }, feerate: context.feerate_per_kw, }; - *context.next_local_commitment_tx_fee_info_cached.lock().unwrap() = Some(commitment_tx_info); + *_funding.next_local_commitment_tx_fee_info_cached.lock().unwrap() = Some(commitment_tx_info); } res } @@ -4100,7 +4102,9 @@ impl ChannelContext where SP::Target: SignerProvider { /// second allows for creating a buffer to ensure a further HTLC can always be accepted/added. /// /// Dust HTLCs are excluded. - fn next_remote_commit_tx_fee_msat(&self, htlc: Option, fee_spike_buffer_htlc: Option<()>) -> u64 { + fn next_remote_commit_tx_fee_msat( + &self, _funding: &FundingScope, htlc: Option, fee_spike_buffer_htlc: Option<()>, + ) -> u64 { debug_assert!(htlc.is_some() || fee_spike_buffer_htlc.is_some(), "At least one of the options must be set"); let context = &self; @@ -4179,7 +4183,7 @@ impl ChannelContext where SP::Target: SignerProvider { }, feerate: context.feerate_per_kw, }; - *context.next_remote_commitment_tx_fee_info_cached.lock().unwrap() = Some(commitment_tx_info); + *_funding.next_remote_commitment_tx_fee_info_cached.lock().unwrap() = Some(commitment_tx_info); } res } @@ -5199,7 +5203,7 @@ impl FundedChannel where { let remote_commit_tx_fee_msat = if self.context.is_outbound() { 0 } else { let htlc_candidate = HTLCCandidate::new(msg.amount_msat, HTLCInitiator::RemoteOffered); - self.context.next_remote_commit_tx_fee_msat(Some(htlc_candidate), None) // Don't include the extra fee spike buffer HTLC in calculations + self.context.next_remote_commit_tx_fee_msat(&self.funding, Some(htlc_candidate), None) // Don't include the extra fee spike buffer HTLC in calculations }; let anchor_outputs_value_msat = if !self.context.is_outbound() && self.context.get_channel_type().supports_anchors_zero_fee_htlc_tx() { ANCHOR_OUTPUT_VALUE_SATOSHI * 2 * 1000 @@ -5222,7 +5226,7 @@ impl FundedChannel where if self.context.is_outbound() { // Check that they won't violate our local required channel reserve by adding this HTLC. let htlc_candidate = HTLCCandidate::new(msg.amount_msat, HTLCInitiator::RemoteOffered); - let local_commit_tx_fee_msat = self.context.next_local_commit_tx_fee_msat(htlc_candidate, None); + let local_commit_tx_fee_msat = self.context.next_local_commit_tx_fee_msat(&self.funding, htlc_candidate, None); if self.funding.value_to_self_msat < self.funding.counterparty_selected_channel_reserve_satoshis.unwrap() * 1000 + local_commit_tx_fee_msat + anchor_outputs_value_msat { return Err(ChannelError::close("Cannot accept HTLC that would put our balance under counterparty-announced channel reserve value".to_owned())); } @@ -5400,8 +5404,8 @@ impl FundedChannel where #[cfg(any(test, fuzzing))] { if self.context.is_outbound() { - let projected_commit_tx_info = self.context.next_local_commitment_tx_fee_info_cached.lock().unwrap().take(); - *self.context.next_remote_commitment_tx_fee_info_cached.lock().unwrap() = None; + let projected_commit_tx_info = self.funding.next_local_commitment_tx_fee_info_cached.lock().unwrap().take(); + *self.funding.next_remote_commitment_tx_fee_info_cached.lock().unwrap() = None; if let Some(info) = projected_commit_tx_info { let total_pending_htlcs = self.context.pending_inbound_htlcs.len() + self.context.pending_outbound_htlcs.len() + self.context.holding_cell_htlc_updates.len(); @@ -5770,8 +5774,8 @@ impl FundedChannel where #[cfg(any(test, fuzzing))] { - *self.context.next_local_commitment_tx_fee_info_cached.lock().unwrap() = None; - *self.context.next_remote_commitment_tx_fee_info_cached.lock().unwrap() = None; + *self.funding.next_local_commitment_tx_fee_info_cached.lock().unwrap() = None; + *self.funding.next_remote_commitment_tx_fee_info_cached.lock().unwrap() = None; } match &self.context.holder_signer { @@ -7411,7 +7415,7 @@ impl FundedChannel where // // A `None` `HTLCCandidate` is used as in this case because we're already accounting for // the incoming HTLC as it has been fully committed by both sides. - let mut remote_fee_cost_incl_stuck_buffer_msat = self.context.next_remote_commit_tx_fee_msat(None, Some(())); + let mut remote_fee_cost_incl_stuck_buffer_msat = self.context.next_remote_commit_tx_fee_msat(&self.funding, None, Some(())); if !self.context.get_channel_type().supports_anchors_zero_fee_htlc_tx() { remote_fee_cost_incl_stuck_buffer_msat *= FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE; } @@ -8353,8 +8357,8 @@ impl FundedChannel where #[cfg(any(test, fuzzing))] { if !self.context.is_outbound() { - let projected_commit_tx_info = self.context.next_remote_commitment_tx_fee_info_cached.lock().unwrap().take(); - *self.context.next_local_commitment_tx_fee_info_cached.lock().unwrap() = None; + let projected_commit_tx_info = self.funding.next_remote_commitment_tx_fee_info_cached.lock().unwrap().take(); + *self.funding.next_local_commitment_tx_fee_info_cached.lock().unwrap() = None; if let Some(info) = projected_commit_tx_info { let total_pending_htlcs = self.context.pending_inbound_htlcs.len() + self.context.pending_outbound_htlcs.len(); if info.total_pending_htlcs == total_pending_htlcs @@ -10375,6 +10379,11 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, u32, &'c Ch holder_max_commitment_tx_output: Mutex::new((0, 0)), #[cfg(debug_assertions)] counterparty_max_commitment_tx_output: Mutex::new((0, 0)), + + #[cfg(any(test, fuzzing))] + next_local_commitment_tx_fee_info_cached: Mutex::new(None), + #[cfg(any(test, fuzzing))] + next_remote_commitment_tx_fee_info_cached: Mutex::new(None), }, context: ChannelContext { user_id, @@ -10470,11 +10479,6 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, u32, &'c Ch announcement_sigs, - #[cfg(any(test, fuzzing))] - next_local_commitment_tx_fee_info_cached: Mutex::new(None), - #[cfg(any(test, fuzzing))] - next_remote_commitment_tx_fee_info_cached: Mutex::new(None), - workaround_lnd_bug_4006: None, sent_message_awaiting_response: None, @@ -10744,7 +10748,7 @@ 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(htlc_candidate, None); + let local_commit_tx_fee = node_a_chan.context.next_local_commit_tx_fee_msat(&node_a_chan.funding, htlc_candidate, None); let local_commit_fee_0_htlcs = commit_tx_fee_sat(node_a_chan.context.feerate_per_kw, 0, node_a_chan.context.get_channel_type()) * 1000; assert_eq!(local_commit_tx_fee, local_commit_fee_0_htlcs); @@ -10753,7 +10757,7 @@ mod tests { node_a_chan.context.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.context.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(Some(htlc_candidate), None); + let remote_commit_tx_fee = node_a_chan.context.next_remote_commit_tx_fee_msat(&node_a_chan.funding, Some(htlc_candidate), None); assert_eq!(remote_commit_tx_fee, remote_commit_fee_3_htlcs); } @@ -10781,13 +10785,13 @@ mod tests { // counted as dust when it shouldn't be. let htlc_amt_above_timeout = ((253 * htlc_timeout_tx_weight(chan.context.get_channel_type()) / 1000) + 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(htlc_candidate, None); + let commitment_tx_fee = chan.context.next_local_commit_tx_fee_msat(&chan.funding, htlc_candidate, None); assert_eq!(commitment_tx_fee, commitment_tx_fee_1_htlc); // If swapped: this HTLC would be counted as non-dust when it shouldn't be. let dust_htlc_amt_below_success = ((253 * htlc_success_tx_weight(chan.context.get_channel_type()) / 1000) + 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(htlc_candidate, None); + let commitment_tx_fee = chan.context.next_local_commit_tx_fee_msat(&chan.funding, htlc_candidate, None); assert_eq!(commitment_tx_fee, commitment_tx_fee_0_htlcs); chan.context.channel_transaction_parameters.is_outbound_from_holder = false; @@ -10795,13 +10799,13 @@ mod tests { // If swapped: this HTLC would be counted as non-dust when it shouldn't be. let dust_htlc_amt_above_timeout = ((253 * htlc_timeout_tx_weight(chan.context.get_channel_type()) / 1000) + 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(Some(htlc_candidate), None); + let commitment_tx_fee = chan.context.next_remote_commit_tx_fee_msat(&chan.funding, Some(htlc_candidate), None); assert_eq!(commitment_tx_fee, commitment_tx_fee_0_htlcs); // If swapped: this HTLC would be counted as dust when it shouldn't be. let htlc_amt_below_success = ((253 * htlc_success_tx_weight(chan.context.get_channel_type()) / 1000) + 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(Some(htlc_candidate), None); + let commitment_tx_fee = chan.context.next_remote_commit_tx_fee_msat(&chan.funding, Some(htlc_candidate), None); assert_eq!(commitment_tx_fee, commitment_tx_fee_1_htlc); }