diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index d5cccc0bc1f..491e66bb88e 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -25,6 +25,8 @@ use bitcoin::secp256k1::constants::PUBLIC_KEY_SIZE; use bitcoin::secp256k1::{PublicKey,SecretKey}; use bitcoin::secp256k1::{Secp256k1,ecdsa::Signature}; use bitcoin::{secp256k1, sighash}; +#[cfg(splicing)] +use bitcoin::{Sequence, Witness}; use crate::ln::types::ChannelId; use crate::types::payment::{PaymentPreimage, PaymentHash}; @@ -1253,7 +1255,10 @@ impl Channel where pub fn unfunded_context_mut(&mut self) -> Option<&mut UnfundedChannelContext> { match &mut self.phase { ChannelPhase::Undefined => unreachable!(), - ChannelPhase::Funded(_) => { debug_assert!(false); None }, + ChannelPhase::Funded(_chan) => { + debug_assert!(false, "FundedChannel is not unfunded!"); + None + } ChannelPhase::UnfundedOutboundV1(chan) => Some(&mut chan.unfunded_context), ChannelPhase::UnfundedInboundV1(chan) => Some(&mut chan.unfunded_context), ChannelPhase::UnfundedV2(chan) => Some(&mut chan.unfunded_context), @@ -1450,6 +1455,56 @@ impl Channel where } } + pub fn tx_add_input(&mut self, msg: &msgs::TxAddInput) -> Result { + match &mut self.phase { + ChannelPhase::UnfundedV2(chan) => Ok(chan.as_negotiating_channel().tx_add_input(msg)), + #[cfg(splicing)] + ChannelPhase::Funded(chan) => Ok(chan.as_renegotiating_channel()? + .tx_add_input(msg)), + _ => Err("Got tx_add_input in an invalid phase"), + } + } + + pub fn tx_add_output(&mut self, msg: &msgs::TxAddOutput) -> Result { + match &mut self.phase { + ChannelPhase::UnfundedV2(chan) => Ok(chan.as_negotiating_channel().tx_add_output(msg)), + #[cfg(splicing)] + ChannelPhase::Funded(chan) => Ok(chan.as_renegotiating_channel()? + .tx_add_output(msg)), + _ => Err("Got tx_add_output in an invalid phase"), + } + } + + pub fn tx_remove_input(&mut self, msg: &msgs::TxRemoveInput) -> Result { + match &mut self.phase { + ChannelPhase::UnfundedV2(chan) => Ok(chan.as_negotiating_channel().tx_remove_input(msg)), + #[cfg(splicing)] + ChannelPhase::Funded(chan) => Ok(chan.as_renegotiating_channel()? + .tx_remove_input(msg)), + _ => Err("Got tx_remove_input in an invalid phase"), + } + } + + pub fn tx_remove_output(&mut self, msg: &msgs::TxRemoveOutput) -> Result { + match &mut self.phase { + ChannelPhase::UnfundedV2(chan) => Ok(chan.as_negotiating_channel().tx_remove_output(msg)), + #[cfg(splicing)] + ChannelPhase::Funded(chan) => Ok(chan.as_renegotiating_channel()? + .tx_remove_output(msg)), + _ => Err("Got tx_remove_output in an invalid phase"), + } + } + + pub fn tx_complete(&mut self, msg: &msgs::TxComplete) -> Result { + match &mut self.phase { + ChannelPhase::UnfundedV2(chan) => Ok(chan.as_negotiating_channel().tx_complete(msg)), + #[cfg(splicing)] + ChannelPhase::Funded(chan) => Ok(chan.as_renegotiating_channel()? + .tx_complete(msg)), + _ => Err("Got tx_complete in an invalid phase"), + } + } + pub fn funding_signed( &mut self, msg: &msgs::FundingSigned, best_block: BestBlock, signer_provider: &SP, logger: &L ) -> Result<(&mut FundedChannel, ChannelMonitor<::EcdsaSigner>), ChannelError> @@ -1487,11 +1542,55 @@ impl Channel where where L::Target: Logger { - if let ChannelPhase::UnfundedV2(chan) = &mut self.phase { - let logger = WithChannelContext::from(logger, &chan.context, None); - chan.funding_tx_constructed(signing_session, &&logger) + match self.phase { + ChannelPhase::UnfundedV2(ref mut chan) => { + let logger = WithChannelContext::from(logger, &chan.context, None); + let (commitment_signed, event) = chan.as_negotiating_channel().funding_tx_constructed(signing_session, &&logger)?; + Ok((commitment_signed, event)) + } + #[cfg(splicing)] + ChannelPhase::Funded(ref mut chan) => { + let logger = WithChannelContext::from(logger, &chan.context, None); + let (commitment_signed, event) = chan.as_renegotiating_channel() + .map_err(|err| ChannelError::Warn(err.into()))? + .funding_tx_constructed(signing_session, &&logger)?; + Ok((commitment_signed, event)) + } + _ => { + Err(ChannelError::Warn("Got a tx_complete message with no interactive transaction construction expected or in-progress".to_owned())) + } + } + } + + #[cfg(splicing)] + pub fn splice_init( + &mut self, msg: &msgs::SpliceInit, our_funding_contribution: i64, + signer_provider: &SP, entropy_source: &ES, our_node_id: &PublicKey, logger: &L + ) -> Result + where + ES::Target: EntropySource, + L::Target: Logger + { + if let Some(funded_channel) = self.as_funded_mut() { + funded_channel.splice_init(msg, our_funding_contribution, signer_provider, entropy_source, our_node_id, logger) } else { - Err(ChannelError::Warn("Got a tx_complete message with no interactive transaction construction expected or in-progress".to_owned())) + Err(ChannelError::Warn("Channel is not funded, cannot be spliced".to_owned())) + } + } + + #[cfg(splicing)] + pub fn splice_ack( + &mut self, msg: &msgs::SpliceAck, + signer_provider: &SP, entropy_source: &ES, our_node_id: &PublicKey, logger: &L + ) -> Result, ChannelError> + where + ES::Target: EntropySource, + L::Target: Logger + { + if let Some(funded_channel) = self.as_funded_mut() { + funded_channel.splice_ack(msg, signer_provider, entropy_source, our_node_id, logger) + } else { + Err(ChannelError::Warn("Channel is not funded, cannot be spliced".to_owned())) } } @@ -1801,10 +1900,36 @@ impl FundingScope { } } -/// Info about a pending splice, used in the pre-splice channel +/// Info about a pending splice #[cfg(splicing)] struct PendingSplice { + /// Intended contributions to the splice from our end pub our_funding_contribution: i64, + /// Set when splice_ack has been processed (on the initiator side), + /// used to prevent processing of multiple splice_ack's. + awaiting_splice_ack: bool, + funding_scope: Option, + funding_negotiation_context: FundingNegotiationContext, + /// The current interactive transaction construction session under negotiation. + interactive_tx_constructor: Option, + interactive_tx_signing_session: Option, +} + +#[cfg(splicing)] +impl PendingSplice { + #[inline] + fn add_checked(base: u64, delta: i64) -> u64 { + if delta >= 0 { + base.saturating_add(delta as u64) + } else { + base.saturating_sub(delta.abs() as u64) + } + } + + /// Compute the post-splice channel value from the pre-splice values and the peer contributions + pub fn compute_post_value(pre_channel_value: u64, our_funding_contribution: i64, their_funding_contribution: i64) -> u64 { + Self::add_checked(pre_channel_value, our_funding_contribution.saturating_add(their_funding_contribution)) + } } /// Contains everything about the channel including state, and various flags. @@ -2103,13 +2228,15 @@ pub(super) struct ChannelContext where SP::Target: SignerProvider { is_holder_quiescence_initiator: Option, } -/// A channel struct implementing this trait can receive an initial counterparty commitment -/// transaction signature. -trait InitialRemoteCommitmentReceiver where SP::Target: SignerProvider { +/// A channel struct implementing this trait has a [`ChannelContext`], exposed through accessors. +pub(super) trait ChannelContextProvider where SP::Target: SignerProvider { fn context(&self) -> &ChannelContext; - fn context_mut(&mut self) -> &mut ChannelContext; +} +/// A channel struct implementing this trait can receive an initial counterparty commitment +/// transaction signature. +trait InitialRemoteCommitmentReceiver: ChannelContextProvider where SP::Target: SignerProvider { fn funding(&self) -> &FundingScope; fn funding_mut(&mut self) -> &mut FundingScope; @@ -2226,7 +2353,7 @@ trait InitialRemoteCommitmentReceiver where SP::Target: SignerProvide } } -impl InitialRemoteCommitmentReceiver for OutboundV1Channel where SP::Target: SignerProvider { +impl ChannelContextProvider for OutboundV1Channel where SP::Target: SignerProvider { fn context(&self) -> &ChannelContext { &self.context } @@ -2234,7 +2361,9 @@ impl InitialRemoteCommitmentReceiver for OutboundV1Channel wh fn context_mut(&mut self) -> &mut ChannelContext { &mut self.context } +} +impl InitialRemoteCommitmentReceiver for OutboundV1Channel where SP::Target: SignerProvider { fn funding(&self) -> &FundingScope { &self.funding } @@ -2248,7 +2377,7 @@ impl InitialRemoteCommitmentReceiver for OutboundV1Channel wh } } -impl InitialRemoteCommitmentReceiver for InboundV1Channel where SP::Target: SignerProvider { +impl ChannelContextProvider for InboundV1Channel where SP::Target: SignerProvider { fn context(&self) -> &ChannelContext { &self.context } @@ -2256,7 +2385,9 @@ impl InitialRemoteCommitmentReceiver for InboundV1Channel whe fn context_mut(&mut self) -> &mut ChannelContext { &mut self.context } +} +impl InitialRemoteCommitmentReceiver for InboundV1Channel where SP::Target: SignerProvider { fn funding(&self) -> &FundingScope { &self.funding } @@ -2270,15 +2401,19 @@ impl InitialRemoteCommitmentReceiver for InboundV1Channel whe } } -impl InitialRemoteCommitmentReceiver for FundedChannel where SP::Target: SignerProvider { +impl ChannelContextProvider for FundedChannel where SP::Target: SignerProvider { + #[inline] fn context(&self) -> &ChannelContext { &self.context } + #[inline] fn context_mut(&mut self) -> &mut ChannelContext { &mut self.context } +} +impl InitialRemoteCommitmentReceiver for FundedChannel where SP::Target: SignerProvider { fn funding(&self) -> &FundingScope { &self.funding } @@ -2292,7 +2427,21 @@ impl InitialRemoteCommitmentReceiver for FundedChannel where } } -impl PendingV2Channel where SP::Target: SignerProvider { +/// A short-lived subset view of a channel, used for V2 funding negotiation or re-negotiation. +/// Can be produced by: +/// - [`PendingV2Channel`], at V2 channel open, and +/// - [`FundedChannel`], when splicing. +struct NegotiatingV2ChannelView<'a, SP: Deref> where SP::Target: SignerProvider { + context: &'a mut ChannelContext, + funding: &'a mut FundingScope, + funding_negotiation_context: &'a mut FundingNegotiationContext, + interactive_tx_constructor: &'a mut Option, + interactive_tx_signing_session: &'a mut Option, + holder_commitment_transaction_number: u64, + is_splice: bool, +} + +impl<'a, SP: Deref> NegotiatingV2ChannelView<'a, SP> where SP::Target: SignerProvider { /// Prepare and start interactive transaction negotiation. /// `change_destination_opt` - Optional destination for optional change; if None, /// default destination address is used. @@ -2301,16 +2450,24 @@ impl PendingV2Channel where SP::Target: SignerProvider { fn begin_interactive_funding_tx_construction( &mut self, signer_provider: &SP, entropy_source: &ES, holder_node_id: PublicKey, change_destination_opt: Option, + prev_funding_input: Option<(TxIn, TransactionU16LenLimited)>, ) -> Result, AbortReason> where ES::Target: EntropySource { - debug_assert!(matches!(self.context.channel_state, ChannelState::NegotiatingFunding(_))); + if self.is_splice { + debug_assert!(matches!(self.context.channel_state, ChannelState::ChannelReady(_))); + } else { + 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); + mem::swap(&mut self.funding_negotiation_context.our_funding_inputs, &mut funding_inputs); - // TODO(splicing): Add prev funding tx as input, must be provided as a parameter + if let Some(prev_funding_input) = prev_funding_input { + funding_inputs.push(prev_funding_input); + } // Add output for funding tx // Note: For the error case when the inputs are insufficient, it will be handled after @@ -2326,7 +2483,7 @@ impl PendingV2Channel where SP::Target: SignerProvider { 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.funding_negotiation_context.our_funding_satoshis, )) ); } else { @@ -2342,9 +2499,9 @@ impl PendingV2Channel where SP::Target: SignerProvider { .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.funding_negotiation_context.our_funding_satoshis, &funding_inputs, &funding_outputs, - self.dual_funding_context.funding_feerate_sat_per_1000_weight, + self.funding_negotiation_context.funding_feerate_sat_per_1000_weight, change_script.minimal_non_dust().to_sat(), )?; if let Some(change_value) = change_value_opt { @@ -2353,7 +2510,7 @@ 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.funding_negotiation_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 { @@ -2367,9 +2524,9 @@ impl PendingV2Channel where SP::Target: SignerProvider { 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, + feerate_sat_per_kw: self.funding_negotiation_context.funding_feerate_sat_per_1000_weight, is_initiator: self.funding.is_outbound(), - funding_tx_locktime: self.dual_funding_context.funding_tx_locktime, + funding_tx_locktime: self.funding_negotiation_context.funding_tx_locktime, inputs_to_contribute: funding_inputs, outputs_to_contribute: funding_outputs, expected_remote_shared_funding_output, @@ -2377,13 +2534,13 @@ 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 = 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 { Some(ref mut tx_constructor) => tx_constructor.handle_tx_add_input(msg).map_err( |reason| reason.into_tx_abort_msg(self.context.channel_id())), None => Err(msgs::TxAbort { @@ -2393,8 +2550,8 @@ impl PendingV2Channel where SP::Target: SignerProvider { }) } - 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 { Some(ref mut tx_constructor) => tx_constructor.handle_tx_add_output(msg).map_err( |reason| reason.into_tx_abort_msg(self.context.channel_id())), None => Err(msgs::TxAbort { @@ -2404,8 +2561,8 @@ impl PendingV2Channel where SP::Target: SignerProvider { }) } - 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 { Some(ref mut tx_constructor) => tx_constructor.handle_tx_remove_input(msg).map_err( |reason| reason.into_tx_abort_msg(self.context.channel_id())), None => Err(msgs::TxAbort { @@ -2415,8 +2572,8 @@ impl PendingV2Channel where SP::Target: SignerProvider { }) } - 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 { Some(ref mut tx_constructor) => tx_constructor.handle_tx_remove_output(msg).map_err( |reason| reason.into_tx_abort_msg(self.context.channel_id())), None => Err(msgs::TxAbort { @@ -2426,9 +2583,9 @@ impl PendingV2Channel where SP::Target: SignerProvider { }) } - 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 tx_constructor = match self.interactive_tx_constructor { + Some(tx_constructor) => tx_constructor, None => { let tx_abort = msgs::TxAbort { channel_id: msg.channel_id, @@ -2452,14 +2609,14 @@ impl PendingV2Channel where SP::Target: SignerProvider { 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.funding_negotiation_context + .our_funding_satoshis; let mut output_index = None; let expected_spk = self.funding.get_funding_redeemscript().to_p2wsh(); @@ -2484,17 +2641,28 @@ impl PendingV2Channel where SP::Target: SignerProvider { ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(false) }, ))); }; - self.funding.channel_transaction_parameters.funding_outpoint = Some(outpoint); + self.funding + .channel_transaction_parameters.funding_outpoint = Some(outpoint); + + if self.is_splice { + // TODO(splicing) Forced error, as the use case is not complete + return Err(ChannelError::Close(( + "TODO Forced error, incomplete implementation".into(), + ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(false) } + ))); + } - self.context.assert_no_commitment_advancement(transaction_number, "initial commitment_signed"); + self.context.assert_no_commitment_advancement(self.holder_commitment_transaction_number, "initial commitment_signed"); let commitment_signed = self.context.get_initial_commitment_signed(&self.funding, logger); let commitment_signed = match commitment_signed { Ok(commitment_signed) => { - self.funding.funding_transaction = Some(signing_session.unsigned_tx.build_unsigned_tx()); + self.funding + .funding_transaction = Some(signing_session.unsigned_tx.build_unsigned_tx()); commitment_signed }, Err(err) => { - self.funding.channel_transaction_parameters.funding_outpoint = None; + self.funding + .channel_transaction_parameters.funding_outpoint = None; return Err(ChannelError::Close((err.to_string(), ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(false) }))); }, }; @@ -2540,13 +2708,25 @@ impl PendingV2Channel where SP::Target: SignerProvider { self.context.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 = None; + *self.interactive_tx_signing_session = Some(signing_session); Ok((commitment_signed, funding_ready_for_sig_event)) } } +impl ChannelContextProvider for PendingV2Channel where SP::Target: SignerProvider { + #[inline] + fn context(&self) -> &ChannelContext { + &self.context + } + + #[inline] + fn context_mut(&mut self) -> &mut ChannelContext { + &mut self.context + } +} + impl ChannelContext where SP::Target: SignerProvider { fn new_for_inbound_channel<'a, ES: Deref, F: Deref, L: Deref>( fee_estimator: &'a LowerBoundedFeeEstimator, @@ -4983,7 +5163,7 @@ fn check_v2_funding_inputs_sufficient( } /// Context for dual-funded channels. -pub(super) struct DualFundingChannelContext { +pub(super) struct FundingNegotiationContext { /// The amount in satoshis we will be contributing to the channel. pub our_funding_satoshis: u64, /// The amount in satoshis our counterparty will be contributing to the channel. @@ -5099,6 +5279,35 @@ impl FundedChannel where SP::Target: SignerProvider, ::EcdsaSigner: EcdsaChannelSigner { + /// If we are in splicing/refunding, return a short-lived [`NegotiatingV2ChannelView`]. + #[cfg(splicing)] + fn as_renegotiating_channel(&mut self) -> Result, &'static str> { + if let Some(ref mut pending_splice) = &mut self.pending_splice { + if let Some(ref mut funding) = &mut pending_splice.funding_scope { + if + pending_splice.funding_negotiation_context.our_funding_satoshis != 0 || + pending_splice.funding_negotiation_context.their_funding_satoshis.unwrap_or_default() != 0 + { + Ok(NegotiatingV2ChannelView { + context: &mut self.context, + funding, + funding_negotiation_context: &mut pending_splice.funding_negotiation_context, + interactive_tx_constructor: &mut pending_splice.interactive_tx_constructor, + interactive_tx_signing_session: &mut pending_splice.interactive_tx_signing_session, + holder_commitment_transaction_number: self.holder_commitment_point.transaction_number(), + is_splice: true, + }) + } else { + Err("Channel is not actively refunding") + } + } else { + Err("Channel is not refunding") + } + } else { + Err("Channel is not splice pending") + } + } + fn check_remote_fee( channel_type: &ChannelTypeFeatures, fee_estimator: &LowerBoundedFeeEstimator, feerate_per_kw: u32, cur_feerate_per_kw: Option, logger: &L @@ -8609,15 +8818,16 @@ impl FundedChannel where /// Includes the witness weight for this input (e.g. P2WPKH_WITNESS_WEIGHT=109 for typical P2WPKH inputs). #[cfg(splicing)] pub fn splice_channel(&mut self, our_funding_contribution_satoshis: i64, - our_funding_inputs: &Vec<(TxIn, Transaction, Weight)>, + our_funding_inputs: Vec<(TxIn, Transaction, Weight)>, funding_feerate_per_kw: u32, locktime: u32, ) -> Result { // Check if a splice has been initiated already. // Note: only a single outstanding splice is supported (per spec) - if let Some(splice_info) = &self.pending_splice { + if let Some(pending_splice) = &self.pending_splice { return Err(APIError::APIMisuseError { err: format!( "Channel {} cannot be spliced, as it has already a splice pending (contribution {})", - self.context.channel_id(), splice_info.our_funding_contribution + self.context.channel_id(), + pending_splice.our_funding_contribution, )}); } @@ -8649,9 +8859,27 @@ impl FundedChannel where "Insufficient inputs for splicing; channel ID {}, err {}", self.context.channel_id(), err, )})?; + // Convert inputs + let mut funding_inputs = Vec::new(); + for (tx_in, tx, _w) in our_funding_inputs.into_iter() { + let tx16 = TransactionU16LenLimited::new(tx.clone()).map_err(|_e| APIError::APIMisuseError { err: format!("Too large transaction")})?; + funding_inputs.push((tx_in.clone(), tx16)); + } + let funding_negotiation_context = FundingNegotiationContext { + our_funding_satoshis: 0, // set at later phase + their_funding_satoshis: None, // set at later phase + funding_tx_locktime: LockTime::from_consensus(locktime), + funding_feerate_sat_per_1000_weight: funding_feerate_per_kw, + our_funding_inputs: funding_inputs, + }; self.pending_splice = Some(PendingSplice { our_funding_contribution: our_funding_contribution_satoshis, + awaiting_splice_ack: true, // we await splice_ack + funding_scope: None, + funding_negotiation_context, + interactive_tx_constructor: None, + interactive_tx_signing_session: None, }); let msg = self.get_splice_init(our_funding_contribution_satoshis, funding_feerate_per_kw, locktime); @@ -8676,17 +8904,19 @@ impl FundedChannel where } } - /// Handle splice_init + /// Checks during handling splice_init #[cfg(splicing)] - pub fn splice_init(&mut self, msg: &msgs::SpliceInit) -> Result { + pub fn splice_init_checks(&mut self, msg: &msgs::SpliceInit) -> Result<(), ChannelError> { let their_funding_contribution_satoshis = msg.funding_contribution_satoshis; // TODO(splicing): Currently not possible to contribute on the splicing-acceptor side let our_funding_contribution_satoshis = 0i64; // Check if a splice has been initiated already. - if let Some(splice_info) = &self.pending_splice { + if let Some(pending_splice) = &self.pending_splice { return Err(ChannelError::Warn(format!( - "Channel has already a splice pending, contribution {}", splice_info.our_funding_contribution, + "Channel {} has already a splice pending, contribution {}", + self.context.channel_id(), + pending_splice.our_funding_contribution, ))); } @@ -8710,11 +8940,117 @@ impl FundedChannel where // Note on channel reserve requirement pre-check: as the splice acceptor does not contribute, // it can't go below reserve, therefore no pre-check is done here. - // TODO(splicing): Once splice acceptor can contribute, add reserve pre-check, similar to the one in `splice_ack`. + let pre_channel_value = self.funding.value_to_self_msat; + let _post_channel_value = PendingSplice::compute_post_value(pre_channel_value, their_funding_contribution_satoshis, our_funding_contribution_satoshis); + let _post_balance = PendingSplice::add_checked(self.funding.value_to_self_msat, our_funding_contribution_satoshis); + // TODO: Early check for reserve requirement + + Ok(()) + } + + /// Helper to build the FundingScope for the splicing channel + #[cfg(splicing)] + fn funding_scope_for_splice(&self, our_funding_satoshis: u64, post_channel_value: u64) -> FundingScope { + let post_value_to_self_msat = self.funding.value_to_self_msat.saturating_add(our_funding_satoshis); + + let mut post_channel_transaction_parameters = self.funding.channel_transaction_parameters.clone(); + post_channel_transaction_parameters.channel_value_satoshis = post_channel_value; + // Update the splicing 'tweak', this will rotate the keys in the signer + let prev_funding_txid = self.funding.channel_transaction_parameters.funding_outpoint + .map(|outpoint| outpoint.txid); + post_channel_transaction_parameters.splice_parent_funding_txid = prev_funding_txid; + match &self.context.holder_signer { + ChannelSignerType::Ecdsa(ecdsa) => { + post_channel_transaction_parameters.holder_pubkeys = + ecdsa.pubkeys(prev_funding_txid, &self.context.secp_ctx); + } + // TODO (taproot|arik) + #[cfg(taproot)] + _ => todo!() + } + + // New reserve values are based on the new channel value, and v2-specific + let counterparty_selected_channel_reserve_satoshis = Some(get_v2_channel_reserve_satoshis( + post_channel_value, self.context.counterparty_dust_limit_satoshis)); + let holder_selected_channel_reserve_satoshis = get_v2_channel_reserve_satoshis( + post_channel_value, MIN_CHAN_DUST_LIMIT_SATOSHIS); + FundingScope { + channel_transaction_parameters: post_channel_transaction_parameters, + value_to_self_msat: post_value_to_self_msat, + funding_transaction: None, + counterparty_selected_channel_reserve_satoshis, + holder_selected_channel_reserve_satoshis, + #[cfg(debug_assertions)] + holder_max_commitment_tx_output: Mutex::new((post_value_to_self_msat, (post_channel_value * 1000).saturating_sub(post_value_to_self_msat))), + #[cfg(debug_assertions)] + counterparty_max_commitment_tx_output: Mutex::new((post_value_to_self_msat, (post_channel_value * 1000).saturating_sub(post_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), + } + } + + /// See also [`splice_init_checks`] + #[cfg(splicing)] + fn splice_init( + &mut self, msg: &msgs::SpliceInit, our_funding_contribution: i64, + signer_provider: &SP, entropy_source: &ES, holder_node_id: &PublicKey, logger: &L, + ) -> Result + where ES::Target: EntropySource, L::Target: Logger + { + let _res = self.splice_init_checks(msg)?; + + let pre_channel_value = self.funding.get_value_satoshis(); + let their_funding_contribution = msg.funding_contribution_satoshis; + + let post_channel_value = PendingSplice::compute_post_value(pre_channel_value, our_funding_contribution, their_funding_contribution); + + let (our_funding_satoshis, their_funding_satoshis) = calculate_funding_values( + pre_channel_value, + our_funding_contribution, + msg.funding_contribution_satoshis, + false, // is_outbound + )?; + + let funding_scope = self.funding_scope_for_splice(our_funding_satoshis, post_channel_value); + + let funding_negotiation_context = FundingNegotiationContext { + our_funding_satoshis, + their_funding_satoshis: Some(their_funding_satoshis), + funding_tx_locktime: LockTime::from_consensus(msg.locktime), + funding_feerate_sat_per_1000_weight: msg.funding_feerate_per_kw, + our_funding_inputs: Vec::new(), + }; + + self.pending_splice = Some(PendingSplice { + our_funding_contribution, + awaiting_splice_ack: false, // we don't need any additional message for the handshake + funding_scope: Some(funding_scope), + funding_negotiation_context, + interactive_tx_constructor: None, + interactive_tx_signing_session: None, + }); // TODO(splicing): Store msg.funding_pubkey - // TODO(splicing): Apply start of splice (splice_start) + // Apply start of splice change in the state + self.splice_start(false, logger); + + let splice_ack_msg = self.get_splice_ack(our_funding_contribution); + + // Start interactive funding negotiation. No extra input, as we are not the splice initiator + let mut refunding = self.as_renegotiating_channel() + .map_err(|err| ChannelError::Warn(err.into()))?; + let _msg = refunding.begin_interactive_funding_tx_construction(signer_provider, entropy_source, holder_node_id.clone(), None, None) + .map_err(|err| ChannelError::Warn(format!("Failed to start interactive transaction construction, {:?}", err)))?; + + Ok(splice_ack_msg) + } + + /// Get the splice_ack message that can be sent in response to splice initiation. + #[cfg(splicing)] + pub fn get_splice_ack(&self, our_funding_contribution_satoshis: i64) -> msgs::SpliceAck { // TODO(splicing): The exisiting pubkey is reused, but a new one should be generated. See #3542. // Note that channel_keys_id is supposed NOT to change let splice_ack_msg = msgs::SpliceAck { @@ -8724,20 +9060,118 @@ impl FundedChannel where require_confirmed_inputs: None, }; // TODO(splicing): start interactive funding negotiation - Ok(splice_ack_msg) + splice_ack_msg } /// Handle splice_ack #[cfg(splicing)] - pub fn splice_ack(&mut self, _msg: &msgs::SpliceAck) -> Result<(), ChannelError> { + pub fn splice_ack( + &mut self, msg: &msgs::SpliceAck, + signer_provider: &SP, entropy_source: &ES, holder_node_id: &PublicKey, logger: &L, + ) -> Result, ChannelError> where ES::Target: EntropySource, L::Target: Logger { // check if splice is pending - if self.pending_splice.is_none() { + let pending_splice = if let Some(pending_splice) = &self.pending_splice { + pending_splice + } else { return Err(ChannelError::Warn(format!("Channel is not in pending splice"))); }; + if !pending_splice.awaiting_splice_ack { + return Err(ChannelError::Warn(format!("Received unexpected splice_ack"))); + } + + let our_funding_contribution = pending_splice.our_funding_contribution; + let their_funding_contribution_satoshis = msg.funding_contribution_satoshis; + // TODO(splicing): Pre-check for reserve requirement // (Note: It should also be checked later at tx_complete) - Ok(()) + let pre_channel_value = self.funding.get_value_satoshis(); + let post_channel_value = PendingSplice::compute_post_value(pre_channel_value, our_funding_contribution, their_funding_contribution_satoshis); + let _post_balance = PendingSplice::add_checked(self.funding.value_to_self_msat, our_funding_contribution); + + let (our_funding_satoshis, their_funding_satoshis) = calculate_funding_values( + pre_channel_value, + our_funding_contribution, + their_funding_contribution_satoshis, + true, // is_outbound + )?; + + let funding_scope = self.funding_scope_for_splice(our_funding_satoshis, post_channel_value); + + let pre_funding_transaction = &self.funding.funding_transaction; + let pre_funding_txo = &self.funding.get_funding_txo(); + // We need the current funding tx as an extra input + let prev_funding_input = Self::get_input_of_previous_funding(pre_funding_transaction, pre_funding_txo)?; + if let Some(ref mut pending_splice) = &mut self.pending_splice { + pending_splice.funding_scope = Some(funding_scope); + // update funding values + pending_splice.funding_negotiation_context.our_funding_satoshis = our_funding_satoshis; + pending_splice.funding_negotiation_context.their_funding_satoshis = Some(their_funding_satoshis); + pending_splice.interactive_tx_constructor = None; + pending_splice.interactive_tx_signing_session = None; + debug_assert!(pending_splice.awaiting_splice_ack); + pending_splice.awaiting_splice_ack = false; + } else { + return Err(ChannelError::Warn(format!("Channel is not in pending splice"))); + }; + + // TODO(splicing): Pre-check for reserve requirement + // (Note: It should also be checked later at tx_complete) + + let _post_channel_value = PendingSplice::compute_post_value(pre_channel_value, our_funding_contribution, their_funding_contribution_satoshis); + let _post_balance = PendingSplice::add_checked(self.funding.value_to_self_msat, our_funding_contribution); + // TODO: Early check for reserve requirement, assuming maximum balance of full channel value + // This will also be checked later at tx_complete + + // Apply start of splice change in the state + self.splice_start(true, logger); + + // Start interactive funding negotiation, with the previous funding transaction as an extra shared input + let mut refunding = self.as_renegotiating_channel() + .map_err(|err| ChannelError::Warn(err.into()))?; + let tx_msg_opt = refunding.begin_interactive_funding_tx_construction(signer_provider, entropy_source, holder_node_id.clone(), None, Some(prev_funding_input)) + .map_err(|err| ChannelError::Warn(format!("V2 channel rejected due to sender error, {:?}", err)))?; + Ok(tx_msg_opt) + } + + /// Get a transaction input that is the previous funding transaction + #[cfg(splicing)] + fn get_input_of_previous_funding(pre_funding_transaction: &Option, pre_funding_txo: &Option) + -> Result<(TxIn, TransactionU16LenLimited), ChannelError> { + if let Some(pre_funding_transaction) = pre_funding_transaction { + if let Some(pre_funding_txo) = pre_funding_txo { + Ok(( + TxIn { + previous_output: pre_funding_txo.into_bitcoin_outpoint(), + script_sig: ScriptBuf::new(), + sequence: Sequence::ZERO, + witness: Witness::new(), + }, + TransactionU16LenLimited::new(pre_funding_transaction.clone()).unwrap(), // TODO err? + )) + } else { + Err(ChannelError::Warn("Internal error: Missing previous funding transaction outpoint".to_string())) + } + } else { + Err(ChannelError::Warn("Internal error: Missing previous funding transaction".to_string())) + } + } + + /// Splice process starting; update state, log, etc. + #[cfg(splicing)] + pub(crate) fn splice_start(&mut self, is_outgoing: bool, logger: &L) where L::Target: Logger { + // Set state, by this point splice_init/splice_ack handshake is complete + // TODO(splicing) + // self.channel_state = ChannelState::NegotiatingFunding( + // NegotiatingFundingFlags::OUR_INIT_SENT | NegotiatingFundingFlags::THEIR_INIT_SENT + // ); + if let Some(pending_splice) = &self.pending_splice { + if let Some(funding_scope) = &pending_splice.funding_scope { + let old_value = self.funding.get_value_satoshis(); + log_info!(logger, "Splicing process started, new channel value {}, old {}, outgoing {}, channel_id {}", + funding_scope.get_value_satoshis(), old_value, is_outgoing, self.context().channel_id); + } + } } // Send stuff to our remote peers: @@ -10039,12 +10473,35 @@ impl InboundV1Channel where SP::Target: SignerProvider { } } +/// Calculate funding values for interactive tx for splicing, based on channel value changes +#[cfg(splicing)] +fn calculate_funding_values( + pre_channel_value: u64, our_funding_contribution: i64, their_funding_contribution: i64, is_initiator: bool, +) -> Result<(u64, u64), ChannelError> { + // Initiator also adds the previous funding as input + let mut our_contribution_with_prev = our_funding_contribution; + let mut their_contribution_with_prev = their_funding_contribution; + if is_initiator { + our_contribution_with_prev = our_contribution_with_prev.saturating_add(pre_channel_value as i64); + } else { + their_contribution_with_prev = their_contribution_with_prev.saturating_add(pre_channel_value as i64); + } + if our_contribution_with_prev < 0 || their_contribution_with_prev < 0 { + return Err(ChannelError::Warn(format!( + "Funding contribution cannot be negative! ours {} theirs {} pre {} initiator {} acceptor {}", + our_contribution_with_prev, their_contribution_with_prev, pre_channel_value, + our_funding_contribution, their_funding_contribution + ))); + } + Ok((our_contribution_with_prev.abs() as u64, their_contribution_with_prev.abs() as u64)) +} + // 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, + pub funding_negotiation_context: FundingNegotiationContext, /// The current interactive transaction construction session under negotiation. pub interactive_tx_constructor: Option, /// The signing session created after `tx_complete` handling @@ -10103,7 +10560,7 @@ impl PendingV2Channel where SP::Target: SignerProvider { unfunded_channel_age_ticks: 0, holder_commitment_point: HolderCommitmentPoint::new(&context.holder_signer, &context.secp_ctx), }; - let dual_funding_context = DualFundingChannelContext { + let funding_negotiation_context = FundingNegotiationContext { our_funding_satoshis: funding_satoshis, // TODO(dual_funding) TODO(splicing) Include counterparty contribution, once that's enabled their_funding_satoshis: None, @@ -10115,7 +10572,7 @@ impl PendingV2Channel where SP::Target: SignerProvider { funding, context, unfunded_context, - dual_funding_context, + funding_negotiation_context, interactive_tx_constructor: None, interactive_tx_signing_session: None, }; @@ -10184,7 +10641,7 @@ impl PendingV2Channel where SP::Target: SignerProvider { }, funding_feerate_sat_per_1000_weight: self.context.feerate_per_kw, second_per_commitment_point, - locktime: self.dual_funding_context.funding_tx_locktime.to_consensus_u32(), + locktime: self.funding_negotiation_context.funding_tx_locktime.to_consensus_u32(), require_confirmed_inputs: None, } } @@ -10255,7 +10712,7 @@ impl PendingV2Channel where SP::Target: SignerProvider { &funding.get_counterparty_pubkeys().revocation_basepoint); context.channel_id = channel_id; - let dual_funding_context = DualFundingChannelContext { + let funding_negotiation_context = FundingNegotiationContext { our_funding_satoshis: our_funding_satoshis, their_funding_satoshis: Some(msg.common_fields.funding_satoshis), funding_tx_locktime: LockTime::from_consensus(msg.locktime), @@ -10269,8 +10726,8 @@ impl PendingV2Channel where SP::Target: SignerProvider { holder_node_id, counterparty_node_id, channel_id: context.channel_id, - feerate_sat_per_kw: dual_funding_context.funding_feerate_sat_per_1000_weight, - funding_tx_locktime: dual_funding_context.funding_tx_locktime, + feerate_sat_per_kw: funding_negotiation_context.funding_feerate_sat_per_1000_weight, + funding_tx_locktime: funding_negotiation_context.funding_tx_locktime, is_initiator: false, inputs_to_contribute: our_funding_inputs, outputs_to_contribute: Vec::new(), @@ -10288,7 +10745,7 @@ impl PendingV2Channel where SP::Target: SignerProvider { Ok(Self { funding, context, - dual_funding_context, + funding_negotiation_context, interactive_tx_constructor, interactive_tx_signing_session: None, unfunded_context, @@ -10353,7 +10810,7 @@ impl PendingV2Channel where SP::Target: SignerProvider { }), channel_type: Some(self.funding.get_channel_type().clone()), }, - funding_satoshis: self.dual_funding_context.our_funding_satoshis, + funding_satoshis: self.funding_negotiation_context.our_funding_satoshis, second_per_commitment_point, require_confirmed_inputs: None, } @@ -10368,6 +10825,19 @@ impl PendingV2Channel where SP::Target: SignerProvider { pub fn get_accept_channel_v2_message(&self) -> msgs::AcceptChannelV2 { self.generate_accept_channel_v2_message() } + + /// Return a short-lived [`NegotiatingV2ChannelView`]. + fn as_negotiating_channel(&mut self) -> NegotiatingV2ChannelView { + NegotiatingV2ChannelView { + context: &mut self.context, + funding: &mut self.funding, + funding_negotiation_context: &mut self.funding_negotiation_context, + interactive_tx_constructor: &mut self.interactive_tx_constructor, + interactive_tx_signing_session: &mut self.interactive_tx_signing_session, + holder_commitment_transaction_number: self.unfunded_context.transaction_number(), + is_splice: false, + } + } } // Unfunded channel utilities @@ -13394,4 +13864,69 @@ mod tests { ); } } + + #[cfg(splicing)] + fn get_pre_and_post(pre_channel_value: u64, our_funding_contribution: i64, their_funding_contribution: i64) -> (u64, u64) { + use crate::ln::channel::PendingSplice; + + let post_channel_value = PendingSplice::compute_post_value(pre_channel_value, our_funding_contribution, their_funding_contribution); + (pre_channel_value, post_channel_value) + } + + #[cfg(splicing)] + #[test] + fn test_splice_compute_post_value() { + { + // increase, small amounts + let (pre_channel_value, post_channel_value) = get_pre_and_post(9_000, 6_000, 0); + assert_eq!(pre_channel_value, 9_000); + assert_eq!(post_channel_value, 15_000); + } + { + // increase, small amounts + let (pre_channel_value, post_channel_value) = get_pre_and_post(9_000, 4_000, 2_000); + assert_eq!(pre_channel_value, 9_000); + assert_eq!(post_channel_value, 15_000); + } + { + // increase, small amounts + let (pre_channel_value, post_channel_value) = get_pre_and_post(9_000, 0, 6_000); + assert_eq!(pre_channel_value, 9_000); + assert_eq!(post_channel_value, 15_000); + } + { + // decrease, small amounts + let (pre_channel_value, post_channel_value) = get_pre_and_post(15_000, -6_000, 0); + assert_eq!(pre_channel_value, 15_000); + assert_eq!(post_channel_value, 9_000); + } + { + // decrease, small amounts + let (pre_channel_value, post_channel_value) = get_pre_and_post(15_000, -4_000, -2_000); + assert_eq!(pre_channel_value, 15_000); + assert_eq!(post_channel_value, 9_000); + } + { + // increase and decrease + let (pre_channel_value, post_channel_value) = get_pre_and_post(15_000, 4_000, -2_000); + assert_eq!(pre_channel_value, 15_000); + assert_eq!(post_channel_value, 17_000); + } + let base2: u64 = 2; + let huge63i3 = (base2.pow(63) - 3) as i64; + assert_eq!(huge63i3, 9223372036854775805); + assert_eq!(-huge63i3, -9223372036854775805); + { + // increase, large amount + let (pre_channel_value, post_channel_value) = get_pre_and_post(9_000, huge63i3, 3); + assert_eq!(pre_channel_value, 9_000); + assert_eq!(post_channel_value, 9223372036854784807); + } + { + // increase, large amounts + let (pre_channel_value, post_channel_value) = get_pre_and_post(9_000, huge63i3, huge63i3); + assert_eq!(pre_channel_value, 9_000); + assert_eq!(post_channel_value, 9223372036854784807); + } + } } diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index d0a73e89992..978414b7dc0 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -4277,7 +4277,7 @@ where let mut res = Ok(()); PersistenceNotifierGuard::optionally_notify(self, || { let result = self.internal_splice_channel( - channel_id, counterparty_node_id, our_funding_contribution_satoshis, &our_funding_inputs, funding_feerate_per_kw, locktime + channel_id, counterparty_node_id, our_funding_contribution_satoshis, our_funding_inputs.clone(), funding_feerate_per_kw, locktime ); res = result; match res { @@ -4292,7 +4292,7 @@ where #[cfg(splicing)] fn internal_splice_channel( &self, channel_id: &ChannelId, counterparty_node_id: &PublicKey, our_funding_contribution_satoshis: i64, - our_funding_inputs: &Vec<(TxIn, Transaction, Weight)>, + our_funding_inputs: Vec<(TxIn, Transaction, Weight)>, funding_feerate_per_kw: u32, locktime: Option, ) -> Result<(), APIError> { let per_peer_state = self.per_peer_state.read().unwrap(); @@ -8042,7 +8042,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ // Inbound V2 channels with contributed inputs are not considered unfunded. if let Some(unfunded_chan) = chan.as_unfunded_v2() { - if unfunded_chan.dual_funding_context.our_funding_satoshis != 0 { + if unfunded_chan.funding_negotiation_context.our_funding_satoshis != 0 { continue; } } @@ -8452,45 +8452,25 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ fn internal_tx_add_input(&self, counterparty_node_id: PublicKey, msg: &msgs::TxAddInput) -> Result<(), MsgHandleErrInternal> { self.internal_tx_msg(&counterparty_node_id, msg.channel_id, |channel: &mut Channel| { - match channel.as_unfunded_v2_mut() { - Some(unfunded_channel) => { - Ok(unfunded_channel.tx_add_input(msg).into_msg_send_event(counterparty_node_id)) - }, - None => Err("tx_add_input"), - } + Ok(channel.tx_add_input(msg)?.into_msg_send_event(counterparty_node_id)) }) } fn internal_tx_add_output(&self, counterparty_node_id: PublicKey, msg: &msgs::TxAddOutput) -> Result<(), MsgHandleErrInternal> { self.internal_tx_msg(&counterparty_node_id, msg.channel_id, |channel: &mut Channel| { - match channel.as_unfunded_v2_mut() { - Some(unfunded_channel) => { - Ok(unfunded_channel.tx_add_output(msg).into_msg_send_event(counterparty_node_id)) - }, - None => Err("tx_add_output"), - } + Ok(channel.tx_add_output(msg)?.into_msg_send_event(counterparty_node_id)) }) } fn internal_tx_remove_input(&self, counterparty_node_id: PublicKey, msg: &msgs::TxRemoveInput) -> Result<(), MsgHandleErrInternal> { self.internal_tx_msg(&counterparty_node_id, msg.channel_id, |channel: &mut Channel| { - match channel.as_unfunded_v2_mut() { - Some(unfunded_channel) => { - Ok(unfunded_channel.tx_remove_input(msg).into_msg_send_event(counterparty_node_id)) - }, - None => Err("tx_remove_input"), - } + Ok(channel.tx_remove_input(msg)?.into_msg_send_event(counterparty_node_id)) }) } fn internal_tx_remove_output(&self, counterparty_node_id: PublicKey, msg: &msgs::TxRemoveOutput) -> Result<(), MsgHandleErrInternal> { self.internal_tx_msg(&counterparty_node_id, msg.channel_id, |channel: &mut Channel| { - match channel.as_unfunded_v2_mut() { - Some(unfunded_channel) => { - Ok(unfunded_channel.tx_remove_output(msg).into_msg_send_event(counterparty_node_id)) - }, - None => Err("tx_remove_output"), - } + Ok(channel.tx_remove_output(msg)?.into_msg_send_event(counterparty_node_id)) }) } @@ -8507,14 +8487,14 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ let peer_state = &mut *peer_state_lock; match peer_state.channel_by_id.entry(msg.channel_id) { hash_map::Entry::Occupied(mut chan_entry) => { - let (msg_send_event_opt, signing_session_opt) = match chan_entry.get_mut().as_unfunded_v2_mut() { - Some(chan) => chan.tx_complete(msg) - .into_msg_send_event_or_signing_session(counterparty_node_id), - None => try_channel_entry!(self, peer_state, Err(ChannelError::Close( - ( - "Got a tx_complete message with no interactive transaction construction expected or in-progress".into(), + let (msg_send_event_opt, signing_session_opt) = match chan_entry.get_mut().tx_complete(msg) { + Ok(res) => res.into_msg_send_event_or_signing_session(counterparty_node_id), + Err(err) => { + try_channel_entry!(self, peer_state, Err(ChannelError::Close(( + err.into(), ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(false) }, ))), chan_entry) + } }; if let Some(msg_send_event) = msg_send_event_opt { peer_state.pending_msg_events.push(msg_send_event); @@ -9540,6 +9520,9 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ let mut peer_state_lock = peer_state_mutex.lock().unwrap(); let peer_state = &mut *peer_state_lock; + // TODO(splicing): Currently not possible to contribute on the splicing-acceptor side + let our_funding_contribution = 0i64; + // Look for the channel match peer_state.channel_by_id.entry(msg.channel_id) { hash_map::Entry::Vacant(_) => return Err(MsgHandleErrInternal::send_err_msg_no_close(format!( @@ -9547,23 +9530,18 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ counterparty_node_id, msg.channel_id, ), msg.channel_id)), hash_map::Entry::Occupied(mut chan_entry) => { - if let Some(chan) = chan_entry.get_mut().as_funded_mut() { - let splice_ack_msg = try_channel_entry!(self, peer_state, chan.splice_init(msg), chan_entry); - peer_state.pending_msg_events.push(MessageSendEvent::SendSpliceAck { - node_id: *counterparty_node_id, - msg: splice_ack_msg, - }); - } else { - return Err(MsgHandleErrInternal::send_err_msg_no_close("Channel is not funded, cannot be spliced".to_owned(), msg.channel_id)); - } + // Handle inside channel (checks, phase change, state change) + // TODO try_channel_entry() + let splice_ack_msg = chan_entry.get_mut().splice_init(msg, our_funding_contribution, &self.signer_provider, + &self.entropy_source, &self.get_our_node_id(), &self.logger) + .map_err(|err| MsgHandleErrInternal::from_chan_no_close(err, msg.channel_id))?; + peer_state.pending_msg_events.push(MessageSendEvent::SendSpliceAck { + node_id: *counterparty_node_id, + msg: splice_ack_msg, + }); }, }; - // TODO(splicing): - // Change channel, change phase (remove and add) - // Create new post-splice channel - // etc. - Ok(()) } @@ -9581,26 +9559,20 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ // Look for the channel match peer_state.channel_by_id.entry(msg.channel_id) { - hash_map::Entry::Vacant(_) => return Err(MsgHandleErrInternal::send_err_msg_no_close(format!( + hash_map::Entry::Vacant(_) => Err(MsgHandleErrInternal::send_err_msg_no_close(format!( "Got a message for a channel from the wrong node! No such channel for the passed counterparty_node_id {}", counterparty_node_id ), msg.channel_id)), hash_map::Entry::Occupied(mut chan_entry) => { - if let Some(chan) = chan_entry.get_mut().as_funded_mut() { - try_channel_entry!(self, peer_state, chan.splice_ack(msg), chan_entry); - } else { - return Err(MsgHandleErrInternal::send_err_msg_no_close("Channel is not funded, cannot splice".to_owned(), msg.channel_id)); + // Handle inside channel + let tx_msg_opt = chan_entry.get_mut().splice_ack(msg, &self.signer_provider, &self.entropy_source, &self.get_our_node_id(), &self.logger) + .map_err(|err| MsgHandleErrInternal::from_chan_no_close(err, msg.channel_id))?; + if let Some(tx_msg) = tx_msg_opt { + peer_state.pending_msg_events.push(tx_msg.into_msg_send_event(counterparty_node_id.clone())); } + Ok(()) }, - }; - - // TODO(splicing): - // Change channel, change phase (remove and add) - // Create new post-splice channel - // Start splice funding transaction negotiation - // etc. - - Err(MsgHandleErrInternal::send_err_msg_no_close("TODO(splicing): Splicing is not implemented (splice_ack)".to_owned(), msg.channel_id)) + } } /// Process pending events from the [`chain::Watch`], returning whether any events were processed. diff --git a/lightning/src/ln/splicing_tests.rs b/lightning/src/ln/splicing_tests.rs index 33f5a500789..bf71491ee92 100644 --- a/lightning/src/ln/splicing_tests.rs +++ b/lightning/src/ln/splicing_tests.rs @@ -28,6 +28,7 @@ fn test_v1_splice_in() { let channel_value_sat = 100_000; let channel_reserve_amnt_sat = 1_000; + let expect_inputs_in_reverse = true; let (_, _, channel_id, _) = create_announced_chan_between_nodes_with_value( &nodes, @@ -48,10 +49,15 @@ fn test_v1_splice_in() { // ==== Channel is now ready for normal operation + // Expected balances + let mut exp_balance1 = 1000 * channel_value_sat; + let mut _exp_balance2 = 0; + // === Start of Splicing // Amount being added to the channel through the splice-in let splice_in_sats = 20_000; + let post_splice_channel_value = channel_value_sat + splice_in_sats; let funding_feerate_per_kw = 1024; // Create additional inputs @@ -121,18 +127,126 @@ fn test_v1_splice_in() { assert!(channel.is_usable); assert!(channel.is_channel_ready); assert_eq!(channel.channel_value_satoshis, channel_value_sat); - assert_eq!( - channel.outbound_capacity_msat, - 1000 * (channel_value_sat - channel_reserve_amnt_sat) - ); + assert_eq!(channel.outbound_capacity_msat, exp_balance1 - 1000 * channel_reserve_amnt_sat); assert!(channel.funding_txo.is_some()); assert!(channel.confirmations.unwrap() > 0); } - let _error_msg = get_err_msg(initiator_node, &acceptor_node.node.get_our_node_id()); + // exp_balance1 += 1000 * splice_in_sats; // increase in balance + + // Negotiate transaction inputs and outputs + + // First input + let tx_add_input_msg = get_event_msg!( + &initiator_node, + MessageSendEvent::SendTxAddInput, + acceptor_node.node.get_our_node_id() + ); + let exp_value = + if expect_inputs_in_reverse { extra_splice_funding_input_sats } else { channel_value_sat }; + assert_eq!( + tx_add_input_msg.prevtx.as_transaction().output[tx_add_input_msg.prevtx_out as usize] + .value + .to_sat(), + exp_value + ); + + let _res = acceptor_node + .node + .handle_tx_add_input(initiator_node.node.get_our_node_id(), &tx_add_input_msg); + let tx_complete_msg = get_event_msg!( + acceptor_node, + MessageSendEvent::SendTxComplete, + initiator_node.node.get_our_node_id() + ); + + let _res = initiator_node + .node + .handle_tx_complete(acceptor_node.node.get_our_node_id(), &tx_complete_msg); + // Second input + let exp_value = + if expect_inputs_in_reverse { channel_value_sat } else { extra_splice_funding_input_sats }; + let tx_add_input2_msg = get_event_msg!( + &initiator_node, + MessageSendEvent::SendTxAddInput, + acceptor_node.node.get_our_node_id() + ); + assert_eq!( + tx_add_input2_msg.prevtx.as_transaction().output[tx_add_input2_msg.prevtx_out as usize] + .value + .to_sat(), + exp_value + ); + + let _res = acceptor_node + .node + .handle_tx_add_input(initiator_node.node.get_our_node_id(), &tx_add_input2_msg); + let tx_complete_msg = get_event_msg!( + acceptor_node, + MessageSendEvent::SendTxComplete, + initiator_node.node.get_our_node_id() + ); + + let _res = initiator_node + .node + .handle_tx_complete(acceptor_node.node.get_our_node_id(), &tx_complete_msg); + + // TxAddOutput for the change output + let tx_add_output_msg = get_event_msg!( + &initiator_node, + MessageSendEvent::SendTxAddOutput, + acceptor_node.node.get_our_node_id() + ); + assert!(tx_add_output_msg.script.is_p2wsh()); + assert_eq!(tx_add_output_msg.sats, post_splice_channel_value); + + let _res = acceptor_node + .node + .handle_tx_add_output(initiator_node.node.get_our_node_id(), &tx_add_output_msg); + let tx_complete_msg = get_event_msg!( + &acceptor_node, + MessageSendEvent::SendTxComplete, + initiator_node.node.get_our_node_id() + ); - // TODO(splicing): continue with splice transaction negotiation + let _res = initiator_node + .node + .handle_tx_complete(acceptor_node.node.get_our_node_id(), &tx_complete_msg); + // TxAddOutput for the splice funding + let tx_add_output2_msg = get_event_msg!( + &initiator_node, + MessageSendEvent::SendTxAddOutput, + acceptor_node.node.get_our_node_id() + ); + assert!(tx_add_output2_msg.script.is_p2wpkh()); + assert_eq!(tx_add_output2_msg.sats, 14094); // extra_splice_input_sats - splice_in_sats + + let _res = acceptor_node + .node + .handle_tx_add_output(initiator_node.node.get_our_node_id(), &tx_add_output2_msg); + let _tx_complete_msg = get_event_msg!( + acceptor_node, + MessageSendEvent::SendTxComplete, + initiator_node.node.get_our_node_id() + ); + + // TODO(splicing) This is the last tx_complete, which triggers the commitment flow, which is not yet fully implemented + let _res = initiator_node + .node + .handle_tx_complete(acceptor_node.node.get_our_node_id(), &tx_complete_msg); + let events = initiator_node.node.get_and_clear_pending_msg_events(); + assert_eq!(events.len(), 2); + match events[0] { + MessageSendEvent::SendTxComplete { .. } => {}, + _ => panic!("Unexpected event {:?}", events[0]), + } + match events[1] { + MessageSendEvent::HandleError { .. } => {}, + _ => panic!("Unexpected event {:?}", events[1]), + } + // TODO(splicing): Continue with commitment flow, new tx confirmation + /* // === Close channel, cooperatively initiator_node.node.close_channel(&channel_id, &acceptor_node.node.get_our_node_id()).unwrap(); let node0_shutdown_message = get_event_msg!( @@ -154,6 +268,7 @@ fn test_v1_splice_in() { MessageSendEvent::SendClosingSigned, acceptor_node.node.get_our_node_id() ); + */ } #[test]