diff --git a/lightning/src/events/mod.rs b/lightning/src/events/mod.rs index a0f26bfbac0..b2cd22abe56 100644 --- a/lightning/src/events/mod.rs +++ b/lightning/src/events/mod.rs @@ -1463,6 +1463,58 @@ pub enum Event { /// /// [`ChannelHandshakeConfig::negotiate_anchors_zero_fee_htlc_tx`]: crate::util::config::ChannelHandshakeConfig::negotiate_anchors_zero_fee_htlc_tx BumpTransaction(BumpTransactionEvent), + + /// Indicates that a transaction constructed via interactive transaction construction for a + /// dual-funded (V2) channel is ready to be signed by the client. This event will only be triggered + /// if at least one input was contributed by the holder. + /// + /// The transaction contains all inputs provided by both parties when the channel was + /// created/accepted along with the channel's funding output and a change output if applicable. + /// + /// No part of the transaction should be changed before signing as the content of the transaction + /// has already been negotiated with the counterparty. + /// + /// Each signature MUST use the SIGHASH_ALL flag to avoid invalidation of initial commitment and + /// hence possible loss of funds. + /// + /// After signing, call [`ChannelManager::funding_transaction_signed`] with the (partially) signed + /// funding transaction. + /// + /// Generated in [`ChannelManager`] message handling. + /// + /// [`ChannelManager`]: crate::ln::channelmanager::ChannelManager + /// [`ChannelManager::funding_transaction_signed`]: crate::ln::channelmanager::ChannelManager::funding_transaction_signed + FundingTransactionReadyForSigning { + /// The channel_id of the V2 channel which you'll need to pass back into + /// [`ChannelManager::funding_transaction_signed`]. + /// + /// [`ChannelManager::funding_transaction_signed`]: crate::ln::channelmanager::ChannelManager::funding_transaction_signed + channel_id: ChannelId, + /// The counterparty's node_id, which you'll need to pass back into + /// [`ChannelManager::funding_transaction_signed`]. + /// + /// [`ChannelManager::funding_transaction_signed`]: crate::ln::channelmanager::ChannelManager::funding_transaction_signed + counterparty_node_id: PublicKey, + /* + /// The `user_channel_id` value passed in to [`ChannelManager::create_dual_funded_channel`] for outbound + /// channels, or to [`ChannelManager::accept_inbound_channel`] or [`ChannelManager::accept_inbound_channel_with_contribution`] + /// for inbound channels if [`UserConfig::manually_accept_inbound_channels`] config flag is set to true. + /// Otherwise `user_channel_id` will be randomized for an inbound channel. + /// This may be zero for objects serialized with LDK versions prior to 0.0.113. + /// + /// [`ChannelManager::create_dual_funded_channel`]: crate::ln::channelmanager::ChannelManager::create_dual_funded_channel + /// [`ChannelManager::accept_inbound_channel`]: crate::ln::channelmanager::ChannelManager::accept_inbound_channel + /// [`ChannelManager::accept_inbound_channel_with_contribution`]: crate::ln::channelmanager::ChannelManager::accept_inbound_channel_with_contribution + /// [`UserConfig::manually_accept_inbound_channels`]: crate::util::config::UserConfig::manually_accept_inbound_channels + user_channel_id: u128, + */ + /// The unsigned transaction to be signed and passed back to + /// [`ChannelManager::funding_transaction_signed`]. + /// + /// [`ChannelManager::funding_transaction_signed`]: crate::ln::channelmanager::ChannelManager::funding_transaction_signed + unsigned_transaction: Transaction, + }, + /// We received an onion message that is intended to be forwarded to a peer /// that is currently offline. This event will only be generated if the /// `OnionMessenger` was initialized with @@ -1768,7 +1820,7 @@ impl Writeable for Event { BumpTransactionEvent::HTLCResolution { .. } => {} } write_tlv_fields!(writer, {}); // Write a length field for forwards compat - } + }, &Event::ChannelReady { ref channel_id, ref user_channel_id, ref counterparty_node_id, ref channel_type } => { 29u8.write(writer)?; write_tlv_fields!(writer, { @@ -1828,6 +1880,13 @@ impl Writeable for Event { (8, former_temporary_channel_id, required), }); }, + &Event::FundingTransactionReadyForSigning { .. } => { + 45u8.write(writer)?; + // We never write out FundingTransactionReadyForSigning events as, upon disconnection, peers + // drop any V2-established channels which have not yet exchanged the initial `commitment_signed`. + // We only exhange the initial `commitment_signed` after the client calls + // `ChannelManager::funding_transaction_signed` and ALWAYS before we send a `tx_signatures` + }, // Note that, going forward, all new events must only write data inside of // `write_tlv_fields`. Versions 0.0.101+ will ignore odd-numbered events that write // data via `write_tlv_fields`. diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index 27ba267c431..2b6d15d33b7 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}; @@ -1191,12 +1193,15 @@ enum ChannelPhase where SP::Target: SignerProvider { UnfundedInboundV1(InboundV1Channel), UnfundedV2(PendingV2Channel), Funded(FundedChannel), + #[cfg(splicing)] + /// Used during splicing, channel is funded but a new funding is being renegotiated. + RefundingV2(SplicingChannel), } impl Channel where SP::Target: SignerProvider, ::EcdsaSigner: ChannelSigner, -{ +{ // Channel pub fn context(&self) -> &ChannelContext { match &self.phase { ChannelPhase::Undefined => unreachable!(), @@ -1204,6 +1209,8 @@ impl Channel where ChannelPhase::UnfundedOutboundV1(chan) => &chan.context, ChannelPhase::UnfundedInboundV1(chan) => &chan.context, ChannelPhase::UnfundedV2(chan) => &chan.context, + #[cfg(splicing)] + ChannelPhase::RefundingV2(chan) => chan.context(), } } @@ -1214,6 +1221,8 @@ impl Channel where ChannelPhase::UnfundedOutboundV1(chan) => &mut chan.context, ChannelPhase::UnfundedInboundV1(chan) => &mut chan.context, ChannelPhase::UnfundedV2(chan) => &mut chan.context, + #[cfg(splicing)] + ChannelPhase::RefundingV2(chan) => chan.context_mut(), } } @@ -1224,6 +1233,8 @@ impl Channel where ChannelPhase::UnfundedOutboundV1(chan) => &chan.funding, ChannelPhase::UnfundedInboundV1(chan) => &chan.funding, ChannelPhase::UnfundedV2(chan) => &chan.funding, + #[cfg(splicing)] + ChannelPhase::RefundingV2(chan) => chan.pre_funded.funding(), } } @@ -1235,6 +1246,8 @@ impl Channel where ChannelPhase::UnfundedOutboundV1(chan) => &mut chan.funding, ChannelPhase::UnfundedInboundV1(chan) => &mut chan.funding, ChannelPhase::UnfundedV2(chan) => &mut chan.funding, + #[cfg(splicing)] + ChannelPhase::RefundingV2(chan) => &mut chan.pre_funded.funding, } } @@ -1245,6 +1258,8 @@ impl Channel where ChannelPhase::UnfundedOutboundV1(chan) => (&chan.funding, &mut chan.context), ChannelPhase::UnfundedInboundV1(chan) => (&chan.funding, &mut chan.context), ChannelPhase::UnfundedV2(chan) => (&chan.funding, &mut chan.context), + #[cfg(splicing)] + ChannelPhase::RefundingV2(chan) => (&chan.pre_funded.funding, &mut chan.pre_funded.context), } } @@ -1255,26 +1270,35 @@ impl Channel where ChannelPhase::UnfundedOutboundV1(chan) => Some(&mut chan.unfunded_context), ChannelPhase::UnfundedInboundV1(chan) => Some(&mut chan.unfunded_context), ChannelPhase::UnfundedV2(chan) => Some(&mut chan.unfunded_context), + #[cfg(splicing)] + ChannelPhase::RefundingV2(_) => { debug_assert!(false); None }, // TODO check/change } } pub fn is_funded(&self) -> bool { - matches!(self.phase, ChannelPhase::Funded(_)) + match &self.phase { + ChannelPhase::Funded(_) => true, + #[cfg(splicing)] + ChannelPhase::RefundingV2(_) => true, + _ => false, + } } pub fn as_funded(&self) -> Option<&FundedChannel> { - if let ChannelPhase::Funded(channel) = &self.phase { - Some(channel) - } else { - None + match &self.phase { + ChannelPhase::Funded(channel) => Some(&channel), + #[cfg(splicing)] + ChannelPhase::RefundingV2(channel) => Some(&channel.pre_funded), + _ => None, } } pub fn as_funded_mut(&mut self) -> Option<&mut FundedChannel> { - if let ChannelPhase::Funded(channel) = &mut self.phase { - Some(channel) - } else { - None + match &mut self.phase { + ChannelPhase::Funded(channel) => Some(channel), + #[cfg(splicing)] + ChannelPhase::RefundingV2(channel) => Some(&mut channel.pre_funded), + _ => None, } } @@ -1323,6 +1347,15 @@ impl Channel where } } + #[cfg(splicing)] + fn as_splicing_mut(&mut self) -> Option<&mut SplicingChannel> { + if let ChannelPhase::RefundingV2(channel) = &mut self.phase { + Some(channel) + } else { + None + } + } + pub fn signer_maybe_unblocked( &mut self, chain_hash: ChainHash, logger: &L, ) -> Option where L::Target: Logger { @@ -1362,6 +1395,8 @@ impl Channel where }) }, ChannelPhase::UnfundedV2(_) => None, + #[cfg(splicing)] + ChannelPhase::RefundingV2(_chan) => panic!("TODO(splicing)"), // TODO change Some(chan.signer_maybe_unblocked(logger)), } } @@ -1381,6 +1416,8 @@ impl Channel where ChannelPhase::UnfundedOutboundV1(chan) => chan.is_resumable(), ChannelPhase::UnfundedInboundV1(_) => false, ChannelPhase::UnfundedV2(_) => false, + #[cfg(splicing)] + ChannelPhase::RefundingV2(_chan) => panic!("TODO(splicing)"), // TODO change chan.remove_uncommitted_htlcs_and_mark_paused(logger).is_ok(), // TODO check } } @@ -1418,6 +1455,8 @@ impl Channel where ReconnectionMsg::None } }, + #[cfg(splicing)] + ChannelPhase::RefundingV2(_chan) => panic!("TODO(splicing)"), // TODO change ReconnectionMsg::Reestablish(chan.get_channel_reestablish(logger)), } } @@ -1445,7 +1484,45 @@ impl Channel where Ok(None) } }, + #[cfg(splicing)] + ChannelPhase::RefundingV2(_) => Ok(None), + } + } + + /// TODO Simplify once unified using traits + pub fn tx_add_input(&mut self, msg: &msgs::TxAddInput) -> InteractiveTxMessageSendResult { + if let Some(unfunded) = self.as_unfunded_v2_mut() { + return unfunded.tx_add_input(msg); + } + #[cfg(splicing)] + if let Some(splicing) = self.as_splicing_mut() { + return splicing.tx_add_input(msg); } + panic!("Got tx_add_input in an invalid phase"); + } + + /// TODO Simplify once unified using traits + pub fn tx_add_output(&mut self, msg: &msgs::TxAddOutput) -> InteractiveTxMessageSendResult { + if let Some(unfunded) = self.as_unfunded_v2_mut() { + return unfunded.tx_add_output(msg); + } + #[cfg(splicing)] + if let Some(splicing) = self.as_splicing_mut() { + return splicing.tx_add_output(msg); + } + panic!("Got tx_add_output in an invalid phase"); + } + + /// TODO Simplify once unified using traits + pub fn tx_complete(&mut self, msg: &msgs::TxComplete) -> HandleTxCompleteResult { + if let Some(unfunded) = self.as_unfunded_v2_mut() { + return unfunded.tx_complete(msg); + } + #[cfg(splicing)] + if let Some(splicing) = self.as_splicing_mut() { + return splicing.tx_complete(msg); + } + panic!("Got tx_complete in an invalid phase"); } pub fn funding_signed( @@ -1480,16 +1557,123 @@ impl Channel where } pub fn funding_tx_constructed( - &mut self, signing_session: InteractiveTxSigningSession, logger: &L + &mut self, counterparty_node_id: &PublicKey, signing_session: InteractiveTxSigningSession, logger: &L ) -> Result<(msgs::CommitmentSigned, Option), ChannelError> 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.funding_tx_constructed(counterparty_node_id, signing_session, &&logger)?; + Ok((commitment_signed, event)) + } + #[cfg(splicing)] + ChannelPhase::RefundingV2(ref mut chan) => { + let logger = WithChannelContext::from(logger, &chan.context(), None); + let (signing_session, holder_commitment_point, commitment_signed, event) = + chan.funding_tx_constructed(counterparty_node_id, signing_session, &&logger)?; + let _res = self.phase_from_splice_to_funded(signing_session, holder_commitment_point)?; + Ok((commitment_signed, event)) + } + _ => { + Err(ChannelError::Warn("Got a tx_complete message with no interactive transaction construction expected or in-progress".to_owned())) + } + } + } + + /// Transition the channel from Funded to SplicingChannel. + /// Done in one go, as the existing ('pre') channel is put in the new channel (alongside a new one). + #[cfg(splicing)] + fn phase_from_funded_to_splice(&mut self, post_funding: FundingScope, dual_funding_context: DualFundingChannelContext, unfunded_context: UnfundedChannelContext, pending_splice_post: PendingSplicePost) -> Result<(), ChannelError> { + let phase = core::mem::replace(&mut self.phase, ChannelPhase::Undefined); + let result = if let ChannelPhase::Funded(prev_chan) = phase { + self.phase = ChannelPhase::RefundingV2(SplicingChannel::new(prev_chan, post_funding, dual_funding_context, unfunded_context, pending_splice_post)); + Ok(()) } else { - Err(ChannelError::Warn("Got a tx_complete message with no interactive transaction construction expected or in-progress".to_owned())) + // revert phase + self.phase = phase; + Err(ChannelError::Warn("Got a splice_init message with no funded channel".to_owned())) + }; + debug_assert!(!matches!(self.phase, ChannelPhase::Undefined)); + result + } + + /// Transition the channel from SplicingChannel to Funded, after negotiating new funded. + #[cfg(splicing)] + fn phase_from_splice_to_funded(&mut self, signing_session: InteractiveTxSigningSession, holder_commitment_point: HolderCommitmentPoint) -> Result<(), ChannelError> { + let phase = core::mem::replace(&mut self.phase, ChannelPhase::Undefined); + let result = if let ChannelPhase::RefundingV2(chan) = phase { + self.phase = ChannelPhase::Funded(FundedChannel { + funding: chan.post_funding, + context: chan.pre_funded.context, + interactive_tx_signing_session: Some(signing_session), + holder_commitment_point, + is_v2_established: true, + pending_splice: None, + // pending_splice_post: None, + }); + Ok(()) + } else { + // revert phase + self.phase = phase; + Err(ChannelError::Warn("Cannot transition away from splicing, not in splicing phase".to_owned())) + }; + debug_assert!(!matches!(self.phase, ChannelPhase::Undefined)); + result + } + + #[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 + { + // Explicit check for Funded, not as_funded; RefundingV2 not allowed + if let ChannelPhase::Funded(prev_chan) = &mut self.phase { + let (pending_splice_post, post_funding, dual_funding_context, unfunded_context) = + prev_chan.splice_init(msg, our_funding_contribution)?; + + let _res = self.phase_from_funded_to_splice(post_funding, dual_funding_context, unfunded_context, pending_splice_post)?; + + if let ChannelPhase::RefundingV2(chan) = &mut self.phase { + let splice_ack_msg = chan.splice_init(msg, our_funding_contribution, signer_provider, entropy_source, our_node_id, logger)?; + Ok(splice_ack_msg) + } else { + unreachable!("Must have been transitioned to RefundingV2 in above call if successful"); + } + } else { + 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 + { + // Explicit check for Funded, not as_funded; RefundingV2 not allowed + if let ChannelPhase::Funded(prev_chan) = &mut self.phase { + let (pending_splice_post, post_funding, dual_funding_context, unfunded_context, our_funding_contribution) = + prev_chan.splice_ack(msg)?; + + let _res = self.phase_from_funded_to_splice(post_funding, dual_funding_context, unfunded_context, pending_splice_post)?; + + if let ChannelPhase::RefundingV2(chan) = &mut self.phase { + let tx_msg_opt = chan.splice_ack(msg, our_funding_contribution, signer_provider, entropy_source, our_node_id, logger)?; + Ok(tx_msg_opt) + } else { + unreachable!("Must have been transitioned to RefundingV2 in above call if successful"); + } + } else { + Err(ChannelError::Warn("Channel is not funded, cannot be spliced".to_owned())) } } @@ -1525,6 +1709,8 @@ impl Channel where is_v2_established: true, #[cfg(splicing)] pending_splice: None, + // #[cfg(splicing)] + // pending_splice_post: None, }; let res = funded_channel.commitment_signed_initial_v2(msg, best_block, signer_provider, logger) .map(|monitor| (Some(monitor), None)) @@ -1548,53 +1734,503 @@ impl Channel where Err(ChannelError::close("Got a commitment_signed message for an unfunded V1 channel!".into())) } } - } -} + } +} + +impl From> for Channel +where + SP::Target: SignerProvider, + ::EcdsaSigner: ChannelSigner, +{ + fn from(channel: OutboundV1Channel) -> Self { + Channel { + phase: ChannelPhase::UnfundedOutboundV1(channel), + } + } +} + +impl From> for Channel +where + SP::Target: SignerProvider, + ::EcdsaSigner: ChannelSigner, +{ + fn from(channel: InboundV1Channel) -> Self { + Channel { + phase: ChannelPhase::UnfundedInboundV1(channel), + } + } +} + +impl From> for Channel +where + SP::Target: SignerProvider, + ::EcdsaSigner: ChannelSigner, +{ + fn from(channel: PendingV2Channel) -> Self { + Channel { + phase: ChannelPhase::UnfundedV2(channel), + } + } +} + +impl From> for Channel +where + SP::Target: SignerProvider, + ::EcdsaSigner: ChannelSigner, +{ + fn from(channel: FundedChannel) -> Self { + Channel { + phase: ChannelPhase::Funded(channel), + } + } +} + +/// Struct holding together various state during splicing negotiation +#[cfg(splicing)] +pub(super) struct SplicingChannel where SP::Target: SignerProvider { + /// TODO: replace it with its fields; done with trait? + pub pre_funded: FundedChannel, + + // Fields from PendingV2Channel follow, except ChannelContext, which is reused from above + pub post_funding: FundingScope, + pub unfunded_context: UnfundedChannelContext, + /// Used when negotiating the splice transaction + pub dual_funding_context: DualFundingChannelContext, + /// The current interactive transaction construction session under negotiation. + pub interactive_tx_constructor: Option, + + /// TODO remove + /// Info about an in-progress, pending splice, on the post-splice channel + pending_splice_post: PendingSplicePost, +} + +#[cfg(splicing)] +impl SplicingChannel where SP::Target: SignerProvider { + fn new(pre_funded: FundedChannel, post_funding: FundingScope, dual_funding_context: DualFundingChannelContext, unfunded_context: UnfundedChannelContext, pending_splice_post: PendingSplicePost) -> Self { + Self { + pre_funded, + post_funding, + dual_funding_context, + unfunded_context, + interactive_tx_constructor: None, + pending_splice_post, + } + } + + fn context(&self) -> &ChannelContext { + &self.pre_funded.context + } + + fn context_mut(&mut self) -> &mut ChannelContext { + &mut self.pre_funded.context + } + + /// Handle splice_init + pub 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 + { + // TODO(splicing): Store msg.funding_pubkey + + // Apply start of splice change in the state + self.splice_start(false, logger); + + let splice_ack_msg = self.pre_funded.get_splice_ack(our_funding_contribution); + + // 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.clone(), None, None) + .map_err(|err| ChannelError::Warn(format!("Failed to start interactive transaction construction, {:?}", err)))?; + + Ok(splice_ack_msg) + } + + /// 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 + { + 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.pre_funded.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.pre_funded.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 + + // We need the current funding tx as an extra input + let prev_funding_input = self.pending_splice_post.get_input_of_previous_funding()?; + + // 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 tx_msg_opt = self.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) + } + + /// Splice process starting; update state, log, etc. + 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 + // ); + log_info!(logger, "Splicing process started, new channel value {}, outgoing {}, channel_id {}", + self.post_funding.get_value_satoshis(), is_outgoing, self.context().channel_id); + } + + /// Copy of PendingV2Channel::begin_interactive_funding_tx_construction + /// Prepare and start interactive transaction negotiation. + /// `change_destination_opt` - Optional destination for optional change; if None, + /// default destination address is used. + /// If error occurs, it is caused by our side, not the counterparty. + #[allow(dead_code)] // TODO(dual_funding): Remove once contribution to V2 channels is enabled + 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.pre_funded.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); + + 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 + // the `calculate_change_output_value` call below + let mut funding_outputs = Vec::new(); + let mut expected_remote_shared_funding_output = None; + + let shared_funding_output = TxOut { + value: Amount::from_sat(self.post_funding.get_value_satoshis()), + script_pubkey: self.post_funding.get_funding_redeemscript().to_p2wsh(), + }; + + if self.pre_funded.funding.is_outbound() { + funding_outputs.push( + OutputOwned::Shared(SharedOwnedOutput::new( + shared_funding_output, self.dual_funding_context.our_funding_satoshis, + )) + ); + } else { + let TxOut { value, script_pubkey } = shared_funding_output; + expected_remote_shared_funding_output = Some((script_pubkey, value.to_sat())); + } + + // Optionally add change output + let change_script = if let Some(script) = change_destination_opt { + script + } else { + signer_provider.get_destination_script(self.pre_funded.context.channel_keys_id) + .map_err(|_err| AbortReason::InternalError("Error getting destination script"))? + }; + let change_value_opt = calculate_change_output_value( + self.pre_funded.funding.is_outbound(), self.dual_funding_context.our_funding_satoshis, + &funding_inputs, &funding_outputs, + self.dual_funding_context.funding_feerate_sat_per_1000_weight, + change_script.minimal_non_dust().to_sat(), + )?; + if let Some(change_value) = change_value_opt { + let mut change_output = TxOut { + value: Amount::from_sat(change_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(self.dual_funding_context.funding_feerate_sat_per_1000_weight, change_output_weight); + let change_value_decreased_with_fee = change_value.saturating_sub(change_output_fee); + // Check dust limit again + if change_value_decreased_with_fee > self.context().holder_dust_limit_satoshis { + change_output.value = Amount::from_sat(change_value_decreased_with_fee); + funding_outputs.push(OutputOwned::Single(change_output)); + } + } + + let constructor_args = InteractiveTxConstructorArgs { + entropy_source, + holder_node_id, + counterparty_node_id: self.context().counterparty_node_id, + channel_id: self.context().channel_id(), + feerate_sat_per_kw: self.dual_funding_context.funding_feerate_sat_per_1000_weight, + is_initiator: self.pre_funded.funding.is_outbound(), + funding_tx_locktime: self.dual_funding_context.funding_tx_locktime, + inputs_to_contribute: funding_inputs, + outputs_to_contribute: funding_outputs, + expected_remote_shared_funding_output, + }; + let mut tx_constructor = InteractiveTxConstructor::new(constructor_args)?; + let msg = tx_constructor.take_initiator_first_message(); + + self.interactive_tx_constructor = Some(tx_constructor); + + Ok(msg) + } + + /// Copied from PendingV2Channel + /// TODO: remove once PendingV2Channel is a trait + pub fn tx_add_input(&mut self, msg: &msgs::TxAddInput) -> InteractiveTxMessageSendResult { + InteractiveTxMessageSendResult(match &mut 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 { + channel_id: self.context().channel_id(), + data: b"No interactive transaction negotiation in progress".to_vec() + }), + }) + } + + /// Copied from PendingV2Channel + /// TODO: remove once PendingV2Channel is a trait + pub fn tx_add_output(&mut self, msg: &msgs::TxAddOutput)-> InteractiveTxMessageSendResult { + InteractiveTxMessageSendResult(match &mut 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 { + channel_id: self.context().channel_id(), + data: b"No interactive transaction negotiation in progress".to_vec() + }), + }) + } + + /// Copied from PendingV2Channel + /// TODO: remove once PendingV2Channel is a trait + 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, + None => { + let tx_abort = msgs::TxAbort { + channel_id: msg.channel_id, + data: b"No interactive transaction negotiation in progress".to_vec(), + }; + return HandleTxCompleteResult(Err(tx_abort)); + }, + }; + + let tx_complete = match tx_constructor.handle_tx_complete(msg) { + Ok(tx_complete) => tx_complete, + Err(reason) => { + return HandleTxCompleteResult(Err(reason.into_tx_abort_msg(msg.channel_id))) + } + }; + + // TODO: Should next_funding_txid be in FundingScope? + if let HandleTxCompleteValue::SendTxComplete(_, ref signing_session) = tx_complete { + self.context_mut().next_funding_txid = Some(signing_session.unsigned_tx.compute_txid()); + }; + + HandleTxCompleteResult(Ok(tx_complete)) + } + + /// Copied from PendingV2Channel::funding_tx_constructed + /// TODO avoid code duplication with traits + fn funding_tx_constructed( + &mut self, counterparty_node_id: &PublicKey, mut signing_session: InteractiveTxSigningSession, logger: &L + ) -> Result<(InteractiveTxSigningSession, HolderCommitmentPoint, 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 is_splice_pending = true; + + let mut output_index = None; + let expected_spk = self.post_funding.get_funding_redeemscript().to_p2wsh(); + for (idx, outp) in signing_session.unsigned_tx.outputs().enumerate() { + if outp.script_pubkey() == &expected_spk && outp.value() == self.post_funding.get_value_satoshis() { + if output_index.is_some() { + return Err(ChannelError::Close(( + "Multiple outputs matched the expected script and value".to_owned(), + ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(false) }, + ))); + } + output_index = Some(idx as u16); + } + } + let outpoint = if let Some(output_index) = output_index { + OutPoint { txid: signing_session.unsigned_tx.compute_txid(), index: output_index } + } else { + return Err(ChannelError::Close(( + "No output matched the funding script_pubkey".to_owned(), + ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(false) }, + ))); + }; + self.post_funding.channel_transaction_parameters.funding_outpoint = Some(outpoint); + // self.context_mut().holder_signer.as_mut_ecdsa().provide_channel_parameters(&self.post_funding.channel_transaction_parameters); + + // Assert for no commitment, unless splicing + if !is_splice_pending { + self.context().assert_no_commitment_advancement(transaction_number, "initial commitment_signed"); + } + let commitment_signed = self.pre_funded.context.get_initial_commitment_signed(&self.post_funding, is_splice_pending, logger); + let commitment_signed = match commitment_signed { + Ok(commitment_signed) => { + self.post_funding.funding_transaction = Some(signing_session.unsigned_tx.build_unsigned_tx()); + commitment_signed + }, + Err(err) => { + self.post_funding.channel_transaction_parameters.funding_outpoint = None; + return Err(ChannelError::Close((err.to_string(), ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(false) }))); + }, + }; + + #[cfg(not(splicing))] + let partly_signed_transaction =signing_session.unsigned_tx.clone().build_unsigned_tx(); + #[cfg(splicing)] + let mut partly_signed_transaction =signing_session.unsigned_tx.clone().build_unsigned_tx(); + #[cfg(splicing)] + if is_splice_pending { + // Add signature for prev funding input + // Note: here the transaction is used for signing, input&output order matters + let (partly_signed_tx, holder_signature) = self.prev_funding_tx_sign(&partly_signed_transaction, None, logger)?; + signing_session.shared_signature = Some(holder_signature); + partly_signed_transaction = partly_signed_tx; + } + + /* TODO remove + let funding_ready_for_sig_event = None; + if signing_session.local_inputs_count() == 0 { + debug_assert_eq!(our_funding_satoshis, 0); + if signing_session.provide_holder_witnesses(self.context().channel_id, Vec::new(), signing_session.shared_signature.clone()).is_err() { + debug_assert!( + false, + "Zero inputs were provided & zero witnesses were provided, but a count mismatch was somehow found", + ); + } + } else { + // TODO(dual_funding): Send event for signing if we've contributed funds. + // Inform the user that SIGHASH_ALL must be used for all signatures when contributing + // inputs/signatures. + // Also warn the user that we don't do anything to prevent the counterparty from + // providing non-standard witnesses which will prevent the funding transaction from + // confirming. This warning must appear in doc comments wherever the user is contributing + // funds, whether they are initiator or acceptor. + // + // The following warning can be used when the APIs allowing contributing inputs become available: + //
+ // WARNING: LDK makes no attempt to prevent the counterparty from using non-standard inputs which + // will prevent the funding transaction from being relayed on the bitcoin network and hence being + // confirmed. + //
+ } + */ -impl From> for Channel -where - SP::Target: SignerProvider, - ::EcdsaSigner: ChannelSigner, -{ - fn from(channel: OutboundV1Channel) -> Self { - Channel { - phase: ChannelPhase::UnfundedOutboundV1(channel), + let mut funding_ready_for_sig_event = None; + if our_funding_satoshis == 0 { + let _res = signing_session.provide_holder_witnesses(self.context().channel_id, Vec::new(), signing_session.shared_signature.clone()); + } else { + funding_ready_for_sig_event = Some(Event::FundingTransactionReadyForSigning { + channel_id: self.context().channel_id, + counterparty_node_id: *counterparty_node_id, + // user_channel_id: self.context().user_id, + // Note: here the transaction is used for signing, input&output order matters + unsigned_transaction: partly_signed_transaction, + }); } - } -} -impl From> for Channel -where - SP::Target: SignerProvider, - ::EcdsaSigner: ChannelSigner, -{ - fn from(channel: InboundV1Channel) -> Self { - Channel { - phase: ChannelPhase::UnfundedInboundV1(channel), + self.context_mut().channel_state = ChannelState::FundingNegotiated; + + // Clear the interactive transaction constructor + self.interactive_tx_constructor.take(); + + match self.unfunded_context.holder_commitment_point { + Some(holder_commitment_point) => { + Ok((signing_session, holder_commitment_point, commitment_signed, funding_ready_for_sig_event)) + }, + None => { + let err = ChannelError::close(format!( + "Expected to have holder commitment points available upon finishing interactive tx construction for channel {}", + self.context().channel_id(), + )); + Err(err) + }, } } -} -impl From> for Channel -where - SP::Target: SignerProvider, - ::EcdsaSigner: ChannelSigner, -{ - fn from(channel: PendingV2Channel) -> Self { - Channel { - phase: ChannelPhase::UnfundedV2(channel), + /// Copied from PendingV2Channel + /// TODO: remove once PendingV2Channel is a trait + /// Prepare the witness on the current funding tx input (used in the splicing case), + /// containing our holder signature, and optionally the counterparty signature, or its empty placholder. + #[cfg(splicing)] + fn prev_funding_tx_sign( + &self, transaction: &Transaction, counterparty_sig: Option, logger: &L + ) -> Result<(Transaction, Signature), ChannelError> where L::Target: Logger { + let (prev_funding_input_index, pre_channel_value) = + ( + self.pending_splice_post.find_input_of_previous_funding(&transaction)?, + self.pending_splice_post.pre_channel_value + ); + debug_assert!(prev_funding_input_index < transaction.input.len()); + + // #SPLICE-SIG + // the redeem script + let sig_order_ours_first = self.post_funding.get_holder_pubkeys().funding_pubkey.serialize() < self.post_funding.counterparty_funding_pubkey().serialize(); + log_info!(logger, "Pubkeys used for redeem script: {} {} {}", &self.post_funding.get_holder_pubkeys().funding_pubkey, &self.post_funding.counterparty_funding_pubkey(), sig_order_ours_first); + + let redeem_script = self.post_funding.get_funding_redeemscript(); + let holder_signature = self.prev_funding_tx_create_holder_sig(&transaction, prev_funding_input_index, pre_channel_value)?; // , &redeem_script)?; + let mut holder_sig = holder_signature.serialize_der().to_vec(); + holder_sig.push(EcdsaSighashType::All as u8); + // counterparty signature + let cp_sig = match counterparty_sig { + Some(s) => { + let mut sb = s.serialize_der().to_vec(); + sb.push(EcdsaSighashType::All as u8); + sb + }, + None => Vec::new(), // placeholder + }; + // prepare witness stack + let mut witness = Witness::new(); + witness.push(Vec::new()); + if sig_order_ours_first { + witness.push(holder_sig); + witness.push(cp_sig); + } else { + witness.push(cp_sig); + witness.push(holder_sig); } + witness.push(redeem_script.clone().into_bytes()); + + let mut tx = transaction.clone(); + tx.input[prev_funding_input_index as usize].witness = witness; + Ok((tx, holder_signature)) } -} -impl From> for Channel -where - SP::Target: SignerProvider, - ::EcdsaSigner: ChannelSigner, -{ - fn from(channel: FundedChannel) -> Self { - Channel { - phase: ChannelPhase::Funded(channel), + /// Copied from PendingV2Channel + /// TODO: remove once PendingV2Channel is a trait + /// Create signature for the current funding tx input, used in the splicing case. + #[cfg(splicing)] + fn prev_funding_tx_create_holder_sig(&self, transaction: &Transaction, input_index: usize, input_value: u64/*, _redeem_script: &ScriptBuf*/) -> Result { + // #SPLICE-SIG + match &self.context().holder_signer { + ChannelSignerType::Ecdsa(ecdsa) => { + ecdsa.sign_splicing_funding_input(&self.post_funding.channel_transaction_parameters, transaction, input_index, input_value, /*&redeem_script, */&self.context().secp_ctx) + .map_err(|_e| { + let msg = "Failed to sign the previous funding input in the new splicing funding tx"; + ChannelError::Close((msg.to_owned(), ClosureReason::ProcessingError { err: msg.to_owned() })) + }) + }, + // TODO (taproot|arik) + #[cfg(taproot)] + _ => todo!() } } } @@ -1734,8 +2370,93 @@ impl FundingScope { /// Info about a pending splice, used in the pre-splice channel #[cfg(splicing)] +#[derive(Clone)] struct PendingSplice { pub our_funding_contribution: i64, + pub funding_feerate_per_kw: u32, + pub locktime: u32, + /// The funding inputs that we plan to contributing to the splice. + pub our_funding_inputs: Vec<(TxIn, TransactionU16LenLimited)>, +} + +#[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)) + } +} + +/// Info about a pending splice, used in the post-splice channel +#[cfg(splicing)] +#[derive(Clone)] +struct PendingSplicePost { + pub pre_channel_value: u64, + pub post_channel_value: u64, + + /// Save here the previous funding transaction + pub pre_funding_transaction: Option, + /// Save here the previous funding TXO + pub pre_funding_txo: Option, +} + +#[cfg(splicing)] +impl PendingSplicePost { + pub(crate) fn new( + pre_channel_value: u64, our_funding_contribution: i64, their_funding_contribution: i64, + pre_funding_transaction: Option, pre_funding_txo: Option, + ) -> Self { + let post_channel_value = PendingSplice::compute_post_value(pre_channel_value, our_funding_contribution, their_funding_contribution); + Self { + pre_channel_value, post_channel_value, + pre_funding_transaction, pre_funding_txo, + } + } + + /// Get a transaction input that is the previous funding transaction + fn get_input_of_previous_funding(&self) -> Result<(TxIn, TransactionU16LenLimited), ChannelError> { + if let Some(pre_funding_transaction) = &self.pre_funding_transaction { + if let Some(pre_funding_txo) = &self.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())) + } + } + + /// Within the given transaction, find the input that corresponds to the previous funding transaction + fn find_input_of_previous_funding(&self, tx: &Transaction) -> Result { + if let Some(pre_funding_txo) = &self.pre_funding_txo { + for idx in 0..tx.input.len() { + if tx.input[idx].previous_output == pre_funding_txo.into_bitcoin_outpoint() { + return Ok(idx); + } + } + // Not found + Err(ChannelError::Warn("Internal error: Previous funding transaction not found in the inputs of the new funding transaction".to_string())) + } else { + Err(ChannelError::Warn("Internal error: Missing previous funding transaction outpoint".to_string())) + } + } } /// Contains everything about the channel including state, and various flags. @@ -2232,6 +2953,7 @@ 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 { @@ -2241,7 +2963,9 @@ impl PendingV2Channel where SP::Target: SignerProvider { let mut funding_inputs = Vec::new(); mem::swap(&mut self.dual_funding_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 @@ -2384,13 +3108,14 @@ impl PendingV2Channel where SP::Target: SignerProvider { } pub fn funding_tx_constructed( - &mut self, mut signing_session: InteractiveTxSigningSession, logger: &L + &mut self, counterparty_node_id: &PublicKey, 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 is_splice_pending = self.is_splice_pending(); let mut output_index = None; let expected_spk = self.funding.get_funding_redeemscript().to_p2wsh(); @@ -2417,8 +3142,11 @@ impl PendingV2Channel where SP::Target: SignerProvider { }; self.funding.channel_transaction_parameters.funding_outpoint = Some(outpoint); - self.context.assert_no_commitment_advancement(transaction_number, "initial commitment_signed"); - let commitment_signed = self.context.get_initial_commitment_signed(&self.funding, logger); + // Assert for no commitment, unless splicing + if !is_splice_pending { + self.context.assert_no_commitment_advancement(transaction_number, "initial commitment_signed"); + } + let commitment_signed = self.context.get_initial_commitment_signed(&self.funding, is_splice_pending, logger); let commitment_signed = match commitment_signed { Ok(commitment_signed) => { self.funding.funding_transaction = Some(signing_session.unsigned_tx.build_unsigned_tx()); @@ -2430,6 +3158,20 @@ impl PendingV2Channel where SP::Target: SignerProvider { }, }; + #[cfg(not(splicing))] + let partly_signed_transaction =signing_session.unsigned_tx.clone().build_unsigned_tx(); + #[cfg(splicing)] + let mut partly_signed_transaction =signing_session.unsigned_tx.clone().build_unsigned_tx(); + #[cfg(splicing)] + if is_splice_pending { + // Add signature for prev funding input + // Note: here the transaction is used for signing, input&output order matters + let (partly_signed_tx, holder_signature) = self.prev_funding_tx_sign(&partly_signed_transaction, None, logger)?; + signing_session.shared_signature = Some(holder_signature); + partly_signed_transaction = partly_signed_tx; + } + + /* TODO remove let funding_ready_for_sig_event = if signing_session.local_inputs_count() == 0 { debug_assert_eq!(our_funding_satoshis, 0); if signing_session.provide_holder_witnesses(self.context.channel_id, Vec::new()).is_err() { @@ -2467,6 +3209,20 @@ impl PendingV2Channel where SP::Target: SignerProvider { ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(false) } ))); }; + */ + + let mut funding_ready_for_sig_event = None; + if our_funding_satoshis == 0 { + let _res = signing_session.provide_holder_witnesses(self.context.channel_id, Vec::new(), signing_session.shared_signature.clone()); + } else { + funding_ready_for_sig_event = Some(Event::FundingTransactionReadyForSigning { + channel_id: self.context.channel_id, + counterparty_node_id: *counterparty_node_id, + // user_channel_id: self.context.user_id, + // Note: here the transaction is used for signing, input&output order matters + unsigned_transaction: partly_signed_transaction, + }); + } self.context.channel_state = ChannelState::FundingNegotiated; @@ -2476,6 +3232,75 @@ impl PendingV2Channel where SP::Target: SignerProvider { Ok((commitment_signed, funding_ready_for_sig_event)) } + + /// Prepare the witness on the current funding tx input (used in the splicing case), + /// containing our holder signature, and optionally the counterparty signature, or its empty placholder. + #[cfg(splicing)] + fn prev_funding_tx_sign( + &self, transaction: &Transaction, counterparty_sig: Option, logger: &L + ) -> Result<(Transaction, Signature), ChannelError> where L::Target: Logger { + let (prev_funding_input_index, pre_channel_value) = if let Some(pending_splice) = &self.pending_splice_post { + ( + pending_splice.find_input_of_previous_funding(&transaction)?, + pending_splice.pre_channel_value + ) + } else { + return Err(ChannelError::Warn(format!("Cannot sign splice transaction, channel is not in active splice, channel_id {}", self.context.channel_id))) + }; + debug_assert!(prev_funding_input_index < transaction.input.len()); + + // #SPLICE-SIG + // the redeem script + let sig_order_ours_first = self.funding.get_holder_pubkeys().funding_pubkey.serialize() < self.funding.counterparty_funding_pubkey().serialize(); + log_info!(logger, "Pubkeys used for redeem script: {} {} {}", &self.funding.get_holder_pubkeys().funding_pubkey, &self.funding.counterparty_funding_pubkey(), sig_order_ours_first); + + let redeem_script = self.funding.get_funding_redeemscript(); + let holder_signature = self.prev_funding_tx_create_holder_sig(&transaction, prev_funding_input_index, pre_channel_value)?; // , &redeem_script)?; + let mut holder_sig = holder_signature.serialize_der().to_vec(); + holder_sig.push(EcdsaSighashType::All as u8); + // counterparty signature + let cp_sig = match counterparty_sig { + Some(s) => { + let mut sb = s.serialize_der().to_vec(); + sb.push(EcdsaSighashType::All as u8); + sb + }, + None => Vec::new(), // placeholder + }; + // prepare witness stack + let mut witness = Witness::new(); + witness.push(Vec::new()); + if sig_order_ours_first { + witness.push(holder_sig); + witness.push(cp_sig); + } else { + witness.push(cp_sig); + witness.push(holder_sig); + } + witness.push(redeem_script.clone().into_bytes()); + + let mut tx = transaction.clone(); + tx.input[prev_funding_input_index as usize].witness = witness; + Ok((tx, holder_signature)) + } + + /// Create signature for the current funding tx input, used in the splicing case. + #[cfg(splicing)] + fn prev_funding_tx_create_holder_sig(&self, transaction: &Transaction, input_index: usize, input_value: u64/*, _redeem_script: &ScriptBuf*/) -> Result { + // #SPLICE-SIG + match &self.context.holder_signer { + ChannelSignerType::Ecdsa(ecdsa) => { + ecdsa.sign_splicing_funding_input(&self.funding.channel_transaction_parameters, transaction, input_index, input_value, /*&redeem_script, */&self.context.secp_ctx) + .map_err(|_e| { + let msg = "Failed to sign the previous funding input in the new splicing funding tx"; + ChannelError::Close((msg.to_owned(), ClosureReason::ProcessingError { err: msg.to_owned() })) + }) + }, + // TODO (taproot|arik) + #[cfg(taproot)] + _ => todo!() + } + } } impl ChannelContext where SP::Target: SignerProvider { @@ -4728,21 +5553,24 @@ impl ChannelContext where SP::Target: SignerProvider { } fn get_initial_commitment_signed( - &mut self, funding: &FundingScope, logger: &L + &mut self, funding: &FundingScope, is_splice: bool, logger: &L ) -> Result where SP::Target: SignerProvider, L::Target: Logger { - if !matches!( - self.channel_state, ChannelState::NegotiatingFunding(flags) - if flags == (NegotiatingFundingFlags::OUR_INIT_SENT | NegotiatingFundingFlags::THEIR_INIT_SENT) - ) { - debug_assert!(false); - return Err(ChannelError::Close(("Tried to get an initial commitment_signed messsage at a time other than \ - immediately after initial handshake completion (or tried to get funding_created twice)".to_string(), - ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(true) } - ))); + // TODO reset state?, put back check always + if !is_splice { + if !matches!( + self.channel_state, ChannelState::NegotiatingFunding(flags) + if flags == (NegotiatingFundingFlags::OUR_INIT_SENT | NegotiatingFundingFlags::THEIR_INIT_SENT) + ) { + debug_assert!(false); + return Err(ChannelError::Close(("Tried to get an initial commitment_signed messsage at a time other than \ + immediately after initial handshake completion (or tried to get funding_created twice)".to_string(), + ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(true) } + ))); + } } let signature = match self.get_initial_counterparty_commitment_signature(funding, logger) { @@ -4954,6 +5782,9 @@ pub(super) struct FundedChannel where SP::Target: SignerProvider { /// Info about an in-progress, pending splice (if any), on the pre-splice channel #[cfg(splicing)] pending_splice: Option, + // /// Info about an in-progress, pending splice (if any), on the post-splice channel + // #[cfg(splicing)] + // pending_splice_post: Option, } #[cfg(any(test, fuzzing))] @@ -8531,9 +9362,18 @@ 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)); + } self.pending_splice = Some(PendingSplice { our_funding_contribution: our_funding_contribution_satoshis, + funding_feerate_per_kw, + locktime, + our_funding_inputs: funding_inputs, }); let msg = self.get_splice_init(our_funding_contribution_satoshis, funding_feerate_per_kw, locktime); @@ -8558,9 +9398,9 @@ 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; @@ -8592,11 +9432,81 @@ 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`. - // TODO(splicing): Store msg.funding_pubkey - // TODO(splicing): Apply start of splice (splice_start) + 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(()) + } + + /// See also [`splice_init_checks`] + #[cfg(splicing)] + fn splice_init( + &mut self, msg: &msgs::SpliceInit, our_funding_contribution: i64, + ) -> Result<(PendingSplicePost, FundingScope, DualFundingChannelContext, UnfundedChannelContext), ChannelError> + { + let _res = self.splice_init_checks(msg)?; + + let pre_channel_value = self.funding.get_value_satoshis(); + + // Save the current funding transaction + let pre_funding_transaction = self.funding.funding_transaction.clone(); + let pre_funding_txo = self.funding.get_funding_txo().clone(); + + let pending_splice_post = PendingSplicePost::new( + pre_channel_value, + our_funding_contribution, + msg.funding_contribution_satoshis, + pre_funding_transaction, pre_funding_txo, + ); + let post_channel_value = pending_splice_post.post_channel_value; + + let (our_funding_satoshis, their_funding_satoshis) = calculate_funding_values( + pre_channel_value, + our_funding_contribution, + msg.funding_contribution_satoshis, + false, // is_outbound + )?; + + 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; + let post_funding = 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: self.funding.counterparty_selected_channel_reserve_satoshis, // TODO check + holder_selected_channel_reserve_satoshis: self.funding.holder_selected_channel_reserve_satoshis, // TODO check + #[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), + }; + + let dual_funding_context = DualFundingChannelContext { + 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(), + }; + let unfunded_context = UnfundedChannelContext { + unfunded_channel_age_ticks: 0, + holder_commitment_point: HolderCommitmentPoint::new(&self.context.holder_signer, &self.context.secp_ctx), + }; + + Ok((pending_splice_post, post_funding, dual_funding_context, unfunded_context)) + } + /// 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 { @@ -8606,20 +9516,87 @@ impl FundedChannel where require_confirmed_inputs: None, }; // TODO(splicing): start interactive funding negotiation - Ok(splice_ack_msg) + splice_ack_msg } - /// Handle splice_ack + /// Checks during handling splice_ack #[cfg(splicing)] - pub fn splice_ack(&mut self, _msg: &msgs::SpliceAck) -> Result<(), ChannelError> { + fn splice_ack_checks(&mut self) -> Result { // check if splice is pending - if self.pending_splice.is_none() { - return Err(ChannelError::Warn(format!("Channel is not in pending splice"))); - }; + if let Some(pending_splice) = &self.pending_splice { + Ok(pending_splice.clone()) + } else { + Err(ChannelError::Warn(format!("Channel is not in pending splice"))) + } + } + + /// See also [`splice_ack_checks`] + #[cfg(splicing)] + fn splice_ack( + &mut self, msg: &msgs::SpliceAck, + ) -> Result<(PendingSplicePost, FundingScope, DualFundingChannelContext, UnfundedChannelContext, i64), ChannelError> + { + let pending_splice = self.splice_ack_checks()?; + + 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); + + // Save the current funding transaction + let pre_funding_transaction = self.funding.funding_transaction.clone(); + let pre_funding_txo = self.funding.get_funding_txo().clone(); + + let pending_splice_post = PendingSplicePost::new( + pre_channel_value, + our_funding_contribution, + their_funding_contribution_satoshis, + pre_funding_transaction, pre_funding_txo, + ); + + let (our_funding_satoshis, their_funding_satoshis) = calculate_funding_values( + pre_channel_value, + our_funding_contribution, + their_funding_contribution_satoshis, + true, // is_outbound + )?; + + 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; + let post_funding = 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: self.funding.counterparty_selected_channel_reserve_satoshis, // TODO check + holder_selected_channel_reserve_satoshis: self.funding.holder_selected_channel_reserve_satoshis, // TODO check + #[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), + }; + + let dual_funding_context = DualFundingChannelContext { + our_funding_satoshis, + their_funding_satoshis: Some(their_funding_satoshis), + funding_tx_locktime: LockTime::from_consensus(pending_splice.locktime), + funding_feerate_sat_per_1000_weight: pending_splice.funding_feerate_per_kw, + our_funding_inputs: pending_splice.our_funding_inputs.clone(), + }; + let unfunded_context = UnfundedChannelContext { + unfunded_channel_age_ticks: 0, + holder_commitment_point: HolderCommitmentPoint::new(&self.context.holder_signer, &self.context.secp_ctx), + }; + + Ok((pending_splice_post, post_funding, dual_funding_context, unfunded_context, pending_splice.our_funding_contribution)) } // Send stuff to our remote peers: @@ -9554,6 +10531,8 @@ impl OutboundV1Channel where SP::Target: SignerProvider { holder_commitment_point, #[cfg(splicing)] pending_splice: None, + // #[cfg(splicing)] + // pending_splice_post: None, }; let need_channel_ready = channel.check_get_channel_ready(0, logger).is_some() @@ -9830,6 +10809,8 @@ impl InboundV1Channel where SP::Target: SignerProvider { holder_commitment_point, #[cfg(splicing)] pending_splice: None, + // #[cfg(splicing)] + // pending_splice_post: None, }; let need_channel_ready = channel.check_get_channel_ready(0, logger).is_some() || channel.context.signer_pending_channel_ready; @@ -9858,6 +10839,29 @@ 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, @@ -9868,6 +10872,9 @@ pub(super) struct PendingV2Channel where SP::Target: SignerProvider { pub interactive_tx_constructor: Option, /// The signing session created after `tx_complete` handling pub interactive_tx_signing_session: Option, + /// Info about an in-progress, pending splice (if any), on the post-splice channel + #[cfg(splicing)] + pending_splice_post: Option, } impl PendingV2Channel where SP::Target: SignerProvider { @@ -9937,6 +10944,8 @@ impl PendingV2Channel where SP::Target: SignerProvider { dual_funding_context, interactive_tx_constructor: None, interactive_tx_signing_session: None, + #[cfg(splicing)] + pending_splice_post: None, }; Ok(chan) } @@ -10111,6 +11120,8 @@ impl PendingV2Channel where SP::Target: SignerProvider { interactive_tx_constructor, interactive_tx_signing_session: None, unfunded_context, + #[cfg(splicing)] + pending_splice_post: None, }) } @@ -10187,6 +11198,15 @@ impl PendingV2Channel where SP::Target: SignerProvider { pub fn get_accept_channel_v2_message(&self) -> msgs::AcceptChannelV2 { self.generate_accept_channel_v2_message() } + + /// Check is a splice is currently in progress + /// Can be called regardless of `splicing` configuration. TODO: remove this note once `cfg(splicing)` is being removed + fn is_splice_pending(&self) -> bool { + #[cfg(splicing)] + return self.pending_splice_post.is_some(); + #[cfg(not(splicing))] + false + } } // Unfunded channel utilities @@ -11267,6 +12287,8 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, &'c Channel holder_commitment_point, #[cfg(splicing)] pending_splice: None, + // #[cfg(splicing)] + // pending_splice_post: None, }) } } @@ -13206,4 +14228,69 @@ mod tests { ); } } + + #[cfg(all(test, 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(all(test, 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 53fc3e6dfde..81ad565ced7 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -5505,6 +5505,18 @@ where result } + /// Handles a signed funding transaction generated by interactive transaction construction and + /// provided by the client. + /// + /// Do NOT broadcast the funding transaction yourself. When we have safely received our + /// counterparty's signature(s) the funding transaction will automatically be broadcast via the + /// [`BroadcasterInterface`] provided when this `ChannelManager` was constructed. + pub fn funding_transaction_signed(&self, _channel_id: &ChannelId, _counterparty_node_id: &PublicKey, + _transaction: Transaction) -> Result<(), APIError> { + // TODO(splicing) TODO(dual_funding) + Ok(()) + } + /// Atomically applies partial updates to the [`ChannelConfig`] of the given channels. /// /// Once the updates are applied, each eligible channel (advertised with a known short channel @@ -8445,23 +8457,13 @@ 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)) }) } @@ -8500,22 +8502,16 @@ 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(), - ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(false) }, - ))), chan_entry) - }; + let (msg_send_event_opt, signing_session_opt) = + chan_entry.get_mut().tx_complete(msg) + .into_msg_send_event_or_signing_session(counterparty_node_id); if let Some(msg_send_event) = msg_send_event_opt { peer_state.pending_msg_events.push(msg_send_event); }; if let Some(signing_session) = signing_session_opt { let (commitment_signed, funding_ready_for_sig_event_opt) = chan_entry .get_mut() - .funding_tx_constructed(signing_session, &self.logger) + .funding_tx_constructed(&counterparty_node_id, signing_session, &self.logger) .map_err(|err| MsgHandleErrInternal::send_err_msg_no_close(format!("{}", err), msg.channel_id))?; if let Some(funding_ready_for_sig_event) = funding_ready_for_sig_event_opt { let mut pending_events = self.pending_events.lock().unwrap(); @@ -9500,6 +9496,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!( @@ -9507,23 +9506,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(()) } @@ -9541,26 +9535,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 (checks, phase change, state change) + 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/functional_test_utils.rs b/lightning/src/ln/functional_test_utils.rs index f71258f358e..f46c639f33c 100644 --- a/lightning/src/ln/functional_test_utils.rs +++ b/lightning/src/ln/functional_test_utils.rs @@ -844,7 +844,7 @@ pub fn get_err_msg(node: &Node, recipient: &PublicKey) -> msgs::ErrorMessage { assert_eq!(node_id, recipient); msg.as_ref().unwrap().clone() }, - _ => panic!("Unexpected event"), + _ => panic!("Unexpected event {:?}", events[0]), } } diff --git a/lightning/src/ln/interactivetxs.rs b/lightning/src/ln/interactivetxs.rs index 28dbd3d1245..0786d9875f7 100644 --- a/lightning/src/ln/interactivetxs.rs +++ b/lightning/src/ln/interactivetxs.rs @@ -15,6 +15,7 @@ use bitcoin::amount::Amount; use bitcoin::consensus::Encodable; use bitcoin::constants::WITNESS_SCALE_FACTOR; use bitcoin::policy::MAX_STANDARD_TX_WEIGHT; +use bitcoin::secp256k1::ecdsa::Signature; use bitcoin::secp256k1::PublicKey; use bitcoin::transaction::Version; use bitcoin::{OutPoint, ScriptBuf, Sequence, Transaction, TxIn, TxOut, Txid, Weight, Witness}; @@ -314,6 +315,9 @@ pub(crate) struct InteractiveTxSigningSession { received_commitment_signed: bool, holder_tx_signatures: Option, counterparty_sent_tx_signatures: bool, + /// Signature for optional shared input -- in case of splicing the previous funding transaction. + /// Note: field included regardless of splicing feature. + pub(crate) shared_signature: Option, } impl InteractiveTxSigningSession { @@ -378,6 +382,7 @@ impl InteractiveTxSigningSession { /// unsigned transaction. pub fn provide_holder_witnesses( &mut self, channel_id: ChannelId, witnesses: Vec, + shared_input_signature: Option, ) -> Result<(), ()> { if self.local_inputs_count() != witnesses.len() { return Err(()); @@ -388,7 +393,7 @@ impl InteractiveTxSigningSession { channel_id, tx_hash: self.unsigned_tx.compute_txid(), witnesses: witnesses.into_iter().collect(), - shared_input_signature: None, + shared_input_signature, }); Ok(()) @@ -1011,6 +1016,7 @@ macro_rules! define_state_transitions { received_commitment_signed: false, holder_tx_signatures: None, counterparty_sent_tx_signatures: false, + shared_signature: None, }; Ok(NegotiationComplete(signing_session)) } diff --git a/lightning/src/ln/splicing_tests.rs b/lightning/src/ln/splicing_tests.rs index 33f5a500789..7726a9afcca 100644 --- a/lightning/src/ln/splicing_tests.rs +++ b/lightning/src/ln/splicing_tests.rs @@ -7,9 +7,11 @@ // You may not use this file except in accordance with one or both of these // licenses. +use crate::events::Event; use crate::ln::functional_test_utils::*; use crate::ln::msgs::{BaseMessageHandler, ChannelMessageHandler, MessageSendEvent}; use crate::util::errors::APIError; +use bitcoin::Witness; /// Splicing test, simple splice-in flow. Starts with opening a V1 channel first. /// Builds on test_channel_open_simple() @@ -45,13 +47,21 @@ fn test_v1_splice_in() { "03c21e841cbc0b48197d060c71e116c185fa0ac281b7d0aa5924f535154437ca3b"; let expected_acceptor_funding_key = "039481c28b904cbe12681e79937373fc76245c1b29871028ae60ba3152162c319b"; + let expect_inputs_in_reverse = true; + let expected_pre_funding_txid = + "4f128bedf1a15baf465ab1bfd6e97c8f82628f4156bf86eb1cbc132cda6733ae"; // ==== 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 @@ -104,7 +114,7 @@ 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, 0); + assert_eq!(channel.outbound_capacity_msat, exp_balance2); assert!(channel.funding_txo.is_some()); assert!(channel.confirmations.unwrap() > 0); } @@ -121,39 +131,190 @@ 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 + ); - // TODO(splicing): continue with splice transaction negotiation + 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() + ); - // === 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!( + 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::SendShutdown, + MessageSendEvent::SendTxAddInput, acceptor_node.node.get_our_node_id() ); - acceptor_node + 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_shutdown(initiator_node.node.get_our_node_id(), &node0_shutdown_message); - let nodes_1_shutdown = get_event_msg!( + .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::SendShutdown, + MessageSendEvent::SendTxComplete, initiator_node.node.get_our_node_id() ); - initiator_node.node.handle_shutdown(acceptor_node.node.get_our_node_id(), &nodes_1_shutdown); - let _ = get_event_msg!( + + 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::SendClosingSigned, + 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() + ); + + 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_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() + ); + + // The last tx_complete + let _res = initiator_node + .node + .handle_tx_complete(acceptor_node.node.get_our_node_id(), &tx_complete_msg); + + let msg_events = initiator_node.node.get_and_clear_pending_msg_events(); + assert_eq!(msg_events.len(), 2); + let _tx_complete_msg = match msg_events[0] { + MessageSendEvent::SendTxComplete { ref node_id, ref msg } => { + assert_eq!(*node_id, acceptor_node.node.get_our_node_id()); + (*msg).clone() + }, + _ => panic!("Unexpected event"), + }; + let _msg_commitment_signed_from_0 = match msg_events[1] { + MessageSendEvent::UpdateHTLCs { ref node_id, ref updates } => { + assert_eq!(*node_id, acceptor_node.node.get_our_node_id()); + updates.commitment_signed.clone() + }, + _ => panic!("Unexpected event"), + }; + // panic!("LOG"); + let (input_idx_prev_fund, input_idx_second_input) = + if expect_inputs_in_reverse { (0, 1) } else { (1, 0) }; + if let Event::FundingTransactionReadyForSigning { + channel_id, + counterparty_node_id, + mut unsigned_transaction, + .. + } = get_event!(initiator_node, Event::FundingTransactionReadyForSigning) + { + assert_eq!(channel_id.to_string(), expected_funded_channel_id); + assert_eq!(counterparty_node_id, acceptor_node.node.get_our_node_id()); + assert_eq!(unsigned_transaction.input.len(), 2); + // Note: input order may vary (based on SerialId) + // This is the previous funding tx input, already signed (partially) + assert_eq!( + unsigned_transaction.input[input_idx_prev_fund].previous_output.txid.to_string(), + expected_pre_funding_txid + ); + assert_eq!(unsigned_transaction.input[input_idx_prev_fund].witness.len(), 4); + // This is the extra input, not yet signed + assert_eq!(unsigned_transaction.input[input_idx_second_input].witness.len(), 0); + + // Placeholder for signature on the contributed input + let mut witness1 = Witness::new(); + witness1.push([7; 72]); + unsigned_transaction.input[input_idx_second_input].witness = witness1; + + let _res = initiator_node + .node + .funding_transaction_signed(&channel_id, &counterparty_node_id, unsigned_transaction) + .unwrap(); + } else { + panic!(); + } + + // TODO(splicing): Continue with commitment flow, new tx confirmation + + // Note: Normal Close does not work, as channel is not yet Funded (again). TODO + // // === 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!( + // initiator_node, + // MessageSendEvent::SendShutdown, + // acceptor_node.node.get_our_node_id() + // ); + // acceptor_node + // .node + // .handle_shutdown(initiator_node.node.get_our_node_id(), &node0_shutdown_message); + // let nodes_1_shutdown = get_event_msg!( + // acceptor_node, + // MessageSendEvent::SendShutdown, + // initiator_node.node.get_our_node_id() + // ); + // initiator_node.node.handle_shutdown(acceptor_node.node.get_our_node_id(), &nodes_1_shutdown); + // let _ = get_event_msg!( + // initiator_node, + // MessageSendEvent::SendClosingSigned, + // acceptor_node.node.get_our_node_id() + // ); } #[test]