diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index 8c0df45a191..9a63c9e78dc 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -32,7 +32,7 @@ use crate::ln::types::ChannelId; use crate::types::payment::{PaymentPreimage, PaymentHash}; use crate::types::features::{ChannelTypeFeatures, InitFeatures}; use crate::ln::interactivetxs::{ - get_output_weight, calculate_change_output_value, HandleTxCompleteValue, HandleTxCompleteResult, InteractiveTxConstructor, + estimate_input_weight, get_output_weight, calculate_change_output_value, HandleTxCompleteValue, HandleTxCompleteResult, InteractiveTxConstructor, InteractiveTxConstructorArgs, InteractiveTxMessageSend, InteractiveTxSigningSession, InteractiveTxMessageSendResult, OutputOwned, SharedOwnedOutput, TX_COMMON_FIELDS_WEIGHT, }; @@ -1143,7 +1143,7 @@ pub(super) enum ChannelPhase where SP::Target: SignerProvider { Funded(Channel), #[cfg(splicing)] /// Used during splicing, channel is funded but a new funding is being renegotiated. - RefundingV2(Channel), + RefundingV2(SplicingChannel), } impl<'a, SP: Deref> ChannelPhase where @@ -1158,7 +1158,7 @@ impl<'a, SP: Deref> ChannelPhase where ChannelPhase::UnfundedOutboundV2(chan) => &chan.context, ChannelPhase::UnfundedInboundV2(chan) => &chan.context, #[cfg(splicing)] - ChannelPhase::RefundingV2(chan) => &chan.context, + ChannelPhase::RefundingV2(chan) => &chan.pre_funded.context, } } @@ -1170,7 +1170,7 @@ impl<'a, SP: Deref> ChannelPhase where ChannelPhase::UnfundedOutboundV2(ref mut chan) => &mut chan.context, ChannelPhase::UnfundedInboundV2(ref mut chan) => &mut chan.context, #[cfg(splicing)] - ChannelPhase::RefundingV2(ref mut chan) => &mut chan.context, + ChannelPhase::RefundingV2(ref mut chan) => &mut chan.pre_funded.context, } } @@ -1178,7 +1178,7 @@ impl<'a, SP: Deref> ChannelPhase where match self { ChannelPhase::Funded(chan) => Some(&chan), #[cfg(splicing)] - ChannelPhase::RefundingV2(chan) => Some(&chan), + ChannelPhase::RefundingV2(chan) => Some(&chan.pre_funded), _ => None } } @@ -1187,12 +1187,65 @@ impl<'a, SP: Deref> ChannelPhase where match self { ChannelPhase::Funded(ref mut chan) => Some(chan), #[cfg(splicing)] - ChannelPhase::RefundingV2(ref mut chan) => Some(chan), + ChannelPhase::RefundingV2(ref mut chan) => Some(&mut chan.pre_funded), _ => None } } } +/// Struct holding together various state dureing splicing negotiation +#[cfg(splicing)] +pub(super) struct SplicingChannel where SP::Target: SignerProvider { + pub pre_funded: Channel, + pub post_pending: PendingV2Channel, + pub post_funded: Option>, +} + +#[cfg(splicing)] +impl SplicingChannel where SP::Target: SignerProvider { + pub(super) fn new(pre_funded: Channel, post_pending: PendingV2Channel) -> Self { + Self { + pre_funded, + post_pending, + post_funded: None, + } + } + + pub fn splice_init( + &mut self, msg: &msgs::SpliceInit, signer_provider: &SP, entropy_source: &ES, holder_node_id: PublicKey, logger: &L, + ) -> Result where ES::Target: EntropySource, L::Target: Logger { + self.post_pending.splice_init(msg, signer_provider, entropy_source, holder_node_id, logger) + } + + pub fn splice_ack( + &mut self, msg: &msgs::SpliceAck, our_funding_contribution: i64, signer_provider: &SP, entropy_source: &ES, holder_node_id: PublicKey, logger: &L, + ) -> Result, ChannelError> where ES::Target: EntropySource, L::Target: Logger { + self.post_pending.splice_ack(msg, our_funding_contribution, signer_provider, entropy_source, holder_node_id, logger) + } + + pub fn tx_add_input(&mut self, msg: &msgs::TxAddInput) -> InteractiveTxMessageSendResult { + self.post_pending.tx_add_input(msg) + } + + pub fn tx_add_output(&mut self, msg: &msgs::TxAddOutput)-> InteractiveTxMessageSendResult { + self.post_pending.tx_add_output(msg) + } + + pub fn tx_complete(&mut self, msg: &msgs::TxComplete) -> HandleTxCompleteResult { + self.post_pending.tx_complete(msg) + } + + pub fn funding_tx_constructed( + &mut self, signing_session: &mut InteractiveTxSigningSession, logger: &L + ) -> Result<(msgs::CommitmentSigned, Option), ChannelError> where L::Target: Logger { + self.post_pending.funding_tx_constructed(signing_session, logger) + } + + pub fn into_channel(self, signing_session: InteractiveTxSigningSession) -> Result, ChannelError>{ + self.post_pending.into_channel(signing_session) + } +} + /// Contains all state common to unfunded inbound/outbound channels. #[derive(Default)] pub(super) struct UnfundedChannelContext { @@ -1219,7 +1272,7 @@ impl UnfundedChannelContext { /// Info about a pending splice, used in the pre-splice channel #[cfg(splicing)] #[derive(Clone)] -struct PendingSplicePre { +pub(super) struct PendingSplicePre { pub our_funding_contribution: i64, pub funding_feerate_perkw: u32, pub locktime: u32, @@ -4646,6 +4699,54 @@ fn get_v2_channel_reserve_satoshis(channel_value_satoshis: u64, dust_limit_satos cmp::min(channel_value_satoshis, cmp::max(q, dust_limit_satoshis)) } +pub(super) fn maybe_add_funding_change_output(signer_provider: &SP, is_initiator: bool, + our_funding_satoshis: u64, funding_inputs_prev_outputs: &Vec, + funding_outputs: &mut Vec, funding_feerate_sat_per_1000_weight: u32, + total_input_satoshis: u64, holder_dust_limit_satoshis: u64, channel_keys_id: [u8; 32], +) -> Result, ChannelError> where + SP::Target: SignerProvider, +{ + let our_funding_inputs_weight = funding_inputs_prev_outputs.iter().fold(0u64, |weight, prev_output| { + weight.saturating_add(estimate_input_weight(prev_output).to_wu()) + }); + let our_funding_outputs_weight = funding_outputs.iter().fold(0u64, |weight, out| { + weight.saturating_add(get_output_weight(&out.tx_out().script_pubkey).to_wu()) + }); + let our_contributed_weight = our_funding_outputs_weight.saturating_add(our_funding_inputs_weight); + let mut fees_sats = fee_for_weight(funding_feerate_sat_per_1000_weight, our_contributed_weight); + + // If we are the initiator, we must pay for weight of all common fields in the funding transaction. + if is_initiator { + let common_fees = fee_for_weight(funding_feerate_sat_per_1000_weight, TX_COMMON_FIELDS_WEIGHT); + fees_sats = fees_sats.saturating_add(common_fees); + } + + let remaining_value = total_input_satoshis + .saturating_sub(our_funding_satoshis) + .saturating_sub(fees_sats); + + if remaining_value < holder_dust_limit_satoshis { + Ok(None) + } else { + let change_script = signer_provider.get_destination_script(channel_keys_id).map_err( + |_| ChannelError::Close(( + "Failed to get change script as new destination script".to_owned(), + ClosureReason::ProcessingError { err: "Failed to get change script as new destination script".to_owned() } + )) + )?; + let mut change_output = TxOut { + value: Amount::from_sat(remaining_value), + script_pubkey: change_script, + }; + let change_output_weight = get_output_weight(&change_output.script_pubkey).to_wu(); + + let change_output_fee = fee_for_weight(funding_feerate_sat_per_1000_weight, change_output_weight); + change_output.value = Amount::from_sat(remaining_value.saturating_sub(change_output_fee)); + funding_outputs.push(OutputOwned::Single(change_output.clone())); + Ok(Some(change_output)) + } +} + pub(super) fn calculate_our_funding_satoshis( is_initiator: bool, funding_inputs: &[(TxIn, TransactionU16LenLimited)], total_witness_weight: Weight, funding_feerate_sat_per_1000_weight: u32, @@ -4716,7 +4817,7 @@ pub(super) struct Channel where SP::Target: SignerProvider { pub interactive_tx_signing_session: Option, /// Info about an in-progress, pending splice (if any), on the pre-splice channel #[cfg(splicing)] - pending_splice_pre: Option, + pub pending_splice_pre: Option, /// Info about an in-progress, pending splice (if any), on the post-splice channel #[cfg(splicing)] pending_splice_post: Option, @@ -8394,11 +8495,11 @@ impl Channel where Ok(msg) } - /// Handle splice_init + /// Checks during handling splice_init #[cfg(splicing)] - pub fn splice_init( - &mut self, msg: &msgs::SpliceInit, _signer_provider: &SP, _entropy_source: &ES, _holder_node_id: PublicKey, logger: &L, - ) -> Result where ES::Target: EntropySource, L::Target: Logger { + pub fn splice_init_checks( + &mut self, msg: &msgs::SpliceInit, _signer_provider: &SP, _entropy_source: &ES, _holder_node_id: PublicKey, + ) -> Result<(), ChannelError> where ES::Target: EntropySource { 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; @@ -8439,52 +8540,7 @@ impl Channel where // Early check for reserve requirement, assuming maximum balance of full channel value // This will also be checked later at tx_complete let _res = self.context.check_balance_meets_reserve_requirements(post_balance, post_channel_value)?; - - // TODO(splicing): Store msg.funding_pubkey - - // Apply start of splice change in the state - self.context.splice_start(false, logger); - - let splice_ack_msg = self.context.get_splice_ack(our_funding_contribution_satoshis); - - // TODO(splicing): start interactive funding negotiation - // let _msg = self.begin_interactive_funding_tx_construction(signer_provider, entropy_source, holder_node_id) - // .map_err(|err| ChannelError::Warn(format!("Failed to start interactive transaction construction, {:?}", err)))?; - - Ok(splice_ack_msg) - } - - /// Handle splice_ack - #[cfg(splicing)] - 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 { - let their_funding_contribution_satoshis = msg.funding_contribution_satoshis; - - // check if splice is pending - let pending_splice = if let Some(pending_splice) = &self.pending_splice_pre { - pending_splice - } else { - return Err(ChannelError::Warn(format!("Channel is not in pending splice"))); - }; - - let our_funding_contribution = pending_splice.our_funding_contribution; - - let pre_channel_value = self.context.get_value_satoshis(); - let post_channel_value = PendingSplicePre::compute_post_value(pre_channel_value, our_funding_contribution, their_funding_contribution_satoshis); - let post_balance = PendingSplicePre::add_checked(self.context.value_to_self_msat, our_funding_contribution); - // Early check for reserve requirement, assuming maximum balance of full channel value - // This will also be checked later at tx_complete - let _res = self.context.check_balance_meets_reserve_requirements(post_balance, post_channel_value)?; - - // Apply start of splice change in the state - self.context.splice_start(true, logger); - - // TODO(splicing): start interactive funding negotiation - // let tx_msg_opt = self.begin_interactive_funding_tx_construction(signer_provider, entropy_source, holder_node_id) - // .map_err(|err| ChannelError::Warn(format!("V2 channel rejected due to sender error, {:?}", err)))?; - // Ok(tx_msg_opt) - Ok(None) + Ok(()) } // Send stuff to our remote peers: @@ -9647,6 +9703,39 @@ impl OutboundV2Channel where SP::Target: SignerProvider { Ok(channel) } + + /// Handle splice_ack + #[cfg(splicing)] + pub fn splice_ack( + &mut self, msg: &msgs::SpliceAck, our_funding_contribution: i64, signer_provider: &SP, entropy_source: &ES, holder_node_id: PublicKey, logger: &L, + ) -> Result, ChannelError> where ES::Target: EntropySource, L::Target: Logger { + let their_funding_contribution_satoshis = msg.funding_contribution_satoshis; + + // check if splice is pending + let pending_splice = if let Some(pending_splice) = &self.pending_splice_post { + pending_splice + } else { + return Err(ChannelError::Warn(format!("Channel is not in pending splice"))); + }; + + let pre_channel_value = self.context.get_value_satoshis(); + let post_channel_value = PendingSplicePre::compute_post_value(pre_channel_value, our_funding_contribution, their_funding_contribution_satoshis); + let post_balance = PendingSplicePre::add_checked(self.context.value_to_self_msat, our_funding_contribution); + // Early check for reserve requirement, assuming maximum balance of full channel value + // This will also be checked later at tx_complete + let _res = self.context.check_balance_meets_reserve_requirements(post_balance, post_channel_value)?; + + // We need the current funding tx as an extra input + let prev_funding_input = pending_splice.get_input_of_previous_funding()?; + + // Apply start of splice change in the state + self.context.splice_start(true, logger); + + // Start interactive funding negotiation, with the previous funding transaction as an extra shared input + let tx_msg_opt = self.begin_interactive_funding_tx_construction(signer_provider, entropy_source, holder_node_id, Some(prev_funding_input)) + .map_err(|err| ChannelError::Warn(format!("V2 channel rejected due to sender error, {:?}", err)))?; + Ok(tx_msg_opt) + } } // A not-yet-funded inbound (from counterparty) channel using V2 channel establishment. @@ -9921,6 +10010,108 @@ impl InboundV2Channel where SP::Target: SignerProvider { Ok(channel) } + + /// Handle splice_init + /// See also [`splice_init_checks`] + #[cfg(splicing)] + pub fn splice_init( + &mut self, _msg: &msgs::SpliceInit, signer_provider: &SP, entropy_source: &ES, holder_node_id: PublicKey, logger: &L, + ) -> Result where ES::Target: EntropySource, L::Target: Logger { + // TODO(splicing): Currently not possible to contribute on the splicing-acceptor side + let our_funding_contribution_satoshis = 0i64; + + // TODO(splicing): Store msg.funding_pubkey + + // Apply start of splice change in the state + self.context.splice_start(false, logger); + + let splice_ack_msg = self.context.get_splice_ack(our_funding_contribution_satoshis); + + // Start interactive funding negotiation. No extra input, as we are not the splice initiator + let _msg = self.begin_interactive_funding_tx_construction(signer_provider, entropy_source, holder_node_id, None) + .map_err(|err| ChannelError::Warn(format!("Failed to start interactive transaction construction, {:?}", err)))?; + + Ok(splice_ack_msg) + } +} + +/// Enum to tie together InboundV2Channel and OutboundV2Channel +/// Note: those two structs could be merged into one with an additional is_outbound field. +#[cfg(splicing)] +pub(super) enum PendingV2Channel where SP::Target: SignerProvider { + Inbound(InboundV2Channel), + Outbound(OutboundV2Channel), +} + +#[cfg(splicing)] +impl InteractivelyFunded for PendingV2Channel where SP::Target: SignerProvider { + fn context(&self) -> &ChannelContext { + match self { + PendingV2Channel::Inbound(chan) => &chan.context, + PendingV2Channel::Outbound(chan) => &chan.context, + } + } + fn context_mut(&mut self) -> &mut ChannelContext { + match self { + PendingV2Channel::Inbound(chan) => &mut chan.context, + PendingV2Channel::Outbound(chan) => &mut chan.context, + } + } + fn dual_funding_context(&self) -> &DualFundingChannelContext { + match self { + PendingV2Channel::Inbound(chan) => &chan.dual_funding_context, + PendingV2Channel::Outbound(chan) => &chan.dual_funding_context, + } + } + fn dual_funding_context_mut(&mut self) -> &mut DualFundingChannelContext { + match self { + PendingV2Channel::Inbound(chan) => &mut chan.dual_funding_context, + PendingV2Channel::Outbound(chan) => &mut chan.dual_funding_context, + } + } + fn interactive_tx_constructor_mut(&mut self) -> &mut Option { + match self { + PendingV2Channel::Inbound(chan) => &mut chan.interactive_tx_constructor, + PendingV2Channel::Outbound(chan) => &mut chan.interactive_tx_constructor, + } + } + fn is_initiator(&self) -> bool { + match &self { + PendingV2Channel::Inbound(_) => false, + PendingV2Channel::Outbound(_) => true, + } + } +} + +#[cfg(splicing)] +impl PendingV2Channel where SP::Target: SignerProvider { + /// Handle splice_init + /// See also [`splice_init_checks`] + pub fn splice_init( + &mut self, msg: &msgs::SpliceInit, signer_provider: &SP, entropy_source: &ES, holder_node_id: PublicKey, logger: &L, + ) -> Result where ES::Target: EntropySource, L::Target: Logger { + match self { + PendingV2Channel::Inbound(chan) => chan.splice_init(msg, signer_provider, entropy_source, holder_node_id, logger), + PendingV2Channel::Outbound(_) => Err(ChannelError::Warn(format!("Splice_init on an outbound channel"))), + } + } + + /// Handle splice_ack + pub fn splice_ack( + &mut self, msg: &msgs::SpliceAck, our_funding_contribution: i64, signer_provider: &SP, entropy_source: &ES, holder_node_id: PublicKey, logger: &L, + ) -> Result, ChannelError> where ES::Target: EntropySource, L::Target: Logger { + match self { + PendingV2Channel::Inbound(_) => Err(ChannelError::Warn(format!("Splice_ack on an inbound channel"))), + PendingV2Channel::Outbound(chan) => chan.splice_ack(msg, our_funding_contribution, signer_provider, entropy_source, holder_node_id, logger), + } + } + + pub fn into_channel(self, signing_session: InteractiveTxSigningSession) -> Result, ChannelError>{ + match self { + PendingV2Channel::Inbound(chan) => chan.into_channel(signing_session), + PendingV2Channel::Outbound(chan) => chan.into_channel(signing_session), + } + } } // Unfunded channel utilities diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index fe72ff81dfd..bbddee26cf2 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -27,6 +27,8 @@ use bitcoin::hashes::{Hash, HashEngine, HmacEngine}; use bitcoin::hashes::hmac::Hmac; use bitcoin::hashes::sha256::Hash as Sha256; use bitcoin::hash_types::{BlockHash, Txid}; +#[cfg(splicing)] +use bitcoin::locktime::absolute::LockTime; use bitcoin::secp256k1::{SecretKey,PublicKey}; use bitcoin::secp256k1::Secp256k1; @@ -49,6 +51,8 @@ use crate::ln::inbound_payment; use crate::ln::types::ChannelId; use crate::types::payment::{PaymentHash, PaymentPreimage, PaymentSecret}; use crate::ln::channel::{self, Channel, ChannelPhase, ChannelError, ChannelUpdateStatus, ShutdownResult, UpdateFulfillCommitFetch, OutboundV1Channel, InboundV1Channel, WithChannelContext, InboundV2Channel, InteractivelyFunded as _}; +#[cfg(splicing)] +use crate::ln::channel::{OutboundV2Channel, PendingV2Channel, SplicingChannel}; use crate::ln::channel_state::ChannelDetails; use crate::types::features::{Bolt12InvoiceFeatures, ChannelFeatures, ChannelTypeFeatures, InitFeatures, NodeFeatures}; #[cfg(any(feature = "_test_utils", test))] @@ -3072,7 +3076,7 @@ macro_rules! convert_chan_phase_err { }, #[cfg(splicing)] ChannelPhase::RefundingV2(channel) => { - convert_chan_phase_err!($self, $peer_state, $err, channel, $channel_id, FUNDED_CHANNEL) + convert_chan_phase_err!($self, $peer_state, $err, &mut channel.pre_funded, $channel_id, FUNDED_CHANNEL) }, } }; @@ -7936,7 +7940,8 @@ where } }, #[cfg(splicing)] - ChannelPhase::RefundingV2(chan) => { + ChannelPhase::RefundingV2(channel) => { + let chan = &channel.pre_funded; // This covers non-zero-conf inbound `Channel`s that we are currently monitoring, but those // which have not yet had any confirmations on-chain. if !chan.context.is_outbound() && chan.context.minimum_depth().unwrap_or(1) != 0 && @@ -8349,6 +8354,10 @@ where ChannelPhase::UnfundedOutboundV2(ref mut channel) => { Ok(channel.tx_add_input(msg).into_msg_send_event(counterparty_node_id)) }, + #[cfg(splicing)] + ChannelPhase::RefundingV2(ref mut channel) => { + Ok(channel.tx_add_input(msg).into_msg_send_event(counterparty_node_id)) + } _ => Err("tx_add_input"), } }) @@ -8363,6 +8372,10 @@ where ChannelPhase::UnfundedOutboundV2(ref mut channel) => { Ok(channel.tx_add_output(msg).into_msg_send_event(counterparty_node_id)) }, + #[cfg(splicing)] + ChannelPhase::RefundingV2(ref mut channel) => { + Ok(channel.tx_add_output(msg).into_msg_send_event(counterparty_node_id)) + } _ => Err("tx_add_output"), } }) @@ -8415,6 +8428,9 @@ where .into_msg_send_event_or_signing_session(counterparty_node_id), ChannelPhase::UnfundedOutboundV2(channel) => channel.tx_complete(msg) .into_msg_send_event_or_signing_session(counterparty_node_id), + #[cfg(splicing)] + ChannelPhase::RefundingV2(channel) => channel.tx_complete(msg) + .into_msg_send_event_or_signing_session(counterparty_node_id), _ => try_chan_phase_entry!(self, peer_state, Err(ChannelError::Close( ( "Got a tx_complete message with no interactive transaction construction expected or in-progress".into(), @@ -8432,6 +8448,10 @@ where ChannelPhase::UnfundedInboundV2(chan) => { chan.funding_tx_constructed(&mut signing_session, &self.logger) }, + #[cfg(splicing)] + ChannelPhase::RefundingV2(chan) => { + chan.funding_tx_constructed(&mut signing_session, &self.logger) + } _ => Err(ChannelError::Warn( "Got a tx_complete message with no interactive transaction construction expected or in-progress" .into())), @@ -8440,6 +8460,8 @@ where let channel = match channel_phase { ChannelPhase::UnfundedOutboundV2(chan) => chan.into_channel(signing_session), ChannelPhase::UnfundedInboundV2(chan) => chan.into_channel(signing_session), + #[cfg(splicing)] + ChannelPhase::RefundingV2(chan) => chan.into_channel(signing_session), _ => { debug_assert!(false); // It cannot be another variant as we are in the `Ok` branch of the above match. Err(ChannelError::Warn( @@ -8972,7 +8994,14 @@ where let peer_state = &mut *peer_state_lock; match peer_state.channel_by_id.entry(msg.channel_id) { hash_map::Entry::Occupied(mut chan_phase_entry) => { - if let Some(chan) = chan_phase_entry.get_mut().funded_channel_mut() { + let just_funded_channel_opt = match chan_phase_entry.get_mut() { + // Note: here we take the funded post-splice channel, not the pre channel + #[cfg(splicing)] + ChannelPhase::RefundingV2(ref mut chan) => chan.post_funded.as_mut(), + ChannelPhase::Funded(ref mut chan) => Some(chan), + _ => None, + }; + if let Some(chan) = just_funded_channel_opt { let logger = WithChannelContext::from(&self.logger, &chan.context, None); let funding_txo = chan.context.get_funding_txo(); @@ -9442,6 +9471,9 @@ where 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!( @@ -9450,27 +9482,63 @@ where ), msg.channel_id)), hash_map::Entry::Occupied(mut chan_entry) => { if let ChannelPhase::Funded(chan) = chan_entry.get_mut() { - match chan.splice_init(msg, &self.signer_provider, &self.entropy_source, self.get_our_node_id(), &self.logger) { - Ok(splice_ack_msg) => { - peer_state.pending_msg_events.push(events::MessageSendEvent::SendSpliceAck { - node_id: *counterparty_node_id, - msg: splice_ack_msg, - }); - }, - Err(err) => { - return Err(MsgHandleErrInternal::from_chan_no_close(err, msg.channel_id)); - } - } + chan.splice_init_checks(msg, &self.signer_provider, &self.entropy_source, self.get_our_node_id()) + .map_err(|err| MsgHandleErrInternal::from_chan_no_close(err, msg.channel_id))?; } else { return Err(MsgHandleErrInternal::send_err_msg_no_close("Channel is not funded, cannot be spliced".to_owned(), msg.channel_id)); } }, }; - // TODO(splicing): - // Change channel, change phase (remove and add) - // Create new post-splice channel - // etc. + // Change channel, phase changes, remove and add + // Remove the pre channel + // Note: this remove-and-add would not be needed if channel phase was wrapped (see #3418) + let prev_chan = match peer_state.channel_by_id.remove(&msg.channel_id) { + None => return 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 {}, channel_id {}", counterparty_node_id, msg.channel_id), msg.channel_id)), + Some(chan_phase) => { + if let ChannelPhase::Funded(chan) = chan_phase { + chan + } else { + return Err(MsgHandleErrInternal::send_err_msg_no_close("Channel in wrong state".to_owned(), msg.channel_id.clone())); + } + } + }; + + let post_chan = InboundV2Channel::new_spliced( + false, + &prev_chan, + &self.signer_provider, + &msg.funding_pubkey, + our_funding_contribution, + msg.funding_contribution_satoshis, + Vec::new(), + LockTime::from_consensus(msg.locktime), + msg.funding_feerate_perkw, + &self.logger, + ).map_err(|e| MsgHandleErrInternal::from_chan_no_close(e, msg.channel_id))?; + + // Add the modified channel + let post_chan_id = post_chan.context.channel_id(); + peer_state.channel_by_id.insert(post_chan_id, ChannelPhase::RefundingV2( + SplicingChannel::new(prev_chan, PendingV2Channel::Inbound(post_chan)) + )); + + // Perform state changes + match peer_state.channel_by_id.entry(post_chan_id) { + hash_map::Entry::Vacant(_) => return Err(MsgHandleErrInternal::send_err_msg_no_close("Internal consistency error".to_string(), post_chan_id)), + hash_map::Entry::Occupied(mut chan_entry) => { + if let ChannelPhase::RefundingV2(chan) = chan_entry.get_mut() { + let splice_ack_msg = chan.splice_init(msg, &self.signer_provider, &self.entropy_source, self.get_our_node_id(), &self.logger) + .map_err(|err| MsgHandleErrInternal::from_chan_no_close(err, post_chan_id))?; + peer_state.pending_msg_events.push(events::MessageSendEvent::SendSpliceAck { + node_id: *counterparty_node_id, + msg: splice_ack_msg, + }); + } else { + return Err(MsgHandleErrInternal::send_err_msg_no_close("Internal consistency error: splice_init while not renegotiating".to_string(), post_chan_id)); + } + } + } Ok(()) } @@ -9487,37 +9555,78 @@ where let mut peer_state_lock = peer_state_mutex.lock().unwrap(); let peer_state = &mut *peer_state_lock; - // 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!( - "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) => { - if let ChannelPhase::Funded(chan) = chan.get_mut() { - match chan.splice_ack(msg, &self.signer_provider, &self.entropy_source, self.get_our_node_id(), &self.logger) { - Ok(tx_msg_opt) => { - if let Some(tx_msg_opt) = tx_msg_opt { - peer_state.pending_msg_events.push(tx_msg_opt.into_msg_send_event(counterparty_node_id.clone())); - } - } - Err(err) => { - return Err(MsgHandleErrInternal::from_chan_no_close(err, msg.channel_id)); - } + // Look for channel + let pending_splice = match peer_state.channel_by_id.entry(msg.channel_id) { + hash_map::Entry::Vacant(_) => return 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(chan) => { + if let ChannelPhase::Funded(chan) = chan.get() { + // check if splice is pending + if let Some(pending_splice) = &chan.pending_splice_pre { + // Note: this is incomplete (their funding contribution is not set) + pending_splice.clone() + } else { + return Err(MsgHandleErrInternal::send_err_msg_no_close("Channel is not in pending splice".to_owned(), msg.channel_id.clone())); } } else { - return Err(MsgHandleErrInternal::send_err_msg_no_close("Channel is not funded, cannot splice".to_owned(), msg.channel_id)); + return Err(MsgHandleErrInternal::send_err_msg_no_close("Channel in wrong state".to_owned(), msg.channel_id.clone())); } }, }; - // TODO(splicing): - // Change channel, change phase (remove and add) - // Create new post-splice channel - // Start splice funding transaction negotiation - // etc. + // Change channel, phase changes, remove and add + // Remove the pre channel + // Note: this remove-and-add would not be needed if channel phase was wrapped (see #3418) + let prev_chan = match peer_state.channel_by_id.remove(&msg.channel_id) { + None => return 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 {}, channel_id {}", counterparty_node_id, msg.channel_id), msg.channel_id)), + Some(chan_phase) => { + if let ChannelPhase::Funded(chan) = chan_phase { + chan + } else { + return Err(MsgHandleErrInternal::send_err_msg_no_close("Channel in wrong state".to_owned(), msg.channel_id.clone())); + } + } + }; - Err(MsgHandleErrInternal::send_err_msg_no_close("TODO(splicing): Splicing is not implemented (splice_ack)".to_owned(), msg.channel_id)) + let post_chan = OutboundV2Channel::new_spliced( + true, + &prev_chan, + &self.signer_provider, + &msg.funding_pubkey, + pending_splice.our_funding_contribution, + msg.funding_contribution_satoshis, + pending_splice.our_funding_inputs, + LockTime::from_consensus(pending_splice.locktime), + pending_splice.funding_feerate_perkw, + &self.logger, + ).map_err(|e| MsgHandleErrInternal::from_chan_no_close(e, msg.channel_id))?; + + // Add the modified channel + let post_chan_id = post_chan.context().channel_id(); + peer_state.channel_by_id.insert(post_chan_id, ChannelPhase::RefundingV2( + SplicingChannel::new(prev_chan, PendingV2Channel::Outbound(post_chan)), + )); + + // Perform state changes + match peer_state.channel_by_id.entry(post_chan_id) { + hash_map::Entry::Vacant(_) => return Err(MsgHandleErrInternal::send_err_msg_no_close("Internal consistency error".to_string(), post_chan_id)), + hash_map::Entry::Occupied(mut chan_entry) => { + if let ChannelPhase::RefundingV2(chan) = chan_entry.get_mut() { + match chan.splice_ack(msg, pending_splice.our_funding_contribution, &self.signer_provider, &self.entropy_source, self.get_our_node_id(), &self.logger) { + Ok(tx_msg_opt) => { + 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(()) + }, + Err(err) => { + Err(MsgHandleErrInternal::from_chan_no_close(err, post_chan_id)) + }, + } + } else { + Err(MsgHandleErrInternal::send_err_msg_no_close("Internal consistency error: splice_ack while not renegotiating".to_string(), post_chan_id)) + } + } + } } /// Process pending events from the [`chain::Watch`], returning whether any events were processed. @@ -11694,7 +11803,8 @@ where &mut chan.context }, #[cfg(splicing)] - ChannelPhase::RefundingV2(chan) => { + ChannelPhase::RefundingV2(channel) => { + let chan = &mut channel.pre_funded; let logger = WithChannelContext::from(&self.logger, &chan.context, None); if chan.remove_uncommitted_htlcs_and_mark_paused(&&logger).is_ok() { // We only retain funded channels that are not shutdown. @@ -11873,7 +11983,8 @@ where } #[cfg(splicing)] - ChannelPhase::RefundingV2(chan) => { + ChannelPhase::RefundingV2(channel) => { + let chan = &mut channel.pre_funded; let logger = WithChannelContext::from(&self.logger, &chan.context, None); pending_msg_events.push(events::MessageSendEvent::SendChannelReestablish { node_id: chan.context.get_counterparty_node_id(), diff --git a/lightning/src/ln/functional_tests_splice.rs b/lightning/src/ln/functional_tests_splice.rs index 824a7cfebcc..48fae40627f 100644 --- a/lightning/src/ln/functional_tests_splice.rs +++ b/lightning/src/ln/functional_tests_splice.rs @@ -40,6 +40,7 @@ fn test_v1_splice_in() { let channel_value_sat = 100_000; // same as funding satoshis let push_msat = 0; let channel_reserve_amnt_sat = 1_000; + let expect_inputs_in_reverse = true; let expected_funded_channel_id = "ae3367da2c13bc1ceb86bf56418f62828f7ce9d6bfb15a46af5ba1f1ed8b124f"; @@ -230,6 +231,7 @@ fn test_v1_splice_in() { // Amount being added to the channel through the splice-in let splice_in_sats: u64 = 20000; + let post_splice_channel_value = channel_value_sat + splice_in_sats; let funding_feerate_perkw = 1024; // TODO let locktime = 0; // TODO @@ -305,9 +307,51 @@ fn test_v1_splice_in() { 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 splice funding + 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_p2wpkh()); + assert_eq!(tx_add_output_msg.sats, 14093); // 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_output_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_output2_msg = get_event_msg!(&initiator_node, MessageSendEvent::SendTxAddOutput, acceptor_node.node.get_our_node_id()); + assert!(tx_add_output2_msg.script.is_p2wsh()); + assert_eq!(tx_add_output2_msg.sats, post_splice_channel_value); + + 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 implemented + // let _res = initiator_node.node.handle_tx_complete(acceptor_node.node.get_our_node_id(), &tx_complete_msg); + + // TODO(splicing): Continue with commitment flow, new tx confirmation - // TODO(splicing): continue with splice transaction negotiation // === Close channel, cooperatively initiator_node.node.close_channel(&channel_id2, &acceptor_node.node.get_our_node_id()).unwrap(); diff --git a/lightning/src/ln/interactivetxs.rs b/lightning/src/ln/interactivetxs.rs index accd2ad4c53..9c95222e39b 100644 --- a/lightning/src/ln/interactivetxs.rs +++ b/lightning/src/ln/interactivetxs.rs @@ -1186,7 +1186,7 @@ pub(super) enum OutputOwned { } impl OutputOwned { - pub fn tx_out(&self) -> &TxOut { + pub(super) fn tx_out(&self) -> &TxOut { match self { OutputOwned::Single(tx_out) | OutputOwned::SharedControlFullyOwned(tx_out) => tx_out, OutputOwned::Shared(output) => &output.tx_out,