From 138574cc32b10cf4bebfd73fb6df37df2d29ba45 Mon Sep 17 00:00:00 2001 From: optout <13562139+optout21@users.noreply.github.com> Date: Sat, 15 Jun 2024 23:54:00 +0200 Subject: [PATCH] Refactor splice contribution handling (splice_init and splice_ack contain their contribution respectively) --- README.md | 10 +- lightning/src/ln/channel.rs | 128 ++++++++---------- lightning/src/ln/channel_splice.rs | 208 ++++++++++++++--------------- lightning/src/ln/channelmanager.rs | 56 ++++---- 4 files changed, 192 insertions(+), 210 deletions(-) diff --git a/README.md b/README.md index d300eb76ebe..90e598ed166 100644 --- a/README.md +++ b/README.md @@ -20,14 +20,14 @@ Splicing Prototype 'Happy Path' PoC for Splicing Objective, Restrictions: -- Splice-in supported (increase channel capacity) +- Splice-in supported (increase channel capacity), splice-out not - between two LDK instances - No quiscence is used/checked - Happy path only, no complex combinations, not all error scenarios -- Splice from only V2 channel is supported, channel ID is not changed +- Splice from V2 channel is supported, from V1 channel not, the channel ID is not changed +- Acceptor does not contribute inputs - It is assumed that all extra inputs belong to the initiator (the full capacity increase is credited to the channel initiator) -- Only a single pending splicing is supported at a time -- Splice-in with contributions from the acceptor is not supported +- RBF of pending splice is not supported, only a single pending splicing is supported at a time Up-to-date with main branch as of v0.0.123 (May 8, 475f736; originally branched off v0.0.115). @@ -44,7 +44,7 @@ Client LDK Counterparty node (acceptor) --> splice_channel() - ChannelManager API Do checks, save pending splice parameters - get_splice() - Channel + get_splice_init() - Channel message out: splice_init --- message in: splice_init diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index fbbcb12a726..e27cc108d40 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -29,7 +29,7 @@ use bitcoin::locktime::absolute::LockTime; use crate::ln::types::{ChannelId, PaymentPreimage, PaymentHash}; #[cfg(splicing)] -use crate::ln::channel_splice::{PendingSpliceInfoPre, PendingSpliceInfoPost}; +use crate::ln::channel_splice::{PendingSpliceInfoPre, PendingSpliceInfoPost, SplicingChannelValues}; use crate::ln::features::{ChannelTypeFeatures, InitFeatures}; #[cfg(any(dual_funding, splicing))] use crate::ln::interactivetxs::{ConstructedTransaction, estimate_input_weight, get_output_weight, HandleTxCompleteResult, InteractiveTxConstructor, InteractiveTxSigningSession, InteractiveTxMessageSend, InteractiveTxMessageSendResult, TX_COMMON_FIELDS_WEIGHT}; @@ -2342,6 +2342,7 @@ impl ChannelContext where SP::Target: SignerProvider { } /// Create channel context for spliced channel, by duplicating and updating the context. + /// TODO change doc /// relative_satoshis: The change in channel value (sats), /// positive for increase (splice-in), negative for decrease (splice out). /// delta_belongs_to_local: @@ -2353,8 +2354,8 @@ impl ChannelContext where SP::Target: SignerProvider { pre_splice_context: Self, is_outgoing: bool, counterparty_funding_pubkey: &PublicKey, - relative_satoshis: i64, - delta_belongs_to_local: i64, + our_funding_contribution: i64, + their_funding_contribution: i64, holder_signer: ::EcdsaSigner, logger: &L, ) -> Result, ChannelError> where L::Target: Logger @@ -2366,7 +2367,6 @@ impl ChannelContext where SP::Target: SignerProvider { } let pre_channel_value = context.channel_value_satoshis; - let post_channel_value = PendingSpliceInfoPre::add_checked(pre_channel_value, relative_satoshis); // Save the current funding transaction let pre_funding_transaction = context.funding_transaction_saved.clone(); @@ -2374,12 +2374,14 @@ impl ChannelContext where SP::Target: SignerProvider { // Save relevant info from pre-splice state let pending_splice_post = PendingSpliceInfoPost::new( - relative_satoshis, pre_channel_value, + our_funding_contribution, + their_funding_contribution, Some(context.channel_id), pre_funding_transaction, pre_funding_txo, ); + let post_channel_value = pending_splice_post.post_channel_value(); context.pending_splice_post = Some(pending_splice_post); // Update funding pubkeys @@ -2390,21 +2392,12 @@ impl ChannelContext where SP::Target: SignerProvider { // Update channel signer context.holder_signer = ChannelSignerType::Ecdsa(holder_signer); - // Update the channel value and adjust balance, after checks - // Check if delta_belongs_to_local is not invalid (invalid abs value or invalid sign) - if delta_belongs_to_local.abs() > relative_satoshis.abs() || - (relative_satoshis > 0 && delta_belongs_to_local < 0) || - (relative_satoshis < 0 && delta_belongs_to_local > 0) - { - // Invalid paramters - return Err(ChannelError::Close(format!("Invalid delta_belongs_to_local value, larger than relative value {} {}", delta_belongs_to_local, relative_satoshis))); - } let old_to_self = context.value_to_self_msat; - let delta_in_value_to_self = delta_belongs_to_local * 1000; + 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::Close(format!("Cannot decrease channel value to requested amount, too low, {} {} {} {} {}", - pre_channel_value, post_channel_value, relative_satoshis, delta_belongs_to_local, old_to_self))); + pre_channel_value, post_channel_value, our_funding_contribution, their_funding_contribution, old_to_self))); } // Perform the updates context.channel_value_satoshis = post_channel_value; @@ -4142,7 +4135,7 @@ impl ChannelContext where SP::Target: SignerProvider { let (prev_funding_input_index, pre_channel_value) = if let Some(pending_splice) = &self.pending_splice_post { ( pending_splice.find_input_of_previous_funding(&transaction)?, - pending_splice.pre_channel_value + pending_splice.pre_channel_value() ) } else { return Err(ChannelError::Warn(format!("Cannot sign splice transaction, channel is not in active splice, channel_id {}", self.channel_id))) @@ -7997,9 +7990,7 @@ impl Channel where /// Inspired by get_open_channel() /// Get the splice message that can be sent during splice initiation #[cfg(splicing)] - pub fn get_splice(&mut self, chain_hash: ChainHash, - // TODO; should this be a param, or stored in the channel? - funding_contribution_satoshis: i64, signer_provider: &SP, + pub fn get_splice_init(&mut self, our_funding_contribution_satoshis: i64, signer_provider: &SP, funding_feerate_perkw: u32, locktime: u32 ) -> msgs::SpliceInit { if !self.context.is_outbound() { @@ -8024,9 +8015,12 @@ impl Channel where // channel value. // Note that channel_keys_id is supposed NOT to change let funding_pubkey = { - // TODO this is NOT the final post_splice value - let post_splice_channel_value = PendingSpliceInfoPre::add_checked(self.context.channel_value_satoshis, funding_contribution_satoshis); - let holder_signer = signer_provider.derive_channel_signer(post_splice_channel_value, self.context.channel_keys_id); + // TODO: Funding pubkey generation requires the post channel value, but that is not known yet, + // the acceptor contribution is missing. There is a need for a way to generate a new funding pubkey, + // not based on the channel value + let pre_channel_value = self.context.channel_value_satoshis; + let incomplete_post_splice_channel_value = SplicingChannelValues::compute_post_value(pre_channel_value, our_funding_contribution_satoshis, 0); + let holder_signer = signer_provider.derive_channel_signer(incomplete_post_splice_channel_value, self.context.channel_keys_id); holder_signer.pubkeys().funding_pubkey }; @@ -8036,7 +8030,7 @@ impl Channel where // TODO how to handle channel capacity, orig is stored in Channel, has to be updated, in the interim there are two msgs::SpliceInit { channel_id: self.context.channel_id, - funding_contribution_satoshis, + funding_contribution_satoshis: our_funding_contribution_satoshis, funding_feerate_perkw, locktime, funding_pubkey, @@ -9098,37 +9092,26 @@ pub(super) struct OutboundV2Channel where SP::Target: SignerProvider #[cfg(splicing)] fn calculate_funding_values( pre_channel_value: u64, - relative_satoshis: i64, - delta_belongs_to_local: i64, + our_funding_contribution: i64, + their_funding_contribution: i64, is_initiator: bool, ) -> Result<(u64, u64), ChannelError> { - // Check that delta_belongs_to_local is valid (not too high absolute value, and same sign) - if delta_belongs_to_local.abs() > relative_satoshis.abs() || - (delta_belongs_to_local > 0 && relative_satoshis < 0) || - (delta_belongs_to_local < 0 && relative_satoshis > 0) { - return Err(ChannelError::Close( - format!("Invalid delta_belongs_to_local value {}, relative_satoshis {}", delta_belongs_to_local, relative_satoshis))); - } - let mut our_funding_signed = delta_belongs_to_local; - let mut their_funding_signed = relative_satoshis.saturating_sub(delta_belongs_to_local); - debug_assert_eq!(our_funding_signed + their_funding_signed, relative_satoshis); - // The initiator side will need to add an input for the current funding + // 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_funding_signed = our_funding_signed.saturating_add(pre_channel_value as i64); + our_contribution_with_prev = our_contribution_with_prev.saturating_add(pre_channel_value as i64); } else { - their_funding_signed = their_funding_signed.saturating_add(pre_channel_value as i64); - } - // Check that both resulting funding values are non-negative - if our_funding_signed < 0 || their_funding_signed < 0 { - return Err(ChannelError::Close( - format!("Negative funding value is invalid, {} {}, pre_channel_value {} relative_satoshis {}, belongs_to_local {}, initiator {}", - our_funding_signed, their_funding_signed, pre_channel_value, relative_satoshis, delta_belongs_to_local, is_initiator))); - } - let our_funding = our_funding_signed.abs() as u64; - let their_funding = their_funding_signed.abs() as u64; - // Double check that the two funding values add up - debug_assert_eq!(our_funding + their_funding, PendingSpliceInfoPre::add_checked(pre_channel_value, relative_satoshis)); - Ok((our_funding, their_funding)) + 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::Close(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)) } #[cfg(any(dual_funding, splicing))] @@ -9195,8 +9178,8 @@ impl OutboundV2Channel where SP::Target: SignerProvider { pre_splice_context: ChannelContext, signer_provider: &SP, counterparty_funding_pubkey: &PublicKey, - relative_satoshis: i64, - delta_belongs_to_local: i64, + our_funding_contribution: i64, + their_funding_contribution: i64, funding_inputs: Vec<(TxIn, TransactionU16LenLimited)>, funding_tx_locktime: LockTime, funding_feerate_sat_per_1000_weight: u32, @@ -9204,7 +9187,7 @@ impl OutboundV2Channel where SP::Target: SignerProvider { ) -> Result where L::Target: Logger { let pre_channel_value = pre_splice_context.get_value_satoshis(); - let post_channel_value = PendingSpliceInfoPre::add_checked(pre_channel_value, relative_satoshis); + let post_channel_value = SplicingChannelValues::compute_post_value(pre_channel_value, our_funding_contribution, their_funding_contribution); // 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_context.channel_keys_id); @@ -9227,13 +9210,18 @@ impl OutboundV2Channel where SP::Target: SignerProvider { pre_splice_context, true, counterparty_funding_pubkey, - relative_satoshis, - delta_belongs_to_local, + our_funding_contribution, + their_funding_contribution, holder_signer, logger, )?; - let (our_funding_satoshis, their_funding_satoshis) = calculate_funding_values(pre_channel_value, relative_satoshis, delta_belongs_to_local, true)?; + let (our_funding_satoshis, their_funding_satoshis) = calculate_funding_values( + pre_channel_value, + our_funding_contribution, + their_funding_contribution, + true, + )?; let post_chan = Self { context, @@ -9450,8 +9438,8 @@ impl InboundV2Channel where SP::Target: SignerProvider { pre_splice_context: ChannelContext, signer_provider: &SP, counterparty_funding_pubkey: &PublicKey, - relative_satoshis: i64, - delta_belongs_to_local: i64, + our_funding_contribution: i64, + their_funding_contribution: i64, funding_inputs: Vec<(TxIn, TransactionU16LenLimited)>, funding_tx_locktime: LockTime, funding_feerate_sat_per_1000_weight: u32, @@ -9459,7 +9447,7 @@ impl InboundV2Channel where SP::Target: SignerProvider { ) -> Result where L::Target: Logger { let pre_channel_value = pre_splice_context.get_value_satoshis(); - let post_channel_value = PendingSpliceInfoPre::add_checked(pre_channel_value, relative_satoshis); + let post_channel_value = SplicingChannelValues::compute_post_value(pre_channel_value, our_funding_contribution, their_funding_contribution); // 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_context.channel_keys_id); @@ -9482,13 +9470,18 @@ impl InboundV2Channel where SP::Target: SignerProvider { pre_splice_context, false, counterparty_funding_pubkey, - relative_satoshis, - delta_belongs_to_local, + our_funding_contribution, + their_funding_contribution, holder_signer, logger, )?; - let (our_funding_satoshis, their_funding_satoshis) = calculate_funding_values(pre_channel_value, relative_satoshis, delta_belongs_to_local, false)?; + let (our_funding_satoshis, their_funding_satoshis) = calculate_funding_values( + pre_channel_value, + our_funding_contribution, + their_funding_contribution, + false, + )?; let post_chan = Self { context, @@ -9608,16 +9601,11 @@ impl InboundV2Channel where SP::Target: SignerProvider { /// Get the splice_ack message that can be sent in response to splice initiation /// TODO move to ChannelContext #[cfg(splicing)] - pub fn get_splice_ack(&mut self, _chain_hash: ChainHash) -> Result { + pub fn get_splice_ack(&mut self, our_funding_contribution_satoshis: i64) -> Result { if self.context.is_outbound() { panic!("Tried to accept a splice on an outound channel?"); } - let pending_splice = match &self.context.pending_splice_post { - None => return Err(ChannelError::Close("get_splice_ack() is invalid on a channel with no active splice".to_owned())), - Some(ps) => ps, - }; - // TODO checks // TODO check @@ -9628,7 +9616,7 @@ impl InboundV2Channel where SP::Target: SignerProvider { // TODO how to handle channel capacity, orig is stored in Channel, has to be updated, in the interim there are two Ok(msgs::SpliceAck { channel_id: self.context.channel_id, // pending_splice.pre_channel_id.unwrap(), // TODO - funding_contribution_satoshis: pending_splice.relative_satoshis(), // TODO, should be acceptor's side, 0 currently + funding_contribution_satoshis: our_funding_contribution_satoshis, funding_pubkey, require_confirmed_inputs: None, }) diff --git a/lightning/src/ln/channel_splice.rs b/lightning/src/ln/channel_splice.rs index 0b911d5fd33..43a54c73f58 100644 --- a/lightning/src/ln/channel_splice.rs +++ b/lightning/src/ln/channel_splice.rs @@ -15,13 +15,46 @@ use crate::ln::channel::ChannelError; use crate::prelude::*; use crate::util::ser::TransactionU16LenLimited; use bitcoin::{ScriptBuf, Sequence, Transaction, TxIn, Witness}; -use core::convert::TryFrom; + +/// Holds the pre-splice channel value, the contributions of the peers, and can compute the post-splice channel value. +#[derive(Clone)] +pub(crate) struct SplicingChannelValues { + /// The pre splice value + pub pre_channel_value: u64, + pub our_funding_contribution: i64, + pub their_funding_contribution: i64, +} + +impl SplicingChannelValues { + 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(Self::add_checked(pre_channel_value, our_funding_contribution), their_funding_contribution) + } + + /// The post-splice channel value, computed from the pre-splice values and the peer contributions + pub fn post_channel_value(&self) -> u64 { + Self::add_checked(self.pre_channel_value, self.delta_channel_value()) + } + + /// The computed change in the channel value + pub fn delta_channel_value(&self) -> i64 { + self.our_funding_contribution.saturating_add(self.their_funding_contribution) + } +} /// Info about a pending splice, used in the pre-splice channel #[derive(Clone)] pub(crate) struct PendingSpliceInfoPre { - /// The post splice value (current + relative) - pub post_channel_value: u64, + /// Previous and next channel values + values: SplicingChannelValues, /// Reference to the post-splice channel (may be missing if channel_id is not yet known or the same) pub post_channel_id: Option, pub funding_feerate_perkw: u32, @@ -30,13 +63,26 @@ pub(crate) struct PendingSpliceInfoPre { pub our_funding_inputs: Vec<(TxIn, TransactionU16LenLimited)>, } +impl PendingSpliceInfoPre { + pub(crate) fn new(pre_channel_value: u64, our_funding_contribution: i64, their_funding_contribution: i64, + post_channel_id: Option, funding_feerate_perkw: u32, locktime: u32, + our_funding_inputs: Vec<(TxIn, TransactionU16LenLimited)>, + ) -> Self { + Self { + values: SplicingChannelValues { pre_channel_value, our_funding_contribution, their_funding_contribution }, + post_channel_id, funding_feerate_perkw, locktime, our_funding_inputs, + } + } + + /// Accessor + pub(crate) fn our_funding_contribution(&self) -> i64 { self.values.our_funding_contribution } +} + /// Info about a pending splice, used in the post-splice channel #[derive(Clone)] pub(crate) struct PendingSpliceInfoPost { - /// The post splice value (current + relative) - pub post_channel_value: u64, // TODO may be removed, it's in the channel capacity - /// The pre splice value (a bit redundant) - pub pre_channel_value: u64, + /// Previous and next channel values + values: SplicingChannelValues, /// Reference to the pre-splice channel (may be missing if channel_id was the same) #[allow(unused)] pub pre_channel_id: Option, @@ -47,62 +93,23 @@ pub(crate) struct PendingSpliceInfoPost { pub pre_funding_txo: Option, } -impl PendingSpliceInfoPre { - pub(crate) fn new(relative_satoshis: i64, pre_channel_value: u64, - post_channel_id: Option, funding_feerate_perkw: u32, locktime: u32, - our_funding_inputs: Vec<(TxIn, TransactionU16LenLimited)>, - ) -> Self { - let post_channel_value = Self::add_checked(pre_channel_value, relative_satoshis); - Self { - post_channel_value, - post_channel_id, - funding_feerate_perkw, - locktime, - our_funding_inputs, - } - } - - /// Add a u64 and an i64, handling i64 overflow cases (doing without cast to i64) - pub(crate) fn add_checked(pre_channel_value: u64, relative_satoshis: i64) -> u64 { - if relative_satoshis >= 0 { - pre_channel_value.saturating_add(relative_satoshis as u64) - } else { - pre_channel_value.saturating_sub((-relative_satoshis) as u64) - } - } - - /// The relative splice value (change in capacity value relative to current value) - pub(crate) fn relative_satoshis(&self, pre_channel_value: u64) -> i64 { - if self.post_channel_value > pre_channel_value { - i64::try_from(self.post_channel_value.saturating_sub(pre_channel_value)).unwrap_or_default() - } else { - -i64::try_from(pre_channel_value.saturating_sub(self.post_channel_value)).unwrap_or_default() - } - } -} - impl PendingSpliceInfoPost { - pub(crate) fn new(relative_satoshis: i64, pre_channel_value: u64, pre_channel_id: Option, - pre_funding_transaction: Option, pre_funding_txo: Option + pub(crate) fn new( + pre_channel_value: u64, our_funding_contribution: i64, their_funding_contribution: i64, + pre_channel_id: Option, + pre_funding_transaction: Option, pre_funding_txo: Option, ) -> Self { - let post_channel_value = PendingSpliceInfoPre::add_checked(pre_channel_value, relative_satoshis); Self { - post_channel_value, - pre_channel_value, - pre_channel_id, - pre_funding_transaction, - pre_funding_txo, + values: SplicingChannelValues { pre_channel_value, our_funding_contribution, their_funding_contribution }, + pre_channel_id, pre_funding_transaction, pre_funding_txo, } } - /// The relative splice value (change in capacity value relative to current value) - pub(crate) fn relative_satoshis(&self) -> i64 { - if self.post_channel_value > self.pre_channel_value { - i64::try_from(self.post_channel_value.saturating_sub(self.pre_channel_value)).unwrap_or_default() - } else { - -i64::try_from(self.pre_channel_value.saturating_sub(self.post_channel_value)).unwrap_or_default() - } - } + /// Accessor + pub(crate) fn pre_channel_value(&self) -> u64 { self.values.pre_channel_value } + + /// The post-splice channel value, computed from the pre-splice values and the peer contributions + pub(crate) fn post_channel_value(&self) -> u64 { self.values.post_channel_value() } /// Get a transaction input that is the previous funding transaction pub(super) fn get_input_of_previous_funding(&self) -> Result<(TxIn, TransactionU16LenLimited), ChannelError> { @@ -146,76 +153,63 @@ impl PendingSpliceInfoPost { mod tests { use crate::ln::channel_splice::PendingSpliceInfoPost; - fn create_pending_splice_info(pre_channel_value: u64, post_channel_value: u64) -> PendingSpliceInfoPost { - PendingSpliceInfoPost { - post_channel_value, - pre_channel_value, - pre_channel_id: None, - pre_funding_transaction: None, - pre_funding_txo: None, - } + fn create_pending_splice_info(pre_channel_value: u64, our_funding_contribution: i64, their_funding_contribution: i64) -> PendingSpliceInfoPost { + PendingSpliceInfoPost::new(pre_channel_value, our_funding_contribution, their_funding_contribution, None, None, None) } #[test] fn test_pending_splice_info_new() { { // increase, small amounts - let ps = create_pending_splice_info(9_000, 15_000); - assert_eq!(ps.pre_channel_value, 9_000); - assert_eq!(ps.post_channel_value, 15_000); - assert_eq!(ps.relative_satoshis(), 6_000); + let ps = create_pending_splice_info(9_000, 6_000, 0); + assert_eq!(ps.pre_channel_value(), 9_000); + assert_eq!(ps.post_channel_value(), 15_000); } { - // decrease, small amounts - let ps = create_pending_splice_info(15_000, 9_000); - assert_eq!(ps.pre_channel_value, 15_000); - assert_eq!(ps.post_channel_value, 9_000); - assert_eq!(ps.relative_satoshis(), -6_000); + // increase, small amounts + let ps = create_pending_splice_info(9_000, 4_000, 2_000); + assert_eq!(ps.pre_channel_value(), 9_000); + assert_eq!(ps.post_channel_value(), 15_000); } - let base2: u64 = 2; - let huge63 = base2.pow(63); - assert_eq!(huge63, 9223372036854775808); { - // increase, one huge amount - let ps = create_pending_splice_info(9_000, huge63 + 9_000 - 1); - assert_eq!(ps.pre_channel_value, 9_000); - assert_eq!(ps.post_channel_value, 9223372036854784807); // 2^63 + 9000 - 1 - assert_eq!(ps.relative_satoshis(), 9223372036854775807); // 2^63 - 1 + // increase, small amounts + let ps = create_pending_splice_info(9_000, 0, 6_000); + assert_eq!(ps.pre_channel_value(), 9_000); + assert_eq!(ps.post_channel_value(), 15_000); } { - // decrease, one huge amount - let ps = create_pending_splice_info(huge63 + 9_000 - 1, 9_000); - assert_eq!(ps.pre_channel_value, 9223372036854784807); // 2^63 + 9000 - 1 - assert_eq!(ps.post_channel_value, 9_000); - assert_eq!(ps.relative_satoshis(), -9223372036854775807); // 2^63 - 1 + // decrease, small amounts + let ps = create_pending_splice_info(15_000, -6_000, 0); + assert_eq!(ps.pre_channel_value(), 15_000); + assert_eq!(ps.post_channel_value(), 9_000); } { - // increase, two huge amounts - let ps = create_pending_splice_info(huge63 + 9_000, huge63 + 15_000); - assert_eq!(ps.pre_channel_value, 9223372036854784808); // 2^63 + 9000 - assert_eq!(ps.post_channel_value, 9223372036854790808); // 2^63 + 15000 - assert_eq!(ps.relative_satoshis(), 6_000); + // decrease, small amounts + let ps = create_pending_splice_info(15_000, -4_000, -2_000); + assert_eq!(ps.pre_channel_value(), 15_000); + assert_eq!(ps.post_channel_value(), 9_000); } { - // decrease, two huge amounts - let ps = create_pending_splice_info(huge63 + 15_000, huge63 + 9_000); - assert_eq!(ps.pre_channel_value, 9223372036854790808); // 2^63 + 15000 - assert_eq!(ps.post_channel_value, 9223372036854784808); // 2^63 + 9000 - assert_eq!(ps.relative_satoshis(), -6_000); + // increase and decrease + let ps = create_pending_splice_info(15_000, 4_000, -2_000); + assert_eq!(ps.pre_channel_value(), 15_000); + assert_eq!(ps.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); { - // underflow - let ps = create_pending_splice_info(9_000, huge63 + 9_000 + 20); - assert_eq!(ps.pre_channel_value, 9_000); - assert_eq!(ps.post_channel_value, 9223372036854784828); // 2^63 + 9000 + 20 - assert_eq!(ps.relative_satoshis(), -0); + // increase, large amount + let ps = create_pending_splice_info(9_000, huge63i3, 3); + assert_eq!(ps.pre_channel_value(), 9_000); + assert_eq!(ps.post_channel_value(), 9223372036854784807); } { - // underflow - let ps = create_pending_splice_info(huge63 + 9_000 + 20, 9_000); - assert_eq!(ps.pre_channel_value, 9223372036854784828); // 2^63 + 9000 + 20 - assert_eq!(ps.post_channel_value, 9_000); - assert_eq!(ps.relative_satoshis(), -0); + // increase, large amounts + let ps = create_pending_splice_info(9_000, huge63i3, huge63i3); + assert_eq!(ps.pre_channel_value(), 9_000); + assert_eq!(ps.post_channel_value(), 9223372036854784807); } } } diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index c820b3d92d3..cc8cdcfef0f 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -52,7 +52,7 @@ pub use crate::ln::channel::{InboundHTLCDetails, InboundHTLCStateDetails, Outbou #[cfg(any(dual_funding, splicing))] use crate::ln::channel::{InboundV2Channel, OutboundV2Channel, InteractivelyFunded as _}; #[cfg(splicing)] -use crate::ln::channel_splice::PendingSpliceInfoPre; +use crate::ln::channel_splice::{PendingSpliceInfoPre, SplicingChannelValues}; use crate::ln::features::{Bolt12InvoiceFeatures, ChannelFeatures, ChannelTypeFeatures, InitFeatures, NodeFeatures}; #[cfg(any(feature = "_test_utils", test))] use crate::ln::features::Bolt11InvoiceFeatures; @@ -3489,7 +3489,7 @@ where /// [new funding tx can be broadcast] #[cfg(splicing)] pub fn splice_channel( - &self, channel_id: &ChannelId, their_network_key: &PublicKey, funding_contribution_satoshis: i64, + &self, channel_id: &ChannelId, their_network_key: &PublicKey, our_funding_contribution_satoshis: i64, funding_inputs: Vec<(TxIn, Transaction)>, funding_feerate_perkw: u32, locktime: u32 ) -> Result<(), APIError> { let funding_inputs = Self::length_limit_holder_input_prev_txs(funding_inputs)?; @@ -3509,26 +3509,28 @@ where hash_map::Entry::Vacant(_) => return Err(APIError::ChannelUnavailable{err: format!("Channel with id {} not found for the passed counterparty node_id {}", channel_id, their_network_key) }), hash_map::Entry::Occupied(mut chan_phase_entry) => { if let ChannelPhase::Funded(chan) = chan_phase_entry.get_mut() { - if funding_contribution_satoshis < 0 { - return Err(APIError::APIMisuseError { err: format!("Splice-out not supported, only splice in, relative {}, channel_id {}", -funding_contribution_satoshis, channel_id) }); - } - let pre_channel_value = chan.context.get_value_satoshis(); // TODO check for i64 overflow - if funding_contribution_satoshis < 0 && -funding_contribution_satoshis > (pre_channel_value as i64) { - return Err(APIError::APIMisuseError { err: format!("Post-splicing channel value cannot be negative. It was {} - {}", pre_channel_value, -funding_contribution_satoshis) }); + if our_funding_contribution_satoshis < 0 && -our_funding_contribution_satoshis > (pre_channel_value as i64) { + return Err(APIError::APIMisuseError { err: format!("Post-splicing channel value cannot be negative. It was {} - {}", pre_channel_value, -our_funding_contribution_satoshis) }); } - let post_channel_value = PendingSpliceInfoPre::add_checked(pre_channel_value, funding_contribution_satoshis); - if post_channel_value < 1000 { - return Err(APIError::APIMisuseError { err: format!("Post-splicing channel value must be at least 1000 satoshis. It was {}", post_channel_value) }); + if our_funding_contribution_satoshis < 0 { + return Err(APIError::APIMisuseError { err: format!("TODO: Splice-out not supported, only splice in, contribution {}, channel_id {}", -our_funding_contribution_satoshis, channel_id) }); } + // Note: post-splice channel value is not yet known at this point, acceptor contribution is not known + // (Cannot test for miminum required post-splice channel value) + if chan.context.pending_splice_pre.is_some() { return Err(APIError::ChannelUnavailable { err: format!("Channel has already a splice pending, channel id {}", channel_id) }); } - chan.context.pending_splice_pre = Some(PendingSpliceInfoPre::new(funding_contribution_satoshis, pre_channel_value, None, funding_feerate_perkw, locktime, funding_inputs)); + let their_funding_contribution = 0i64; // not yet known + chan.context.pending_splice_pre = Some(PendingSpliceInfoPre::new( + pre_channel_value, our_funding_contribution_satoshis, their_funding_contribution, + None, funding_feerate_perkw, locktime, funding_inputs + )); // Check channel id let post_splice_v2_channel_id = chan.context.generate_v2_channel_id_from_revocation_basepoints(); @@ -3537,7 +3539,7 @@ where chan.context.channel_id(), post_splice_v2_channel_id) }); } - let msg = chan.get_splice(self.chain_hash.clone(), funding_contribution_satoshis, &self.signer_provider, funding_feerate_perkw, locktime); + let msg = chan.get_splice_init(our_funding_contribution_satoshis, &self.signer_provider, funding_feerate_perkw, locktime); peer_state.pending_msg_events.push(events::MessageSendEvent::SendSpliceInit { node_id: *their_network_key, @@ -9240,22 +9242,24 @@ where let mut peer_state_lock = peer_state_mutex.lock().unwrap(); let peer_state = &mut *peer_state_lock; + let our_funding_contribution = 0i64; + // Look for channel match peer_state.channel_by_id.entry(msg.channel_id) { hash_map::Entry::Vacant(_) => return Err(MsgHandleErrInternal::send_err_msg_no_close(format!("Got a message for a channel from the wrong node! No such channel for the passed counterparty_node_id {}, channel_id {}", counterparty_node_id, msg.channel_id), msg.channel_id)), hash_map::Entry::Occupied(chan_entry) => { if let ChannelPhase::Funded(chan) = chan_entry.get() { - if msg.funding_contribution_satoshis < 0 { - return Err(MsgHandleErrInternal::send_err_msg_no_close(format!("Splice-out not supported, only splice in, relative {}", -msg.funding_contribution_satoshis), msg.channel_id)); - } - let pre_channel_value = chan.context.get_value_satoshis(); // TODO check for i64 overflow if msg.funding_contribution_satoshis < 0 && -msg.funding_contribution_satoshis > (pre_channel_value as i64) { return Err(MsgHandleErrInternal::send_err_msg_no_close(format!("Post-splicing channel value cannot be negative. It was {} - {}", pre_channel_value, -msg.funding_contribution_satoshis), msg.channel_id)); } - let post_channel_value = PendingSpliceInfoPre::add_checked(pre_channel_value, msg.funding_contribution_satoshis); + if msg.funding_contribution_satoshis < 0 { + return Err(MsgHandleErrInternal::send_err_msg_no_close(format!("Splice-out not supported, only splice in, relative {}", -msg.funding_contribution_satoshis), msg.channel_id)); + } + + let post_channel_value = SplicingChannelValues::compute_post_value(pre_channel_value, msg.funding_contribution_satoshis, our_funding_contribution); if post_channel_value < 1000 { return Err(MsgHandleErrInternal::send_err_msg_no_close(format!("Post-splicing channel value must be at least 1000 satoshis. It was {}", post_channel_value), msg.channel_id)); } @@ -9295,8 +9299,8 @@ where prev_chan.context, &self.signer_provider, &msg.funding_pubkey, - msg.funding_contribution_satoshis, // TODO - 0, + our_funding_contribution, + msg.funding_contribution_satoshis, Vec::new(), LockTime::from_consensus(msg.locktime), msg.funding_feerate_perkw, @@ -9315,7 +9319,7 @@ where // Apply start of splice change in the state post_chan.context.splice_start(false, &self.logger); - let new_msg = post_chan.get_splice_ack(self.chain_hash.clone()) + let new_msg = post_chan.get_splice_ack(our_funding_contribution) .map_err(|e| MsgHandleErrInternal::from_chan_no_close(e, post_chan.context.channel_id()))?; peer_state.pending_msg_events.push(events::MessageSendEvent::SendSpliceAck { node_id: *counterparty_node_id, @@ -9360,11 +9364,7 @@ where if let ChannelPhase::Funded(chan) = chan.get() { // check if splice is pending if let Some(pending_splice) = &chan.context.pending_splice_pre { - // Check the splice_ack parameters (relative amnt) match saved splice parematers - let pending_relative = pending_splice.relative_satoshis(chan.context.get_value_satoshis()); - if msg.funding_contribution_satoshis != pending_relative { - return Err(MsgHandleErrInternal::send_err_msg_no_close(format!("The splice_ack parameters do not match the pending splice parameters. {} vs. {}", msg.funding_contribution_satoshis, pending_relative), msg.channel_id.clone())); - } + // 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())); @@ -9393,8 +9393,8 @@ where prev_chan.context, &self.signer_provider, &msg.funding_pubkey, - msg.funding_contribution_satoshis, // TODO - msg.funding_contribution_satoshis, // TODO + 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,