diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index f1a97b39c27..78beff60fce 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -11,7 +11,6 @@ use bitcoin::amount::Amount; use bitcoin::constants::ChainHash; use bitcoin::script::{Script, ScriptBuf, Builder, WScriptHash}; use bitcoin::transaction::{Transaction, TxIn}; -use bitcoin::sighash; use bitcoin::sighash::EcdsaSighashType; use bitcoin::consensus::encode; use bitcoin::absolute::LockTime; @@ -25,7 +24,7 @@ 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; +use bitcoin::{secp256k1, sighash}; use crate::ln::types::ChannelId; use crate::types::payment::{PaymentPreimage, PaymentHash}; @@ -1187,6 +1186,30 @@ impl UnfundedChannelContext { } } +/// Info about a pending splice, used in the pre-splice channel +#[cfg(splicing)] +#[derive(Clone)] +struct PendingSplice { + pub our_funding_contribution: i64, +} + +#[cfg(splicing)] +impl PendingSplice { + #[inline] + fn add_checked(base: u64, delta: i64) -> u64 { + if delta >= 0 { + base.saturating_add(delta as u64) + } else { + base.saturating_sub(delta.abs() as u64) + } + } + + /// Compute the post-splice channel value from the pre-splice values and the peer contributions + pub fn compute_post_value(pre_channel_value: u64, our_funding_contribution: i64, their_funding_contribution: i64) -> u64 { + Self::add_checked(pre_channel_value, our_funding_contribution.saturating_add(their_funding_contribution)) + } +} + /// Contains everything about the channel including state, and various flags. pub(super) struct ChannelContext where SP::Target: SignerProvider { config: LegacyChannelConfig, @@ -3635,6 +3658,33 @@ impl ChannelContext where SP::Target: SignerProvider { (context.holder_selected_channel_reserve_satoshis, context.counterparty_selected_channel_reserve_satoshis) } + /// Check that a balance value meets the channel reserve requirements or violates them (below reserve). + /// The channel value is an input as opposed to using from self, so that this can be used in case of splicing + /// to checks with new channel value (before being comitted to it). + #[cfg(splicing)] + pub fn check_balance_meets_reserve_requirements(&self, balance: u64, channel_value: u64) -> Result<(), ChannelError> { + if balance == 0 { + return Ok(()); + } + let holder_selected_channel_reserve_satoshis = get_v2_channel_reserve_satoshis( + channel_value, self.holder_dust_limit_satoshis); + if balance < holder_selected_channel_reserve_satoshis { + return Err(ChannelError::Warn(format!( + "Balance below reserve mandated by holder, {} vs {}", + balance, holder_selected_channel_reserve_satoshis, + ))); + } + let counterparty_selected_channel_reserve_satoshis = get_v2_channel_reserve_satoshis( + channel_value, self.counterparty_dust_limit_satoshis); + if balance < counterparty_selected_channel_reserve_satoshis { + return Err(ChannelError::Warn(format!( + "Balance below reserve mandated by counterparty, {} vs {}", + balance, counterparty_selected_channel_reserve_satoshis, + ))); + } + Ok(()) + } + /// Get the commitment tx fee for the local's (i.e. our) next commitment transaction based on the /// number of pending HTLCs that are on track to be in our next commitment tx. /// @@ -4097,6 +4147,38 @@ impl ChannelContext where SP::Target: SignerProvider { self.channel_transaction_parameters = channel_transaction_parameters; self.get_initial_counterparty_commitment_signature(logger) } + + /// Get the splice message that can be sent during splice initiation. + #[cfg(splicing)] + pub fn get_splice_init(&self, our_funding_contribution_satoshis: i64, + funding_feerate_perkw: u32, locktime: u32, + ) -> msgs::SpliceInit { + // Reuse the existing funding pubkey, in spite of the channel value changing + // (though at this point we don't know the new value yet, due tue the optional counterparty contribution) + // Note that channel_keys_id is supposed NOT to change + let funding_pubkey = self.get_holder_pubkeys().funding_pubkey.clone(); + msgs::SpliceInit { + channel_id: self.channel_id, + funding_contribution_satoshis: our_funding_contribution_satoshis, + funding_feerate_perkw, + locktime, + funding_pubkey, + require_confirmed_inputs: None, + } + } + + /// 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.get_holder_pubkeys().funding_pubkey; + msgs::SpliceAck { + channel_id: self.channel_id, + funding_contribution_satoshis: our_funding_contribution_satoshis, + funding_pubkey, + require_confirmed_inputs: None, + } + } } // Internal utility functions for channels @@ -4221,6 +4303,9 @@ pub(super) struct Channel where SP::Target: SignerProvider { pub context: ChannelContext, pub interactive_tx_signing_session: Option, holder_commitment_point: HolderCommitmentPoint, + /// Info about an in-progress, pending splice (if any), on the pre-splice channel + #[cfg(splicing)] + pending_splice_pre: Option, } #[cfg(any(test, fuzzing))] @@ -7832,6 +7917,135 @@ impl Channel where } } + /// Initiate splicing + #[cfg(splicing)] + pub fn splice_channel(&mut self, our_funding_contribution_satoshis: i64, + our_funding_inputs: Vec<(TxIn, Transaction)>, funding_feerate_perkw: u32, locktime: u32, + ) -> Result { + // Check if a splice has been initiated already. + // Note: this could be handled more nicely, and support multiple outstanding splice's, the incoming splice_ack matters anyways. + 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 + ))); + } + + if !matches!(self.context.channel_state, ChannelState::ChannelReady(_)) { + return Err(ChannelError::Warn(format!("Cannot initiate splicing, as channel is not Ready"))); + } + + let pre_channel_value = self.context.get_value_satoshis(); + // Sanity check: capacity cannot decrease below 0 + if (pre_channel_value as i64).saturating_add(our_funding_contribution_satoshis) < 0 { + return Err(ChannelError::Warn(format!( + "Post-splicing channel value cannot be negative. It was {} + {}", + pre_channel_value, our_funding_contribution_satoshis + ))); + } + + if our_funding_contribution_satoshis < 0 { + return Err(ChannelError::Warn(format!( + "TODO(splicing): Splice-out not supported, only splice in, contribution {}", + our_funding_contribution_satoshis, + ))); + } + + // Note: post-splice channel value is not yet known at this point, counterpary contribution is not known + // (Cannot test for miminum required post-splice channel value) + + // Check that inputs are sufficient to cover our contribution + let sum_input: i64 = our_funding_inputs.into_iter().map( + |(txin, tx)| tx.output.get(txin.previous_output.vout as usize).map(|tx| tx.value.to_sat() as i64).unwrap_or(0) + ).sum(); + if sum_input < our_funding_contribution_satoshis { + return Err(ChannelError::Warn(format!( + "Provided inputs are insufficient for our contribution, {} {}", + sum_input, our_funding_contribution_satoshis, + ))); + } + + self.pending_splice_pre = Some(PendingSplice { + our_funding_contribution: our_funding_contribution_satoshis, + }); + + let msg = self.context.get_splice_init(our_funding_contribution_satoshis, funding_feerate_perkw, locktime); + Ok(msg) + } + + /// Handle splice_init + #[cfg(splicing)] + pub fn splice_init(&mut self, msg: &msgs::SpliceInit) -> Result { + 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. + // Note: this could be handled more nicely, and support multiple outstanding splice's, the incoming splice_ack matters anyways. + 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, + ))); + } + + if !matches!(self.context.channel_state, ChannelState::ChannelReady(_)) { + return Err(ChannelError::Warn(format!("Splicing requested on a channel that is not Ready"))); + } + + let pre_channel_value = self.context.get_value_satoshis(); + // Sanity check: capacity cannot decrease below 0 + if (pre_channel_value as i64) + .saturating_add(their_funding_contribution_satoshis) + .saturating_add(our_funding_contribution_satoshis) < 0 + { + return Err(ChannelError::Warn(format!( + "Post-splicing channel value cannot be negative. It was {} + {} + {}", + pre_channel_value, their_funding_contribution_satoshis, our_funding_contribution_satoshis, + ))); + } + + if their_funding_contribution_satoshis.saturating_add(our_funding_contribution_satoshis) < 0 { + return Err(ChannelError::Warn(format!( + "Splice-out not supported, only splice in, relative {} + {}", + their_funding_contribution_satoshis, our_funding_contribution_satoshis, + ))); + } + + let post_channel_value = PendingSplice::compute_post_value(pre_channel_value, their_funding_contribution_satoshis, our_funding_contribution_satoshis); + let post_balance = PendingSplice::add_checked(self.context.value_to_self_msat, our_funding_contribution_satoshis); + // Early check for reserve requirement, assuming maximum balance of full channel value + // This will also be checked later at tx_complete + let _res = self.context.check_balance_meets_reserve_requirements(post_balance, post_channel_value)?; + + // TODO(splicing): Store msg.funding_pubkey + // TODO(splicing): Apply start of splice (splice_start) + + let splice_ack_msg = self.context.get_splice_ack(our_funding_contribution_satoshis); + // TODO(splicing): start interactive funding negotiation + Ok(splice_ack_msg) + } + + /// Handle splice_ack + #[cfg(splicing)] + pub fn splice_ack(&mut self, msg: &msgs::SpliceAck) -> Result<(), ChannelError> { + let their_funding_contribution_satoshis = msg.funding_contribution_satoshis; + + // check if splice is pending + let pending_splice = if let Some(pending_splice) = &self.pending_splice_pre { + pending_splice + } else { + return Err(ChannelError::Warn(format!("Channel is not in pending splice"))); + }; + + let our_funding_contribution = pending_splice.our_funding_contribution; + + let pre_channel_value = self.context.get_value_satoshis(); + let post_channel_value = PendingSplice::compute_post_value(pre_channel_value, our_funding_contribution, their_funding_contribution_satoshis); + let post_balance = PendingSplice::add_checked(self.context.value_to_self_msat, our_funding_contribution); + // Early check for reserve requirement, assuming maximum balance of full channel value + // This will also be checked later at tx_complete + let _res = self.context.check_balance_meets_reserve_requirements(post_balance, post_channel_value)?; + Ok(()) + } // Send stuff to our remote peers: @@ -8526,6 +8740,8 @@ impl OutboundV1Channel where SP::Target: SignerProvider { context: self.context, interactive_tx_signing_session: None, holder_commitment_point, + #[cfg(splicing)] + pending_splice_pre: None, }; let need_channel_ready = channel.check_get_channel_ready(0, logger).is_some() @@ -8791,6 +9007,8 @@ impl InboundV1Channel where SP::Target: SignerProvider { context: self.context, interactive_tx_signing_session: None, holder_commitment_point, + #[cfg(splicing)] + pending_splice_pre: None, }; let need_channel_ready = channel.check_get_channel_ready(0, logger).is_some() || channel.context.signer_pending_channel_ready; @@ -8965,6 +9183,8 @@ impl OutboundV2Channel where SP::Target: SignerProvider { context: self.context, interactive_tx_signing_session: Some(signing_session), holder_commitment_point, + #[cfg(splicing)] + pending_splice_pre: None, }; Ok(channel) @@ -9170,6 +9390,8 @@ impl InboundV2Channel where SP::Target: SignerProvider { context: self.context, interactive_tx_signing_session: Some(signing_session), holder_commitment_point, + #[cfg(splicing)] + pending_splice_pre: None, }; Ok(channel) @@ -10250,6 +10472,8 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, u32, &'c Ch }, interactive_tx_signing_session: None, holder_commitment_point, + #[cfg(splicing)] + pending_splice_pre: None, }) } } @@ -12032,4 +12256,69 @@ mod tests { assert_eq!(node_a_chan.context.channel_state, ChannelState::AwaitingChannelReady(AwaitingChannelReadyFlags::THEIR_CHANNEL_READY)); assert!(node_a_chan.check_get_channel_ready(0, &&logger).is_some()); } + + #[cfg(all(test, splicing))] + fn get_pre_and_post(pre_channel_value: u64, our_funding_contribution: i64, their_funding_contribution: i64) -> (u64, u64) { + use crate::ln::channel::PendingSplice; + + let post_channel_value = PendingSplice::compute_post_value(pre_channel_value, our_funding_contribution, their_funding_contribution); + (pre_channel_value, post_channel_value) + } + + #[cfg(all(test, splicing))] + #[test] + fn test_splice_compute_post_value() { + { + // increase, small amounts + let (pre_channel_value, post_channel_value) = get_pre_and_post(9_000, 6_000, 0); + assert_eq!(pre_channel_value, 9_000); + assert_eq!(post_channel_value, 15_000); + } + { + // increase, small amounts + let (pre_channel_value, post_channel_value) = get_pre_and_post(9_000, 4_000, 2_000); + assert_eq!(pre_channel_value, 9_000); + assert_eq!(post_channel_value, 15_000); + } + { + // increase, small amounts + let (pre_channel_value, post_channel_value) = get_pre_and_post(9_000, 0, 6_000); + assert_eq!(pre_channel_value, 9_000); + assert_eq!(post_channel_value, 15_000); + } + { + // decrease, small amounts + let (pre_channel_value, post_channel_value) = get_pre_and_post(15_000, -6_000, 0); + assert_eq!(pre_channel_value, 15_000); + assert_eq!(post_channel_value, 9_000); + } + { + // decrease, small amounts + let (pre_channel_value, post_channel_value) = get_pre_and_post(15_000, -4_000, -2_000); + assert_eq!(pre_channel_value, 15_000); + assert_eq!(post_channel_value, 9_000); + } + { + // increase and decrease + let (pre_channel_value, post_channel_value) = get_pre_and_post(15_000, 4_000, -2_000); + assert_eq!(pre_channel_value, 15_000); + assert_eq!(post_channel_value, 17_000); + } + let base2: u64 = 2; + let huge63i3 = (base2.pow(63) - 3) as i64; + assert_eq!(huge63i3, 9223372036854775805); + assert_eq!(-huge63i3, -9223372036854775805); + { + // increase, large amount + let (pre_channel_value, post_channel_value) = get_pre_and_post(9_000, huge63i3, 3); + assert_eq!(pre_channel_value, 9_000); + assert_eq!(post_channel_value, 9223372036854784807); + } + { + // increase, large amounts + let (pre_channel_value, post_channel_value) = get_pre_and_post(9_000, huge63i3, huge63i3); + assert_eq!(pre_channel_value, 9_000); + assert_eq!(post_channel_value, 9223372036854784807); + } + } } diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index a5ae07eab7f..825244017c8 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -49,7 +49,7 @@ use crate::ln::inbound_payment; use crate::ln::types::ChannelId; use crate::types::payment::{PaymentHash, PaymentPreimage, PaymentSecret}; use crate::ln::channel::{self, Channel, ChannelPhase, ChannelError, ChannelUpdateStatus, ShutdownResult, UpdateFulfillCommitFetch, OutboundV1Channel, InboundV1Channel, WithChannelContext, InteractivelyFunded as _}; -#[cfg(any(dual_funding, splicing))] +#[cfg(dual_funding)] use crate::ln::channel::InboundV2Channel; use crate::ln::channel_state::ChannelDetails; use crate::types::features::{Bolt12InvoiceFeatures, ChannelFeatures, ChannelTypeFeatures, InitFeatures, NodeFeatures}; @@ -4220,6 +4220,63 @@ where } } + /// Initiate a splice, to change the channel capacity of an existing funded channel. + /// After completion of splicing, the funding transaction will be replaced by a new one, spending the old funding transaction, + /// with optional extra inputs (splice-in) and/or extra outputs (splice-out or change). + /// TODO(splicing): Implementation is currently incomplete. + /// Note: Currently only splice-in is supported (increase in channel capacity), splice-out is not. + /// - our_funding_contribution_satoshis: the amount contributed by us to the channel. This will increase our channel balance. + /// - our_funding_inputs: the funding inputs provided by us. If our contribution is positive, our funding inputs must cover at least that amount. + #[cfg(splicing)] + pub fn splice_channel( + &self, channel_id: &ChannelId, counterparty_node_id: &PublicKey, our_funding_contribution_satoshis: i64, + our_funding_inputs: Vec<(TxIn, Transaction)>, funding_feerate_perkw: u32, locktime: u32, + ) -> 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 mut peer_state_lock = peer_state_mutex.lock().unwrap(); + let peer_state = &mut *peer_state_lock; + + // Look for the channel + match peer_state.channel_by_id.entry(*channel_id) { + hash_map::Entry::Occupied(mut chan_phase_entry) => { + if let ChannelPhase::Funded(chan) = chan_phase_entry.get_mut() { + let msg = chan.splice_channel(our_funding_contribution_satoshis, our_funding_inputs, funding_feerate_perkw, locktime) + .map_err(|err| APIError::APIMisuseError { + err: format!( + "Cannot initiate Splicing, {}, channel ID {}", err, channel_id + ) + })?; + + peer_state.pending_msg_events.push(events::MessageSendEvent::SendSpliceInit { + node_id: *counterparty_node_id, + msg, + }); + + Ok(()) + } else { + Err(APIError::ChannelUnavailable { + err: format!( + "Channel with id {} is not funded, cannot splice it", + channel_id + ) + }) + } + }, + hash_map::Entry::Vacant(_) => { + return Err(APIError::ChannelUnavailable { + err: format!( + "Channel with id {} not found for the passed counterparty node_id {}", + channel_id, counterparty_node_id, + ) + }); + }, + } + } + fn can_forward_htlc_to_outgoing_channel( &self, chan: &mut Channel, msg: &msgs::UpdateAddHTLC, next_packet: &NextPacketDetails ) -> Result<(), (&'static str, u16)> { @@ -9321,6 +9378,94 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ Ok(NotifyOption::SkipPersistHandleEvents) } + /// Handle incoming splice request, transition channel to splice-pending (unless some check fails). + #[cfg(splicing)] + fn internal_splice_init(&self, counterparty_node_id: &PublicKey, msg: &msgs::SpliceInit) -> Result<(), MsgHandleErrInternal> { + // TODO(splicing): if we accept splicing, quiescence + + let per_peer_state = self.per_peer_state.read().unwrap(); + let peer_state_mutex = per_peer_state.get(counterparty_node_id) + .ok_or_else(|| { + debug_assert!(false); + MsgHandleErrInternal::send_err_msg_no_close(format!("Can't find a peer matching the passed counterparty node_id {}", counterparty_node_id), msg.channel_id) + })?; + let mut peer_state_lock = peer_state_mutex.lock().unwrap(); + let peer_state = &mut *peer_state_lock; + + // Look for the channel + match peer_state.channel_by_id.entry(msg.channel_id) { + hash_map::Entry::Vacant(_) => return Err(MsgHandleErrInternal::send_err_msg_no_close(format!( + "Got a message for a channel from the wrong node! No such channel for the passed counterparty_node_id {}, channel_id {}", + counterparty_node_id, msg.channel_id, + ), msg.channel_id)), + hash_map::Entry::Occupied(mut chan_entry) => { + if let ChannelPhase::Funded(chan) = chan_entry.get_mut() { + match chan.splice_init(msg) { + Ok(splice_ack_msg) => { + peer_state.pending_msg_events.push(events::MessageSendEvent::SendSpliceAck { + node_id: *counterparty_node_id, + msg: splice_ack_msg, + }); + }, + Err(err) => { + return Err(MsgHandleErrInternal::from_chan_no_close(err, msg.channel_id)); + } + } + } else { + return Err(MsgHandleErrInternal::send_err_msg_no_close("Channel is not funded, cannot be spliced".to_owned(), msg.channel_id)); + } + }, + }; + + // TODO(splicing): + // Change channel, change phase (remove and add) + // Create new post-splice channel + // etc. + + Ok(()) + } + + /// Handle incoming splice request ack, transition channel to splice-pending (unless some check fails). + #[cfg(splicing)] + fn internal_splice_ack(&self, counterparty_node_id: &PublicKey, msg: &msgs::SpliceAck) -> Result<(), MsgHandleErrInternal> { + let per_peer_state = self.per_peer_state.read().unwrap(); + let peer_state_mutex = per_peer_state.get(counterparty_node_id) + .ok_or_else(|| { + debug_assert!(false); + MsgHandleErrInternal::send_err_msg_no_close(format!("Can't find a peer matching the passed counterparty node_id {}", counterparty_node_id), msg.channel_id) + })?; + let mut peer_state_lock = peer_state_mutex.lock().unwrap(); + let peer_state = &mut *peer_state_lock; + + // Look for the channel + match peer_state.channel_by_id.entry(msg.channel_id) { + hash_map::Entry::Vacant(_) => return Err(MsgHandleErrInternal::send_err_msg_no_close(format!( + "Got a message for a channel from the wrong node! No such channel for the passed counterparty_node_id {}", + counterparty_node_id + ), msg.channel_id)), + hash_map::Entry::Occupied(mut chan) => { + if let ChannelPhase::Funded(chan) = chan.get_mut() { + match chan.splice_ack(msg) { + Ok(_) => {} + Err(err) => { + return Err(MsgHandleErrInternal::from_chan_no_close(err, msg.channel_id)); + } + } + } else { + return Err(MsgHandleErrInternal::send_err_msg_no_close("Channel is not funded, cannot splice".to_owned(), msg.channel_id)); + } + }, + }; + + // TODO(splicing): + // Change channel, change phase (remove and add) + // Create new post-splice channel + // Start splice funding transaction negotiation + // etc. + + Err(MsgHandleErrInternal::send_err_msg_no_close("TODO(splicing): Splicing is not implemented (splice_ack)".to_owned(), msg.channel_id)) + } + /// Process pending events from the [`chain::Watch`], returning whether any events were processed. fn process_pending_monitor_events(&self) -> bool { debug_assert!(self.total_consistency_lock.try_write().is_err()); // Caller holds read lock @@ -11343,28 +11488,42 @@ where fn handle_stfu(&self, counterparty_node_id: PublicKey, msg: &msgs::Stfu) { let _: Result<(), _> = handle_error!(self, Err(MsgHandleErrInternal::send_err_msg_no_close( "Quiescence not supported".to_owned(), - msg.channel_id.clone())), counterparty_node_id); + msg.channel_id)), counterparty_node_id); } #[cfg(splicing)] fn handle_splice_init(&self, counterparty_node_id: PublicKey, msg: &msgs::SpliceInit) { - let _: Result<(), _> = handle_error!(self, Err(MsgHandleErrInternal::send_err_msg_no_close( - "Splicing not supported".to_owned(), - msg.channel_id.clone())), counterparty_node_id); + let _persistence_guard = PersistenceNotifierGuard::optionally_notify(self, || { + let res = self.internal_splice_init(&counterparty_node_id, msg); + let persist = match &res { + Err(e) if e.closes_channel() => NotifyOption::DoPersist, + Err(_) => NotifyOption::SkipPersistHandleEvents, + Ok(()) => NotifyOption::SkipPersistNoEvents, + }; + let _ = handle_error!(self, res, counterparty_node_id); + persist + }); } #[cfg(splicing)] fn handle_splice_ack(&self, counterparty_node_id: PublicKey, msg: &msgs::SpliceAck) { - let _: Result<(), _> = handle_error!(self, Err(MsgHandleErrInternal::send_err_msg_no_close( - "Splicing not supported (splice_ack)".to_owned(), - msg.channel_id.clone())), counterparty_node_id); + let _persistence_guard = PersistenceNotifierGuard::optionally_notify(self, || { + let res = self.internal_splice_ack(&counterparty_node_id, msg); + let persist = match &res { + Err(e) if e.closes_channel() => NotifyOption::DoPersist, + Err(_) => NotifyOption::SkipPersistHandleEvents, + Ok(()) => NotifyOption::SkipPersistNoEvents, + }; + let _ = handle_error!(self, res, counterparty_node_id); + persist + }); } #[cfg(splicing)] fn handle_splice_locked(&self, counterparty_node_id: PublicKey, msg: &msgs::SpliceLocked) { let _: Result<(), _> = handle_error!(self, Err(MsgHandleErrInternal::send_err_msg_no_close( "Splicing not supported (splice_locked)".to_owned(), - msg.channel_id.clone())), counterparty_node_id); + msg.channel_id)), counterparty_node_id); } fn handle_shutdown(&self, counterparty_node_id: PublicKey, msg: &msgs::Shutdown) { diff --git a/lightning/src/ln/functional_test_utils.rs b/lightning/src/ln/functional_test_utils.rs index b4f172b4a27..477ea7f05fc 100644 --- a/lightning/src/ln/functional_test_utils.rs +++ b/lightning/src/ln/functional_test_utils.rs @@ -801,7 +801,7 @@ macro_rules! get_event_msg { assert_eq!(*node_id, $node_id); (*msg).clone() }, - _ => panic!("Unexpected event"), + _ => panic!("Unexpected event {:?}", events[0]), } } } diff --git a/lightning/src/ln/functional_tests_splice.rs b/lightning/src/ln/functional_tests_splice.rs new file mode 100644 index 00000000000..c17334ef63a --- /dev/null +++ b/lightning/src/ln/functional_tests_splice.rs @@ -0,0 +1,332 @@ +// This file is Copyright its original authors, visible in version control +// history. +// +// This file is licensed under the Apache License, Version 2.0 or the MIT license +// , at your option. +// You may not use this file except in accordance with one or both of these +// licenses. + +//! Tests that test standing up a network of ChannelManagers, creating channels, sending +//! payments/messages between them, and often checking the resulting ChannelMonitors are able to +//! claim outputs on-chain. + +use crate::events::{Event, MessageSendEvent, MessageSendEventsProvider}; +use crate::ln::functional_test_utils::*; +use crate::ln::msgs::ChannelMessageHandler; +use crate::util::config::{ChannelHandshakeConfig, UserConfig}; + +/// Splicing test, simple splice-in flow. Starts with opening a V1 channel first. +/// Builds on test_channel_open_simple() +#[test] +fn test_v1_splice_in() { + // Set up a network of 2 nodes + let cfg = UserConfig { + channel_handshake_config: ChannelHandshakeConfig { ..Default::default() }, + ..Default::default() + }; + let chanmon_cfgs = create_chanmon_cfgs(2); + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[Some(cfg), None]); + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + + // Initiator and Acceptor nodes + let initiator_node_index = 0; + let acceptor_node_index = 1; + let initiator_node = &nodes[initiator_node_index]; + let acceptor_node = &nodes[acceptor_node_index]; + + // Instantiate channel parameters where we push the maximum msats given our funding satoshis + let channel_value_sat = 100_000; // same as funding satoshis + let push_msat = 0; + let channel_reserve_amnt_sat = 1_000; + + let expected_funded_channel_id = + "ae3367da2c13bc1ceb86bf56418f62828f7ce9d6bfb15a46af5ba1f1ed8b124f"; + + // Have initiator_node initiate a channel to acceptor_node with aforementioned parameters + let channel_id_temp1 = initiator_node + .node + .create_channel( + acceptor_node.node.get_our_node_id(), + channel_value_sat, + push_msat, + 42, + None, + None, + ) + .unwrap(); + + // Extract the channel open message from initiator_node to acceptor_node + let open_channel_message = get_event_msg!( + initiator_node, + MessageSendEvent::SendOpenChannel, + acceptor_node.node.get_our_node_id() + ); + let expected_initiator_funding_key = + "03c21e841cbc0b48197d060c71e116c185fa0ac281b7d0aa5924f535154437ca3b"; + assert_eq!( + open_channel_message.common_fields.funding_pubkey.to_string(), + expected_initiator_funding_key + ); + + let _res = acceptor_node + .node + .handle_open_channel(initiator_node.node.get_our_node_id(), &open_channel_message.clone()); + // Extract the accept channel message from acceptor_node to initiator_node + let accept_channel_message = get_event_msg!( + acceptor_node, + MessageSendEvent::SendAcceptChannel, + initiator_node.node.get_our_node_id() + ); + let expected_acceptor_funding_key = + "039481c28b904cbe12681e79937373fc76245c1b29871028ae60ba3152162c319b"; + assert_eq!( + accept_channel_message.common_fields.funding_pubkey.to_string(), + expected_acceptor_funding_key + ); + + let _res = initiator_node.node.handle_accept_channel( + acceptor_node.node.get_our_node_id(), + &accept_channel_message.clone(), + ); + // Note: FundingGenerationReady emitted, checked and used below + let (_channel_id_temp2, funding_tx, _funding_output) = create_funding_transaction( + &initiator_node, + &acceptor_node.node.get_our_node_id(), + channel_value_sat, + 42, + ); + + // Funding transation created, provide it + let _res = initiator_node + .node + .funding_transaction_generated( + channel_id_temp1, + acceptor_node.node.get_our_node_id(), + funding_tx.clone(), + ) + .unwrap(); + + let funding_created_message = get_event_msg!( + initiator_node, + MessageSendEvent::SendFundingCreated, + acceptor_node.node.get_our_node_id() + ); + + let _res = acceptor_node + .node + .handle_funding_created(initiator_node.node.get_our_node_id(), &funding_created_message); + + assert_eq!(initiator_node.node.list_channels().len(), 1); + { + let channel = &initiator_node.node.list_channels()[0]; + assert!(!channel.is_channel_ready); + } + // do checks on the acceptor node as well (capacity, etc.) + assert_eq!(acceptor_node.node.list_channels().len(), 1); + { + let channel = &acceptor_node.node.list_channels()[0]; + assert!(!channel.is_channel_ready); + } + + let funding_signed_message = get_event_msg!( + acceptor_node, + MessageSendEvent::SendFundingSigned, + initiator_node.node.get_our_node_id() + ); + let _res = initiator_node + .node + .handle_funding_signed(acceptor_node.node.get_our_node_id(), &funding_signed_message); + // Take new channel ID + let channel_id2 = funding_signed_message.channel_id; + assert_eq!(channel_id2.to_string(), expected_funded_channel_id); + + // Check that funding transaction has been broadcasted + assert_eq!( + chanmon_cfgs[initiator_node_index].tx_broadcaster.txn_broadcasted.lock().unwrap().len(), + 1 + ); + let broadcasted_funding_tx = + chanmon_cfgs[initiator_node_index].tx_broadcaster.txn_broadcasted.lock().unwrap()[0] + .clone(); + + check_added_monitors!(initiator_node, 1); + let _ev = get_event!(initiator_node, Event::ChannelPending); + check_added_monitors!(acceptor_node, 1); + let _ev = get_event!(acceptor_node, Event::ChannelPending); + + // Simulate confirmation of the funding tx + confirm_transaction(&initiator_node, &broadcasted_funding_tx); + let channel_ready_message = get_event_msg!( + initiator_node, + MessageSendEvent::SendChannelReady, + acceptor_node.node.get_our_node_id() + ); + + confirm_transaction(&acceptor_node, &broadcasted_funding_tx); + let channel_ready_message2 = get_event_msg!( + acceptor_node, + MessageSendEvent::SendChannelReady, + initiator_node.node.get_our_node_id() + ); + + let _res = acceptor_node + .node + .handle_channel_ready(initiator_node.node.get_our_node_id(), &channel_ready_message); + let _ev = get_event!(acceptor_node, Event::ChannelReady); + let _channel_update = get_event_msg!( + acceptor_node, + MessageSendEvent::SendChannelUpdate, + initiator_node.node.get_our_node_id() + ); + + let _res = initiator_node + .node + .handle_channel_ready(acceptor_node.node.get_our_node_id(), &channel_ready_message2); + let _ev = get_event!(initiator_node, Event::ChannelReady); + let _channel_update = get_event_msg!( + initiator_node, + MessageSendEvent::SendChannelUpdate, + acceptor_node.node.get_our_node_id() + ); + + // check channel capacity and other parameters + assert_eq!(initiator_node.node.list_channels().len(), 1); + { + let channel = &initiator_node.node.list_channels()[0]; + assert_eq!(channel.channel_id.to_string(), expected_funded_channel_id); + 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.funding_txo.unwrap().txid, funding_tx.compute_txid()); + assert_eq!(channel.confirmations.unwrap(), 10); + } + // do checks on the acceptor node as well (capacity, etc.) + assert_eq!(acceptor_node.node.list_channels().len(), 1); + { + let channel = &acceptor_node.node.list_channels()[0]; + assert_eq!(channel.channel_id.to_string(), expected_funded_channel_id); + 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.funding_txo.unwrap().txid, funding_tx.compute_txid()); + assert_eq!(channel.confirmations.unwrap(), 10); + } + + // ==== Channel is now ready for normal operation + + // === Start of Splicing + println!("Start of Splicing ..., channel_id {}", channel_id2); + + // Amount being added to the channel through the splice-in + let splice_in_sats: u64 = 20000; + let funding_feerate_perkw = 1024; // TODO + let locktime = 0; // TODO + + // Create additional inputs + let extra_splice_funding_input_sats = 35_000; + let funding_inputs = create_dual_funding_utxos_with_prev_txs( + &initiator_node, + &[extra_splice_funding_input_sats], + ); + // Initiate splice-in (on initiator_node) + let _res = initiator_node + .node + .splice_channel( + &channel_id2, + &acceptor_node.node.get_our_node_id(), + splice_in_sats as i64, + funding_inputs, + funding_feerate_perkw, + locktime, + ) + .unwrap(); + // Extract the splice message from node0 to node1 + let splice_init_msg = get_event_msg!( + initiator_node, + MessageSendEvent::SendSpliceInit, + acceptor_node.node.get_our_node_id() + ); + assert_eq!(splice_init_msg.funding_contribution_satoshis, splice_in_sats as i64); + assert_eq!(splice_init_msg.funding_feerate_perkw, funding_feerate_perkw); + assert_eq!(splice_init_msg.funding_pubkey.to_string(), expected_initiator_funding_key); + assert!(splice_init_msg.require_confirmed_inputs.is_none()); + + let _res = acceptor_node + .node + .handle_splice_init(initiator_node.node.get_our_node_id(), &splice_init_msg); + // Extract the splice_ack message from node1 to node0 + let splice_ack_msg = get_event_msg!( + acceptor_node, + MessageSendEvent::SendSpliceAck, + initiator_node.node.get_our_node_id() + ); + assert_eq!(splice_ack_msg.funding_contribution_satoshis, 0); + assert_eq!(splice_ack_msg.funding_pubkey.to_string(), expected_acceptor_funding_key); + assert!(splice_ack_msg.require_confirmed_inputs.is_none()); + + // still pre-splice channel: capacity not updated, channel usable, and funding tx set + assert_eq!(acceptor_node.node.list_channels().len(), 1); + { + let channel = &acceptor_node.node.list_channels()[0]; + assert_eq!(channel.channel_id.to_string(), expected_funded_channel_id); + 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!(channel.funding_txo.is_some()); + assert!(channel.confirmations.unwrap() > 0); + } + + let _res = initiator_node + .node + .handle_splice_ack(acceptor_node.node.get_our_node_id(), &splice_ack_msg); + + // still pre-splice channel: capacity not updated, channel usable, and funding tx set + assert_eq!(initiator_node.node.list_channels().len(), 1); + { + let channel = &initiator_node.node.list_channels()[0]; + assert_eq!(channel.channel_id.to_string(), expected_funded_channel_id); + 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!(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()); + + // TODO(splicing): continue with splice transaction negotiation + + // === Close channel, cooperatively + initiator_node.node.close_channel(&channel_id2, &acceptor_node.node.get_our_node_id()).unwrap(); + let node0_shutdown_message = get_event_msg!( + initiator_node, + MessageSendEvent::SendShutdown, + acceptor_node.node.get_our_node_id() + ); + acceptor_node + .node + .handle_shutdown(initiator_node.node.get_our_node_id(), &node0_shutdown_message); + let nodes_1_shutdown = get_event_msg!( + acceptor_node, + MessageSendEvent::SendShutdown, + initiator_node.node.get_our_node_id() + ); + initiator_node.node.handle_shutdown(acceptor_node.node.get_our_node_id(), &nodes_1_shutdown); + let _ = get_event_msg!( + initiator_node, + MessageSendEvent::SendClosingSigned, + acceptor_node.node.get_our_node_id() + ); +} diff --git a/lightning/src/ln/mod.rs b/lightning/src/ln/mod.rs index 1f6a1096e11..9f3d5cfaf4b 100644 --- a/lightning/src/ln/mod.rs +++ b/lightning/src/ln/mod.rs @@ -58,6 +58,9 @@ mod blinded_payment_tests; #[cfg(test)] #[allow(unused_mut)] mod functional_tests; +#[cfg(all(test, splicing))] +#[allow(unused_mut)] +mod functional_tests_splice; #[cfg(test)] #[allow(unused_mut)] mod max_payment_path_len_tests;