From cd1b44e5fb4786b3b691420a1315d97734db2508 Mon Sep 17 00:00:00 2001 From: optout <13562139+optout21@users.noreply.github.com> Date: Thu, 3 Apr 2025 00:59:33 +0200 Subject: [PATCH 1/2] Put PendingV2Channel implementation behind a trait --- lightning/src/ln/channel.rs | 177 ++++++++++++++++++++--------- lightning/src/ln/channelmanager.rs | 2 +- 2 files changed, 125 insertions(+), 54 deletions(-) diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index d5cccc0bc1f..3dc5de6b494 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -2292,7 +2292,20 @@ impl InitialRemoteCommitmentReceiver for FundedChannel where } } -impl PendingV2Channel where SP::Target: SignerProvider { +// TODO Naming +pub(super) trait PendingV2ChannelTrait where SP::Target: SignerProvider { + fn context(&self) -> &ChannelContext; + fn context_mut(&mut self) -> &mut ChannelContext; + fn funding(&self) -> &FundingScope; + fn funding_mut(&mut self) -> &mut FundingScope; + fn funding_and_context_mut(&mut self) -> (&mut FundingScope, &mut ChannelContext); + fn dual_funding_context(&self) -> &DualFundingChannelContext; + fn swap_out_dual_funding_context_inputs(&mut self, funding_inputs: &mut Vec<(TxIn, TransactionU16LenLimited)>); + fn unfunded_context(&self) -> &UnfundedChannelContext; + fn interactive_tx_constructor(&self) -> Option<&InteractiveTxConstructor>; + fn interactive_tx_constructor_mut(&mut self) -> &mut Option; + fn interactive_tx_signing_session_mut(&mut self) -> &mut Option; + /// Prepare and start interactive transaction negotiation. /// `change_destination_opt` - Optional destination for optional change; if None, /// default destination address is used. @@ -2304,11 +2317,11 @@ impl PendingV2Channel where SP::Target: SignerProvider { ) -> Result, AbortReason> where ES::Target: EntropySource { - debug_assert!(matches!(self.context.channel_state, ChannelState::NegotiatingFunding(_))); - debug_assert!(self.interactive_tx_constructor.is_none()); + debug_assert!(matches!(self.context().channel_state, ChannelState::NegotiatingFunding(_))); + debug_assert!(self.interactive_tx_constructor().is_none()); let mut funding_inputs = Vec::new(); - mem::swap(&mut self.dual_funding_context.our_funding_inputs, &mut funding_inputs); + self.swap_out_dual_funding_context_inputs(&mut funding_inputs); // TODO(splicing): Add prev funding tx as input, must be provided as a parameter @@ -2319,14 +2332,14 @@ impl PendingV2Channel where SP::Target: SignerProvider { let mut expected_remote_shared_funding_output = None; let shared_funding_output = TxOut { - value: Amount::from_sat(self.funding.get_value_satoshis()), - script_pubkey: self.funding.get_funding_redeemscript().to_p2wsh(), + value: Amount::from_sat(self.funding().get_value_satoshis()), + script_pubkey: self.funding().get_funding_redeemscript().to_p2wsh(), }; - if self.funding.is_outbound() { + if self.funding().is_outbound() { funding_outputs.push( OutputOwned::Shared(SharedOwnedOutput::new( - shared_funding_output, self.dual_funding_context.our_funding_satoshis, + shared_funding_output, self.dual_funding_context().our_funding_satoshis, )) ); } else { @@ -2338,13 +2351,13 @@ impl PendingV2Channel where SP::Target: SignerProvider { let change_script = if let Some(script) = change_destination_opt { script } else { - signer_provider.get_destination_script(self.context.channel_keys_id) + signer_provider.get_destination_script(self.context().channel_keys_id) .map_err(|_err| AbortReason::InternalError("Error getting destination script"))? }; let change_value_opt = calculate_change_output_value( - self.funding.is_outbound(), self.dual_funding_context.our_funding_satoshis, + self.funding().is_outbound(), self.dual_funding_context().our_funding_satoshis, &funding_inputs, &funding_outputs, - self.dual_funding_context.funding_feerate_sat_per_1000_weight, + self.dual_funding_context().funding_feerate_sat_per_1000_weight, change_script.minimal_non_dust().to_sat(), )?; if let Some(change_value) = change_value_opt { @@ -2353,10 +2366,10 @@ impl PendingV2Channel where SP::Target: SignerProvider { script_pubkey: change_script, }; let change_output_weight = get_output_weight(&change_output.script_pubkey).to_wu(); - let change_output_fee = fee_for_weight(self.dual_funding_context.funding_feerate_sat_per_1000_weight, change_output_weight); + let change_output_fee = fee_for_weight(self.dual_funding_context().funding_feerate_sat_per_1000_weight, change_output_weight); let change_value_decreased_with_fee = change_value.saturating_sub(change_output_fee); // Check dust limit again - if change_value_decreased_with_fee > self.context.holder_dust_limit_satoshis { + if change_value_decreased_with_fee > self.context().holder_dust_limit_satoshis { change_output.value = Amount::from_sat(change_value_decreased_with_fee); funding_outputs.push(OutputOwned::Single(change_output)); } @@ -2365,11 +2378,11 @@ impl PendingV2Channel where SP::Target: SignerProvider { let constructor_args = InteractiveTxConstructorArgs { entropy_source, holder_node_id, - counterparty_node_id: self.context.counterparty_node_id, - channel_id: self.context.channel_id(), - feerate_sat_per_kw: self.dual_funding_context.funding_feerate_sat_per_1000_weight, - is_initiator: self.funding.is_outbound(), - funding_tx_locktime: self.dual_funding_context.funding_tx_locktime, + counterparty_node_id: self.context().counterparty_node_id, + channel_id: self.context().channel_id(), + feerate_sat_per_kw: self.dual_funding_context().funding_feerate_sat_per_1000_weight, + is_initiator: self.funding().is_outbound(), + funding_tx_locktime: self.dual_funding_context().funding_tx_locktime, inputs_to_contribute: funding_inputs, outputs_to_contribute: funding_outputs, expected_remote_shared_funding_output, @@ -2377,58 +2390,59 @@ impl PendingV2Channel where SP::Target: SignerProvider { let mut tx_constructor = InteractiveTxConstructor::new(constructor_args)?; let msg = tx_constructor.take_initiator_first_message(); - self.interactive_tx_constructor = Some(tx_constructor); + *self.interactive_tx_constructor_mut() = Some(tx_constructor); Ok(msg) } - pub fn tx_add_input(&mut self, msg: &msgs::TxAddInput) -> InteractiveTxMessageSendResult { - InteractiveTxMessageSendResult(match &mut self.interactive_tx_constructor { + fn tx_add_input(&mut self, msg: &msgs::TxAddInput) -> InteractiveTxMessageSendResult { + InteractiveTxMessageSendResult(match self.interactive_tx_constructor_mut() { Some(ref mut tx_constructor) => tx_constructor.handle_tx_add_input(msg).map_err( - |reason| reason.into_tx_abort_msg(self.context.channel_id())), + |reason| reason.into_tx_abort_msg(self.context().channel_id())), None => Err(msgs::TxAbort { - channel_id: self.context.channel_id(), + channel_id: self.context().channel_id(), data: b"No interactive transaction negotiation in progress".to_vec() }), }) } - pub fn tx_add_output(&mut self, msg: &msgs::TxAddOutput)-> InteractiveTxMessageSendResult { - InteractiveTxMessageSendResult(match &mut self.interactive_tx_constructor { + fn tx_add_output(&mut self, msg: &msgs::TxAddOutput)-> InteractiveTxMessageSendResult { + InteractiveTxMessageSendResult(match self.interactive_tx_constructor_mut() { Some(ref mut tx_constructor) => tx_constructor.handle_tx_add_output(msg).map_err( - |reason| reason.into_tx_abort_msg(self.context.channel_id())), + |reason| reason.into_tx_abort_msg(self.context().channel_id())), None => Err(msgs::TxAbort { - channel_id: self.context.channel_id(), + channel_id: self.context().channel_id(), data: b"No interactive transaction negotiation in progress".to_vec() }), }) } - pub fn tx_remove_input(&mut self, msg: &msgs::TxRemoveInput)-> InteractiveTxMessageSendResult { - InteractiveTxMessageSendResult(match &mut self.interactive_tx_constructor { + fn tx_remove_input(&mut self, msg: &msgs::TxRemoveInput)-> InteractiveTxMessageSendResult { + InteractiveTxMessageSendResult(match self.interactive_tx_constructor_mut() { Some(ref mut tx_constructor) => tx_constructor.handle_tx_remove_input(msg).map_err( - |reason| reason.into_tx_abort_msg(self.context.channel_id())), + |reason| reason.into_tx_abort_msg(self.context().channel_id())), None => Err(msgs::TxAbort { - channel_id: self.context.channel_id(), + channel_id: self.context().channel_id(), data: b"No interactive transaction negotiation in progress".to_vec() }), }) } - pub fn tx_remove_output(&mut self, msg: &msgs::TxRemoveOutput)-> InteractiveTxMessageSendResult { - InteractiveTxMessageSendResult(match &mut self.interactive_tx_constructor { + fn tx_remove_output(&mut self, msg: &msgs::TxRemoveOutput)-> InteractiveTxMessageSendResult { + InteractiveTxMessageSendResult(match self.interactive_tx_constructor_mut() { Some(ref mut tx_constructor) => tx_constructor.handle_tx_remove_output(msg).map_err( - |reason| reason.into_tx_abort_msg(self.context.channel_id())), + |reason| reason.into_tx_abort_msg(self.context().channel_id())), None => Err(msgs::TxAbort { - channel_id: self.context.channel_id(), + channel_id: self.context().channel_id(), data: b"No interactive transaction negotiation in progress".to_vec() }), }) } - pub fn tx_complete(&mut self, msg: &msgs::TxComplete) -> HandleTxCompleteResult { - let tx_constructor = match &mut self.interactive_tx_constructor { - Some(ref mut tx_constructor) => tx_constructor, + fn tx_complete(&mut self, msg: &msgs::TxComplete) -> HandleTxCompleteResult { + let interactive_tx_constructor = self.interactive_tx_constructor_mut(); + let tx_constructor = match interactive_tx_constructor { + Some(tx_constructor) => tx_constructor, None => { let tx_abort = msgs::TxAbort { channel_id: msg.channel_id, @@ -2446,25 +2460,25 @@ impl PendingV2Channel where SP::Target: SignerProvider { }; if let HandleTxCompleteValue::SendTxComplete(_, ref signing_session) = tx_complete { - self.context.next_funding_txid = Some(signing_session.unsigned_tx.compute_txid()); + self.context_mut().next_funding_txid = Some(signing_session.unsigned_tx.compute_txid()); }; HandleTxCompleteResult(Ok(tx_complete)) } - pub fn funding_tx_constructed( + fn funding_tx_constructed( &mut self, mut signing_session: InteractiveTxSigningSession, logger: &L ) -> Result<(msgs::CommitmentSigned, Option), ChannelError> where L::Target: Logger { - let our_funding_satoshis = self.dual_funding_context.our_funding_satoshis; - let transaction_number = self.unfunded_context.transaction_number(); + let our_funding_satoshis = self.dual_funding_context().our_funding_satoshis; + let transaction_number = self.unfunded_context().transaction_number(); let mut output_index = None; - let expected_spk = self.funding.get_funding_redeemscript().to_p2wsh(); + let expected_spk = self.funding().get_funding_redeemscript().to_p2wsh(); for (idx, outp) in signing_session.unsigned_tx.outputs().enumerate() { - if outp.script_pubkey() == &expected_spk && outp.value() == self.funding.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( ( @@ -2484,24 +2498,25 @@ impl PendingV2Channel where SP::Target: SignerProvider { ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(false) }, ))); }; - self.funding.channel_transaction_parameters.funding_outpoint = Some(outpoint); + self.funding_mut().channel_transaction_parameters.funding_outpoint = Some(outpoint); - self.context.assert_no_commitment_advancement(transaction_number, "initial commitment_signed"); - let commitment_signed = self.context.get_initial_commitment_signed(&self.funding, logger); + self.context().assert_no_commitment_advancement(transaction_number, "initial commitment_signed"); + let (funding_mut, context_mut) = self.funding_and_context_mut(); + let commitment_signed = context_mut.get_initial_commitment_signed(&funding_mut, logger); let commitment_signed = match commitment_signed { Ok(commitment_signed) => { - self.funding.funding_transaction = Some(signing_session.unsigned_tx.build_unsigned_tx()); + self.funding_mut().funding_transaction = Some(signing_session.unsigned_tx.build_unsigned_tx()); commitment_signed }, Err(err) => { - self.funding.channel_transaction_parameters.funding_outpoint = None; + self.funding_mut().channel_transaction_parameters.funding_outpoint = None; return Err(ChannelError::Close((err.to_string(), ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(false) }))); }, }; let funding_ready_for_sig_event = if signing_session.local_inputs_count() == 0 { debug_assert_eq!(our_funding_satoshis, 0); - if signing_session.provide_holder_witnesses(self.context.channel_id, Vec::new()).is_err() { + if signing_session.provide_holder_witnesses(self.context().channel_id, Vec::new()).is_err() { debug_assert!( false, "Zero inputs were provided & zero witnesses were provided, but a count mismatch was somehow found", @@ -2537,16 +2552,72 @@ impl PendingV2Channel where SP::Target: SignerProvider { ))); }; - self.context.channel_state = ChannelState::FundingNegotiated; + self.context_mut().channel_state = ChannelState::FundingNegotiated; // Clear the interactive transaction constructor - self.interactive_tx_constructor.take(); - self.interactive_tx_signing_session = Some(signing_session); + *self.interactive_tx_constructor_mut() = None; + *self.interactive_tx_signing_session_mut() = Some(signing_session); Ok((commitment_signed, funding_ready_for_sig_event)) } } +impl PendingV2ChannelTrait for PendingV2Channel where SP::Target: SignerProvider { + #[inline] + fn context(&self) -> &ChannelContext { + &self.context + } + + #[inline] + fn context_mut(&mut self) -> &mut ChannelContext { + &mut self.context + } + + #[inline] + fn funding(&self) -> &FundingScope { + &self.funding + } + + #[inline] + fn funding_mut(&mut self) -> &mut FundingScope { + &mut self.funding + } + + #[inline] + fn funding_and_context_mut(&mut self) -> (&mut FundingScope, &mut ChannelContext) { + (&mut self.funding, &mut self.context) + } + + #[inline] + fn dual_funding_context(&self) -> &DualFundingChannelContext { + &self.dual_funding_context + } + + fn swap_out_dual_funding_context_inputs(&mut self, funding_inputs: &mut Vec<(TxIn, TransactionU16LenLimited)>) { + mem::swap(&mut self.dual_funding_context.our_funding_inputs, funding_inputs); + } + + #[inline] + fn unfunded_context(&self) -> &UnfundedChannelContext { + &self.unfunded_context + } + + #[inline] + fn interactive_tx_constructor(&self) -> Option<&InteractiveTxConstructor> { + self.interactive_tx_constructor.as_ref() + } + + #[inline] + fn interactive_tx_constructor_mut(&mut self) -> &mut Option { + &mut self.interactive_tx_constructor + } + + #[inline] + fn interactive_tx_signing_session_mut(&mut self) -> &mut Option { + &mut self.interactive_tx_signing_session + } +} + impl ChannelContext where SP::Target: SignerProvider { fn new_for_inbound_channel<'a, ES: Deref, F: Deref, L: Deref>( fee_estimator: &'a LowerBoundedFeeEstimator, diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index d0a73e89992..5a74936b610 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -51,7 +51,7 @@ use crate::ln::inbound_payment; use crate::ln::types::ChannelId; use crate::types::payment::{PaymentHash, PaymentPreimage, PaymentSecret}; use crate::ln::channel::{self, Channel, ChannelError, ChannelUpdateStatus, FundedChannel, ShutdownResult, UpdateFulfillCommitFetch, OutboundV1Channel, ReconnectionMsg, InboundV1Channel, WithChannelContext}; -use crate::ln::channel::PendingV2Channel; +use crate::ln::channel::{PendingV2Channel, PendingV2ChannelTrait}; use crate::ln::channel_state::ChannelDetails; use crate::types::features::{Bolt12InvoiceFeatures, ChannelFeatures, ChannelTypeFeatures, InitFeatures, NodeFeatures}; #[cfg(any(feature = "_test_utils", test))] From 94f60518b04f0b37eb9d18c62ff34fff5092a157 Mon Sep 17 00:00:00 2001 From: optout <13562139+optout21@users.noreply.github.com> Date: Tue, 8 Apr 2025 23:34:27 +0200 Subject: [PATCH 2/2] Add RefundingChannel, that can act as both funded and pendingV2 --- lightning/src/ln/channel.rs | 71 +++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index 3dc5de6b494..350a2479e69 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -2618,6 +2618,77 @@ impl PendingV2ChannelTrait for PendingV2Channel where SP::Tar } } +#[cfg(splicing)] +struct RefundingChannel where SP::Target: SignerProvider { + funded_channel: FundedChannel, + + // Fields belonging for PendingV2Channel, except duplicate context + pending_funding: FundingScope, + // Note: there is a single context + pending_unfunded_context: UnfundedChannelContext, + pending_dual_funding_context: DualFundingChannelContext, + /// The current interactive transaction construction session under negotiation. + pending_interactive_tx_constructor: Option, + pending_interactive_tx_signing_session: Option, +} + +#[cfg(splicing)] +impl PendingV2ChannelTrait for RefundingChannel where SP::Target: SignerProvider { + #[inline] + fn context(&self) -> &ChannelContext { + &self.funded_channel.context + } + + #[inline] + fn context_mut(&mut self) -> &mut ChannelContext { + &mut self.funded_channel.context + } + + #[inline] + fn funding(&self) -> &FundingScope { + &self.pending_funding + } + + #[inline] + fn funding_mut(&mut self) -> &mut FundingScope { + &mut self.pending_funding + } + + #[inline] + fn funding_and_context_mut(&mut self) -> (&mut FundingScope, &mut ChannelContext) { + (&mut self.pending_funding, &mut self.funded_channel.context) + } + + #[inline] + fn dual_funding_context(&self) -> &DualFundingChannelContext { + &self.pending_dual_funding_context + } + + fn swap_out_dual_funding_context_inputs(&mut self, funding_inputs: &mut Vec<(TxIn, TransactionU16LenLimited)>) { + mem::swap(&mut self.pending_dual_funding_context.our_funding_inputs, funding_inputs); + } + + #[inline] + fn unfunded_context(&self) -> &UnfundedChannelContext { + &self.pending_unfunded_context + } + + #[inline] + fn interactive_tx_constructor(&self) -> Option<&InteractiveTxConstructor> { + self.pending_interactive_tx_constructor.as_ref() + } + + #[inline] + fn interactive_tx_constructor_mut(&mut self) -> &mut Option { + &mut self.pending_interactive_tx_constructor + } + + #[inline] + fn interactive_tx_signing_session_mut(&mut self) -> &mut Option { + &mut self.pending_interactive_tx_signing_session + } +} + impl ChannelContext where SP::Target: SignerProvider { fn new_for_inbound_channel<'a, ES: Deref, F: Deref, L: Deref>( fee_estimator: &'a LowerBoundedFeeEstimator,