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 d6fbd162388..a676e4c001a 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -10,7 +10,7 @@ use bitcoin::amount::Amount; use bitcoin::constants::ChainHash; use bitcoin::script::{Script, ScriptBuf, Builder, WScriptHash}; -use bitcoin::transaction::{Transaction, TxIn}; +use bitcoin::transaction::{Transaction, TxIn, TxOut}; use bitcoin::sighash::EcdsaSighashType; use bitcoin::consensus::encode; use bitcoin::absolute::LockTime; @@ -24,15 +24,17 @@ use bitcoin::hash_types::{Txid, BlockHash}; use bitcoin::secp256k1::constants::PUBLIC_KEY_SIZE; use bitcoin::secp256k1::{PublicKey,SecretKey}; use bitcoin::secp256k1::{Secp256k1,ecdsa::Signature}; -use bitcoin::{secp256k1, sighash}; +use bitcoin::{secp256k1, sighash, Witness}; +#[cfg(splicing)] +use bitcoin::Sequence; use crate::ln::types::ChannelId; use crate::types::payment::{PaymentPreimage, PaymentHash}; use crate::types::features::{ChannelTypeFeatures, InitFeatures}; use crate::ln::interactivetxs::{ - get_output_weight, HandleTxCompleteValue, HandleTxCompleteResult, InteractiveTxConstructor, - InteractiveTxConstructorArgs, InteractiveTxSigningSession, InteractiveTxMessageSendResult, - TX_COMMON_FIELDS_WEIGHT, + get_output_weight, calculate_change_output_value, HandleTxCompleteValue, HandleTxCompleteResult, InteractiveTxConstructor, + InteractiveTxConstructorArgs, InteractiveTxMessageSend, InteractiveTxSigningSession, InteractiveTxMessageSendResult, + OutputOwned, SharedOwnedOutput, TX_COMMON_FIELDS_WEIGHT, }; use crate::ln::msgs; use crate::ln::msgs::{ClosingSigned, ClosingSignedFeeRange, DecodeError, OnionErrorPacket}; @@ -115,6 +117,7 @@ enum FeeUpdateState { Outbound, } +#[derive(Clone)] enum InboundHTLCRemovalReason { FailRelay(msgs::OnionErrorPacket), FailMalformed(([u8; 32], u16)), @@ -149,6 +152,7 @@ impl_writeable_tlv_based_enum!(InboundHTLCResolution, }, ); +#[derive(Clone)] enum InboundHTLCState { /// Offered by remote, to be included in next local commitment tx. I.e., the remote sent an /// update_add_htlc message for this HTLC. @@ -223,6 +227,7 @@ impl From<&InboundHTLCState> for Option { } } +#[derive(Clone)] struct InboundHTLCOutput { htlc_id: u64, amount_msat: u64, @@ -231,7 +236,8 @@ struct InboundHTLCOutput { state: InboundHTLCState, } -#[cfg_attr(test, derive(Clone, Debug, PartialEq))] +#[derive(Clone)] +#[cfg_attr(test, derive(Debug, PartialEq))] enum OutboundHTLCState { /// Added by us and included in a commitment_signed (if we were AwaitingRemoteRevoke when we /// created it we would have put it in the holding cell instead). When they next revoke_and_ack @@ -313,7 +319,8 @@ impl<'a> Into> for &'a OutboundHTLCOutcome { } } -#[cfg_attr(test, derive(Clone, Debug, PartialEq))] +#[derive(Clone)] +#[cfg_attr(test, derive(Debug, PartialEq))] struct OutboundHTLCOutput { htlc_id: u64, amount_msat: u64, @@ -326,7 +333,8 @@ struct OutboundHTLCOutput { } /// See AwaitingRemoteRevoke ChannelState for more info -#[cfg_attr(test, derive(Clone, Debug, PartialEq))] +#[derive(Clone)] +#[cfg_attr(test, derive(Debug, PartialEq))] enum HTLCUpdateAwaitingACK { AddHTLC { // TODO: Time out if we're getting close to cltv_expiry // always outbound @@ -836,7 +844,7 @@ pub(super) enum ChannelUpdateStatus { } /// We track when we sent an `AnnouncementSignatures` to our peer in a few states, described here. -#[derive(PartialEq)] +#[derive(Clone, PartialEq)] pub enum AnnouncementSigsState { /// We have not sent our peer an `AnnouncementSignatures` yet, or our peer disconnected since /// we sent the last `AnnouncementSignatures`. @@ -1167,6 +1175,7 @@ pub(crate) const UNFUNDED_CHANNEL_AGE_LIMIT_TICKS: usize = 60; /// Number of blocks needed for an output from a coinbase transaction to be spendable. pub(crate) const COINBASE_MATURITY: u32 = 100; +#[derive(Clone)] struct PendingChannelMonitorUpdate { update: ChannelMonitorUpdate, } @@ -1189,12 +1198,15 @@ enum ChannelPhase where SP::Target: SignerProvider { UnfundedInboundV1(InboundV1Channel), UnfundedV2(PendingV2Channel), Funded(FundedChannel), + /// Used during splicing, channel is funded but a new funding is being renegotiated. + #[cfg(splicing)] + RefundingV2(SplicingChannel), } impl Channel where SP::Target: SignerProvider, ::EcdsaSigner: ChannelSigner, -{ +{ // impl Channel pub fn context(&self) -> &ChannelContext { match &self.phase { ChannelPhase::Undefined => unreachable!(), @@ -1202,6 +1214,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(), } } @@ -1212,6 +1226,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(), } } @@ -1222,6 +1238,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.funding(), } } @@ -1233,6 +1251,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) => chan.funding_mut(), } } @@ -1243,6 +1263,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.funding_and_context_mut(), } } @@ -1253,26 +1275,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 }, } } 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, } } @@ -1314,10 +1345,17 @@ impl Channel where } pub fn as_unfunded_v2_mut(&mut self) -> Option<&mut PendingV2Channel> { - if let ChannelPhase::UnfundedV2(channel) = &mut self.phase { - Some(channel) - } else { - None + match &mut self.phase { + ChannelPhase::UnfundedV2(channel) => Some(channel), + #[cfg(splicing)] + ChannelPhase::RefundingV2(channel) => { + if let Some(ref mut post_pending) = &mut channel.post_pending { + Some(post_pending) + } else { + None + } + } + _ => None, } } @@ -1360,6 +1398,8 @@ impl Channel where }) }, ChannelPhase::UnfundedV2(_) => None, + #[cfg(splicing)] + ChannelPhase::RefundingV2(chan) => Some(chan.pre_funded.signer_maybe_unblocked(logger)), } } @@ -1379,6 +1419,8 @@ impl Channel where ChannelPhase::UnfundedOutboundV1(chan) => chan.is_resumable(), ChannelPhase::UnfundedInboundV1(_) => false, ChannelPhase::UnfundedV2(_) => false, + #[cfg(splicing)] + ChannelPhase::RefundingV2(chan) => chan.pre_funded.remove_uncommitted_htlcs_and_mark_paused(logger).is_ok(), } } @@ -1416,6 +1458,9 @@ impl Channel where ReconnectionMsg::None } }, + #[cfg(splicing)] + ChannelPhase::RefundingV2(chan) => + ReconnectionMsg::Reestablish(chan.pre_funded.get_channel_reestablish(logger)), } } @@ -1443,6 +1488,8 @@ impl Channel where Ok(None) } }, + #[cfg(splicing)] + ChannelPhase::RefundingV2(_) => Ok(None), } } @@ -1478,16 +1525,47 @@ 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) - } else { - Err(ChannelError::Warn("Got a tx_complete message with no interactive transaction construction expected or in-progress".to_owned())) + match &mut self.phase { + ChannelPhase::UnfundedV2(ref mut chan) => { + let logger = WithChannelContext::from(logger, &chan.context, None); + chan.funding_tx_constructed(counterparty_node_id, signing_session, &&logger) + } + #[cfg(splicing)] + ChannelPhase::RefundingV2(chan) => { + let logger = WithChannelContext::from(logger, chan.context(), None); + chan.funding_tx_constructed(counterparty_node_id, signing_session, &&logger) + } + _ => { + Err(ChannelError::Warn("Got a tx_complete message with no interactive transaction construction expected or in-progress".to_owned())) + } + } + } + + pub fn funding_transaction_signed(&mut self, witnesses: Vec) -> Result, APIError> { + match &mut self.phase { + ChannelPhase::Funded(chan) => { + chan.funding_transaction_signed(witnesses) + } + #[cfg(splicing)] + ChannelPhase::RefundingV2(chan) => { + if let Some(ref mut post_funded) = &mut chan.post_funded { + post_funded.funding_transaction_signed(witnesses) + } else { + Err(APIError::APIMisuseError { + err: "No negotiation in progress, called in wrong state".to_owned() + }) + } + } + _ => { + return Err(APIError::APIMisuseError { + err: "Got a funding_transaction_signed call with no funding negotiation in-progress".to_owned() + }); + } } } @@ -1503,7 +1581,7 @@ impl Channel where L::Target: Logger { let phase = core::mem::replace(&mut self.phase, ChannelPhase::Undefined); - match phase { + let res = match phase { ChannelPhase::UnfundedV2(chan) => { let holder_commitment_point = match chan.unfunded_context.holder_commitment_point { Some(point) => point, @@ -1522,7 +1600,9 @@ impl Channel where holder_commitment_point, is_v2_established: true, #[cfg(splicing)] - pending_splice: None, + pending_splice_pre: 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)) @@ -1540,11 +1620,135 @@ impl Channel where self.phase = ChannelPhase::Funded(funded_channel); res }, + #[cfg(splicing)] + ChannelPhase::RefundingV2(chan) => { + if let Some(mut post_funded) = chan.post_funded { + let res = post_funded.commitment_signed_initial_v2(msg, best_block, signer_provider, logger) + .map(|monitor| (Some(monitor), None)) + // TODO: Change to `inspect_err` when MSRV is high enough. + .map_err(|err| { + // We always expect a `ChannelError` close. + debug_assert!(matches!(err, ChannelError::Close(_))); + err + }); + self.phase = ChannelPhase::Funded(post_funded); + res + } else { + self.phase = ChannelPhase::RefundingV2(chan); + Err(ChannelError::close("Got a commitment_signed message for an unfunded channel!".into())) + } + } _ => { self.phase = phase; debug_assert!(!matches!(self.phase, ChannelPhase::Undefined)); - Err(ChannelError::close("Got a commitment_signed message for an unfunded V1 channel!".into())) + Err(ChannelError::close("Got a commitment_signed message for an unfunded channel!".into())) + } + }; + debug_assert!(!matches!(self.phase, ChannelPhase::Undefined)); + res + } + + /// 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_to_splice(&mut self, post_chan: PendingV2Channel) -> 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_chan)); + Ok(()) + } else { + // 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 + } + + #[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 _res = prev_chan.splice_init_checks(msg)?; + + let post_chan = PendingV2Channel::new_spliced( + false, + prev_chan, + signer_provider, + // &msg.funding_pubkey, + our_funding_contribution, + msg.funding_contribution_satoshis, + Vec::new(), + LockTime::from_consensus(msg.locktime), + msg.funding_feerate_per_kw, + logger, + )?; + + let _res = self.phase_to_splice(post_chan)?; + + if let ChannelPhase::RefundingV2(chan) = &mut self.phase { + if let Some(ref mut post_pending) = &mut chan.post_pending { + let splice_ack_msg = post_pending.splice_init(msg, signer_provider, entropy_source, our_node_id, logger)?; + Ok(splice_ack_msg) + } else { + Err(ChannelError::Warn("Internal error: splicing channel is not negotiating after splice_init".to_owned())) + } + } 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 = prev_chan.splice_ack_checks()?; + + let post_chan = PendingV2Channel::new_spliced( + true, + prev_chan, + signer_provider, + // &msg.funding_pubkey, + pending_splice.our_funding_contribution, + msg.funding_contribution_satoshis, + pending_splice.our_funding_inputs.clone(), + LockTime::from_consensus(pending_splice.locktime), + pending_splice.funding_feerate_per_kw, + logger, + )?; + + let _res = self.phase_to_splice(post_chan)?; + + if let ChannelPhase::RefundingV2(chan) = &mut self.phase { + if let Some(post_pending) = &mut chan.post_pending { + let tx_msg_opt = post_pending.splice_ack(msg, pending_splice.our_funding_contribution, signer_provider, entropy_source, our_node_id, logger)?; + Ok(tx_msg_opt) + } else { + Err(ChannelError::Warn("Internal error: splicing channel is not negotiating after splice_ack".to_owned())) + } + } 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())) } } } @@ -1597,6 +1801,112 @@ where } } +/// Struct holding together various state dureing splicing negotiation +#[cfg(splicing)] +pub(super) struct SplicingChannel where SP::Target: SignerProvider { + pub pre_funded: FundedChannel, + pub post_pending: Option>, + pub post_funded: Option>, +} + +#[cfg(splicing)] +impl SplicingChannel where SP::Target: SignerProvider { + pub(super) fn new(pre_funded: FundedChannel, post_pending: PendingV2Channel) -> Self { + Self { + pre_funded, + post_pending: Some(post_pending), + post_funded: None, + } + } + + pub(super) fn context(&self) -> &ChannelContext { + // If post is funded, use that, otherwise use pre + if let Some(ref post_funded) = &self.post_funded { + &post_funded.context + } else { + &self.pre_funded.context + } + } + + pub(super) fn context_mut(&mut self) -> &mut ChannelContext { + // If post is funded, use that, otherwise use pre + if let Some(ref mut post_funded) = &mut self.post_funded { + &mut post_funded.context + } else { + &mut self.pre_funded.context + } + } + + pub(super) fn funding(&self) -> &FundingScope { + // If post is funded, use that, otherwise use pre + if let Some(ref post_funded) = &self.post_funded { + &post_funded.funding + } else { + &self.pre_funded.funding + } + } + + #[cfg(any(test, feature = "_externalize_tests"))] + pub(super) fn funding_mut(&mut self) -> &mut FundingScope { + // If post is funded, use that, otherwise use pre + if let Some(ref mut post_funded) = &mut self.post_funded { + &mut post_funded.funding + } else { + &mut self.pre_funded.funding + } + } + + pub(super) fn funding_and_context_mut(&mut self) -> (&FundingScope, &mut ChannelContext) { + // If post is funded, use that, otherwise use pre + if let Some(ref mut post_funded) = &mut self.post_funded { + (&mut post_funded.funding, &mut post_funded.context) + } else { + (&mut self.pre_funded.funding, &mut self.pre_funded.context) + } + } + + pub(super) fn funding_tx_constructed( + &mut self, counterparty_node_id: &PublicKey, signing_session: InteractiveTxSigningSession, logger: &L, + ) -> Result<(msgs::CommitmentSigned, Option), ChannelError> where L::Target: Logger { + if let Some(mut post_pending) = self.post_pending.take() { + let logger = WithChannelContext::from(logger, &post_pending.context, None); + let res = post_pending.funding_tx_constructed(counterparty_node_id, signing_session, &&logger)?; + + // Promote pending channel to Funded + let holder_commitment_point = match post_pending.unfunded_context.holder_commitment_point { + Some(point) => point, + None => { + let channel_id = self.context().channel_id(); + // TODO(dual_funding): Add async signing support. + return Err( ChannelError::close(format!( + "Expected to have holder commitment points available upon finishing interactive tx construction for channel {}", + channel_id, + ))); + } + }; + let funded_channel = FundedChannel { + funding: post_pending.funding, + context: post_pending.context, + interactive_tx_signing_session: post_pending.interactive_tx_signing_session, + holder_commitment_point, + is_v2_established: true, + #[cfg(splicing)] + pending_splice_pre: None, + #[cfg(splicing)] + pending_splice_post: post_pending.pending_splice_post, + }; + self.post_funded = Some(funded_channel); + self.post_pending = None; + + Ok(res) + } else { + Err(ChannelError::Warn( + "Got a tx_complete message with no interactive transaction construction expected or in-progress".to_owned() + )) + } + } +} + /// Contains all state common to unfunded inbound/outbound channels. pub(super) struct UnfundedChannelContext { /// A counter tracking how many ticks have elapsed since this unfunded channel was @@ -1727,8 +2037,98 @@ impl FundingScope { /// Info about a pending splice, used in the pre-splice channel #[cfg(splicing)] -struct PendingSplice { +#[derive(Clone)] +pub(super) struct PendingSplicePre { 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 PendingSplicePre { + #[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 { + pre_channel_value: u64, + // 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 = PendingSplicePre::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, + } + } + + fn pre_channel_value(&self) -> u64 { self.pre_channel_value } + + // fn post_channel_value(&self) -> u64 { self.post_channel_value } + + /// 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. @@ -2220,6 +2620,106 @@ impl InitialRemoteCommitmentReceiver for FundedChannel where } impl PendingV2Channel where SP::Target: SignerProvider { + #[allow(dead_code)] // TODO(dual_funding): Remove once contribution to V2 channels is enabled + pub fn begin_interactive_funding_tx_construction( + &mut self, signer_provider: &SP, entropy_source: &ES, holder_node_id: PublicKey, + prev_funding_input: Option<(TxIn, TransactionU16LenLimited)>, + ) -> Result, APIError> + where ES::Target: EntropySource + { + 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); + } + + let mut funding_inputs_prev_outputs: Vec<&TxOut> = Vec::with_capacity(funding_inputs.len()); + // Check that vouts exist for each TxIn in provided transactions. + for (idx, (txin, tx)) in funding_inputs.iter().enumerate() { + if let Some(output) = tx.as_transaction().output.get(txin.previous_output.vout as usize) { + funding_inputs_prev_outputs.push(output); + } else { + return Err(APIError::APIMisuseError { + err: format!("Transaction with txid {} does not have an output with vout of {} corresponding to TxIn at funding_inputs[{}]", + tx.as_transaction().compute_txid(), txin.previous_output.vout, idx) }); + } + } + + let total_input_satoshis: u64 = funding_inputs.iter().map( + |(txin, tx)| tx.as_transaction().output.get(txin.previous_output.vout as usize).map(|out| out.value.to_sat()).unwrap_or(0) + ).sum(); + if total_input_satoshis < self.dual_funding_context.our_funding_satoshis { + return Err(APIError::APIMisuseError { + err: format!("Total value of funding inputs must be at least funding amount. It was {} sats", + total_input_satoshis) }); + } + + // Add output for funding tx + let mut funding_outputs = Vec::new(); + let funding_output_value_satoshis = self.funding.get_value_satoshis(); + let funding_output_script_pubkey = self.funding.get_funding_redeemscript().to_p2wsh(); + let expected_remote_shared_funding_output = if self.funding.is_outbound() { + let tx_out = TxOut { + value: Amount::from_sat(funding_output_value_satoshis), + script_pubkey: funding_output_script_pubkey, + }; + funding_outputs.push( + if self.dual_funding_context.their_funding_satoshis.unwrap_or(0) == 0 { + OutputOwned::SharedControlFullyOwned(tx_out) + } else { + OutputOwned::Shared(SharedOwnedOutput::new( + tx_out, self.dual_funding_context.our_funding_satoshis + )) + } + ); + None + } else { + Some((funding_output_script_pubkey, funding_output_value_satoshis)) + }; + + // Optionally add change output + if let Some(change_value) = calculate_change_output_value( + self.funding.is_outbound(), self.dual_funding_context.our_funding_satoshis, + &funding_inputs_prev_outputs, &funding_outputs, + self.dual_funding_context.funding_feerate_sat_per_1000_weight, + self.context.holder_dust_limit_satoshis, + ) { + let change_script = signer_provider.get_destination_script(self.context.channel_keys_id).map_err( + |err| APIError::APIMisuseError { + err: format!("Failed to get change script as new destination script, {:?}", err), + })?; + 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); + change_output.value = Amount::from_sat(change_value.saturating_sub(change_output_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.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) + .map_err(|_| APIError::APIMisuseError { err: "Incorrect shared output provided".into() })?; + let msg = tx_constructor.take_initiator_first_message(); + + self.interactive_tx_constructor = Some(tx_constructor); + + Ok(msg) + } + pub fn tx_add_input(&mut self, msg: &msgs::TxAddInput) -> InteractiveTxMessageSendResult { InteractiveTxMessageSendResult(match &mut self.interactive_tx_constructor { Some(ref mut tx_constructor) => tx_constructor.handle_tx_add_input(msg).map_err( @@ -2291,14 +2791,16 @@ 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(); + // Find the funding output let mut output_index = None; let expected_spk = self.funding.get_funding_redeemscript().to_p2wsh(); for (idx, outp) in signing_session.unsigned_tx.outputs().enumerate() { @@ -2324,8 +2826,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()); @@ -2337,6 +2842,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() { @@ -2374,6 +2893,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; @@ -2383,6 +2916,76 @@ 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 { @@ -4656,21 +5259,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) { @@ -4705,6 +5311,209 @@ impl ChannelContext where SP::Target: SignerProvider { self.counterparty_cur_commitment_point = Some(counterparty_cur_commitment_point_override); self.get_initial_counterparty_commitment_signature(funding, logger) } + + /// Clone, each field, with the exception of the channel signer. + #[allow(unused)] + fn clone(&self, holder_signer: ::EcdsaSigner) -> Self { + Self { + // Use provided channel signer + holder_signer: ChannelSignerType::Ecdsa(holder_signer), + + config: self.config, + prev_config: self.prev_config, + inbound_handshake_limits_override: self.inbound_handshake_limits_override, + user_id: self.user_id, + channel_id: self.channel_id, + temporary_channel_id: self.temporary_channel_id, + channel_state: self.channel_state, + announcement_sigs_state: self.announcement_sigs_state.clone(), + secp_ctx: self.secp_ctx.clone(), + // channel_value_satoshis: self.channel_value_satoshis, + latest_monitor_update_id: self.latest_monitor_update_id, + shutdown_scriptpubkey: self.shutdown_scriptpubkey.clone(), + destination_script: self.destination_script.clone(), + cur_counterparty_commitment_transaction_number: self.cur_counterparty_commitment_transaction_number, + // value_to_self_msat: self.value_to_self_msat, + pending_inbound_htlcs: self.pending_inbound_htlcs.clone(), + pending_outbound_htlcs: self.pending_outbound_htlcs.clone(), + holding_cell_htlc_updates: self.holding_cell_htlc_updates.clone(), + resend_order: self.resend_order.clone(), + monitor_pending_channel_ready: self.monitor_pending_channel_ready, + monitor_pending_revoke_and_ack: self.monitor_pending_revoke_and_ack, + monitor_pending_commitment_signed: self.monitor_pending_commitment_signed, + monitor_pending_forwards: self.monitor_pending_forwards.clone(), + monitor_pending_failures: self.monitor_pending_failures.clone(), + monitor_pending_finalized_fulfills: self.monitor_pending_finalized_fulfills.clone(), + monitor_pending_update_adds: self.monitor_pending_update_adds.clone(), + monitor_pending_tx_signatures: self.monitor_pending_tx_signatures.clone(), + signer_pending_revoke_and_ack: self.signer_pending_revoke_and_ack, + signer_pending_commitment_update: self.signer_pending_commitment_update, + signer_pending_funding: self.signer_pending_funding, + signer_pending_closing: self.signer_pending_closing, + signer_pending_channel_ready: self.signer_pending_channel_ready, + pending_update_fee: self.pending_update_fee, + holding_cell_update_fee: self.holding_cell_update_fee, + next_holder_htlc_id: self.next_holder_htlc_id, + next_counterparty_htlc_id: self.next_counterparty_htlc_id, + feerate_per_kw: self.feerate_per_kw, + update_time_counter: self.update_time_counter, + // Create new mutex with copied values + // #[cfg(debug_assertions)] + // holder_max_commitment_tx_output: self.holder_max_commitment_tx_output.clone(), + // #[cfg(debug_assertions)] + // counterparty_max_commitment_tx_output: self.counterparty_max_commitment_tx_output.clone(), + last_sent_closing_fee: self.last_sent_closing_fee.clone(), + last_received_closing_sig: self.last_received_closing_sig, + target_closing_feerate_sats_per_kw: self.target_closing_feerate_sats_per_kw, + pending_counterparty_closing_signed: self.pending_counterparty_closing_signed.clone(), + closing_fee_limits: self.closing_fee_limits, + expecting_peer_commitment_signed: self.expecting_peer_commitment_signed, + funding_tx_confirmed_in: self.funding_tx_confirmed_in, + funding_tx_confirmation_height: self.funding_tx_confirmation_height, + short_channel_id: self.short_channel_id, + channel_creation_height: self.channel_creation_height, + counterparty_dust_limit_satoshis: self.counterparty_dust_limit_satoshis, + holder_dust_limit_satoshis: self.holder_dust_limit_satoshis, + counterparty_max_htlc_value_in_flight_msat: self.counterparty_max_htlc_value_in_flight_msat, + holder_max_htlc_value_in_flight_msat: self.holder_max_htlc_value_in_flight_msat, + // counterparty_selected_channel_reserve_satoshis: self.counterparty_selected_channel_reserve_satoshis, + // holder_selected_channel_reserve_satoshis: self.holder_selected_channel_reserve_satoshis, + counterparty_htlc_minimum_msat: self.counterparty_htlc_minimum_msat, + holder_htlc_minimum_msat: self.holder_htlc_minimum_msat, + counterparty_max_accepted_htlcs: self.counterparty_max_accepted_htlcs, + holder_max_accepted_htlcs: self.holder_max_accepted_htlcs, + minimum_depth: self.minimum_depth, + counterparty_forwarding_info: self.counterparty_forwarding_info.clone(), + // channel_transaction_parameters: self.channel_transaction_parameters.clone(), + // funding_transaction: self.funding_transaction.clone(), + is_manual_broadcast: self.is_manual_broadcast, + is_batch_funding: self.is_batch_funding, + counterparty_cur_commitment_point: self.counterparty_cur_commitment_point, + counterparty_prev_commitment_point: self.counterparty_prev_commitment_point, + counterparty_node_id: self.counterparty_node_id, + counterparty_shutdown_scriptpubkey: self.counterparty_shutdown_scriptpubkey.clone(), + commitment_secrets: self.commitment_secrets.clone(), + channel_update_status: self.channel_update_status, + closing_signed_in_flight: self.closing_signed_in_flight, + announcement_sigs: self.announcement_sigs, + // Create new mutex with copied values + // #[cfg(any(test, fuzzing))] + // next_local_commitment_tx_fee_info_cached: self.next_local_commitment_tx_fee_info_cached.clone(), + // #[cfg(any(test, fuzzing))] + // next_remote_commitment_tx_fee_info_cached: self.next_remote_commitment_tx_fee_info_cached.clone(), + workaround_lnd_bug_4006: self.workaround_lnd_bug_4006.clone(), + sent_message_awaiting_response: self.sent_message_awaiting_response, + channel_type: self.channel_type.clone(), + latest_inbound_scid_alias: self.latest_inbound_scid_alias, + outbound_scid_alias: self.outbound_scid_alias, + channel_pending_event_emitted: self.channel_pending_event_emitted, + funding_tx_broadcast_safe_event_emitted: self.funding_tx_broadcast_safe_event_emitted, + channel_ready_event_emitted: self.channel_ready_event_emitted, + local_initiated_shutdown: self.local_initiated_shutdown.clone(), + channel_keys_id: self.channel_keys_id, + blocked_monitor_updates: self.blocked_monitor_updates.clone(), + next_funding_txid: self.next_funding_txid.clone(), + is_holder_quiescence_initiator: self.is_holder_quiescence_initiator, + } + } + + /// Create channel context for spliced channel, by duplicating and updating the context. + /// relative_satoshis: The change in channel value (sats), + /// positive for increase (splice-in), negative for decrease (splice out). + /// delta_belongs_to_local: + /// The amount from the channel value change that belongs to the local (sats). + /// Its sign has to be the same as the sign of relative_satoshis, and its absolute value + /// less or equal (e.g. for +100 in the range of 0..100, for -100 in the range of -100..0). + #[cfg(splicing)] + fn new_for_splice( + pre_splice_context: &Self, + pre_splice_funding: &FundingScope, + is_outgoing: bool, + // counterparty_funding_pubkey: &PublicKey, + our_funding_contribution: i64, + their_funding_contribution: i64, + holder_signer: ::EcdsaSigner, + logger: &L, + ) -> Result<(ChannelContext, FundingScope), ChannelError> where L::Target: Logger + { + let pre_channel_value = pre_splice_funding.get_value_satoshis(); + let post_channel_value = PendingSplicePre::compute_post_value(pre_channel_value, our_funding_contribution, their_funding_contribution); + + // Compute our new balance + let old_to_self = pre_splice_funding.value_to_self_msat; + let delta_in_value_to_self = our_funding_contribution * 1000; + if delta_in_value_to_self < 0 && delta_in_value_to_self.abs() as u64 > old_to_self { + // Change would make our balance negative + return Err(ChannelError::Warn(format!("Cannot decrease channel value to requested amount, too low, {} {} {} {} {}", + pre_channel_value, post_channel_value, our_funding_contribution, their_funding_contribution, old_to_self))); + } + let value_to_self_msat = (old_to_self as i64).saturating_add(delta_in_value_to_self) as u64; + + let mut context = pre_splice_context.clone(holder_signer); + + // Reset funding + context.funding_tx_confirmed_in = None; + context.funding_tx_confirmation_height = 0; + // Reset state + context.channel_state = ChannelState::NegotiatingFunding( + if is_outgoing { NegotiatingFundingFlags::OUR_INIT_SENT } else { NegotiatingFundingFlags::OUR_INIT_SENT | NegotiatingFundingFlags::THEIR_INIT_SENT } + ); + context.next_funding_txid = None; + // Reset monitor update + context.latest_monitor_update_id = 0; + // Note on commitment transaction numbers and commitment points: + // we could step 'back' here (i.e. increase number by one, set cur to prev), but that does not work, + // because latest commitment point would be lost. + // Instead, we take the previous values in relevant cases when splicing is pending. + // We'll add our counterparty's `funding_satoshis` to these max commitment output assertions + // Clear these state flags, for sending `ChannelPending` and `ChannelReady` again + context.channel_pending_event_emitted = false; + context.channel_ready_event_emitted = false; + // Reset + context.blocked_monitor_updates = Vec::new(); + + let value_to_cp = post_channel_value.saturating_sub(value_to_self_msat); + let mut post_channel_transaction_parameters = pre_splice_funding.channel_transaction_parameters.clone(); + post_channel_transaction_parameters.channel_value_satoshis = post_channel_value; + post_channel_transaction_parameters.funding_outpoint = None; + let funding = FundingScope { + value_to_self_msat: value_to_self_msat, + + counterparty_selected_channel_reserve_satoshis: pre_splice_funding.counterparty_selected_channel_reserve_satoshis, + holder_selected_channel_reserve_satoshis: pre_splice_funding.holder_selected_channel_reserve_satoshis, + #[cfg(debug_assertions)] + holder_max_commitment_tx_output: Mutex::new((value_to_self_msat, value_to_cp)), + #[cfg(debug_assertions)] + counterparty_max_commitment_tx_output: Mutex::new((value_to_self_msat, value_to_cp)), + #[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), + + channel_transaction_parameters: post_channel_transaction_parameters, + funding_transaction: None, + }; + + log_debug!(logger, "Splicing channel context: value {} old {}, dir {}, value to self {}", + funding.get_value_satoshis(), pre_channel_value, + if is_outgoing { "outgoing" } else { "incoming" }, + funding.value_to_self_msat, + ); + + Ok((context, funding)) + } + + /// Splice process starting; update state, log, etc. + #[cfg(splicing)] + pub(crate) fn splice_start(&mut self, is_outgoing: bool, channel_value_satoshis: u64, 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, old channel value {}, outgoing {}, channel_id {}", + channel_value_satoshis, is_outgoing, self.channel_id); + } } // Internal utility functions for channels @@ -4849,6 +5658,8 @@ fn check_v2_funding_inputs_sufficient( pub(super) struct DualFundingChannelContext { /// The amount in satoshis we will be contributing to the channel. pub our_funding_satoshis: u64, + /// The amount in satoshis our counterparty will be contributing to the channel. + pub their_funding_satoshis: Option, /// The funding transaction locktime suggested by the initiator. If set by us, it is always set /// to the current block height to align incentives against fee-sniping. pub funding_tx_locktime: LockTime, @@ -4860,6 +5671,8 @@ pub(super) struct DualFundingChannelContext { /// Note that the `our_funding_satoshis` field is equal to the total value of `our_funding_inputs` /// minus any fees paid for our contributed weight. This means that change will never be generated /// and the maximum value possible will go towards funding the channel. + /// + /// Note that this field may be emptied once the interactive negotiation has been started. #[allow(dead_code)] // TODO(dual_funding): Remove once contribution to V2 channels is enabled. pub our_funding_inputs: Vec<(TxIn, TransactionU16LenLimited)>, } @@ -4876,10 +5689,14 @@ pub(super) struct FundedChannel where SP::Target: SignerProvider { is_v2_established: bool, /// Info about an in-progress, pending splice (if any), on the pre-splice channel #[cfg(splicing)] - pending_splice: Option, + pending_splice_pre: 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))] +#[derive(Clone)] struct CommitmentTxInfoCached { fee: u64, total_pending_htlcs: usize, @@ -4955,7 +5772,7 @@ impl FailHTLCMessageName for msgs::UpdateFailMalformedHTLC { impl FundedChannel where SP::Target: SignerProvider, ::EcdsaSigner: EcdsaChannelSigner -{ +{ // impl FundedChannel fn check_remote_fee( channel_type: &ChannelTypeFeatures, fee_estimator: &LowerBoundedFeeEstimator, feerate_per_kw: u32, cur_feerate_per_kw: Option, logger: &L @@ -5804,6 +6621,59 @@ impl FundedChannel where return Ok(self.push_ret_blockable_mon_update(monitor_update)); } + /// 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 + } + + pub(super) fn funding_transaction_signed(&mut self, witnesses: Vec) -> Result, APIError> { + self.verify_interactive_tx_signatures(&witnesses); + let is_splice = self.is_splice_pending(); + if let Some(ref mut signing_session) = self.interactive_tx_signing_session { + // Shared signature (used in splicing): holder signature on the prev funding tx input should have been saved. + // include it in tlvs field + let tlvs = if is_splice { + if let Some(s) = signing_session.shared_signature { + Some(s) + } else { + panic!("TODO error!"); + } + } else { + None + }; + let (tx_signatures_opt, funding_tx_opt) = + signing_session.provide_holder_witnesses(self.context.channel_id(), witnesses, tlvs) + .map_err(|_| APIError::APIMisuseError { + err: "Internal error in funding_transaction_signed, provide_holder_witnesses".to_owned(), + })?; + if funding_tx_opt.is_some() { + self.funding.funding_transaction = funding_tx_opt.clone(); + /* TODO + #[cfg(splicing)] + { + self.context.funding_transaction_saved = funding_tx_opt.clone(); + } + */ + } + Ok(tx_signatures_opt) + } else { + Err(APIError::APIMisuseError { + err: format!("Channel with id {} has no pending signing session, not expecting funding signatures", self.context.channel_id()) + }) + } + } + + fn verify_interactive_tx_signatures(&mut self, _witnesses: &Vec) { + if let Some(ref mut _signing_session) = self.interactive_tx_signing_session { + // Check that sighash_all was used: + // TODO(dual_funding): Check sig for sighash + } + } + /// Public version of the below, checking relevant preconditions first. /// If we're not in a state where freeing the holding cell makes sense, this is a no-op and /// returns `(None, Vec::new())`. @@ -8411,7 +9281,7 @@ impl FundedChannel where ) -> Result { // Check if a splice has been initiated already. // Note: only a single outstanding splice is supported (per spec) - if let Some(splice_info) = &self.pending_splice { + if let Some(splice_info) = &self.pending_splice_pre { return Err(APIError::APIMisuseError { err: format!( "Channel {} cannot be spliced, as it has already a splice pending (contribution {})", self.context.channel_id(), splice_info.our_funding_contribution @@ -8447,8 +9317,20 @@ impl FundedChannel where self.context.channel_id(), err, )})?; - self.pending_splice = Some(PendingSplice { + // 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, {:?}", e)} + )?; + funding_inputs.push((tx_in.clone(), tx16)); + } + + self.pending_splice_pre = Some(PendingSplicePre { 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); @@ -8473,15 +9355,15 @@ impl FundedChannel where } } - /// Handle splice_init + /// Checks during handling splice_init #[cfg(splicing)] - pub fn splice_init(&mut self, msg: &msgs::SpliceInit) -> Result { + pub fn splice_init_checks(&mut self, msg: &msgs::SpliceInit) -> Result<(), ChannelError> { let their_funding_contribution_satoshis = msg.funding_contribution_satoshis; // TODO(splicing): Currently not possible to contribute on the splicing-acceptor side let our_funding_contribution_satoshis = 0i64; // Check if a splice has been initiated already. - if let Some(splice_info) = &self.pending_splice { + if let Some(splice_info) = &self.pending_splice_pre { return Err(ChannelError::Warn(format!( "Channel has already a splice pending, contribution {}", splice_info.our_funding_contribution, ))); @@ -8508,33 +9390,18 @@ 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) - - // 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 { - channel_id: self.context.channel_id, - funding_contribution_satoshis: our_funding_contribution_satoshis, - funding_pubkey: self.funding.get_holder_pubkeys().funding_pubkey, - require_confirmed_inputs: None, - }; - // TODO(splicing): start interactive funding negotiation - Ok(splice_ack_msg) + Ok(()) } - /// Handle splice_ack + /// Checks during handling splice_ack #[cfg(splicing)] - pub fn splice_ack(&mut self, _msg: &msgs::SpliceAck) -> Result<(), ChannelError> { + pub 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"))); - }; - - // TODO(splicing): Pre-check for reserve requirement - // (Note: It should also be checked later at tx_complete) - Ok(()) + if let Some(pending_splice) = &self.pending_splice_pre { + Ok(pending_splice.clone()) + } else { + Err(ChannelError::Warn(format!("Channel is not in pending splice"))) + } } // Send stuff to our remote peers: @@ -9460,7 +10327,9 @@ impl OutboundV1Channel where SP::Target: SignerProvider { is_v2_established: false, holder_commitment_point, #[cfg(splicing)] - pending_splice: None, + pending_splice_pre: None, + #[cfg(splicing)] + pending_splice_post: None, }; let need_channel_ready = channel.check_get_channel_ready(0, logger).is_some() @@ -9736,7 +10605,9 @@ impl InboundV1Channel where SP::Target: SignerProvider { is_v2_established: false, holder_commitment_point, #[cfg(splicing)] - pending_splice: None, + pending_splice_pre: 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; @@ -9765,6 +10636,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, @@ -9775,6 +10669,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 { @@ -9835,16 +10732,94 @@ impl PendingV2Channel where SP::Target: SignerProvider { unfunded_context, dual_funding_context: DualFundingChannelContext { our_funding_satoshis: funding_satoshis, + their_funding_satoshis: None, funding_tx_locktime, funding_feerate_sat_per_1000_weight, our_funding_inputs: funding_inputs, }, interactive_tx_constructor: None, interactive_tx_signing_session: None, + #[cfg(splicing)] + pending_splice_post: None, }; Ok(chan) } + /// Create new channel for splicing + #[cfg(splicing)] + pub fn new_spliced( + is_outbound: bool, + pre_splice_channel: &FundedChannel, + signer_provider: &SP, + // counterparty_funding_pubkey: &PublicKey, + our_funding_contribution: i64, + their_funding_contribution: i64, + funding_inputs: Vec<(TxIn, TransactionU16LenLimited)>, + funding_tx_locktime: LockTime, + funding_feerate_sat_per_1000_weight: u32, + logger: &L, + ) -> Result where L::Target: Logger + { + if pre_splice_channel.pending_splice_post.is_some() { + return Err(ChannelError::Warn(format!("Internal error: Channel is already splicing, channel_id {}", pre_splice_channel.context.channel_id))); + } + + let pre_channel_value = pre_splice_channel.funding.get_value_satoshis(); + + // Save the current funding transaction + let pre_funding_transaction = pre_splice_channel.funding.funding_transaction.clone(); + let pre_funding_txo = pre_splice_channel.funding.get_funding_txo().clone(); + + let pending_splice_post = PendingSplicePost::new( + pre_channel_value, our_funding_contribution, their_funding_contribution, + pre_funding_transaction, pre_funding_txo, + ); + + // Create new signer, using the new channel value. + // Note: channel_keys_id is not changed + let holder_signer = signer_provider.derive_channel_signer(pre_splice_channel.context.channel_keys_id); + + let (context, funding) = ChannelContext::new_for_splice( + &pre_splice_channel.context, + &pre_splice_channel.funding, + is_outbound, + // counterparty_funding_pubkey, + our_funding_contribution, + their_funding_contribution, + holder_signer, + logger, + )?; + + let (our_funding_satoshis, their_funding_satoshis) = calculate_funding_values( + pre_channel_value, + our_funding_contribution, + their_funding_contribution, + is_outbound, + )?; + + let dual_funding_context = DualFundingChannelContext { + our_funding_satoshis, + their_funding_satoshis: Some(their_funding_satoshis), + funding_tx_locktime, + funding_feerate_sat_per_1000_weight, + our_funding_inputs: funding_inputs, + }; + let unfunded_context = UnfundedChannelContext { + unfunded_channel_age_ticks: 0, + holder_commitment_point: HolderCommitmentPoint::new(&context.holder_signer, &context.secp_ctx), + }; + let post_chan = Self { + context, + funding, + dual_funding_context, + unfunded_context, + interactive_tx_constructor: None, + interactive_tx_signing_session: None, + pending_splice_post: Some(pending_splice_post), + }; + Ok(post_chan) + } + /// If we receive an error message, it may only be a rejection of the channel type we tried, /// not of our ability to open any channel at all. Thus, on error, we should first call this /// and see if we get a new `OpenChannelV2` message, otherwise the channel is failed. @@ -9912,6 +10887,41 @@ impl PendingV2Channel where SP::Target: SignerProvider { } } + /// 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.funding.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.funding.value_to_self_msat, our_funding_contribution); + // TODO(splicing): Pre-check for reserve requirement + // (Note: It should also be checked later at tx_complete) + + // 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, pre_channel_value, 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(), Some(prev_funding_input)) + .map_err(|err| ChannelError::Warn(format!("V2 channel rejected due to sender error, {:?}", err)))?; + Ok(tx_msg_opt) + } + /// Creates a new dual-funded channel from a remote side's request for one. /// Assumes chain_hash has already been checked and corresponds with what we expect! /// TODO(dual_funding): Allow contributions, pass intended amount and inputs @@ -9980,6 +10990,7 @@ impl PendingV2Channel where SP::Target: SignerProvider { let dual_funding_context = DualFundingChannelContext { our_funding_satoshis: our_funding_satoshis, + their_funding_satoshis: Some(msg.common_fields.funding_satoshis), funding_tx_locktime: LockTime::from_consensus(msg.locktime), funding_feerate_sat_per_1000_weight: msg.funding_feerate_sat_per_1000_weight, our_funding_inputs: our_funding_inputs.clone(), @@ -10014,6 +11025,8 @@ impl PendingV2Channel where SP::Target: SignerProvider { interactive_tx_constructor, interactive_tx_signing_session: None, unfunded_context, + #[cfg(splicing)] + pending_splice_post: None, }) } @@ -10090,6 +11103,54 @@ impl PendingV2Channel where SP::Target: SignerProvider { pub fn get_accept_channel_v2_message(&self) -> msgs::AcceptChannelV2 { self.generate_accept_channel_v2_message() } + + /// 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, self.funding.get_value_satoshis(), logger); + + let splice_ack_msg = self.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.clone(), None) + .map_err(|err| ChannelError::Warn(format!("Failed to start interactive transaction construction, {:?}", err)))?; + + Ok(splice_ack_msg) + } + + /// Get the splice_ack message that can be sent in response to splice initiation. + #[cfg(splicing)] + pub fn get_splice_ack(&self, our_funding_contribution_satoshis: i64) -> msgs::SpliceAck { + // Reuse the existing funding pubkey, in spite of the channel value changing + let funding_pubkey = self.funding.get_holder_pubkeys().funding_pubkey; + msgs::SpliceAck { + channel_id: self.context.channel_id, + funding_contribution_satoshis: our_funding_contribution_satoshis, + funding_pubkey, + require_confirmed_inputs: None, + } + } + + /// 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 @@ -11117,7 +12178,9 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, &'c Channel is_v2_established, holder_commitment_point, #[cfg(splicing)] - pending_splice: None, + pending_splice_pre: None, + #[cfg(splicing)] + pending_splice_post: None, }) } } @@ -11140,7 +12203,7 @@ mod tests { use crate::ln::channel_keys::{RevocationKey, RevocationBasepoint}; use crate::ln::channelmanager::{self, HTLCSource, PaymentId}; use crate::ln::channel::InitFeatures; - use crate::ln::channel::{AwaitingChannelReadyFlags, ChannelState, FundedChannel, InboundHTLCOutput, OutboundV1Channel, InboundV1Channel, OutboundHTLCOutput, InboundHTLCState, OutboundHTLCState, HTLCCandidate, HTLCInitiator, HTLCUpdateAwaitingACK, commit_tx_fee_sat}; + use crate::ln::channel::{AwaitingChannelReadyFlags, ChannelContext, ChannelId, ChannelPublicKeys, ChannelState, FundedChannel, InboundHTLCOutput, OutboundV1Channel, InboundV1Channel, OutboundHTLCOutput, InboundHTLCState, OutboundHTLCState, HTLCCandidate, HTLCInitiator, HTLCUpdateAwaitingACK, commit_tx_fee_sat}; use crate::ln::channel::{MAX_FUNDING_SATOSHIS_NO_WUMBO, TOTAL_BITCOIN_SUPPLY_SATOSHIS, MIN_THEIR_CHAN_RESERVE_SATOSHIS}; use crate::types::features::{ChannelFeatures, ChannelTypeFeatures, NodeFeatures}; use crate::ln::msgs; @@ -13039,4 +14102,122 @@ mod tests { ); } } + + #[test] + fn channel_context_clone() { + let fee_estimator = TestFeeEstimator {fee_est: 253 }; + let bounded_fee_estimator = LowerBoundedFeeEstimator::new(&fee_estimator); + let seed = [42; 32]; + let network = Network::Testnet; + let keys_provider = test_utils::TestKeysInterface::new(&seed, network); + let secp_ctx = Secp256k1::new(); + let node_a_node_id = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap()); + let config = UserConfig::default(); + + let signer_provider: &TestKeysInterface = &&keys_provider; + let channel_value_satoshis = 10000000; + let user_id = 42; + let channel_keys_id = signer_provider.generate_channel_keys_id(false, user_id); + let holder_signer = signer_provider.derive_channel_signer(channel_keys_id); + let logger = test_utils::TestLogger::new(); + + let temporary_channel_id_fn = Some(|pubkeys: &ChannelPublicKeys| { + ChannelId::temporary_v2_from_revocation_basepoint(&pubkeys.revocation_basepoint) + }); + + // Create a context + let context = ChannelContext::<&TestKeysInterface>::new_for_outbound_channel( + &bounded_fee_estimator, + &&keys_provider, + &signer_provider, + node_a_node_id, + &channelmanager::provided_init_features(&config), + channel_value_satoshis, + 100000, + user_id, + &config, + 0, + 42, + temporary_channel_id_fn, + 100000, + [42; 32], + holder_signer, + &logger, + ).unwrap().1; + + // Clone it + let holder_signer2 = signer_provider.derive_channel_signer(channel_keys_id); + let context_cloned = context.clone(holder_signer2); + + // Compare some fields + // assert_eq!(context_cloned.channel_value_satoshis, context.channel_value_satoshis); + assert_eq!(context_cloned.channel_id, context.channel_id); + assert_eq!(context_cloned.funding_tx_broadcast_safe_event_emitted, context.funding_tx_broadcast_safe_event_emitted); + assert_eq!(context_cloned.channel_keys_id, context.channel_keys_id); + } + + #[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::PendingSplicePre; + + let post_channel_value = PendingSplicePre::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 fab15bfea28..ba5815f41f0 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -5526,6 +5526,45 @@ 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> { + let per_peer_state = self.per_peer_state.read().unwrap(); + let peer_state_mutex = per_peer_state.get(counterparty_node_id) + .ok_or_else(|| APIError::ChannelUnavailable { + err: format!("Can't find a peer matching the passed counterparty node_id {}", + counterparty_node_id) })?; + + let witnesses: Vec<_> = transaction.input.into_iter().filter_map(|input| { + if input.witness.is_empty() { None } else { Some(input.witness) } + }).collect(); + + let mut peer_state_lock = peer_state_mutex.lock().unwrap(); + let peer_state = &mut *peer_state_lock; + + if let Some(channel) = peer_state.channel_by_id.get_mut(channel_id) { + let tx_signatures_opt = channel.funding_transaction_signed(witnesses)?; + if let Some(tx_signatures) = tx_signatures_opt { + peer_state.pending_msg_events.push(MessageSendEvent::SendTxSignatures { + node_id: *counterparty_node_id, + msg: tx_signatures, + }); + } + } else { + return Err(APIError::ChannelUnavailable { err: format!( + "Channel with ID {} and counterparty_node_id {} not found", channel_id, counterparty_node_id + )}); + } + + 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 @@ -8536,7 +8575,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ 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(); @@ -8553,6 +8592,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ update_fee: None, }, }); + // channel.set_next_funding_txid(&funding_txid); // TODO ? } Ok(()) }, @@ -9522,6 +9562,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!( @@ -9529,23 +9572,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) + 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(()) } @@ -9561,28 +9599,19 @@ 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; - // Look for the channel + // Look for 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::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_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())); } - }, - }; - - // 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)) + Ok(()) + } + } } /// Process pending events from the [`chain::Watch`], returning whether any events were processed. diff --git a/lightning/src/ln/interactivetxs.rs b/lightning/src/ln/interactivetxs.rs index 9fbcaef92d2..1c77f853f75 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}; @@ -293,24 +294,16 @@ 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 { pub fn received_commitment_signed(&mut self) -> Option { self.received_commitment_signed = true; - if self.holder_sends_tx_signatures_first { - self.holder_tx_signatures.clone() - } else { - None - } - } - pub fn get_tx_signatures(&self) -> Option { - if self.received_commitment_signed { - self.holder_tx_signatures.clone() - } else { - None - } + self.get_tx_signatures() } /// Handles a `tx_signatures` message received from the counterparty. @@ -333,22 +326,7 @@ impl InteractiveTxSigningSession { self.unsigned_tx.add_remote_witnesses(tx_signatures.witnesses.clone()); self.counterparty_sent_tx_signatures = true; - let holder_tx_signatures = if !self.holder_sends_tx_signatures_first { - self.holder_tx_signatures.clone() - } else { - None - }; - - // Check if the holder has provided its signatures and if so, - // return the finalized funding transaction. - let funding_tx_opt = if self.holder_tx_signatures.is_some() { - Some(self.finalize_funding_tx()) - } else { - // This means we're still waiting for the holder to provide their signatures. - None - }; - - Ok((holder_tx_signatures, funding_tx_opt)) + Ok((self.get_tx_signatures(), self.get_finalized_funding_tx())) } /// Provides the holder witnesses for the unsigned transaction. @@ -357,7 +335,8 @@ impl InteractiveTxSigningSession { /// unsigned transaction. pub fn provide_holder_witnesses( &mut self, channel_id: ChannelId, witnesses: Vec, - ) -> Result<(), ()> { + shared_input_signature: Option, + ) -> Result<(Option, Option), ()> { if self.local_inputs_count() != witnesses.len() { return Err(()); } @@ -367,10 +346,38 @@ impl InteractiveTxSigningSession { channel_id, tx_hash: self.unsigned_tx.compute_txid(), witnesses: witnesses.into_iter().collect(), - shared_input_signature: None, + shared_input_signature, }); - Ok(()) + Ok((self.get_tx_signatures(), self.get_finalized_funding_tx())) + } + + /// Decide if we need to send `TxSignatures` at this stage or not + fn get_tx_signatures(&mut self) -> Option { + if self.holder_tx_signatures.is_none() { + return None; // no local signature yet + } + if !self.received_commitment_signed { + return None; // no counterparty commitment received yet + } + if (!self.holder_sends_tx_signatures_first && self.counterparty_sent_tx_signatures) + || (self.holder_sends_tx_signatures_first && !self.counterparty_sent_tx_signatures) + { + self.holder_tx_signatures.clone() + } else { + None + } + } + + /// Decide if we have the funding transaction signed from both parties + fn get_finalized_funding_tx(&mut self) -> Option { + if self.holder_tx_signatures.is_none() { + return None; // no local signature yet + } + if !self.counterparty_sent_tx_signatures { + return None; // no counterparty signature received yet + } + Some(self.finalize_funding_tx()) } pub fn remote_inputs_count(&self) -> usize { @@ -990,6 +997,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)) } @@ -1152,13 +1160,13 @@ pub(crate) enum InteractiveTxInput { } #[derive(Clone, Debug, Eq, PartialEq)] -pub struct SharedOwnedOutput { +pub(super) struct SharedOwnedOutput { tx_out: TxOut, local_owned: u64, } impl SharedOwnedOutput { - fn new(tx_out: TxOut, local_owned: u64) -> SharedOwnedOutput { + pub fn new(tx_out: TxOut, local_owned: u64) -> SharedOwnedOutput { debug_assert!( local_owned <= tx_out.value.to_sat(), "SharedOwnedOutput: Inconsistent local_owned value {}, larger than output value {}", @@ -1177,7 +1185,7 @@ impl SharedOwnedOutput { /// its control -- exclusive by the adder or shared --, and /// its ownership -- value fully owned by the adder or jointly #[derive(Clone, Debug, Eq, PartialEq)] -pub enum OutputOwned { +pub(super) enum OutputOwned { /// Belongs to a single party -- controlled exclusively and fully belonging to a single party Single(TxOut), /// Output with shared control, but fully belonging to local node @@ -1187,7 +1195,7 @@ pub enum OutputOwned { } impl OutputOwned { - 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, @@ -1663,14 +1671,57 @@ impl InteractiveTxConstructor { } } +/// Determine whether a change output should be added or not, and if so, of what size, +/// considering our given inputs, outputs, and intended contribution. +/// Computes and takes into account fees. +/// Return value is the value computed for the change output (in satoshis), +/// or None if a change is not needed/possible. +#[allow(dead_code)] // TODO(dual_funding): Remove once begin_interactive_funding_tx_construction() is used +pub(super) fn calculate_change_output_value( + is_initiator: bool, our_contribution: u64, funding_inputs_prev_outputs: &Vec<&TxOut>, + funding_outputs: &Vec, funding_feerate_sat_per_1000_weight: u32, + holder_dust_limit_satoshis: u64, +) -> Option { + 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 total_input_satoshis: u64 = + funding_inputs_prev_outputs.iter().map(|out| out.value.to_sat()).sum(); + + let remaining_value = + total_input_satoshis.saturating_sub(our_contribution).saturating_sub(fees_sats); + + if remaining_value <= holder_dust_limit_satoshis { + None + } else { + Some(remaining_value) + } +} + #[cfg(test)] mod tests { use crate::chain::chaininterface::{fee_for_weight, FEERATE_FLOOR_SATS_PER_KW}; use crate::ln::channel::TOTAL_BITCOIN_SUPPLY_SATOSHIS; use crate::ln::interactivetxs::{ - generate_holder_serial_id, AbortReason, HandleTxCompleteValue, InteractiveTxConstructor, - InteractiveTxConstructorArgs, InteractiveTxMessageSend, MAX_INPUTS_OUTPUTS_COUNT, - MAX_RECEIVED_TX_ADD_INPUT_COUNT, MAX_RECEIVED_TX_ADD_OUTPUT_COUNT, + calculate_change_output_value, generate_holder_serial_id, AbortReason, + HandleTxCompleteValue, InteractiveTxConstructor, InteractiveTxConstructorArgs, + InteractiveTxMessageSend, MAX_INPUTS_OUTPUTS_COUNT, MAX_RECEIVED_TX_ADD_INPUT_COUNT, + MAX_RECEIVED_TX_ADD_OUTPUT_COUNT, }; use crate::ln::types::ChannelId; use crate::sign::EntropySource; @@ -2595,4 +2646,148 @@ mod tests { assert_eq!(generate_holder_serial_id(&&entropy_source, true) % 2, 0); assert_eq!(generate_holder_serial_id(&&entropy_source, false) % 2, 1) } + + #[test] + fn test_calculate_change_output_value_open() { + let input_prevouts_owned = vec![ + TxOut { value: Amount::from_sat(70_000), script_pubkey: ScriptBuf::new() }, + TxOut { value: Amount::from_sat(60_000), script_pubkey: ScriptBuf::new() }, + ]; + let input_prevouts: Vec<&TxOut> = input_prevouts_owned.iter().collect(); + let our_contributed = 110_000; + let txout = TxOut { value: Amount::from_sat(128_000), script_pubkey: ScriptBuf::new() }; + let outputs = vec![OutputOwned::SharedControlFullyOwned(txout)]; + let funding_feerate_sat_per_1000_weight = 3000; + + let total_inputs: u64 = input_prevouts.iter().map(|o| o.value.to_sat()).sum(); + let gross_change = total_inputs - our_contributed; + let fees = 1746; + let common_fees = 126; + { + // There is leftover for change + let res = calculate_change_output_value( + true, + our_contributed, + &input_prevouts, + &outputs, + funding_feerate_sat_per_1000_weight, + 300, + ); + assert_eq!(res.unwrap(), gross_change - fees - common_fees); + } + { + // There is leftover for change, without common fees + let res = calculate_change_output_value( + false, + our_contributed, + &input_prevouts, + &outputs, + funding_feerate_sat_per_1000_weight, + 300, + ); + assert_eq!(res.unwrap(), gross_change - fees); + } + { + // Larger fee, smaller change + let res = calculate_change_output_value( + true, + our_contributed, + &input_prevouts, + &outputs, + 9000, + 300, + ); + assert_eq!(res.unwrap(), 14384); + } + { + // Insufficient inputs, no leftover + let res = calculate_change_output_value( + false, + 130_000, + &input_prevouts, + &outputs, + funding_feerate_sat_per_1000_weight, + 300, + ); + assert!(res.is_none()); + } + { + // Very small leftover + let res = calculate_change_output_value( + false, + 128_100, + &input_prevouts, + &outputs, + funding_feerate_sat_per_1000_weight, + 300, + ); + assert!(res.is_none()); + } + { + // Small leftover, but not dust + let res = calculate_change_output_value( + false, + 128_100, + &input_prevouts, + &outputs, + funding_feerate_sat_per_1000_weight, + 100, + ); + assert_eq!(res.unwrap(), 154); + } + } + + #[test] + fn test_calculate_change_output_value_splice() { + let input_prevouts_owned = vec![ + TxOut { value: Amount::from_sat(70_000), script_pubkey: ScriptBuf::new() }, + TxOut { value: Amount::from_sat(60_000), script_pubkey: ScriptBuf::new() }, + ]; + let input_prevouts: Vec<&TxOut> = input_prevouts_owned.iter().collect(); + let our_contributed = 110_000; + let txout = TxOut { value: Amount::from_sat(148_000), script_pubkey: ScriptBuf::new() }; + let outputs = vec![OutputOwned::Shared(SharedOwnedOutput::new(txout, our_contributed))]; + let funding_feerate_sat_per_1000_weight = 3000; + + let total_inputs: u64 = input_prevouts.iter().map(|o| o.value.to_sat()).sum(); + let gross_change = total_inputs - our_contributed; + let fees = 1746; + let common_fees = 126; + { + // There is leftover for change + let res = calculate_change_output_value( + true, + our_contributed, + &input_prevouts, + &outputs, + funding_feerate_sat_per_1000_weight, + 300, + ); + assert_eq!(res.unwrap(), gross_change - fees - common_fees); + } + { + // Very small leftover + let res = calculate_change_output_value( + false, + 128_100, + &input_prevouts, + &outputs, + funding_feerate_sat_per_1000_weight, + 300, + ); + assert!(res.is_none()); + } + { + // Small leftover, but not dust + let res = calculate_change_output_value( + false, + 128_100, + &input_prevouts, + &outputs, + funding_feerate_sat_per_1000_weight, + 100, + ); + assert_eq!(res.unwrap(), 154); + } + } } diff --git a/lightning/src/ln/splicing_tests.rs b/lightning/src/ln/splicing_tests.rs index 33f5a500789..36742789b2b 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,23 @@ fn test_v1_splice_in() { "03c21e841cbc0b48197d060c71e116c185fa0ac281b7d0aa5924f535154437ca3b"; let expected_acceptor_funding_key = "039481c28b904cbe12681e79937373fc76245c1b29871028ae60ba3152162c319b"; + let expect_inputs_in_reverse = true; + let expected_pre_funding_txid = + "4f128bedf1a15baf465ab1bfd6e97c8f82628f4156bf86eb1cbc132cda6733ae"; + let expected_splice_funding_txid = + "0a6d624a6a6ec0f72e50317856cb8169c1a2272559ba84f20fbb37838c13bf82"; // ==== 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 +116,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,17 +133,202 @@ fn test_v1_splice_in() { assert!(channel.is_usable); assert!(channel.is_channel_ready); assert_eq!(channel.channel_value_satoshis, channel_value_sat); - assert_eq!( - channel.outbound_capacity_msat, - 1000 * (channel_value_sat - channel_reserve_amnt_sat) - ); + assert_eq!(channel.outbound_capacity_msat, exp_balance1 - 1000 * channel_reserve_amnt_sat); assert!(channel.funding_txo.is_some()); assert!(channel.confirmations.unwrap() > 0); } - let _error_msg = get_err_msg(initiator_node, &acceptor_node.node.get_our_node_id()); + // exp_balance1 += 1000 * splice_in_sats; // increase in balance + + // Negotiate transaction inputs and outputs + + // First input + let tx_add_input_msg = get_event_msg!( + initiator_node, + MessageSendEvent::SendTxAddInput, + acceptor_node.node.get_our_node_id() + ); + let exp_value = + if expect_inputs_in_reverse { extra_splice_funding_input_sats } else { channel_value_sat }; + assert_eq!( + tx_add_input_msg.prevtx.as_transaction().output[tx_add_input_msg.prevtx_out as usize] + .value + .to_sat(), + exp_value + ); + + let _res = acceptor_node + .node + .handle_tx_add_input(initiator_node.node.get_our_node_id(), &tx_add_input_msg); + let tx_complete_msg = get_event_msg!( + acceptor_node, + MessageSendEvent::SendTxComplete, + initiator_node.node.get_our_node_id() + ); + + let _res = initiator_node + .node + .handle_tx_complete(acceptor_node.node.get_our_node_id(), &tx_complete_msg); + // Second input + let exp_value = + if expect_inputs_in_reverse { channel_value_sat } else { extra_splice_funding_input_sats }; + let tx_add_input2_msg = get_event_msg!( + initiator_node, + MessageSendEvent::SendTxAddInput, + acceptor_node.node.get_our_node_id() + ); + assert_eq!( + tx_add_input2_msg.prevtx.as_transaction().output[tx_add_input2_msg.prevtx_out as usize] + .value + .to_sat(), + exp_value + ); + + let _res = acceptor_node + .node + .handle_tx_add_input(initiator_node.node.get_our_node_id(), &tx_add_input2_msg); + let tx_complete_msg = get_event_msg!( + acceptor_node, + MessageSendEvent::SendTxComplete, + initiator_node.node.get_our_node_id() + ); + + let _res = initiator_node + .node + .handle_tx_complete(acceptor_node.node.get_our_node_id(), &tx_complete_msg); + + // TxAddOutput for the change output + let tx_add_output_msg = get_event_msg!( + initiator_node, + MessageSendEvent::SendTxAddOutput, + acceptor_node.node.get_our_node_id() + ); + assert!(tx_add_output_msg.script.is_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 splice funding + let tx_add_output2_msg = get_event_msg!( + initiator_node, + MessageSendEvent::SendTxAddOutput, + acceptor_node.node.get_our_node_id() + ); + assert!(tx_add_output2_msg.script.is_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() + ); + + // 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"), + }; + let (input_idx_prev_fund, input_idx_second_input) = + if expect_inputs_in_reverse { (0, 1) } else { (1, 0) }; + let _channel_id1 = 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(); + channel_id + } else { + panic!("Expected event FundingTransactionReadyForSigning"); + }; + + // check new funding tx + assert_eq!(initiator_node.node.list_channels().len(), 1); + { + let channel = &initiator_node.node.list_channels()[0]; + assert!(!channel.is_channel_ready); + assert_eq!(channel.channel_value_satoshis, post_splice_channel_value); + assert_eq!(channel.funding_txo.unwrap().txid.to_string(), expected_splice_funding_txid); + assert_eq!(channel.confirmations.unwrap(), 0); + } + + let _res = acceptor_node + .node + .handle_tx_complete(initiator_node.node.get_our_node_id(), &tx_complete_msg); + let msg_events = acceptor_node.node.get_and_clear_pending_msg_events(); + // First messsage is commitment_signed, second is tx_signatures (see below for more) + assert_eq!(msg_events.len(), 1); + let _msg_commitment_signed_from_1 = match msg_events[0] { + MessageSendEvent::UpdateHTLCs { ref node_id, ref updates } => { + assert_eq!(*node_id, initiator_node.node.get_our_node_id()); + let res = updates.commitment_signed.clone(); + res + }, + _ => panic!("Unexpected event {:?}", msg_events[0]), + }; + + // check new funding tx (acceptor side) + assert_eq!(acceptor_node.node.list_channels().len(), 1); + { + let channel = &acceptor_node.node.list_channels()[0]; + assert!(!channel.is_channel_ready); + assert_eq!(channel.channel_value_satoshis, post_splice_channel_value); + assert_eq!(channel.funding_txo.unwrap().txid.to_string(), expected_splice_funding_txid); + assert_eq!(channel.confirmations.unwrap(), 0); + } - // TODO(splicing): continue with splice transaction negotiation + // TODO(splicing): Continue with commitment flow, new tx confirmation // === Close channel, cooperatively initiator_node.node.close_channel(&channel_id, &acceptor_node.node.get_our_node_id()).unwrap();