diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index 32e81cabda0..9a63c9e78dc 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -10,8 +10,7 @@ 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::transaction::{Transaction, TxIn, TxOut}; use bitcoin::sighash::EcdsaSighashType; use bitcoin::consensus::encode; use bitcoin::absolute::LockTime; @@ -25,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; +use bitcoin::{secp256k1, sighash}; +#[cfg(splicing)] +use bitcoin::{Sequence, Witness}; 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, + estimate_input_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}; @@ -69,7 +70,7 @@ use crate::prelude::*; use core::{cmp,mem,fmt}; use core::ops::Deref; #[cfg(any(test, fuzzing, debug_assertions))] -use crate::sync::Mutex; +use crate::sync::{Arc, Mutex}; use crate::sign::type_resolver::ChannelSignerType; use super::channel_keys::{DelayedPaymentBasepoint, HtlcBasepoint, RevocationBasepoint}; @@ -112,6 +113,7 @@ enum FeeUpdateState { Outbound, } +#[derive(Clone)] enum InboundHTLCRemovalReason { FailRelay(msgs::OnionErrorPacket), FailMalformed(([u8; 32], u16)), @@ -146,6 +148,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. @@ -220,6 +223,7 @@ impl From<&InboundHTLCState> for Option { } } +#[derive(Clone)] struct InboundHTLCOutput { htlc_id: u64, amount_msat: u64, @@ -228,7 +232,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 @@ -310,7 +315,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, @@ -323,7 +329,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 @@ -798,7 +805,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`. @@ -1116,6 +1123,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, } @@ -1133,6 +1141,9 @@ pub(super) enum ChannelPhase where SP::Target: SignerProvider { UnfundedOutboundV2(OutboundV2Channel), UnfundedInboundV2(InboundV2Channel), Funded(Channel), + #[cfg(splicing)] + /// Used during splicing, channel is funded but a new funding is being renegotiated. + RefundingV2(SplicingChannel), } impl<'a, SP: Deref> ChannelPhase where @@ -1146,6 +1157,8 @@ impl<'a, SP: Deref> ChannelPhase where ChannelPhase::UnfundedInboundV1(chan) => &chan.context, ChannelPhase::UnfundedOutboundV2(chan) => &chan.context, ChannelPhase::UnfundedInboundV2(chan) => &chan.context, + #[cfg(splicing)] + ChannelPhase::RefundingV2(chan) => &chan.pre_funded.context, } } @@ -1156,11 +1169,85 @@ impl<'a, SP: Deref> ChannelPhase where ChannelPhase::UnfundedInboundV1(ref mut chan) => &mut chan.context, ChannelPhase::UnfundedOutboundV2(ref mut chan) => &mut chan.context, ChannelPhase::UnfundedInboundV2(ref mut chan) => &mut chan.context, + #[cfg(splicing)] + ChannelPhase::RefundingV2(ref mut chan) => &mut chan.pre_funded.context, + } + } + + pub fn funded_channel(&self) -> Option<&Channel> { + match self { + ChannelPhase::Funded(chan) => Some(&chan), + #[cfg(splicing)] + ChannelPhase::RefundingV2(chan) => Some(&chan.pre_funded), + _ => None + } + } + + pub fn funded_channel_mut(&mut self) -> Option<&mut Channel> { + match self { + ChannelPhase::Funded(ref mut chan) => Some(chan), + #[cfg(splicing)] + ChannelPhase::RefundingV2(ref mut chan) => Some(&mut chan.pre_funded), + _ => None } } } +/// Struct holding together various state dureing splicing negotiation +#[cfg(splicing)] +pub(super) struct SplicingChannel where SP::Target: SignerProvider { + pub pre_funded: Channel, + pub post_pending: PendingV2Channel, + pub post_funded: Option>, +} + +#[cfg(splicing)] +impl SplicingChannel where SP::Target: SignerProvider { + pub(super) fn new(pre_funded: Channel, post_pending: PendingV2Channel) -> Self { + Self { + pre_funded, + post_pending, + post_funded: None, + } + } + + pub fn splice_init( + &mut self, msg: &msgs::SpliceInit, signer_provider: &SP, entropy_source: &ES, holder_node_id: PublicKey, logger: &L, + ) -> Result where ES::Target: EntropySource, L::Target: Logger { + self.post_pending.splice_init(msg, signer_provider, entropy_source, holder_node_id, logger) + } + + pub fn splice_ack( + &mut self, msg: &msgs::SpliceAck, our_funding_contribution: i64, signer_provider: &SP, entropy_source: &ES, holder_node_id: PublicKey, logger: &L, + ) -> Result, ChannelError> where ES::Target: EntropySource, L::Target: Logger { + self.post_pending.splice_ack(msg, our_funding_contribution, signer_provider, entropy_source, holder_node_id, logger) + } + + pub fn tx_add_input(&mut self, msg: &msgs::TxAddInput) -> InteractiveTxMessageSendResult { + self.post_pending.tx_add_input(msg) + } + + pub fn tx_add_output(&mut self, msg: &msgs::TxAddOutput)-> InteractiveTxMessageSendResult { + self.post_pending.tx_add_output(msg) + } + + pub fn tx_complete(&mut self, msg: &msgs::TxComplete) -> HandleTxCompleteResult { + self.post_pending.tx_complete(msg) + } + + pub fn funding_tx_constructed( + &mut self, signing_session: &mut InteractiveTxSigningSession, logger: &L + ) -> Result<(msgs::CommitmentSigned, Option), ChannelError> where L::Target: Logger { + self.post_pending.funding_tx_constructed(signing_session, logger) + } + + pub fn into_channel(self, signing_session: InteractiveTxSigningSession) -> Result, ChannelError>{ + self.post_pending.into_channel(signing_session) + } +} + /// Contains all state common to unfunded inbound/outbound channels. +#[derive(Default)] pub(super) struct UnfundedChannelContext { /// A counter tracking how many ticks have elapsed since this unfunded channel was /// created. If this unfunded channel reaches peer has yet to respond after reaching @@ -1182,6 +1269,101 @@ impl UnfundedChannelContext { } } +/// Info about a pending splice, used in the pre-splice channel +#[cfg(splicing)] +#[derive(Clone)] +pub(super) struct PendingSplicePre { + pub our_funding_contribution: i64, + pub funding_feerate_perkw: 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 as u16); + } + } + // 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. pub(super) struct ChannelContext where SP::Target: SignerProvider { config: LegacyChannelConfig, @@ -1305,10 +1487,10 @@ pub(super) struct ChannelContext where SP::Target: SignerProvider { #[cfg(debug_assertions)] /// Max to_local and to_remote outputs in a locally-generated commitment transaction - holder_max_commitment_tx_output: Mutex<(u64, u64)>, + holder_max_commitment_tx_output: Arc>, #[cfg(debug_assertions)] /// Max to_local and to_remote outputs in a remote-generated commitment transaction - counterparty_max_commitment_tx_output: Mutex<(u64, u64)>, + counterparty_max_commitment_tx_output: Arc>, // (fee_sats, skip_remote_output, fee_range, holder_sig) last_sent_closing_fee: Option<(u64, bool, ClosingSignedFeeRange, Option)>, @@ -1420,9 +1602,9 @@ pub(super) struct ChannelContext where SP::Target: SignerProvider { // be, by comparing the cached values to the fee of the tranaction generated by // `build_commitment_transaction`. #[cfg(any(test, fuzzing))] - next_local_commitment_tx_fee_info_cached: Mutex>, + next_local_commitment_tx_fee_info_cached: Arc>>, #[cfg(any(test, fuzzing))] - next_remote_commitment_tx_fee_info_cached: Mutex>, + next_remote_commitment_tx_fee_info_cached: Arc>>, /// lnd has a long-standing bug where, upon reconnection, if the channel is not yet confirmed /// they will not send a channel_reestablish until the channel locks in. Then, they will send a @@ -1684,6 +1866,109 @@ pub(super) trait InteractivelyFunded where SP::Target: SignerProvider fn dual_funding_context(&self) -> &DualFundingChannelContext; + fn dual_funding_context_mut(&mut self) -> &mut DualFundingChannelContext; + + fn is_initiator(&self) -> bool; + + 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_mut().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.context().get_value_satoshis(); + let funding_output_script_pubkey = self.context().get_funding_redeemscript().to_p2wsh(); + let expected_remote_shared_funding_output = if self.is_initiator() { + 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.is_initiator(), 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.is_initiator(), + 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_mut() = Some(tx_constructor); + + Ok(msg) + } + fn tx_add_input(&mut self, msg: &msgs::TxAddInput) -> InteractiveTxMessageSendResult { InteractiveTxMessageSendResult(match self.interactive_tx_constructor_mut() { Some(ref mut tx_constructor) => tx_constructor.handle_tx_add_input(msg).map_err( @@ -1846,9 +2131,15 @@ impl InteractivelyFunded for OutboundV2Channel where SP::Targ fn dual_funding_context(&self) -> &DualFundingChannelContext { &self.dual_funding_context } + fn dual_funding_context_mut(&mut self) -> &mut DualFundingChannelContext { + &mut self.dual_funding_context + } fn interactive_tx_constructor_mut(&mut self) -> &mut Option { &mut self.interactive_tx_constructor } + fn is_initiator(&self) -> bool { + true + } } impl InteractivelyFunded for InboundV2Channel where SP::Target: SignerProvider { @@ -1861,9 +2152,15 @@ impl InteractivelyFunded for InboundV2Channel where SP::Targe fn dual_funding_context(&self) -> &DualFundingChannelContext { &self.dual_funding_context } + fn dual_funding_context_mut(&mut self) -> &mut DualFundingChannelContext { + &mut self.dual_funding_context + } fn interactive_tx_constructor_mut(&mut self) -> &mut Option { &mut self.interactive_tx_constructor } + fn is_initiator(&self) -> bool { + false + } } impl ChannelContext where SP::Target: SignerProvider { @@ -2120,9 +2417,9 @@ impl ChannelContext where SP::Target: SignerProvider { #[cfg(debug_assertions)] - holder_max_commitment_tx_output: Mutex::new((value_to_self_msat, (channel_value_satoshis * 1000 - msg_push_msat).saturating_sub(value_to_self_msat))), + holder_max_commitment_tx_output: Arc::new(Mutex::new((value_to_self_msat, (channel_value_satoshis * 1000 - msg_push_msat).saturating_sub(value_to_self_msat)))), #[cfg(debug_assertions)] - counterparty_max_commitment_tx_output: Mutex::new((value_to_self_msat, (channel_value_satoshis * 1000 - msg_push_msat).saturating_sub(value_to_self_msat))), + counterparty_max_commitment_tx_output: Arc::new(Mutex::new((value_to_self_msat, (channel_value_satoshis * 1000 - msg_push_msat).saturating_sub(value_to_self_msat)))), last_sent_closing_fee: None, last_received_closing_sig: None, @@ -2180,9 +2477,9 @@ impl ChannelContext where SP::Target: SignerProvider { announcement_sigs: None, #[cfg(any(test, fuzzing))] - next_local_commitment_tx_fee_info_cached: Mutex::new(None), + next_local_commitment_tx_fee_info_cached: Arc::new(Mutex::new(None)), #[cfg(any(test, fuzzing))] - next_remote_commitment_tx_fee_info_cached: Mutex::new(None), + next_remote_commitment_tx_fee_info_cached: Arc::new(Mutex::new(None)), workaround_lnd_bug_4006: None, sent_message_awaiting_response: None, @@ -2358,9 +2655,9 @@ impl ChannelContext where SP::Target: SignerProvider { // We'll add our counterparty's `funding_satoshis` to these max commitment output assertions // when we receive `accept_channel2`. #[cfg(debug_assertions)] - holder_max_commitment_tx_output: Mutex::new((channel_value_satoshis * 1000 - push_msat, push_msat)), + holder_max_commitment_tx_output: Arc::new(Mutex::new((channel_value_satoshis * 1000 - push_msat, push_msat))), #[cfg(debug_assertions)] - counterparty_max_commitment_tx_output: Mutex::new((channel_value_satoshis * 1000 - push_msat, push_msat)), + counterparty_max_commitment_tx_output: Arc::new(Mutex::new((channel_value_satoshis * 1000 - push_msat, push_msat))), last_sent_closing_fee: None, last_received_closing_sig: None, @@ -2416,9 +2713,9 @@ impl ChannelContext where SP::Target: SignerProvider { announcement_sigs: None, #[cfg(any(test, fuzzing))] - next_local_commitment_tx_fee_info_cached: Mutex::new(None), + next_local_commitment_tx_fee_info_cached: Arc::new(Mutex::new(None)), #[cfg(any(test, fuzzing))] - next_remote_commitment_tx_fee_info_cached: Mutex::new(None), + next_remote_commitment_tx_fee_info_cached: Arc::new(Mutex::new(None)), workaround_lnd_bug_4006: None, sent_message_awaiting_response: None, @@ -3623,6 +3920,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. /// @@ -4093,6 +4417,231 @@ impl ChannelContext where SP::Target: SignerProvider { self.channel_transaction_parameters = channel_transaction_parameters; self.get_initial_counterparty_commitment_signature(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(), + holder_commitment_point: self.holder_commitment_point, + 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, + 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, + #[cfg(any(test, fuzzing))] + historical_inbound_htlc_fulfills: self.historical_inbound_htlc_fulfills.clone(), + 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(), + } + } + + /// 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, + is_outgoing: bool, + counterparty_funding_pubkey: &PublicKey, + our_funding_contribution: i64, + their_funding_contribution: i64, + holder_signer: ::EcdsaSigner, + logger: &L, + ) -> Result, ChannelError> where L::Target: Logger + { + let pre_channel_value = pre_splice_context.channel_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_context.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); + + // New channel value + context.channel_value_satoshis = post_channel_value; + // Update value to self + context.value_to_self_msat = value_to_self_msat; + // Reset funding + context.funding_transaction = None; + context.funding_tx_confirmed_in = None; + context.funding_tx_confirmation_height = 0; + context.channel_transaction_parameters.funding_outpoint = None; + // 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; + #[cfg(debug_assertions)] + { + context.holder_max_commitment_tx_output = Arc::new(Mutex::new((value_to_self_msat, post_channel_value.saturating_sub(value_to_self_msat)))); + context.counterparty_max_commitment_tx_output = Arc::new(Mutex::new((value_to_self_msat, post_channel_value.saturating_sub(value_to_self_msat)))); + } + // Reset + context.blocked_monitor_updates = Vec::new(); + + log_debug!(logger, "Splicing channel context: value {} old {}, dir {}, value to self {}, funding keys local {} cp {}", + context.channel_value_satoshis, pre_channel_value, + if is_outgoing { "outgoing" } else { "incoming" }, + context.value_to_self_msat, + context.channel_transaction_parameters.holder_pubkeys.funding_pubkey, counterparty_funding_pubkey + ); + + Ok(context) + } + + /// Splice process starting; update state, log, etc. + #[cfg(splicing)] + pub(crate) fn splice_start(&mut self, is_outgoing: bool, logger: &L) where L::Target: Logger { + // Set state, by this point splice_init/splice_ack handshake is complete + // TODO(splicing) + // self.channel_state = ChannelState::NegotiatingFunding( + // NegotiatingFundingFlags::OUR_INIT_SENT | NegotiatingFundingFlags::THEIR_INIT_SENT + // ); + log_info!(logger, "Splicing process started, new channel value {}, outgoing {}, channel_id {}", + self.channel_value_satoshis, is_outgoing, self.channel_id); + } + + /// 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 @@ -4150,6 +4699,54 @@ fn get_v2_channel_reserve_satoshis(channel_value_satoshis: u64, dust_limit_satos cmp::min(channel_value_satoshis, cmp::max(q, dust_limit_satoshis)) } +pub(super) fn maybe_add_funding_change_output(signer_provider: &SP, is_initiator: bool, + our_funding_satoshis: u64, funding_inputs_prev_outputs: &Vec, + funding_outputs: &mut Vec, funding_feerate_sat_per_1000_weight: u32, + total_input_satoshis: u64, holder_dust_limit_satoshis: u64, channel_keys_id: [u8; 32], +) -> Result, ChannelError> where + SP::Target: SignerProvider, +{ + let our_funding_inputs_weight = funding_inputs_prev_outputs.iter().fold(0u64, |weight, prev_output| { + weight.saturating_add(estimate_input_weight(prev_output).to_wu()) + }); + let our_funding_outputs_weight = funding_outputs.iter().fold(0u64, |weight, out| { + weight.saturating_add(get_output_weight(&out.tx_out().script_pubkey).to_wu()) + }); + let our_contributed_weight = our_funding_outputs_weight.saturating_add(our_funding_inputs_weight); + let mut fees_sats = fee_for_weight(funding_feerate_sat_per_1000_weight, our_contributed_weight); + + // If we are the initiator, we must pay for weight of all common fields in the funding transaction. + if is_initiator { + let common_fees = fee_for_weight(funding_feerate_sat_per_1000_weight, TX_COMMON_FIELDS_WEIGHT); + fees_sats = fees_sats.saturating_add(common_fees); + } + + let remaining_value = total_input_satoshis + .saturating_sub(our_funding_satoshis) + .saturating_sub(fees_sats); + + if remaining_value < holder_dust_limit_satoshis { + Ok(None) + } else { + let change_script = signer_provider.get_destination_script(channel_keys_id).map_err( + |_| ChannelError::Close(( + "Failed to get change script as new destination script".to_owned(), + ClosureReason::ProcessingError { err: "Failed to get change script as new destination script".to_owned() } + )) + )?; + let mut change_output = TxOut { + value: Amount::from_sat(remaining_value), + script_pubkey: change_script, + }; + let change_output_weight = get_output_weight(&change_output.script_pubkey).to_wu(); + + let change_output_fee = fee_for_weight(funding_feerate_sat_per_1000_weight, change_output_weight); + change_output.value = Amount::from_sat(remaining_value.saturating_sub(change_output_fee)); + funding_outputs.push(OutputOwned::Single(change_output.clone())); + Ok(Some(change_output)) + } +} + pub(super) fn calculate_our_funding_satoshis( is_initiator: bool, funding_inputs: &[(TxIn, TransactionU16LenLimited)], total_witness_weight: Weight, funding_feerate_sat_per_1000_weight: u32, @@ -4195,6 +4792,8 @@ pub(super) fn calculate_our_funding_satoshis( 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, @@ -4205,6 +4804,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)>, } @@ -4214,9 +4815,16 @@ pub(super) struct DualFundingChannelContext { pub(super) struct Channel where SP::Target: SignerProvider { pub context: ChannelContext, pub interactive_tx_signing_session: Option, + /// Info about an in-progress, pending splice (if any), on the pre-splice channel + #[cfg(splicing)] + pub pending_splice_pre: Option, + /// Info about an in-progress, pending splice (if any), on the post-splice channel + #[cfg(splicing)] + pending_splice_post: Option, } #[cfg(any(test, fuzzing))] +#[derive(Clone)] struct CommitmentTxInfoCached { fee: u64, total_pending_htlcs: usize, @@ -7815,6 +8423,125 @@ impl Channel where } } + /// 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 + pub fn is_splice_pending(&self) -> bool { + #[cfg(splicing)] + return self.pending_splice_post.is_some(); + #[cfg(not(splicing))] + false + } + + /// 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) + + // Sum and convert inputs + let mut sum_input = 0i64; + let mut funding_inputs = Vec::new(); + for (tx_in, tx) in our_funding_inputs.into_iter() { + sum_input += tx.output.get(tx_in.previous_output.vout as usize).map(|tx| tx.value.to_sat() as i64).unwrap_or(0); + let tx16 = TransactionU16LenLimited::new(tx).map_err(|_e| ChannelError::Warn(format!("Too large transaction")))?; + funding_inputs.push((tx_in, tx16)); + } + // Check that inputs are sufficient to cover our contribution + 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(PendingSplicePre { + our_funding_contribution: our_funding_contribution_satoshis, + funding_feerate_perkw, + locktime, + our_funding_inputs: funding_inputs, + }); + + let msg = self.context.get_splice_init(our_funding_contribution_satoshis, funding_feerate_perkw, locktime); + Ok(msg) + } + + /// Checks during handling splice_init + #[cfg(splicing)] + pub fn splice_init_checks( + &mut self, msg: &msgs::SpliceInit, _signer_provider: &SP, _entropy_source: &ES, _holder_node_id: PublicKey, + ) -> Result<(), ChannelError> where ES::Target: EntropySource { + let their_funding_contribution_satoshis = msg.funding_contribution_satoshis; + // TODO(splicing): Currently not possible to contribute on the splicing-acceptor side + let our_funding_contribution_satoshis = 0i64; + + // 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 = PendingSplicePre::compute_post_value(pre_channel_value, their_funding_contribution_satoshis, our_funding_contribution_satoshis); + let post_balance = PendingSplicePre::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)?; + Ok(()) + } // Send stuff to our remote peers: @@ -8295,7 +9022,7 @@ impl OutboundV1Channel where SP::Target: SignerProvider { pubkeys, logger, )?, - unfunded_context: UnfundedChannelContext { unfunded_channel_age_ticks: 0 } + unfunded_context: UnfundedChannelContext::default(), }; Ok(chan) } @@ -8491,6 +9218,10 @@ impl OutboundV1Channel where SP::Target: SignerProvider { let mut channel = Channel { context: self.context, interactive_tx_signing_session: None, + #[cfg(splicing)] + pending_splice_pre: None, + #[cfg(splicing)] + pending_splice_post: None, }; let need_channel_ready = channel.check_get_channel_ready(0, logger).is_some(); @@ -8599,7 +9330,7 @@ impl InboundV1Channel where SP::Target: SignerProvider { msg.push_msat, msg.common_fields.clone(), )?, - unfunded_context: UnfundedChannelContext { unfunded_channel_age_ticks: 0 }, + unfunded_context: UnfundedChannelContext::default(), }; Ok(chan) } @@ -8716,6 +9447,10 @@ impl InboundV1Channel where SP::Target: SignerProvider { let mut channel = Channel { context: self.context, interactive_tx_signing_session: None, + #[cfg(splicing)] + pending_splice_pre: None, + #[cfg(splicing)] + pending_splice_post: None, }; let need_channel_ready = channel.check_get_channel_ready(0, logger).is_some(); channel.monitor_updating_paused(false, false, need_channel_ready, Vec::new(), Vec::new(), Vec::new()); @@ -8724,6 +9459,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 outbound (from holder) channel using V2 channel establishment. pub(super) struct OutboundV2Channel where SP::Target: SignerProvider { pub context: ChannelContext, @@ -8731,6 +9489,9 @@ pub(super) struct OutboundV2Channel where SP::Target: SignerProvider pub dual_funding_context: DualFundingChannelContext, /// The current interactive transaction construction session under negotiation. interactive_tx_constructor: Option, + /// Info about an in-progress, pending splice (if any), on the post-splice channel + #[cfg(splicing)] + pending_splice_post: Option, } impl OutboundV2Channel where SP::Target: SignerProvider { @@ -8782,18 +9543,91 @@ impl OutboundV2Channel where SP::Target: SignerProvider { pubkeys, logger, )?, - unfunded_context: UnfundedChannelContext { unfunded_channel_age_ticks: 0 }, + unfunded_context: UnfundedChannelContext::default(), 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, + #[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: &Channel, + 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.is_splice_pending() { + 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.context.get_value_satoshis(); + + // Save the current funding transaction + let pre_funding_transaction = pre_splice_channel.context.funding_transaction.clone(); + let pre_funding_txo = pre_splice_channel.context.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, + ); + let post_channel_value = pending_splice_post.post_channel_value(); + + // Create new signer, using the new channel value. + // Note: channel_keys_id is not changed + let holder_signer = signer_provider.derive_channel_signer(post_channel_value, pre_splice_channel.context.channel_keys_id); + + let context = ChannelContext::new_for_splice( + &pre_splice_channel.context, + true, + 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::default(); + let post_chan = Self { + context, + dual_funding_context, + unfunded_context, + interactive_tx_constructor: 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. @@ -8861,10 +9695,47 @@ impl OutboundV2Channel where SP::Target: SignerProvider { let channel = Channel { context: self.context, interactive_tx_signing_session: Some(signing_session), + #[cfg(splicing)] + pending_splice_pre: None, + #[cfg(splicing)] + pending_splice_post: self.pending_splice_post, }; Ok(channel) } + + /// Handle splice_ack + #[cfg(splicing)] + pub fn splice_ack( + &mut self, msg: &msgs::SpliceAck, our_funding_contribution: i64, signer_provider: &SP, entropy_source: &ES, holder_node_id: PublicKey, logger: &L, + ) -> Result, ChannelError> where ES::Target: EntropySource, L::Target: Logger { + let their_funding_contribution_satoshis = msg.funding_contribution_satoshis; + + // check if splice is pending + let pending_splice = if let Some(pending_splice) = &self.pending_splice_post { + pending_splice + } else { + return Err(ChannelError::Warn(format!("Channel is not in pending splice"))); + }; + + let pre_channel_value = self.context.get_value_satoshis(); + let post_channel_value = PendingSplicePre::compute_post_value(pre_channel_value, our_funding_contribution, their_funding_contribution_satoshis); + let post_balance = PendingSplicePre::add_checked(self.context.value_to_self_msat, our_funding_contribution); + // Early check for reserve requirement, assuming maximum balance of full channel value + // This will also be checked later at tx_complete + let _res = self.context.check_balance_meets_reserve_requirements(post_balance, post_channel_value)?; + + // We need the current funding tx as an extra input + let prev_funding_input = pending_splice.get_input_of_previous_funding()?; + + // Apply start of splice change in the state + self.context.splice_start(true, logger); + + // Start interactive funding negotiation, with the previous funding transaction as an extra shared input + let tx_msg_opt = self.begin_interactive_funding_tx_construction(signer_provider, entropy_source, holder_node_id, Some(prev_funding_input)) + .map_err(|err| ChannelError::Warn(format!("V2 channel rejected due to sender error, {:?}", err)))?; + Ok(tx_msg_opt) + } } // A not-yet-funded inbound (from counterparty) channel using V2 channel establishment. @@ -8874,6 +9745,9 @@ pub(super) struct InboundV2Channel where SP::Target: SignerProvider { pub dual_funding_context: DualFundingChannelContext, /// The current interactive transaction construction session under negotiation. interactive_tx_constructor: Option, + /// Info about an in-progress, pending splice (if any), on the post-splice channel + #[cfg(splicing)] + pending_splice_post: Option, } impl InboundV2Channel where SP::Target: SignerProvider { @@ -8948,6 +9822,7 @@ impl InboundV2Channel where SP::Target: SignerProvider { let dual_funding_context = DualFundingChannelContext { our_funding_satoshis: 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: funding_inputs.clone(), @@ -8975,10 +9850,82 @@ impl InboundV2Channel where SP::Target: SignerProvider { context, dual_funding_context, interactive_tx_constructor, - unfunded_context: UnfundedChannelContext { unfunded_channel_age_ticks: 0 }, + unfunded_context: UnfundedChannelContext::default(), + #[cfg(splicing)] + pending_splice_post: None, }) } + /// Create new channel for splicing + #[cfg(splicing)] + pub fn new_spliced( + is_outbound: bool, + pre_splice_channel: &Channel, + 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.is_splice_pending() { + 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.context.get_value_satoshis(); + + // Save the current funding transaction + let pre_funding_transaction = pre_splice_channel.context.funding_transaction.clone(); + let pre_funding_txo = pre_splice_channel.context.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, + ); + let post_channel_value = pending_splice_post.post_channel_value(); + + // Create new signer, using the new channel value. + // Note: channel_keys_id is not changed + let holder_signer = signer_provider.derive_channel_signer(post_channel_value, pre_splice_channel.context.channel_keys_id); + + let context = ChannelContext::new_for_splice( + &pre_splice_channel.context, + false, + 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::default(); + let post_chan = Self { + context, + dual_funding_context, + unfunded_context, + interactive_tx_constructor: None, + pending_splice_post: Some(pending_splice_post), + }; + Ok(post_chan) + } + /// Marks an inbound channel as accepted and generates a [`msgs::AcceptChannelV2`] message which /// should be sent back to the counterparty node. /// @@ -9055,10 +10002,116 @@ impl InboundV2Channel where SP::Target: SignerProvider { let channel = Channel { context: self.context, interactive_tx_signing_session: Some(signing_session), + #[cfg(splicing)] + pending_splice_pre: None, + #[cfg(splicing)] + pending_splice_post: self.pending_splice_post, }; Ok(channel) } + + /// Handle splice_init + /// See also [`splice_init_checks`] + #[cfg(splicing)] + pub fn splice_init( + &mut self, _msg: &msgs::SpliceInit, signer_provider: &SP, entropy_source: &ES, holder_node_id: PublicKey, logger: &L, + ) -> Result where ES::Target: EntropySource, L::Target: Logger { + // TODO(splicing): Currently not possible to contribute on the splicing-acceptor side + let our_funding_contribution_satoshis = 0i64; + + // TODO(splicing): Store msg.funding_pubkey + + // Apply start of splice change in the state + self.context.splice_start(false, logger); + + let splice_ack_msg = self.context.get_splice_ack(our_funding_contribution_satoshis); + + // Start interactive funding negotiation. No extra input, as we are not the splice initiator + let _msg = self.begin_interactive_funding_tx_construction(signer_provider, entropy_source, holder_node_id, None) + .map_err(|err| ChannelError::Warn(format!("Failed to start interactive transaction construction, {:?}", err)))?; + + Ok(splice_ack_msg) + } +} + +/// Enum to tie together InboundV2Channel and OutboundV2Channel +/// Note: those two structs could be merged into one with an additional is_outbound field. +#[cfg(splicing)] +pub(super) enum PendingV2Channel where SP::Target: SignerProvider { + Inbound(InboundV2Channel), + Outbound(OutboundV2Channel), +} + +#[cfg(splicing)] +impl InteractivelyFunded for PendingV2Channel where SP::Target: SignerProvider { + fn context(&self) -> &ChannelContext { + match self { + PendingV2Channel::Inbound(chan) => &chan.context, + PendingV2Channel::Outbound(chan) => &chan.context, + } + } + fn context_mut(&mut self) -> &mut ChannelContext { + match self { + PendingV2Channel::Inbound(chan) => &mut chan.context, + PendingV2Channel::Outbound(chan) => &mut chan.context, + } + } + fn dual_funding_context(&self) -> &DualFundingChannelContext { + match self { + PendingV2Channel::Inbound(chan) => &chan.dual_funding_context, + PendingV2Channel::Outbound(chan) => &chan.dual_funding_context, + } + } + fn dual_funding_context_mut(&mut self) -> &mut DualFundingChannelContext { + match self { + PendingV2Channel::Inbound(chan) => &mut chan.dual_funding_context, + PendingV2Channel::Outbound(chan) => &mut chan.dual_funding_context, + } + } + fn interactive_tx_constructor_mut(&mut self) -> &mut Option { + match self { + PendingV2Channel::Inbound(chan) => &mut chan.interactive_tx_constructor, + PendingV2Channel::Outbound(chan) => &mut chan.interactive_tx_constructor, + } + } + fn is_initiator(&self) -> bool { + match &self { + PendingV2Channel::Inbound(_) => false, + PendingV2Channel::Outbound(_) => true, + } + } +} + +#[cfg(splicing)] +impl PendingV2Channel where SP::Target: SignerProvider { + /// Handle splice_init + /// See also [`splice_init_checks`] + pub fn splice_init( + &mut self, msg: &msgs::SpliceInit, signer_provider: &SP, entropy_source: &ES, holder_node_id: PublicKey, logger: &L, + ) -> Result where ES::Target: EntropySource, L::Target: Logger { + match self { + PendingV2Channel::Inbound(chan) => chan.splice_init(msg, signer_provider, entropy_source, holder_node_id, logger), + PendingV2Channel::Outbound(_) => Err(ChannelError::Warn(format!("Splice_init on an outbound channel"))), + } + } + + /// Handle splice_ack + pub fn splice_ack( + &mut self, msg: &msgs::SpliceAck, our_funding_contribution: i64, signer_provider: &SP, entropy_source: &ES, holder_node_id: PublicKey, logger: &L, + ) -> Result, ChannelError> where ES::Target: EntropySource, L::Target: Logger { + match self { + PendingV2Channel::Inbound(_) => Err(ChannelError::Warn(format!("Splice_ack on an inbound channel"))), + PendingV2Channel::Outbound(chan) => chan.splice_ack(msg, our_funding_contribution, signer_provider, entropy_source, holder_node_id, logger), + } + } + + pub fn into_channel(self, signing_session: InteractiveTxSigningSession) -> Result, ChannelError>{ + match self { + PendingV2Channel::Inbound(chan) => chan.into_channel(signing_session), + PendingV2Channel::Outbound(chan) => chan.into_channel(signing_session), + } + } } // Unfunded channel utilities @@ -10052,9 +11105,9 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, u32, &'c Ch feerate_per_kw, #[cfg(debug_assertions)] - holder_max_commitment_tx_output: Mutex::new((0, 0)), + holder_max_commitment_tx_output: Arc::new(Mutex::new((0, 0))), #[cfg(debug_assertions)] - counterparty_max_commitment_tx_output: Mutex::new((0, 0)), + counterparty_max_commitment_tx_output: Arc::new(Mutex::new((0, 0))), last_sent_closing_fee: None, last_received_closing_sig: None, @@ -10099,9 +11152,9 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, u32, &'c Ch announcement_sigs, #[cfg(any(test, fuzzing))] - next_local_commitment_tx_fee_info_cached: Mutex::new(None), + next_local_commitment_tx_fee_info_cached: Arc::new(Mutex::new(None)), #[cfg(any(test, fuzzing))] - next_remote_commitment_tx_fee_info_cached: Mutex::new(None), + next_remote_commitment_tx_fee_info_cached: Arc::new(Mutex::new(None)), workaround_lnd_bug_4006: None, sent_message_awaiting_response: None, @@ -10133,6 +11186,10 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, u32, &'c Ch next_funding_txid: None, }, interactive_tx_signing_session: None, + #[cfg(splicing)] + pending_splice_pre: None, + #[cfg(splicing)] + pending_splice_post: None, }) } } @@ -10151,7 +11208,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, Channel, ChannelState, InboundHTLCOutput, OutboundV1Channel, InboundV1Channel, OutboundHTLCOutput, InboundHTLCState, OutboundHTLCState, HTLCCandidate, HTLCInitiator, HTLCUpdateAwaitingACK, commit_tx_fee_sat}; + use crate::ln::channel::{AwaitingChannelReadyFlags, Channel, ChannelContext, ChannelState, 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; @@ -11915,4 +12972,120 @@ 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()); } + + #[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, channel_value_satoshis, user_id); + let holder_signer = signer_provider.derive_channel_signer(channel_value_satoshis, channel_keys_id); + let logger = test_utils::TestLogger::new(); + let pubkeys = holder_signer.pubkeys().clone(); + + // 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, + None, + 100000, + [42; 32], + holder_signer, + pubkeys, + &logger, + ).unwrap(); + + // Clone it + let holder_signer2 = signer_provider.derive_channel_signer(channel_value_satoshis, 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 a58863ab1a2..bbddee26cf2 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -27,6 +27,8 @@ use bitcoin::hashes::{Hash, HashEngine, HmacEngine}; use bitcoin::hashes::hmac::Hmac; use bitcoin::hashes::sha256::Hash as Sha256; use bitcoin::hash_types::{BlockHash, Txid}; +#[cfg(splicing)] +use bitcoin::locktime::absolute::LockTime; use bitcoin::secp256k1::{SecretKey,PublicKey}; use bitcoin::secp256k1::Secp256k1; @@ -49,6 +51,8 @@ use crate::ln::inbound_payment; use crate::ln::types::ChannelId; use crate::types::payment::{PaymentHash, PaymentPreimage, PaymentSecret}; use crate::ln::channel::{self, Channel, ChannelPhase, ChannelError, ChannelUpdateStatus, ShutdownResult, UpdateFulfillCommitFetch, OutboundV1Channel, InboundV1Channel, WithChannelContext, InboundV2Channel, InteractivelyFunded as _}; +#[cfg(splicing)] +use crate::ln::channel::{OutboundV2Channel, PendingV2Channel, SplicingChannel}; use crate::ln::channel_state::ChannelDetails; use crate::types::features::{Bolt12InvoiceFeatures, ChannelFeatures, ChannelTypeFeatures, InitFeatures, NodeFeatures}; #[cfg(any(feature = "_test_utils", test))] @@ -1351,6 +1355,8 @@ impl PeerState where SP::Target: SignerProvider { ChannelPhase::UnfundedInboundV1(_) => false, ChannelPhase::UnfundedOutboundV2(_) => true, ChannelPhase::UnfundedInboundV2(_) => false, + #[cfg(splicing)] + ChannelPhase::RefundingV2(_) => true, } ) && self.monitor_update_blocked_actions.is_empty() @@ -3068,6 +3074,10 @@ macro_rules! convert_chan_phase_err { ChannelPhase::UnfundedInboundV2(channel) => { convert_chan_phase_err!($self, $peer_state, $err, channel, $channel_id, UNFUNDED_CHANNEL) }, + #[cfg(splicing)] + ChannelPhase::RefundingV2(channel) => { + convert_chan_phase_err!($self, $peer_state, $err, &mut channel.pre_funded, $channel_id, FUNDED_CHANNEL) + }, } }; } @@ -3674,11 +3684,7 @@ where let mut peer_state_lock = peer_state_mutex.lock().unwrap(); let peer_state = &mut *peer_state_lock; res.extend(peer_state.channel_by_id.iter() - .filter_map(|(chan_id, phase)| match phase { - // Only `Channels` in the `ChannelPhase::Funded` phase can be considered funded. - ChannelPhase::Funded(chan) => Some((chan_id, chan)), - _ => None, - }) + .filter_map(|(chan_id, phase)| phase.funded_channel().map(|chan| (chan_id, chan))) .filter(f) .map(|(_channel_id, channel)| { ChannelDetails::from_channel_context(&channel.context, best_block_height, @@ -3806,7 +3812,7 @@ where match peer_state.channel_by_id.entry(channel_id.clone()) { hash_map::Entry::Occupied(mut chan_phase_entry) => { - if let ChannelPhase::Funded(chan) = chan_phase_entry.get_mut() { + if let Some(chan) = chan_phase_entry.get_mut().funded_channel_mut() { let funding_txo_opt = chan.context.get_funding_txo(); let their_features = &peer_state.latest_features; let (shutdown_msg, mut monitor_update_opt, htlcs) = @@ -3937,7 +3943,7 @@ where let peer_state = &mut *peer_state_lock; match peer_state.channel_by_id.entry(channel_id) { hash_map::Entry::Occupied(mut chan_phase) => { - if let ChannelPhase::Funded(chan) = chan_phase.get_mut() { + if let Some(chan) = chan_phase.get_mut().funded_channel_mut() { let completed = handle_new_monitor_update!(self, funding_txo, monitor_update, peer_state_lock, peer_state, per_peer_state, chan); return if completed { ChannelMonitorUpdateStatus::Completed } else { ChannelMonitorUpdateStatus::InProgress }; @@ -4099,18 +4105,14 @@ where let logger = WithContext::from(&self.logger, Some(*peer_node_id), Some(*channel_id), None); if let hash_map::Entry::Occupied(mut chan_phase_entry) = peer_state.channel_by_id.entry(channel_id.clone()) { log_error!(logger, "Force-closing channel {}", channel_id); - let (mut shutdown_res, update_opt) = match chan_phase_entry.get_mut() { - ChannelPhase::Funded(ref mut chan) => { - ( - chan.context.force_shutdown(broadcast, closure_reason), - self.get_channel_update_for_broadcast(&chan).ok(), - ) - }, - ChannelPhase::UnfundedOutboundV1(_) | ChannelPhase::UnfundedInboundV1(_) | - ChannelPhase::UnfundedOutboundV2(_) | ChannelPhase::UnfundedInboundV2(_) => { - // Unfunded channel has no update - (chan_phase_entry.get_mut().context_mut().force_shutdown(false, closure_reason), None) - }, + let (mut shutdown_res, update_opt) = if let Some(chan) = chan_phase_entry.get_mut().funded_channel_mut() { + ( + chan.context.force_shutdown(broadcast, closure_reason), + self.get_channel_update_for_broadcast(&chan).ok(), + ) + } else { + // Unfunded channel has no update + (chan_phase_entry.get_mut().context_mut().force_shutdown(false, closure_reason), None) }; let chan_phase = remove_channel_phase!(self, peer_state, chan_phase_entry, shutdown_res); mem::drop(peer_state); @@ -4213,6 +4215,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)> { @@ -4270,7 +4329,7 @@ where let mut peer_state_lock = peer_state_mutex_opt.unwrap().lock().unwrap(); let peer_state = &mut *peer_state_lock; match peer_state.channel_by_id.get_mut(&channel_id).and_then( - |chan_phase| if let ChannelPhase::Funded(chan) = chan_phase { Some(chan) } else { None } + |chan_phase| chan_phase.funded_channel_mut(), ) { None => None, Some(chan) => Some(callback(chan)), @@ -4564,40 +4623,39 @@ where let mut peer_state_lock = peer_state_mutex.lock().unwrap(); let peer_state = &mut *peer_state_lock; if let hash_map::Entry::Occupied(mut chan_phase_entry) = peer_state.channel_by_id.entry(id) { - match chan_phase_entry.get_mut() { - ChannelPhase::Funded(chan) => { - if !chan.context.is_live() { - return Err(APIError::ChannelUnavailable{err: "Peer for first hop currently disconnected".to_owned()}); - } - let funding_txo = chan.context.get_funding_txo().unwrap(); - let logger = WithChannelContext::from(&self.logger, &chan.context, Some(*payment_hash)); - let send_res = chan.send_htlc_and_commit(htlc_msat, payment_hash.clone(), - htlc_cltv, HTLCSource::OutboundRoute { - path: path.clone(), - session_priv: session_priv.clone(), - first_hop_htlc_msat: htlc_msat, - payment_id, - }, onion_packet, None, &self.fee_estimator, &&logger); - match break_chan_phase_entry!(self, peer_state, send_res, chan_phase_entry) { - Some(monitor_update) => { - match handle_new_monitor_update!(self, funding_txo, monitor_update, peer_state_lock, peer_state, per_peer_state, chan) { - false => { - // Note that MonitorUpdateInProgress here indicates (per function - // docs) that we will resend the commitment update once monitor - // updating completes. Therefore, we must return an error - // indicating that it is unsafe to retry the payment wholesale, - // which we do in the send_payment check for - // MonitorUpdateInProgress, below. - return Err(APIError::MonitorUpdateInProgress); - }, - true => {}, - } - }, - None => {}, - } - }, - _ => return Err(APIError::ChannelUnavailable{err: "Channel to first hop is unfunded".to_owned()}), - }; + if let Some(chan) = chan_phase_entry.get_mut().funded_channel_mut() { + if !chan.context.is_live() { + return Err(APIError::ChannelUnavailable{err: "Peer for first hop currently disconnected".to_owned()}); + } + let funding_txo = chan.context.get_funding_txo().unwrap(); + let logger = WithChannelContext::from(&self.logger, &chan.context, Some(*payment_hash)); + let send_res = chan.send_htlc_and_commit(htlc_msat, payment_hash.clone(), + htlc_cltv, HTLCSource::OutboundRoute { + path: path.clone(), + session_priv: session_priv.clone(), + first_hop_htlc_msat: htlc_msat, + payment_id, + }, onion_packet, None, &self.fee_estimator, &&logger); + match break_chan_phase_entry!(self, peer_state, send_res, chan_phase_entry) { + Some(monitor_update) => { + match handle_new_monitor_update!(self, funding_txo, monitor_update, peer_state_lock, peer_state, per_peer_state, chan) { + false => { + // Note that MonitorUpdateInProgress here indicates (per function + // docs) that we will resend the commitment update once monitor + // updating completes. Therefore, we must return an error + // indicating that it is unsafe to retry the payment wholesale, + // which we do in the send_payment check for + // MonitorUpdateInProgress, below. + return Err(APIError::MonitorUpdateInProgress); + }, + true => {}, + } + }, + None => {}, + } + } else { + return Err(APIError::ChannelUnavailable{err: "Channel to first hop is unfunded".to_owned()}); + } } else { // The channel was likely removed after we fetched the id from the // `short_to_chan_info` map, but before we successfully locked the @@ -5432,7 +5490,7 @@ where if !channel_phase.context_mut().update_config(&config) { continue; } - if let ChannelPhase::Funded(channel) = channel_phase { + if let Some(channel) = channel_phase.funded_channel_mut() { if let Ok(msg) = self.get_channel_update_for_broadcast(channel) { let mut pending_broadcast_messages = self.pending_broadcast_messages.lock().unwrap(); pending_broadcast_messages.push(events::MessageSendEvent::BroadcastChannelUpdate { msg }); @@ -5520,18 +5578,21 @@ where let mut peer_state_lock = peer_state_mutex.lock().unwrap(); let peer_state = &mut *peer_state_lock; match peer_state.channel_by_id.get(next_hop_channel_id) { - Some(ChannelPhase::Funded(chan)) => { - if !chan.context.is_usable() { + Some(phase) => { + if let Some(chan) = phase.funded_channel() { + if !chan.context.is_usable() { + return Err(APIError::ChannelUnavailable { + err: format!("Channel with id {} not fully established", next_hop_channel_id) + }) + } + chan.context.get_short_channel_id().unwrap_or(chan.context.outbound_scid_alias()) + } else { return Err(APIError::ChannelUnavailable { - err: format!("Channel with id {} not fully established", next_hop_channel_id) - }) + err: format!("Channel with id {} for the passed counterparty node_id {} is still opening.", + next_hop_channel_id, next_node_id) + }); } - chan.context.get_short_channel_id().unwrap_or(chan.context.outbound_scid_alias()) - }, - Some(_) => return Err(APIError::ChannelUnavailable { - err: format!("Channel with id {} for the passed counterparty node_id {} is still opening.", - next_hop_channel_id, next_node_id) - }), + } None => { let error = format!("Channel with id {} not found for the passed counterparty node_id {}", next_hop_channel_id, next_node_id); @@ -5539,7 +5600,7 @@ where log_error!(logger, "{} when attempting to forward intercepted HTLC", error); return Err(APIError::ChannelUnavailable { err: error - }) + }); } } }; @@ -5931,8 +5992,8 @@ where // applying non-strict forwarding. // The channel with the least amount of outbound liquidity will be used to maximize the // probability of being able to successfully forward a subsequent HTLC. - let maybe_optimal_channel = peer_state.channel_by_id.values_mut().filter_map(|phase| match phase { - ChannelPhase::Funded(chan) => { + let maybe_optimal_channel = peer_state.channel_by_id.values_mut().filter_map(|phase| + if let Some(chan) = phase.funded_channel_mut() { let balances = chan.context.get_available_balances(&self.fee_estimator); if outgoing_amt_msat <= balances.next_outbound_htlc_limit_msat && outgoing_amt_msat >= balances.next_outbound_htlc_minimum_msat && @@ -5941,15 +6002,21 @@ where } else { None } - }, - _ => None, - }).min_by_key(|(_, balances)| balances.next_outbound_htlc_limit_msat).map(|(c, _)| c); + } else { + None + } + ).min_by_key(|(_, balances)| balances.next_outbound_htlc_limit_msat).map(|(c, _)| c); let optimal_channel = match maybe_optimal_channel { Some(chan) => chan, None => { // Fall back to the specified channel to return an appropriate error. - if let Some(ChannelPhase::Funded(ref mut chan)) = peer_state.channel_by_id.get_mut(&forward_chan_id) { - chan + if let Some(phase) = peer_state.channel_by_id.get_mut(&forward_chan_id) { + if let Some(chan) = phase.funded_channel_mut() { + chan + } else { + forwarding_channel_not_found!(core::iter::once(forward_info).chain(draining_pending_forwards)); + break; + } } else { forwarding_channel_not_found!(core::iter::once(forward_info).chain(draining_pending_forwards)); break; @@ -5976,7 +6043,7 @@ where panic!("Stated return value requirements in send_htlc() were not met"); } - if let Some(ChannelPhase::Funded(ref mut chan)) = peer_state.channel_by_id.get_mut(&forward_chan_id) { + if let Some(Some(ref mut chan)) = peer_state.channel_by_id.get_mut(&forward_chan_id).map(|phase| phase.funded_channel_mut()) { let failure_code = 0x1000|7; let data = self.get_htlc_inbound_temp_fail_data(failure_code); failed_forwards.push((htlc_source, payment_hash, @@ -5994,7 +6061,7 @@ where panic!("short_channel_id != 0 should imply any pending_forward entries are of type Forward"); }, HTLCForwardInfo::FailHTLC { htlc_id, ref err_packet } => { - if let Some(ChannelPhase::Funded(ref mut chan)) = peer_state.channel_by_id.get_mut(&forward_chan_id) { + if let Some(Some(ref mut chan)) = peer_state.channel_by_id.get_mut(&forward_chan_id).map(|phase| phase.funded_channel_mut()) { let logger = WithChannelContext::from(&self.logger, &chan.context, None); log_trace!(logger, "Failing HTLC back to channel with short id {} (backward HTLC ID {}) after delay", short_chan_id, htlc_id); Some((chan.queue_fail_htlc(htlc_id, err_packet.clone(), &&logger), htlc_id)) @@ -6004,7 +6071,7 @@ where } }, HTLCForwardInfo::FailMalformedHTLC { htlc_id, failure_code, sha256_of_onion } => { - if let Some(ChannelPhase::Funded(ref mut chan)) = peer_state.channel_by_id.get_mut(&forward_chan_id) { + if let Some(Some(ref mut chan)) = peer_state.channel_by_id.get_mut(&forward_chan_id).map(|phase| phase.funded_channel_mut()) { let logger = WithChannelContext::from(&self.logger, &chan.context, None); log_trace!(logger, "Failing malformed HTLC back to channel with short id {} (backward HTLC ID {}) after delay", short_chan_id, htlc_id); let res = chan.queue_fail_malformed_htlc( @@ -6020,7 +6087,7 @@ where if let Some((queue_fail_htlc_res, htlc_id)) = queue_fail_htlc_res { if let Err(e) = queue_fail_htlc_res { if let ChannelError::Ignore(msg) = e { - if let Some(ChannelPhase::Funded(ref mut chan)) = peer_state.channel_by_id.get_mut(&forward_chan_id) { + if let Some(Some(ref mut chan)) = peer_state.channel_by_id.get_mut(&forward_chan_id).map(|phase| phase.funded_channel_mut()) { let logger = WithChannelContext::from(&self.logger, &chan.context, None); log_trace!(logger, "Failed to fail HTLC with ID {} backwards to short_id {}: {}", htlc_id, short_chan_id, msg); } @@ -6330,7 +6397,7 @@ where if let Some(peer_state_mutex) = per_peer_state.get(&counterparty_node_id) { let mut peer_state_lock = peer_state_mutex.lock().unwrap(); let peer_state = &mut *peer_state_lock; - if let Some(ChannelPhase::Funded(chan)) = peer_state.channel_by_id.get_mut(&channel_id) { + if let Some(Some(chan)) = peer_state.channel_by_id.get_mut(&channel_id).map(|phase| phase.funded_channel_mut()) { handle_monitor_update_completion!(self, peer_state_lock, peer_state, per_peer_state, chan); } else { let update_actions = peer_state.monitor_update_blocked_actions @@ -6391,7 +6458,7 @@ where let mut peer_state_lock = peer_state_mutex.lock().unwrap(); let peer_state = &mut *peer_state_lock; for (chan_id, chan) in peer_state.channel_by_id.iter_mut().filter_map( - |(chan_id, phase)| if let ChannelPhase::Funded(chan) = phase { Some((chan_id, chan)) } else { None } + |(chan_id, phase)| phase.funded_channel_mut().map(|chan| ((chan_id, chan))) ) { let new_feerate = if chan.context.get_channel_type().supports_anchors_zero_fee_htlc_tx() { anchor_feerate @@ -6476,93 +6543,95 @@ where let pending_msg_events = &mut peer_state.pending_msg_events; let counterparty_node_id = *counterparty_node_id; peer_state.channel_by_id.retain(|chan_id, phase| { - match phase { - ChannelPhase::Funded(chan) => { - let new_feerate = if chan.context.get_channel_type().supports_anchors_zero_fee_htlc_tx() { - anchor_feerate - } else { - non_anchor_feerate - }; - let chan_needs_persist = self.update_channel_fee(chan_id, chan, new_feerate); - if chan_needs_persist == NotifyOption::DoPersist { should_persist = NotifyOption::DoPersist; } + if let Some(chan) = phase.funded_channel_mut() { + let new_feerate = if chan.context.get_channel_type().supports_anchors_zero_fee_htlc_tx() { + anchor_feerate + } else { + non_anchor_feerate + }; + let chan_needs_persist = self.update_channel_fee(chan_id, chan, new_feerate); + if chan_needs_persist == NotifyOption::DoPersist { should_persist = NotifyOption::DoPersist; } - if let Err(e) = chan.timer_check_closing_negotiation_progress() { - let (needs_close, err) = convert_chan_phase_err!(self, peer_state, e, chan, chan_id, FUNDED_CHANNEL); - handle_errors.push((Err(err), counterparty_node_id)); - if needs_close { return false; } - } + if let Err(e) = chan.timer_check_closing_negotiation_progress() { + let (needs_close, err) = convert_chan_phase_err!(self, peer_state, e, chan, chan_id, FUNDED_CHANNEL); + handle_errors.push((Err(err), counterparty_node_id)); + if needs_close { return false; } + } - match chan.channel_update_status() { - ChannelUpdateStatus::Enabled if !chan.context.is_live() => chan.set_channel_update_status(ChannelUpdateStatus::DisabledStaged(0)), - ChannelUpdateStatus::Disabled if chan.context.is_live() => chan.set_channel_update_status(ChannelUpdateStatus::EnabledStaged(0)), - ChannelUpdateStatus::DisabledStaged(_) if chan.context.is_live() - => chan.set_channel_update_status(ChannelUpdateStatus::Enabled), - ChannelUpdateStatus::EnabledStaged(_) if !chan.context.is_live() - => chan.set_channel_update_status(ChannelUpdateStatus::Disabled), - ChannelUpdateStatus::DisabledStaged(mut n) if !chan.context.is_live() => { - n += 1; - if n >= DISABLE_GOSSIP_TICKS { - chan.set_channel_update_status(ChannelUpdateStatus::Disabled); - if let Ok(update) = self.get_channel_update_for_broadcast(&chan) { - let mut pending_broadcast_messages = self.pending_broadcast_messages.lock().unwrap(); - pending_broadcast_messages.push(events::MessageSendEvent::BroadcastChannelUpdate { - msg: update - }); - } - should_persist = NotifyOption::DoPersist; - } else { - chan.set_channel_update_status(ChannelUpdateStatus::DisabledStaged(n)); + match chan.channel_update_status() { + ChannelUpdateStatus::Enabled if !chan.context.is_live() => chan.set_channel_update_status(ChannelUpdateStatus::DisabledStaged(0)), + ChannelUpdateStatus::Disabled if chan.context.is_live() => chan.set_channel_update_status(ChannelUpdateStatus::EnabledStaged(0)), + ChannelUpdateStatus::DisabledStaged(_) if chan.context.is_live() + => chan.set_channel_update_status(ChannelUpdateStatus::Enabled), + ChannelUpdateStatus::EnabledStaged(_) if !chan.context.is_live() + => chan.set_channel_update_status(ChannelUpdateStatus::Disabled), + ChannelUpdateStatus::DisabledStaged(mut n) if !chan.context.is_live() => { + n += 1; + if n >= DISABLE_GOSSIP_TICKS { + chan.set_channel_update_status(ChannelUpdateStatus::Disabled); + if let Ok(update) = self.get_channel_update_for_broadcast(&chan) { + let mut pending_broadcast_messages = self.pending_broadcast_messages.lock().unwrap(); + pending_broadcast_messages.push(events::MessageSendEvent::BroadcastChannelUpdate { + msg: update + }); } - }, - ChannelUpdateStatus::EnabledStaged(mut n) if chan.context.is_live() => { - n += 1; - if n >= ENABLE_GOSSIP_TICKS { - chan.set_channel_update_status(ChannelUpdateStatus::Enabled); - if let Ok(update) = self.get_channel_update_for_broadcast(&chan) { - let mut pending_broadcast_messages = self.pending_broadcast_messages.lock().unwrap(); - pending_broadcast_messages.push(events::MessageSendEvent::BroadcastChannelUpdate { - msg: update - }); - } - should_persist = NotifyOption::DoPersist; - } else { - chan.set_channel_update_status(ChannelUpdateStatus::EnabledStaged(n)); + should_persist = NotifyOption::DoPersist; + } else { + chan.set_channel_update_status(ChannelUpdateStatus::DisabledStaged(n)); + } + }, + ChannelUpdateStatus::EnabledStaged(mut n) if chan.context.is_live() => { + n += 1; + if n >= ENABLE_GOSSIP_TICKS { + chan.set_channel_update_status(ChannelUpdateStatus::Enabled); + if let Ok(update) = self.get_channel_update_for_broadcast(&chan) { + let mut pending_broadcast_messages = self.pending_broadcast_messages.lock().unwrap(); + pending_broadcast_messages.push(events::MessageSendEvent::BroadcastChannelUpdate { + msg: update + }); } - }, - _ => {}, - } + should_persist = NotifyOption::DoPersist; + } else { + chan.set_channel_update_status(ChannelUpdateStatus::EnabledStaged(n)); + } + }, + _ => {}, + } - chan.context.maybe_expire_prev_config(); + chan.context.maybe_expire_prev_config(); - if chan.should_disconnect_peer_awaiting_response() { - let logger = WithChannelContext::from(&self.logger, &chan.context, None); - log_debug!(logger, "Disconnecting peer {} due to not making any progress on channel {}", - counterparty_node_id, chan_id); - pending_msg_events.push(MessageSendEvent::HandleError { - node_id: counterparty_node_id, - action: msgs::ErrorAction::DisconnectPeerWithWarning { - msg: msgs::WarningMessage { - channel_id: *chan_id, - data: "Disconnecting due to timeout awaiting response".to_owned(), - }, + if chan.should_disconnect_peer_awaiting_response() { + let logger = WithChannelContext::from(&self.logger, &chan.context, None); + log_debug!(logger, "Disconnecting peer {} due to not making any progress on channel {}", + counterparty_node_id, chan_id); + pending_msg_events.push(MessageSendEvent::HandleError { + node_id: counterparty_node_id, + action: msgs::ErrorAction::DisconnectPeerWithWarning { + msg: msgs::WarningMessage { + channel_id: *chan_id, + data: "Disconnecting due to timeout awaiting response".to_owned(), }, - }); - } + }, + }); + } - true - }, - ChannelPhase::UnfundedInboundV1(chan) => { - process_unfunded_channel_tick!(peer_state, chan, pending_msg_events) - }, - ChannelPhase::UnfundedOutboundV1(chan) => { - process_unfunded_channel_tick!(peer_state, chan, pending_msg_events) - }, - ChannelPhase::UnfundedInboundV2(chan) => { - process_unfunded_channel_tick!(peer_state, chan, pending_msg_events) - }, - ChannelPhase::UnfundedOutboundV2(chan) => { - process_unfunded_channel_tick!(peer_state, chan, pending_msg_events) - }, + true + } else { + match phase { + ChannelPhase::UnfundedInboundV1(chan) => { + process_unfunded_channel_tick!(peer_state, chan, pending_msg_events) + }, + ChannelPhase::UnfundedOutboundV1(chan) => { + process_unfunded_channel_tick!(peer_state, chan, pending_msg_events) + }, + ChannelPhase::UnfundedInboundV2(chan) => { + process_unfunded_channel_tick!(peer_state, chan, pending_msg_events) + }, + ChannelPhase::UnfundedOutboundV2(chan) => { + process_unfunded_channel_tick!(peer_state, chan, pending_msg_events) + }, + _ => panic!("unreachable"), + } } }); @@ -6772,7 +6841,7 @@ where let peer_state = &mut *peer_state_lock; match peer_state.channel_by_id.entry(channel_id) { hash_map::Entry::Occupied(chan_phase_entry) => { - if let ChannelPhase::Funded(_chan) = chan_phase_entry.get() { + if let Some(_chan) = chan_phase_entry.get().funded_channel() { let failure_code = 0x1000|7; let data = self.get_htlc_inbound_temp_fail_data(failure_code); (failure_code, data) @@ -7101,7 +7170,7 @@ where let mut peer_state_lock = peer_state_opt.unwrap(); let peer_state = &mut *peer_state_lock; if let hash_map::Entry::Occupied(mut chan_phase_entry) = peer_state.channel_by_id.entry(chan_id) { - if let ChannelPhase::Funded(chan) = chan_phase_entry.get_mut() { + if let Some(chan) = chan_phase_entry.get_mut().funded_channel_mut() { let counterparty_node_id = chan.context.get_counterparty_node_id(); let logger = WithChannelContext::from(&self.logger, &chan.context, None); let fulfill_res = @@ -7870,6 +7939,17 @@ where num_unfunded_channels += 1; } }, + #[cfg(splicing)] + ChannelPhase::RefundingV2(channel) => { + let chan = &channel.pre_funded; + // This covers non-zero-conf inbound `Channel`s that we are currently monitoring, but those + // which have not yet had any confirmations on-chain. + if !chan.context.is_outbound() && chan.context.minimum_depth().unwrap_or(1) != 0 && + chan.context.get_funding_tx_confirmations(best_block_height) == 0 + { + num_unfunded_channels += 1; + } + }, ChannelPhase::UnfundedInboundV1(chan) => { if chan.context.minimum_depth().unwrap_or(1) != 0 { num_unfunded_channels += 1; @@ -8274,6 +8354,10 @@ where ChannelPhase::UnfundedOutboundV2(ref mut channel) => { Ok(channel.tx_add_input(msg).into_msg_send_event(counterparty_node_id)) }, + #[cfg(splicing)] + ChannelPhase::RefundingV2(ref mut channel) => { + Ok(channel.tx_add_input(msg).into_msg_send_event(counterparty_node_id)) + } _ => Err("tx_add_input"), } }) @@ -8288,6 +8372,10 @@ where ChannelPhase::UnfundedOutboundV2(ref mut channel) => { Ok(channel.tx_add_output(msg).into_msg_send_event(counterparty_node_id)) }, + #[cfg(splicing)] + ChannelPhase::RefundingV2(ref mut channel) => { + Ok(channel.tx_add_output(msg).into_msg_send_event(counterparty_node_id)) + } _ => Err("tx_add_output"), } }) @@ -8340,6 +8428,9 @@ where .into_msg_send_event_or_signing_session(counterparty_node_id), ChannelPhase::UnfundedOutboundV2(channel) => channel.tx_complete(msg) .into_msg_send_event_or_signing_session(counterparty_node_id), + #[cfg(splicing)] + ChannelPhase::RefundingV2(channel) => channel.tx_complete(msg) + .into_msg_send_event_or_signing_session(counterparty_node_id), _ => try_chan_phase_entry!(self, peer_state, Err(ChannelError::Close( ( "Got a tx_complete message with no interactive transaction construction expected or in-progress".into(), @@ -8357,6 +8448,10 @@ where ChannelPhase::UnfundedInboundV2(chan) => { chan.funding_tx_constructed(&mut signing_session, &self.logger) }, + #[cfg(splicing)] + ChannelPhase::RefundingV2(chan) => { + chan.funding_tx_constructed(&mut signing_session, &self.logger) + } _ => Err(ChannelError::Warn( "Got a tx_complete message with no interactive transaction construction expected or in-progress" .into())), @@ -8365,6 +8460,8 @@ where let channel = match channel_phase { ChannelPhase::UnfundedOutboundV2(chan) => chan.into_channel(signing_session), ChannelPhase::UnfundedInboundV2(chan) => chan.into_channel(signing_session), + #[cfg(splicing)] + ChannelPhase::RefundingV2(chan) => chan.into_channel(signing_session), _ => { debug_assert!(false); // It cannot be another variant as we are in the `Ok` branch of the above match. Err(ChannelError::Warn( @@ -8479,6 +8576,13 @@ where establishment".into(), )), chan_phase_entry) }, + #[cfg(splicing)] + ChannelPhase::RefundingV2(_) => { + // TODO(splicing) + try_chan_phase_entry!(self, peer_state, Err(ChannelError::Warn( + "Tx_abort on splicing not supported".into(), + )), chan_phase_entry) + } }; // This checks for and resets the interactive negotiation state by `take()`ing it from the channel. // The existence of the `tx_constructor` indicates that we have not moved into the signing @@ -8522,7 +8626,7 @@ where let peer_state = &mut *peer_state_lock; match peer_state.channel_by_id.entry(msg.channel_id) { hash_map::Entry::Occupied(mut chan_phase_entry) => { - if let ChannelPhase::Funded(chan) = chan_phase_entry.get_mut() { + if let Some(chan) = chan_phase_entry.get_mut().funded_channel_mut() { let logger = WithChannelContext::from(&self.logger, &chan.context, None); let announcement_sigs_opt = try_chan_phase_entry!(self, peer_state, chan.channel_ready(&msg, &self.node_signer, self.chain_hash, &self.default_configuration, &self.best_block.read().unwrap(), &&logger), chan_phase_entry); @@ -8578,44 +8682,40 @@ where let peer_state = &mut *peer_state_lock; if let hash_map::Entry::Occupied(mut chan_phase_entry) = peer_state.channel_by_id.entry(msg.channel_id.clone()) { let phase = chan_phase_entry.get_mut(); - match phase { - ChannelPhase::Funded(chan) => { - if !chan.received_shutdown() { - let logger = WithChannelContext::from(&self.logger, &chan.context, None); - log_info!(logger, "Received a shutdown message from our counterparty for channel {}{}.", - msg.channel_id, - if chan.sent_shutdown() { " after we initiated shutdown" } else { "" }); - } + if let Some(chan) = phase.funded_channel_mut() { + if !chan.received_shutdown() { + let logger = WithChannelContext::from(&self.logger, &chan.context, None); + log_info!(logger, "Received a shutdown message from our counterparty for channel {}{}.", + msg.channel_id, + if chan.sent_shutdown() { " after we initiated shutdown" } else { "" }); + } - let funding_txo_opt = chan.context.get_funding_txo(); - let (shutdown, monitor_update_opt, htlcs) = try_chan_phase_entry!(self, peer_state, - chan.shutdown(&self.signer_provider, &peer_state.latest_features, &msg), chan_phase_entry); - dropped_htlcs = htlcs; - - if let Some(msg) = shutdown { - // We can send the `shutdown` message before updating the `ChannelMonitor` - // here as we don't need the monitor update to complete until we send a - // `shutdown_signed`, which we'll delay if we're pending a monitor update. - peer_state.pending_msg_events.push(events::MessageSendEvent::SendShutdown { - node_id: *counterparty_node_id, - msg, - }); - } - // Update the monitor with the shutdown script if necessary. - if let Some(monitor_update) = monitor_update_opt { - handle_new_monitor_update!(self, funding_txo_opt.unwrap(), monitor_update, - peer_state_lock, peer_state, per_peer_state, chan); - } - }, - ChannelPhase::UnfundedInboundV1(_) | ChannelPhase::UnfundedOutboundV1(_) | - ChannelPhase::UnfundedInboundV2(_) | ChannelPhase::UnfundedOutboundV2(_) => { - let context = phase.context_mut(); - let logger = WithChannelContext::from(&self.logger, context, None); - log_error!(logger, "Immediately closing unfunded channel {} as peer asked to cooperatively shut it down (which is unnecessary)", &msg.channel_id); - let mut close_res = phase.context_mut().force_shutdown(false, ClosureReason::CounterpartyCoopClosedUnfundedChannel); - remove_channel_phase!(self, peer_state, chan_phase_entry, close_res); - finish_shutdown = Some(close_res); - }, + let funding_txo_opt = chan.context.get_funding_txo(); + let (shutdown, monitor_update_opt, htlcs) = try_chan_phase_entry!(self, peer_state, + chan.shutdown(&self.signer_provider, &peer_state.latest_features, &msg), chan_phase_entry); + dropped_htlcs = htlcs; + + if let Some(msg) = shutdown { + // We can send the `shutdown` message before updating the `ChannelMonitor` + // here as we don't need the monitor update to complete until we send a + // `shutdown_signed`, which we'll delay if we're pending a monitor update. + peer_state.pending_msg_events.push(events::MessageSendEvent::SendShutdown { + node_id: *counterparty_node_id, + msg, + }); + } + // Update the monitor with the shutdown script if necessary. + if let Some(monitor_update) = monitor_update_opt { + handle_new_monitor_update!(self, funding_txo_opt.unwrap(), monitor_update, + peer_state_lock, peer_state, per_peer_state, chan); + } + } else { + let context = phase.context_mut(); + let logger = WithChannelContext::from(&self.logger, context, None); + log_error!(logger, "Immediately closing unfunded channel {} as peer asked to cooperatively shut it down (which is unnecessary)", &msg.channel_id); + let mut close_res = phase.context_mut().force_shutdown(false, ClosureReason::CounterpartyCoopClosedUnfundedChannel); + remove_channel_phase!(self, peer_state, chan_phase_entry, close_res); + finish_shutdown = Some(close_res); } } else { 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)) @@ -8645,7 +8745,7 @@ where let peer_state = &mut *peer_state_lock; match peer_state.channel_by_id.entry(msg.channel_id.clone()) { hash_map::Entry::Occupied(mut chan_phase_entry) => { - if let ChannelPhase::Funded(chan) = chan_phase_entry.get_mut() { + if let Some(ref mut chan) = chan_phase_entry.get_mut().funded_channel_mut() { let logger = WithChannelContext::from(&self.logger, &chan.context, None); let (closing_signed, tx, shutdown_result) = try_chan_phase_entry!(self, peer_state, chan.closing_signed(&self.fee_estimator, &msg, &&logger), chan_phase_entry); debug_assert_eq!(shutdown_result.is_some(), chan.is_shutdown()); @@ -8681,7 +8781,7 @@ where log_info!(WithContext::from(&self.logger, Some(*counterparty_node_id), channel_id, None), "Broadcasting {}", log_tx!(broadcast_tx)); self.tx_broadcaster.broadcast_transactions(&[&broadcast_tx]); } - if let Some(ChannelPhase::Funded(chan)) = chan_option { + if let Some(Some(chan)) = chan_option.as_ref().map(|phase| phase.funded_channel()) { if let Ok(update) = self.get_channel_update_for_broadcast(&chan) { let mut pending_broadcast_messages = self.pending_broadcast_messages.lock().unwrap(); pending_broadcast_messages.push(events::MessageSendEvent::BroadcastChannelUpdate { @@ -8720,7 +8820,7 @@ where let peer_state = &mut *peer_state_lock; match peer_state.channel_by_id.entry(msg.channel_id) { hash_map::Entry::Occupied(mut chan_phase_entry) => { - if let ChannelPhase::Funded(chan) = chan_phase_entry.get_mut() { + if let Some(chan) = chan_phase_entry.get_mut().funded_channel_mut() { let mut pending_forward_info = match decoded_hop_res { Ok((next_hop, shared_secret, next_packet_pk_opt)) => self.construct_pending_htlc_status( @@ -8792,7 +8892,7 @@ where let peer_state = &mut *peer_state_lock; match peer_state.channel_by_id.entry(msg.channel_id) { hash_map::Entry::Occupied(mut chan_phase_entry) => { - if let ChannelPhase::Funded(chan) = chan_phase_entry.get_mut() { + if let Some(chan) = chan_phase_entry.get_mut().funded_channel_mut() { let res = try_chan_phase_entry!(self, peer_state, chan.update_fulfill_htlc(&msg), chan_phase_entry); if let HTLCSource::PreviousHopData(prev_hop) = &res.0 { let logger = WithChannelContext::from(&self.logger, &chan.context, None); @@ -8841,7 +8941,7 @@ where let peer_state = &mut *peer_state_lock; match peer_state.channel_by_id.entry(msg.channel_id) { hash_map::Entry::Occupied(mut chan_phase_entry) => { - if let ChannelPhase::Funded(chan) = chan_phase_entry.get_mut() { + if let Some(chan) = chan_phase_entry.get_mut().funded_channel_mut() { try_chan_phase_entry!(self, peer_state, chan.update_fail_htlc(&msg, HTLCFailReason::from_msg(msg)), chan_phase_entry); } else { return try_chan_phase_entry!(self, peer_state, Err(ChannelError::close( @@ -8870,7 +8970,7 @@ where let chan_err = ChannelError::close("Got update_fail_malformed_htlc with BADONION not set".to_owned()); try_chan_phase_entry!(self, peer_state, Err(chan_err), chan_phase_entry); } - if let ChannelPhase::Funded(chan) = chan_phase_entry.get_mut() { + if let Some(chan) = chan_phase_entry.get_mut().funded_channel_mut() { try_chan_phase_entry!(self, peer_state, chan.update_fail_malformed_htlc(&msg, HTLCFailReason::reason(msg.failure_code, msg.sha256_of_onion.to_vec())), chan_phase_entry); } else { return try_chan_phase_entry!(self, peer_state, Err(ChannelError::close( @@ -8894,7 +8994,14 @@ where let peer_state = &mut *peer_state_lock; match peer_state.channel_by_id.entry(msg.channel_id) { hash_map::Entry::Occupied(mut chan_phase_entry) => { - if let ChannelPhase::Funded(chan) = chan_phase_entry.get_mut() { + let just_funded_channel_opt = match chan_phase_entry.get_mut() { + // Note: here we take the funded post-splice channel, not the pre channel + #[cfg(splicing)] + ChannelPhase::RefundingV2(ref mut chan) => chan.post_funded.as_mut(), + ChannelPhase::Funded(ref mut chan) => Some(chan), + _ => None, + }; + if let Some(chan) = just_funded_channel_opt { let logger = WithChannelContext::from(&self.logger, &chan.context, None); let funding_txo = chan.context.get_funding_txo(); @@ -9111,7 +9218,7 @@ where let peer_state = &mut *peer_state_lock; match peer_state.channel_by_id.entry(msg.channel_id) { hash_map::Entry::Occupied(mut chan_phase_entry) => { - if let ChannelPhase::Funded(chan) = chan_phase_entry.get_mut() { + if let Some(chan) = chan_phase_entry.get_mut().funded_channel_mut() { let logger = WithChannelContext::from(&self.logger, &chan.context, None); let funding_txo_opt = chan.context.get_funding_txo(); let mon_update_blocked = if let Some(funding_txo) = funding_txo_opt { @@ -9151,7 +9258,7 @@ where let peer_state = &mut *peer_state_lock; match peer_state.channel_by_id.entry(msg.channel_id) { hash_map::Entry::Occupied(mut chan_phase_entry) => { - if let ChannelPhase::Funded(chan) = chan_phase_entry.get_mut() { + if let Some(chan) = chan_phase_entry.get_mut().funded_channel_mut() { let logger = WithChannelContext::from(&self.logger, &chan.context, None); try_chan_phase_entry!(self, peer_state, chan.update_fee(&self.fee_estimator, &msg, &&logger), chan_phase_entry); } else { @@ -9175,7 +9282,7 @@ where let peer_state = &mut *peer_state_lock; match peer_state.channel_by_id.entry(msg.channel_id) { hash_map::Entry::Occupied(mut chan_phase_entry) => { - if let ChannelPhase::Funded(chan) = chan_phase_entry.get_mut() { + if let Some(chan) = chan_phase_entry.get_mut().funded_channel_mut() { if !chan.context.is_usable() { return Err(MsgHandleErrInternal::from_no_close(LightningError{err: "Got an announcement_signatures before we were ready for it".to_owned(), action: msgs::ErrorAction::IgnoreError})); } @@ -9217,7 +9324,7 @@ where let peer_state = &mut *peer_state_lock; match peer_state.channel_by_id.entry(chan_id) { hash_map::Entry::Occupied(mut chan_phase_entry) => { - if let ChannelPhase::Funded(chan) = chan_phase_entry.get_mut() { + if let Some(chan) = chan_phase_entry.get_mut().funded_channel_mut() { if chan.context.get_counterparty_node_id() != *counterparty_node_id { if chan.context.should_announce() { // If the announcement is about a channel of ours which is public, some @@ -9268,7 +9375,7 @@ where let peer_state = &mut *peer_state_lock; match peer_state.channel_by_id.entry(msg.channel_id) { hash_map::Entry::Occupied(mut chan_phase_entry) => { - if let ChannelPhase::Funded(chan) = chan_phase_entry.get_mut() { + if let Some(chan) = chan_phase_entry.get_mut().funded_channel_mut() { // Currently, we expect all holding cell update_adds to be dropped on peer // disconnect, so Channel's reestablish will never hand us any holding cell // freed HTLCs to fail backwards. If in the future we no longer drop pending @@ -9350,6 +9457,178 @@ where 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; + + // 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!( + "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() { + chan.splice_init_checks(msg, &self.signer_provider, &self.entropy_source, self.get_our_node_id()) + .map_err(|err| MsgHandleErrInternal::from_chan_no_close(err, msg.channel_id))?; + } else { + return Err(MsgHandleErrInternal::send_err_msg_no_close("Channel is not funded, cannot be spliced".to_owned(), msg.channel_id)); + } + }, + }; + + // Change channel, phase changes, remove and add + // Remove the pre channel + // Note: this remove-and-add would not be needed if channel phase was wrapped (see #3418) + let prev_chan = match peer_state.channel_by_id.remove(&msg.channel_id) { + None => return Err(MsgHandleErrInternal::send_err_msg_no_close(format!("Got a message for a channel from the wrong node! No such channel for the passed counterparty_node_id {}, channel_id {}", counterparty_node_id, msg.channel_id), msg.channel_id)), + Some(chan_phase) => { + if let ChannelPhase::Funded(chan) = chan_phase { + chan + } else { + return Err(MsgHandleErrInternal::send_err_msg_no_close("Channel in wrong state".to_owned(), msg.channel_id.clone())); + } + } + }; + + let post_chan = InboundV2Channel::new_spliced( + false, + &prev_chan, + &self.signer_provider, + &msg.funding_pubkey, + our_funding_contribution, + msg.funding_contribution_satoshis, + Vec::new(), + LockTime::from_consensus(msg.locktime), + msg.funding_feerate_perkw, + &self.logger, + ).map_err(|e| MsgHandleErrInternal::from_chan_no_close(e, msg.channel_id))?; + + // Add the modified channel + let post_chan_id = post_chan.context.channel_id(); + peer_state.channel_by_id.insert(post_chan_id, ChannelPhase::RefundingV2( + SplicingChannel::new(prev_chan, PendingV2Channel::Inbound(post_chan)) + )); + + // Perform state changes + match peer_state.channel_by_id.entry(post_chan_id) { + hash_map::Entry::Vacant(_) => return Err(MsgHandleErrInternal::send_err_msg_no_close("Internal consistency error".to_string(), post_chan_id)), + hash_map::Entry::Occupied(mut chan_entry) => { + if let ChannelPhase::RefundingV2(chan) = chan_entry.get_mut() { + let splice_ack_msg = chan.splice_init(msg, &self.signer_provider, &self.entropy_source, self.get_our_node_id(), &self.logger) + .map_err(|err| MsgHandleErrInternal::from_chan_no_close(err, post_chan_id))?; + peer_state.pending_msg_events.push(events::MessageSendEvent::SendSpliceAck { + node_id: *counterparty_node_id, + msg: splice_ack_msg, + }); + } else { + return Err(MsgHandleErrInternal::send_err_msg_no_close("Internal consistency error: splice_init while not renegotiating".to_string(), post_chan_id)); + } + } + } + + Ok(()) + } + + /// 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 channel + let pending_splice = match peer_state.channel_by_id.entry(msg.channel_id) { + hash_map::Entry::Vacant(_) => return Err(MsgHandleErrInternal::send_err_msg_no_close(format!("Got a message for a channel from the wrong node! No such channel for the passed counterparty_node_id {}", counterparty_node_id), msg.channel_id)), + hash_map::Entry::Occupied(chan) => { + if let ChannelPhase::Funded(chan) = chan.get() { + // check if splice is pending + if let Some(pending_splice) = &chan.pending_splice_pre { + // Note: this is incomplete (their funding contribution is not set) + pending_splice.clone() + } else { + return Err(MsgHandleErrInternal::send_err_msg_no_close("Channel is not in pending splice".to_owned(), msg.channel_id.clone())); + } + } else { + return Err(MsgHandleErrInternal::send_err_msg_no_close("Channel in wrong state".to_owned(), msg.channel_id.clone())); + } + }, + }; + + // Change channel, phase changes, remove and add + // Remove the pre channel + // Note: this remove-and-add would not be needed if channel phase was wrapped (see #3418) + let prev_chan = match peer_state.channel_by_id.remove(&msg.channel_id) { + None => return Err(MsgHandleErrInternal::send_err_msg_no_close(format!("Got a message for a channel from the wrong node! No such channel for the passed counterparty_node_id {}, channel_id {}", counterparty_node_id, msg.channel_id), msg.channel_id)), + Some(chan_phase) => { + if let ChannelPhase::Funded(chan) = chan_phase { + chan + } else { + return Err(MsgHandleErrInternal::send_err_msg_no_close("Channel in wrong state".to_owned(), msg.channel_id.clone())); + } + } + }; + + let post_chan = OutboundV2Channel::new_spliced( + true, + &prev_chan, + &self.signer_provider, + &msg.funding_pubkey, + pending_splice.our_funding_contribution, + msg.funding_contribution_satoshis, + pending_splice.our_funding_inputs, + LockTime::from_consensus(pending_splice.locktime), + pending_splice.funding_feerate_perkw, + &self.logger, + ).map_err(|e| MsgHandleErrInternal::from_chan_no_close(e, msg.channel_id))?; + + // Add the modified channel + let post_chan_id = post_chan.context().channel_id(); + peer_state.channel_by_id.insert(post_chan_id, ChannelPhase::RefundingV2( + SplicingChannel::new(prev_chan, PendingV2Channel::Outbound(post_chan)), + )); + + // Perform state changes + match peer_state.channel_by_id.entry(post_chan_id) { + hash_map::Entry::Vacant(_) => return Err(MsgHandleErrInternal::send_err_msg_no_close("Internal consistency error".to_string(), post_chan_id)), + hash_map::Entry::Occupied(mut chan_entry) => { + if let ChannelPhase::RefundingV2(chan) = chan_entry.get_mut() { + match chan.splice_ack(msg, pending_splice.our_funding_contribution, &self.signer_provider, &self.entropy_source, self.get_our_node_id(), &self.logger) { + Ok(tx_msg_opt) => { + if let Some(tx_msg) = tx_msg_opt { + peer_state.pending_msg_events.push(tx_msg.into_msg_send_event(counterparty_node_id.clone())); + } + Ok(()) + }, + Err(err) => { + Err(MsgHandleErrInternal::from_chan_no_close(err, post_chan_id)) + }, + } + } else { + Err(MsgHandleErrInternal::send_err_msg_no_close("Internal consistency error: splice_ack while not renegotiating".to_string(), post_chan_id)) + } + } + } + } + /// Process pending events from the [`chain::Watch`], returning whether any events were processed. fn process_pending_monitor_events(&self) -> bool { debug_assert!(self.total_consistency_lock.try_write().is_err()); // Caller holds read lock @@ -9461,7 +9740,7 @@ where let mut peer_state_lock = peer_state_mutex.lock().unwrap(); let peer_state: &mut PeerState<_> = &mut *peer_state_lock; for (channel_id, chan) in peer_state.channel_by_id.iter_mut().filter_map( - |(chan_id, phase)| if let ChannelPhase::Funded(chan) = phase { Some((chan_id, chan)) } else { None } + |(chan_id, phase)| phase.funded_channel_mut().map(|chan| (chan_id, chan)) ) { let counterparty_node_id = chan.context.get_counterparty_node_id(); let funding_txo = chan.context.get_funding_txo(); @@ -9570,6 +9849,8 @@ where None } ChannelPhase::UnfundedInboundV1(_) | ChannelPhase::UnfundedInboundV2(_) | ChannelPhase::UnfundedOutboundV2(_) => None, + #[cfg(splicing)] + ChannelPhase::RefundingV2(_) => None, } }; @@ -9621,46 +9902,44 @@ where let peer_state = &mut *peer_state_lock; let pending_msg_events = &mut peer_state.pending_msg_events; peer_state.channel_by_id.retain(|channel_id, phase| { - match phase { - ChannelPhase::Funded(chan) => { - let logger = WithChannelContext::from(&self.logger, &chan.context, None); - match chan.maybe_propose_closing_signed(&self.fee_estimator, &&logger) { - Ok((msg_opt, tx_opt, shutdown_result_opt)) => { - if let Some(msg) = msg_opt { - has_update = true; - pending_msg_events.push(events::MessageSendEvent::SendClosingSigned { - node_id: chan.context.get_counterparty_node_id(), msg, + if let Some(chan) = phase.funded_channel_mut() { + let logger = WithChannelContext::from(&self.logger, &chan.context, None); + match chan.maybe_propose_closing_signed(&self.fee_estimator, &&logger) { + Ok((msg_opt, tx_opt, shutdown_result_opt)) => { + if let Some(msg) = msg_opt { + has_update = true; + pending_msg_events.push(events::MessageSendEvent::SendClosingSigned { + node_id: chan.context.get_counterparty_node_id(), msg, + }); + } + debug_assert_eq!(shutdown_result_opt.is_some(), chan.is_shutdown()); + if let Some(shutdown_result) = shutdown_result_opt { + shutdown_results.push(shutdown_result); + } + if let Some(tx) = tx_opt { + // We're done with this channel. We got a closing_signed and sent back + // a closing_signed with a closing transaction to broadcast. + if let Ok(update) = self.get_channel_update_for_broadcast(&chan) { + let mut pending_broadcast_messages = self.pending_broadcast_messages.lock().unwrap(); + pending_broadcast_messages.push(events::MessageSendEvent::BroadcastChannelUpdate { + msg: update }); } - debug_assert_eq!(shutdown_result_opt.is_some(), chan.is_shutdown()); - if let Some(mut shutdown_result) = shutdown_result_opt { - locked_close_channel!(self, peer_state, &chan.context, shutdown_result); - shutdown_results.push(shutdown_result); - } - if let Some(tx) = tx_opt { - // We're done with this channel. We got a closing_signed and sent back - // a closing_signed with a closing transaction to broadcast. - if let Ok(update) = self.get_channel_update_for_broadcast(&chan) { - let mut pending_broadcast_messages = self.pending_broadcast_messages.lock().unwrap(); - pending_broadcast_messages.push(events::MessageSendEvent::BroadcastChannelUpdate { - msg: update - }); - } - log_info!(logger, "Broadcasting {}", log_tx!(tx)); - self.tx_broadcaster.broadcast_transactions(&[&tx]); - false - } else { true } - }, - Err(e) => { - has_update = true; - let (close_channel, res) = convert_chan_phase_err!(self, peer_state, e, chan, channel_id, FUNDED_CHANNEL); - handle_errors.push((chan.context.get_counterparty_node_id(), Err(res))); - !close_channel - } + log_info!(logger, "Broadcasting {}", log_tx!(tx)); + self.tx_broadcaster.broadcast_transactions(&[&tx]); + false + } else { true } + }, + Err(e) => { + has_update = true; + let (close_channel, res) = convert_chan_phase_err!(self, peer_state, e, chan, channel_id, FUNDED_CHANNEL); + handle_errors.push((chan.context.get_counterparty_node_id(), Err(res))); + !close_channel } - }, - _ => true, // Retain unfunded channels if present. + } + } else { + true // Retain unfunded channels if present. } }); } @@ -10564,7 +10843,7 @@ where let mut peer_state_lock = peer_state_mutex.lock().unwrap(); let peer_state = &mut *peer_state_lock; for chan in peer_state.channel_by_id.values().filter_map( - |phase| if let ChannelPhase::Funded(chan) = phase { Some(chan) } else { None } + |phase| phase.funded_channel() ) { for (htlc_source, _) in chan.inflight_htlc_sources() { if let HTLCSource::OutboundRoute { path, .. } = htlc_source { @@ -10644,7 +10923,7 @@ where if let hash_map::Entry::Occupied(mut chan_phase_entry) = peer_state.channel_by_id.entry( channel_id) { - if let ChannelPhase::Funded(chan) = chan_phase_entry.get_mut() { + if let Some(chan) = chan_phase_entry.get_mut().funded_channel_mut() { debug_assert_eq!(chan.context.get_funding_txo().unwrap(), channel_funding_outpoint); if let Some((monitor_update, further_update_exists)) = chan.unblock_next_blocked_monitor_update() { log_debug!(logger, "Unlocking monitor updating for channel {} and updating monitor", @@ -10940,7 +11219,7 @@ where for (_cp_id, peer_state_mutex) in self.per_peer_state.read().unwrap().iter() { let mut peer_state_lock = peer_state_mutex.lock().unwrap(); let peer_state = &mut *peer_state_lock; - for chan in peer_state.channel_by_id.values().filter_map(|phase| if let ChannelPhase::Funded(chan) = phase { Some(chan) } else { None }) { + for chan in peer_state.channel_by_id.values().filter_map(|phase| phase.funded_channel()) { let txid_opt = chan.context.get_funding_txo(); let height_opt = chan.context.get_funding_tx_confirmation_height(); let hash_opt = chan.context.get_funding_tx_confirmed_in(); @@ -10997,11 +11276,7 @@ where let pending_msg_events = &mut peer_state.pending_msg_events; peer_state.channel_by_id.retain(|_, phase| { - match phase { - // Retain unfunded channels. - ChannelPhase::UnfundedOutboundV1(_) | ChannelPhase::UnfundedInboundV1(_) | - ChannelPhase::UnfundedOutboundV2(_) | ChannelPhase::UnfundedInboundV2(_) => true, - ChannelPhase::Funded(channel) => { + if let Some(channel) = phase.funded_channel_mut() { let res = f(channel); if let Ok((channel_ready_opt, mut timed_out_pending_htlcs, announcement_sigs)) = res { for (source, payment_hash) in timed_out_pending_htlcs.drain(..) { @@ -11113,7 +11388,9 @@ where return false; } true - } + } else { + // Retain unfunded channels. + true } }); } @@ -11345,28 +11622,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) { @@ -11511,6 +11802,16 @@ where } &mut chan.context }, + #[cfg(splicing)] + ChannelPhase::RefundingV2(channel) => { + let chan = &mut channel.pre_funded; + let logger = WithChannelContext::from(&self.logger, &chan.context, None); + if chan.remove_uncommitted_htlcs_and_mark_paused(&&logger).is_ok() { + // We only retain funded channels that are not shutdown. + return true; + } + &mut chan.context + }, // If we get disconnected and haven't yet committed to a funding // transaction, we can replay the `open_channel` on reconnection, so don't // bother dropping the channel here. However, if we already committed to @@ -11681,6 +11982,16 @@ where }); } + #[cfg(splicing)] + ChannelPhase::RefundingV2(channel) => { + let chan = &mut channel.pre_funded; + let logger = WithChannelContext::from(&self.logger, &chan.context, None); + pending_msg_events.push(events::MessageSendEvent::SendChannelReestablish { + node_id: chan.context.get_counterparty_node_id(), + msg: chan.get_channel_reestablish(&&logger), + }); + } + ChannelPhase::UnfundedOutboundV1(chan) => { pending_msg_events.push(events::MessageSendEvent::SendOpenChannel { node_id: chan.context.get_counterparty_node_id(), @@ -11731,7 +12042,7 @@ where let peer_state_mutex_opt = per_peer_state.get(&counterparty_node_id); if peer_state_mutex_opt.is_none() { return NotifyOption::SkipPersistNoEvents; } let mut peer_state = peer_state_mutex_opt.unwrap().lock().unwrap(); - if let Some(ChannelPhase::Funded(chan)) = peer_state.channel_by_id.get(&msg.channel_id) { + if let Some(Some(chan)) = peer_state.channel_by_id.get(&msg.channel_id).as_ref().map(|phase| phase.funded_channel()) { if let Some(msg) = chan.get_outbound_shutdown() { peer_state.pending_msg_events.push(events::MessageSendEvent::SendShutdown { node_id: counterparty_node_id, @@ -11807,6 +12118,8 @@ where } }, None | Some(ChannelPhase::UnfundedInboundV1(_) | ChannelPhase::UnfundedInboundV2(_) | ChannelPhase::Funded(_)) => (), + #[cfg(splicing)] + Some(ChannelPhase::RefundingV2(_)) => (), } } @@ -12757,7 +13070,7 @@ where } number_of_funded_channels += peer_state.channel_by_id.iter().filter( - |(_, phase)| if let ChannelPhase::Funded(chan) = phase { chan.context.is_funding_broadcast() } else { false } + |(_, phase)| if let Some(chan) = phase.funded_channel() { chan.context.is_funding_broadcast() } else { false } ).count(); } @@ -12767,7 +13080,7 @@ where let mut peer_state_lock = peer_state_mutex.lock().unwrap(); let peer_state = &mut *peer_state_lock; for channel in peer_state.channel_by_id.iter().filter_map( - |(_, phase)| if let ChannelPhase::Funded(channel) = phase { + |(_, phase)| if let Some(channel) = phase.funded_channel() { if channel.context.is_funding_broadcast() { Some(channel) } else { None } } else { None } ) { @@ -13567,7 +13880,7 @@ where let mut peer_state_lock = peer_state_mtx.lock().unwrap(); let peer_state = &mut *peer_state_lock; for phase in peer_state.channel_by_id.values() { - if let ChannelPhase::Funded(chan) = phase { + if let Some(chan) = phase.funded_channel() { let logger = WithChannelContext::from(&args.logger, &chan.context, None); // Channels that were persisted have to be funded, otherwise they should have been @@ -14009,7 +14322,7 @@ where let mut peer_state_lock = peer_state_mutex.lock().unwrap(); let peer_state = &mut *peer_state_lock; for (chan_id, phase) in peer_state.channel_by_id.iter_mut() { - if let ChannelPhase::Funded(chan) = phase { + if let Some(chan) = phase.funded_channel_mut() { let logger = WithChannelContext::from(&args.logger, &chan.context, None); if chan.context.outbound_scid_alias() == 0 { let mut outbound_scid_alias; @@ -14250,7 +14563,7 @@ where let peer_state_mutex = per_peer_state.get(&peer_node_id).unwrap(); let mut peer_state_lock = peer_state_mutex.lock().unwrap(); let peer_state = &mut *peer_state_lock; - if let Some(ChannelPhase::Funded(channel)) = peer_state.channel_by_id.get_mut(&previous_channel_id) { + if let Some(Some(channel)) = peer_state.channel_by_id.get_mut(&previous_channel_id).as_mut().map(|phase| phase.funded_channel_mut()) { let logger = WithChannelContext::from(&channel_manager.logger, &channel.context, Some(payment_hash)); channel.claim_htlc_while_disconnected_dropping_mon_update_legacy( claimable_htlc.prev_hop.htlc_id, payment_preimage, &&logger diff --git a/lightning/src/ln/functional_test_utils.rs b/lightning/src/ln/functional_test_utils.rs index 6c6b24bce7c..0a425ffd56b 100644 --- a/lightning/src/ln/functional_test_utils.rs +++ b/lightning/src/ln/functional_test_utils.rs @@ -796,7 +796,7 @@ macro_rules! get_event_msg { assert_eq!(*node_id, $node_id); (*msg).clone() }, - _ => panic!("Unexpected event"), + _ => panic!("Unexpected event {:?}", events[0]), } } } @@ -3618,7 +3618,7 @@ macro_rules! get_channel_value_stat { let peer_state_lock = $node.node.per_peer_state.read().unwrap(); let chan_lock = peer_state_lock.get(&$counterparty_node.node.get_our_node_id()).unwrap().lock().unwrap(); let chan = chan_lock.channel_by_id.get(&$channel_id).map( - |phase| if let ChannelPhase::Funded(chan) = phase { Some(chan) } else { None } + |phase| phase.funded_channel() ).flatten().unwrap(); chan.get_value_stat() }} diff --git a/lightning/src/ln/functional_tests_splice.rs b/lightning/src/ln/functional_tests_splice.rs new file mode 100644 index 00000000000..48fae40627f --- /dev/null +++ b/lightning/src/ln/functional_tests_splice.rs @@ -0,0 +1,377 @@ +// 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 expect_inputs_in_reverse = true; + + 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 + + // Expected balances + let mut exp_balance1 = 1000 * channel_value_sat; + let mut exp_balance2 = 0; + + // === 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 post_splice_channel_value = channel_value_sat + splice_in_sats; + 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, exp_balance2); + assert_eq!(channel.funding_txo.unwrap().txid, funding_tx.compute_txid()); + 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, + exp_balance1 - 1000 * channel_reserve_amnt_sat + ); + assert_eq!(channel.funding_txo.unwrap().txid, funding_tx.compute_txid()); + assert!(channel.confirmations.unwrap() > 0); + } + + exp_balance1 += 1000 * splice_in_sats; // increase in balance + + // Negotiate transaction inputs and outputs + + // First input + let tx_add_input_msg = get_event_msg!(&initiator_node, MessageSendEvent::SendTxAddInput, acceptor_node.node.get_our_node_id()); + let exp_value = if expect_inputs_in_reverse { extra_splice_funding_input_sats } else { channel_value_sat }; + assert_eq!(tx_add_input_msg.prevtx.as_transaction().output[tx_add_input_msg.prevtx_out as usize].value.to_sat(), exp_value); + + let _res = acceptor_node.node.handle_tx_add_input(initiator_node.node.get_our_node_id(), &tx_add_input_msg); + let tx_complete_msg = get_event_msg!(acceptor_node, MessageSendEvent::SendTxComplete, initiator_node.node.get_our_node_id()); + + let _res = initiator_node.node.handle_tx_complete(acceptor_node.node.get_our_node_id(), &tx_complete_msg); + // Second input + let exp_value = if expect_inputs_in_reverse { channel_value_sat } else { extra_splice_funding_input_sats }; + let tx_add_input2_msg = get_event_msg!(&initiator_node, MessageSendEvent::SendTxAddInput, acceptor_node.node.get_our_node_id()); + assert_eq!(tx_add_input2_msg.prevtx.as_transaction().output[tx_add_input2_msg.prevtx_out as usize].value.to_sat(), exp_value); + + let _res = acceptor_node.node.handle_tx_add_input(initiator_node.node.get_our_node_id(), &tx_add_input2_msg); + let tx_complete_msg = get_event_msg!(acceptor_node, MessageSendEvent::SendTxComplete, initiator_node.node.get_our_node_id()); + + let _res = initiator_node.node.handle_tx_complete(acceptor_node.node.get_our_node_id(), &tx_complete_msg); + + // TxAddOutput for the splice funding + let tx_add_output_msg = get_event_msg!(&initiator_node, MessageSendEvent::SendTxAddOutput, acceptor_node.node.get_our_node_id()); + assert!(tx_add_output_msg.script.is_p2wpkh()); + assert_eq!(tx_add_output_msg.sats, 14093); // extra_splice_input_sats - splice_in_sats + + let _res = acceptor_node.node.handle_tx_add_output(initiator_node.node.get_our_node_id(), &tx_add_output_msg); + let tx_complete_msg = get_event_msg!(&acceptor_node, MessageSendEvent::SendTxComplete, initiator_node.node.get_our_node_id()); + + let _res = initiator_node.node.handle_tx_complete(acceptor_node.node.get_our_node_id(), &tx_complete_msg); + // TxAddOutput for the change output + let tx_add_output2_msg = get_event_msg!(&initiator_node, MessageSendEvent::SendTxAddOutput, acceptor_node.node.get_our_node_id()); + assert!(tx_add_output2_msg.script.is_p2wsh()); + assert_eq!(tx_add_output2_msg.sats, post_splice_channel_value); + + let _res = acceptor_node.node.handle_tx_add_output(initiator_node.node.get_our_node_id(), &tx_add_output2_msg); + let _tx_complete_msg = get_event_msg!(acceptor_node, MessageSendEvent::SendTxComplete, initiator_node.node.get_our_node_id()); + + // TODO(splicing) This is the last tx_complete, which triggers the commitment flow, which is not yet implemented + // let _res = initiator_node.node.handle_tx_complete(acceptor_node.node.get_our_node_id(), &tx_complete_msg); + + // TODO(splicing): Continue with commitment flow, new tx confirmation + + + // === 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/interactivetxs.rs b/lightning/src/ln/interactivetxs.rs index 2b72133ec09..9c95222e39b 100644 --- a/lightning/src/ln/interactivetxs.rs +++ b/lightning/src/ln/interactivetxs.rs @@ -1151,13 +1151,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 {}", @@ -1176,7 +1176,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 @@ -1186,7 +1186,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, @@ -1662,14 +1662,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; @@ -2594,4 +2637,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/mod.rs b/lightning/src/ln/mod.rs index e1631a2892c..aa1849bcd5a 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;