diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index 56273d126ce..40f402b7354 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -2242,6 +2242,28 @@ impl<'a> From<&'a Transaction> for ConfirmedTransaction<'a> { } } +#[cfg(splicing)] +impl PendingSplice { + #[inline] + fn add_checked(base: u64, delta: i64) -> u64 { + if delta >= 0 { + base.saturating_add(delta as u64) + } else { + base.saturating_sub(delta.abs() as u64) + } + } + + /// Compute the post-splice channel value from the pre-splice values and the peer contributions + pub fn compute_post_value( + pre_channel_value: u64, our_funding_contribution: i64, their_funding_contribution: i64, + ) -> u64 { + Self::add_checked( + pre_channel_value, + our_funding_contribution.saturating_add(their_funding_contribution), + ) + } +} + /// Contains everything about the channel including state, and various flags. pub(super) struct ChannelContext where @@ -2739,6 +2761,8 @@ where "commitment_signed" } + /// Checks whether the channel was opened through V2 channel open (negotiation). + /// See also: is_v2() fn is_v2_established(&self) -> bool { let channel_parameters = &self.funding().channel_transaction_parameters; // This will return false if `counterparty_parameters` is `None`, but for a `FundedChannel`, it @@ -3616,6 +3640,13 @@ where self.latest_monitor_update_id } + pub fn get_latest_unblocked_monitor_update_id(&self) -> u64 { + if self.blocked_monitor_updates.is_empty() { + return self.get_latest_monitor_update_id(); + } + self.blocked_monitor_updates[0].update.update_id - 1 + } + pub fn should_announce(&self) -> bool { self.config.announce_for_forwarding } @@ -5379,7 +5410,7 @@ where // monitor update to the user, even if we return one). // See test_duplicate_chan_id and test_pre_lockin_no_chan_closed_update for more. if !self.channel_state.is_pre_funded_state() { - self.latest_monitor_update_id += 1; + self.latest_monitor_update_id = self.get_latest_unblocked_monitor_update_id() + 1; let update = ChannelMonitorUpdate { update_id: self.latest_monitor_update_id, updates: vec![ChannelMonitorUpdateStep::ChannelForceClosed { @@ -9301,10 +9332,7 @@ where /// Gets the latest [`ChannelMonitorUpdate`] ID which has been released and is in-flight. pub fn get_latest_unblocked_monitor_update_id(&self) -> u64 { - if self.context.blocked_monitor_updates.is_empty() { - return self.context.get_latest_monitor_update_id(); - } - self.context.blocked_monitor_updates[0].update.update_id - 1 + self.context.get_latest_unblocked_monitor_update_id() } /// Returns the next blocked monitor update, if one exists, and a bool which indicates a @@ -10328,16 +10356,113 @@ where Ok(splice_ack_msg) } + /// Compute the channel balances (local & remote, in msats) by taking into account fees, + // anchor values, and dust limits. + /// Pending HTLCs are not taken into account, this method should be used when there is no such, + /// e.g. in quiescence state + #[cfg(splicing)] + fn compute_balances_less_fees( + &self, channel_value_sats: u64, value_to_self_msat: u64, is_local: bool, + ) -> (u64, u64) { + // We should get here only when there are no pending HTLCs, as they are not taken into account + debug_assert!(self.context.pending_inbound_htlcs.is_empty()); + debug_assert!(self.context.pending_outbound_htlcs.is_empty()); + + let feerate_per_kw = self.context.feerate_per_kw; + + // compute 'raw' counterparty balance + let value_to_remote_msat: i64 = + ((channel_value_sats * 1000) as i64).saturating_sub(value_to_self_msat as i64); + debug_assert!(value_to_remote_msat >= 0); + + let total_fee_sats = commit_tx_fee_sat( + feerate_per_kw, + 0, + &self.funding.channel_transaction_parameters.channel_type_features, + ); + let anchors_val_sats = if self + .funding + .channel_transaction_parameters + .channel_type_features + .supports_anchors_zero_fee_htlc_tx() + { + ANCHOR_OUTPUT_VALUE_SATOSHI * 2 + } else { + 0 + } as i64; + + // consider fees and anchor values + let (mut new_value_to_self_msat, mut new_value_to_remote_msat) = if self + .funding + .is_outbound() + { + ( + value_to_self_msat as i64 - anchors_val_sats * 1000 - total_fee_sats as i64 * 1000, + value_to_remote_msat, + ) + } else { + ( + value_to_self_msat as i64, + value_to_remote_msat - anchors_val_sats * 1000 - total_fee_sats as i64 * 1000, + ) + }; + + // consider dust limit + let broadcaster_dust_limit_sats = if is_local { + self.context.holder_dust_limit_satoshis + } else { + self.context.counterparty_dust_limit_satoshis + } as i64; + if new_value_to_self_msat < (broadcaster_dust_limit_sats * 1000) { + new_value_to_self_msat = 0; + } + debug_assert!(new_value_to_self_msat >= 0); + if new_value_to_remote_msat < (broadcaster_dust_limit_sats * 1000) { + new_value_to_remote_msat = 0; + } + debug_assert!(new_value_to_remote_msat >= 0); + + (new_value_to_self_msat as u64, new_value_to_remote_msat as u64) + } + /// Handle splice_ack #[cfg(splicing)] - pub fn splice_ack(&mut self, _msg: &msgs::SpliceAck) -> Result<(), ChannelError> { + pub fn splice_ack(&mut self, msg: &msgs::SpliceAck) -> Result<(), ChannelError> { // check if splice is pending - if self.pending_splice.is_none() { + let pending_splice = if let Some(pending_splice) = &self.pending_splice { + pending_splice + } else { return Err(ChannelError::Warn(format!("Channel is not in pending splice"))); }; - // TODO(splicing): Pre-check for reserve requirement + // Pre-check for reserve requirement // (Note: It should also be checked later at tx_complete) + let our_funding_contribution_sats = pending_splice.our_funding_contribution; + let their_funding_contribution_sats = msg.funding_contribution_satoshis; + + let pre_channel_value_sats = self.funding.get_value_satoshis(); + let post_channel_value_sats = PendingSplice::compute_post_value( + pre_channel_value_sats, + our_funding_contribution_sats, + their_funding_contribution_sats, + ); + let pre_balance_self_msat = self.funding.value_to_self_msat; + let post_balance_self_msat = + PendingSplice::add_checked(pre_balance_self_msat, our_funding_contribution_sats * 1000); + let (pre_balance_self_less_fees_msat, pre_balance_counterparty_less_fees_msat) = + self.compute_balances_less_fees(pre_channel_value_sats, pre_balance_self_msat, true); + let (post_balance_self_less_fees_msat, post_balance_counterparty_less_fees_msat) = + self.compute_balances_less_fees(post_channel_value_sats, post_balance_self_msat, true); + // Pre-check for reserve requirement + // This will also be checked later at tx_complete + let _res = self.check_splice_balances_meet_v2_reserve_requirements( + pre_balance_self_less_fees_msat, + post_balance_self_less_fees_msat, + pre_balance_counterparty_less_fees_msat, + post_balance_counterparty_less_fees_msat, + pre_channel_value_sats, + post_channel_value_sats, + )?; Ok(()) } @@ -10416,6 +10541,82 @@ where Ok((None, None)) } + /// Check that balances (self and counterparty) meet the channel reserve requirements or violates them (below reserve). + /// The channel value is an input as opposed to using from the FundingScope, so that this can be used in case of splicing + /// to check with new channel value (before being committed to it). + #[cfg(splicing)] + pub fn check_splice_balances_meet_v2_reserve_requirements( + &self, self_balance_pre_msat: u64, self_balance_post_msat: u64, + counterparty_balance_pre_msat: u64, counterparty_balance_post_msat: u64, + channel_value_pre_sats: u64, channel_value_post_sats: u64, + ) -> Result<(), ChannelError> { + let is_ok_self = self.check_splice_balance_meets_v2_reserve_requirement( + self_balance_pre_msat, + self_balance_post_msat, + channel_value_pre_sats, + channel_value_post_sats, + self.context.holder_dust_limit_satoshis, + ); + if let Err(channel_reserve_self) = is_ok_self { + return Err(ChannelError::Warn(format!( + "Balance below reserve, mandated by holder, {} vs {}", + self_balance_post_msat, channel_reserve_self, + ))); + } + let is_ok_cp = self.check_splice_balance_meets_v2_reserve_requirement( + counterparty_balance_pre_msat, + counterparty_balance_post_msat, + channel_value_pre_sats, + channel_value_post_sats, + self.context.counterparty_dust_limit_satoshis, + ); + if let Err(channel_reserve_cp) = is_ok_cp { + return Err(ChannelError::Warn(format!( + "Balance below reserve mandated by counterparty, {} vs {}", + counterparty_balance_post_msat, channel_reserve_cp, + ))); + } + Ok(()) + } + + /// Check that post-splicing balance meets reserve requirements, but only if it met it pre-splice as well. + /// In case of error, it returns the minimum channel reserve that was violated (in sats) + #[cfg(splicing)] + pub fn check_splice_balance_meets_v2_reserve_requirement( + &self, pre_balance_msat: u64, post_balance_msat: u64, pre_channel_value_sats: u64, + post_channel_value_sats: u64, dust_limit_sats: u64, + ) -> Result<(), u64> { + let post_channel_reserve_sats = + get_v2_channel_reserve_satoshis(post_channel_value_sats, dust_limit_sats); + if post_balance_msat >= (post_channel_reserve_sats * 1000) { + return Ok(()); + } + // We're not allowed to dip below the reserve once we've been above, + // check differently for originally v1 and v2 channels + if self.is_v2() { + let pre_channel_reserve_sats = + get_v2_channel_reserve_satoshis(pre_channel_value_sats, dust_limit_sats); + if pre_balance_msat >= (pre_channel_reserve_sats * 1000) { + return Err(post_channel_reserve_sats); + } + } else { + if pre_balance_msat >= (self.funding.holder_selected_channel_reserve_satoshis * 1000) { + return Err(post_channel_reserve_sats); + } + if let Some(cp_reserve) = self.funding.counterparty_selected_channel_reserve_satoshis { + if pre_balance_msat >= (cp_reserve * 1000) { + return Err(post_channel_reserve_sats); + } + } + } + // Make sure we either remain with the same balance or move towards the reserve. + if post_balance_msat >= pre_balance_msat { + Ok(()) + } else { + Err(post_channel_reserve_sats) + } + } + // Send stuff to our remote peers: /// Queues up an outbound HTLC to send by placing it in the holding cell. You should call @@ -11187,6 +11388,18 @@ where // CHANNEL_ANNOUNCEMENT_PROPAGATION_DELAY confirmations. self.context.historical_scids.drain(0..end) } + + /// Check is channel is currently v2: + /// - established as v2 + /// - or past a splice (which implicitly makes the channel v2) + #[cfg(splicing)] + fn is_v2(&self) -> bool { + if self.funding.channel_transaction_parameters.splice_parent_funding_txid.is_some() { + true + } else { + self.is_v2_established() + } + } } /// A not-yet-funded outbound (from holder) channel using V1 channel establishment. @@ -15102,4 +15315,76 @@ mod tests { ); } } + + #[cfg(splicing)] + fn get_pre_and_post( + pre_channel_value: u64, our_funding_contribution: i64, their_funding_contribution: i64, + ) -> (u64, u64) { + use crate::ln::channel::PendingSplice; + + let post_channel_value = PendingSplice::compute_post_value( + pre_channel_value, + our_funding_contribution, + their_funding_contribution, + ); + (pre_channel_value, post_channel_value) + } + + #[cfg(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); + } + } }