Skip to content

Commit db21aad

Browse files
committed
New splice_channel() for initiating splicing, handle splice_init and splice_ack messages, but fail afterwards
1 parent d5646d6 commit db21aad

File tree

5 files changed

+780
-10
lines changed

5 files changed

+780
-10
lines changed

lightning/src/ln/channel.rs

Lines changed: 280 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ use bitcoin::amount::Amount;
1111
use bitcoin::constants::ChainHash;
1212
use bitcoin::script::{Script, ScriptBuf, Builder, WScriptHash};
1313
use bitcoin::transaction::{Transaction, TxIn, TxOut};
14-
use bitcoin::sighash;
1514
use bitcoin::sighash::EcdsaSighashType;
1615
use bitcoin::consensus::encode;
1716
use bitcoin::absolute::LockTime;
@@ -25,7 +24,7 @@ use bitcoin::hash_types::{Txid, BlockHash};
2524
use bitcoin::secp256k1::constants::PUBLIC_KEY_SIZE;
2625
use bitcoin::secp256k1::{PublicKey,SecretKey};
2726
use bitcoin::secp256k1::{Secp256k1,ecdsa::Signature};
28-
use bitcoin::secp256k1;
27+
use bitcoin::{secp256k1, sighash};
2928

3029
use crate::ln::types::ChannelId;
3130
use crate::types::payment::{PaymentPreimage, PaymentHash};
@@ -1718,6 +1717,30 @@ impl FundingScope {
17181717
}
17191718
}
17201719

1720+
/// Info about a pending splice, used in the pre-splice channel
1721+
#[cfg(splicing)]
1722+
#[derive(Clone)]
1723+
struct PendingSpliceInfoPre {
1724+
pub our_funding_contribution: i64,
1725+
}
1726+
1727+
#[cfg(splicing)]
1728+
impl PendingSpliceInfoPre {
1729+
#[inline]
1730+
fn add_checked(base: u64, delta: i64) -> u64 {
1731+
if delta >= 0 {
1732+
base.saturating_add(delta as u64)
1733+
} else {
1734+
base.saturating_sub(delta.abs() as u64)
1735+
}
1736+
}
1737+
1738+
/// Compute the post-splice channel value from the pre-splice values and the peer contributions
1739+
pub fn compute_post_value(pre_channel_value: u64, our_funding_contribution: i64, their_funding_contribution: i64) -> u64 {
1740+
Self::add_checked(pre_channel_value, our_funding_contribution.saturating_add(their_funding_contribution))
1741+
}
1742+
}
1743+
17211744
/// Contains everything about the channel including state, and various flags.
17221745
pub(super) struct ChannelContext<SP: Deref> where SP::Target: SignerProvider {
17231746
config: LegacyChannelConfig,
@@ -1752,6 +1775,10 @@ pub(super) struct ChannelContext<SP: Deref> where SP::Target: SignerProvider {
17521775

17531776
secp_ctx: Secp256k1<secp256k1::All>,
17541777

1778+
/// Info about an in-progress, pending splice (if any), on the pre-splice channel
1779+
#[cfg(splicing)]
1780+
pending_splice_pre: Option<PendingSpliceInfoPre>,
1781+
17551782
latest_monitor_update_id: u64,
17561783

17571784
holder_signer: ChannelSignerType<SP>,
@@ -2816,6 +2843,9 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
28162843
next_funding_txid: None,
28172844

28182845
is_holder_quiescence_initiator: None,
2846+
2847+
#[cfg(splicing)]
2848+
pending_splice_pre: None,
28192849
};
28202850

28212851
Ok((funding, channel_context))
@@ -3048,6 +3078,9 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
30483078
next_funding_txid: None,
30493079

30503080
is_holder_quiescence_initiator: None,
3081+
3082+
#[cfg(splicing)]
3083+
pending_splice_pre: None,
30513084
};
30523085

30533086
Ok((funding, channel_context))
@@ -4204,6 +4237,33 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
42044237
}
42054238
}
42064239

4240+
/// Check that a balance value meets the channel reserve requirements or violates them (below reserve).
4241+
/// The channel value is an input as opposed to using from self, so that this can be used in case of splicing
4242+
/// to checks with new channel value (before being comitted to it).
4243+
#[cfg(splicing)]
4244+
pub fn check_balance_meets_reserve_requirements(&self, balance: u64, channel_value: u64) -> Result<(), ChannelError> {
4245+
if balance == 0 {
4246+
return Ok(());
4247+
}
4248+
let holder_selected_channel_reserve_satoshis = get_v2_channel_reserve_satoshis(
4249+
channel_value, self.holder_dust_limit_satoshis);
4250+
if balance < holder_selected_channel_reserve_satoshis {
4251+
return Err(ChannelError::Warn(format!(
4252+
"Balance below reserve mandated by holder, {} vs {}",
4253+
balance, holder_selected_channel_reserve_satoshis,
4254+
)));
4255+
}
4256+
let counterparty_selected_channel_reserve_satoshis = get_v2_channel_reserve_satoshis(
4257+
channel_value, self.counterparty_dust_limit_satoshis);
4258+
if balance < counterparty_selected_channel_reserve_satoshis {
4259+
return Err(ChannelError::Warn(format!(
4260+
"Balance below reserve mandated by counterparty, {} vs {}",
4261+
balance, counterparty_selected_channel_reserve_satoshis,
4262+
)));
4263+
}
4264+
Ok(())
4265+
}
4266+
42074267
/// Get the commitment tx fee for the local's (i.e. our) next commitment transaction based on the
42084268
/// number of pending HTLCs that are on track to be in our next commitment tx.
42094269
///
@@ -4680,6 +4740,38 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
46804740
self.counterparty_cur_commitment_point = Some(counterparty_cur_commitment_point_override);
46814741
self.get_initial_counterparty_commitment_signature(funding, logger)
46824742
}
4743+
4744+
/// Get the splice message that can be sent during splice initiation.
4745+
#[cfg(splicing)]
4746+
pub fn get_splice_init(&self, our_funding_contribution_satoshis: i64,
4747+
funding_feerate_perkw: u32, locktime: u32,
4748+
) -> msgs::SpliceInit {
4749+
// Reuse the existing funding pubkey, in spite of the channel value changing
4750+
// (though at this point we don't know the new value yet, due tue the optional counterparty contribution)
4751+
// Note that channel_keys_id is supposed NOT to change
4752+
let funding_pubkey = self.get_holder_pubkeys().funding_pubkey.clone();
4753+
msgs::SpliceInit {
4754+
channel_id: self.channel_id,
4755+
funding_contribution_satoshis: our_funding_contribution_satoshis,
4756+
funding_feerate_perkw,
4757+
locktime,
4758+
funding_pubkey,
4759+
require_confirmed_inputs: None,
4760+
}
4761+
}
4762+
4763+
/// Get the splice_ack message that can be sent in response to splice initiation.
4764+
#[cfg(splicing)]
4765+
pub fn get_splice_ack(&self, our_funding_contribution_satoshis: i64) -> msgs::SpliceAck {
4766+
// Reuse the existing funding pubkey, in spite of the channel value changing
4767+
let funding_pubkey = self.get_holder_pubkeys().funding_pubkey;
4768+
msgs::SpliceAck {
4769+
channel_id: self.channel_id,
4770+
funding_contribution_satoshis: our_funding_contribution_satoshis,
4771+
funding_pubkey,
4772+
require_confirmed_inputs: None,
4773+
}
4774+
}
46834775
}
46844776

46854777
// Internal utility functions for channels
@@ -8427,6 +8519,124 @@ impl<SP: Deref> FundedChannel<SP> where
84278519
}
84288520
}
84298521

8522+
/// Initiate splicing
8523+
#[cfg(splicing)]
8524+
pub fn splice_channel(&mut self, our_funding_contribution_satoshis: i64,
8525+
funding_feerate_perkw: u32, locktime: u32,
8526+
) -> Result<msgs::SpliceInit, ChannelError> {
8527+
// Check if a splice has been initiated already.
8528+
// Note: this could be handled more nicely, and support multiple outstanding splice's, the incoming splice_ack matters anyways.
8529+
if let Some(splice_info) = &self.context.pending_splice_pre {
8530+
return Err(ChannelError::Warn(format!(
8531+
"Channel has already a splice pending, contribution {}", splice_info.our_funding_contribution
8532+
)));
8533+
}
8534+
8535+
if !matches!(self.context.channel_state, ChannelState::ChannelReady(_)) {
8536+
return Err(ChannelError::Warn(format!("Cannot initiate splicing, as channel is not Ready")));
8537+
}
8538+
8539+
let pre_channel_value = self.funding.get_value_satoshis();
8540+
// Sanity check: capacity cannot decrease below 0
8541+
if (pre_channel_value as i64).saturating_add(our_funding_contribution_satoshis) < 0 {
8542+
return Err(ChannelError::Warn(format!(
8543+
"Post-splicing channel value cannot be negative. It was {} + {}",
8544+
pre_channel_value, our_funding_contribution_satoshis
8545+
)));
8546+
}
8547+
8548+
if our_funding_contribution_satoshis < 0 {
8549+
return Err(ChannelError::Warn(format!(
8550+
"TODO(splicing): Splice-out not supported, only splice in, contribution {}",
8551+
our_funding_contribution_satoshis,
8552+
)));
8553+
}
8554+
8555+
// Note: post-splice channel value is not yet known at this point, counterpary contribution is not known
8556+
// (Cannot test for miminum required post-splice channel value)
8557+
8558+
self.context.pending_splice_pre = Some(PendingSpliceInfoPre {
8559+
our_funding_contribution: our_funding_contribution_satoshis,
8560+
});
8561+
8562+
let msg = self.context.get_splice_init(our_funding_contribution_satoshis, funding_feerate_perkw, locktime);
8563+
Ok(msg)
8564+
}
8565+
8566+
/// Handle splice_init
8567+
#[cfg(splicing)]
8568+
pub fn splice_init(&mut self, msg: &msgs::SpliceInit) -> Result<msgs::SpliceAck, ChannelError> {
8569+
let their_funding_contribution_satoshis = msg.funding_contribution_satoshis;
8570+
// TODO(splicing): Currently not possible to contribute on the splicing-acceptor side
8571+
let our_funding_contribution_satoshis = 0i64;
8572+
8573+
// Check if a splice has been initiated already.
8574+
// Note: this could be handled more nicely, and support multiple outstanding splice's, the incoming splice_ack matters anyways.
8575+
if let Some(splice_info) = &self.context.pending_splice_pre {
8576+
return Err(ChannelError::Warn(format!(
8577+
"Channel has already a splice pending, contribution {}", splice_info.our_funding_contribution,
8578+
)));
8579+
}
8580+
8581+
if !matches!(self.context.channel_state, ChannelState::ChannelReady(_)) {
8582+
return Err(ChannelError::Warn(format!("Splicing requested on a channel that is not Ready")));
8583+
}
8584+
8585+
let pre_channel_value = self.funding.get_value_satoshis();
8586+
// Sanity check: capacity cannot decrease below 0
8587+
if (pre_channel_value as i64)
8588+
.saturating_add(their_funding_contribution_satoshis)
8589+
.saturating_add(our_funding_contribution_satoshis) < 0
8590+
{
8591+
return Err(ChannelError::Warn(format!(
8592+
"Post-splicing channel value cannot be negative. It was {} + {} + {}",
8593+
pre_channel_value, their_funding_contribution_satoshis, our_funding_contribution_satoshis,
8594+
)));
8595+
}
8596+
8597+
if their_funding_contribution_satoshis.saturating_add(our_funding_contribution_satoshis) < 0 {
8598+
return Err(ChannelError::Warn(format!(
8599+
"Splice-out not supported, only splice in, relative {} + {}",
8600+
their_funding_contribution_satoshis, our_funding_contribution_satoshis,
8601+
)));
8602+
}
8603+
8604+
let post_channel_value = PendingSpliceInfoPre::compute_post_value(pre_channel_value, their_funding_contribution_satoshis, our_funding_contribution_satoshis);
8605+
let post_balance = PendingSpliceInfoPre::add_checked(self.funding.value_to_self_msat, our_funding_contribution_satoshis);
8606+
// Early check for reserve requirement, assuming maximum balance of full channel value
8607+
// This will also be checked later at tx_complete
8608+
let _res = self.context.check_balance_meets_reserve_requirements(post_balance, post_channel_value)?;
8609+
8610+
// TODO(splicing): Store msg.funding_pubkey
8611+
// TODO(splicing): Apply start of splice (splice_start)
8612+
8613+
let splice_ack_msg = self.context.get_splice_ack(our_funding_contribution_satoshis);
8614+
// TODO(splicing): start interactive funding negotiation
8615+
Ok(splice_ack_msg)
8616+
}
8617+
8618+
/// Handle splice_ack
8619+
#[cfg(splicing)]
8620+
pub fn splice_ack(&mut self, msg: &msgs::SpliceAck) -> Result<(), ChannelError> {
8621+
let their_funding_contribution_satoshis = msg.funding_contribution_satoshis;
8622+
8623+
// check if splice is pending
8624+
let pending_splice = if let Some(pending_splice) = &self.context.pending_splice_pre {
8625+
pending_splice
8626+
} else {
8627+
return Err(ChannelError::Warn(format!("Channel is not in pending splice")));
8628+
};
8629+
8630+
let our_funding_contribution = pending_splice.our_funding_contribution;
8631+
8632+
let pre_channel_value = self.funding.get_value_satoshis();
8633+
let post_channel_value = PendingSpliceInfoPre::compute_post_value(pre_channel_value, our_funding_contribution, their_funding_contribution_satoshis);
8634+
let post_balance = PendingSpliceInfoPre::add_checked(self.funding.value_to_self_msat, our_funding_contribution);
8635+
// Early check for reserve requirement, assuming maximum balance of full channel value
8636+
// This will also be checked later at tx_complete
8637+
let _res = self.context.check_balance_meets_reserve_requirements(post_balance, post_channel_value)?;
8638+
Ok(())
8639+
}
84308640

84318641
// Send stuff to our remote peers:
84328642

@@ -10972,6 +11182,9 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, &'c Channel
1097211182
next_funding_txid: None,
1097311183

1097411184
is_holder_quiescence_initiator: None,
11185+
11186+
#[cfg(splicing)]
11187+
pending_splice_pre: None,
1097511188
},
1097611189
interactive_tx_signing_session: None,
1097711190
is_v2_established,
@@ -12790,4 +13003,69 @@ mod tests {
1279013003
320
1279113004
);
1279213005
}
13006+
13007+
#[cfg(all(test, splicing))]
13008+
fn get_pre_and_post(pre_channel_value: u64, our_funding_contribution: i64, their_funding_contribution: i64) -> (u64, u64) {
13009+
use crate::ln::channel::PendingSpliceInfoPre;
13010+
13011+
let post_channel_value = PendingSpliceInfoPre::compute_post_value(pre_channel_value, our_funding_contribution, their_funding_contribution);
13012+
(pre_channel_value, post_channel_value)
13013+
}
13014+
13015+
#[cfg(all(test, splicing))]
13016+
#[test]
13017+
fn test_splice_compute_post_value() {
13018+
{
13019+
// increase, small amounts
13020+
let (pre_channel_value, post_channel_value) = get_pre_and_post(9_000, 6_000, 0);
13021+
assert_eq!(pre_channel_value, 9_000);
13022+
assert_eq!(post_channel_value, 15_000);
13023+
}
13024+
{
13025+
// increase, small amounts
13026+
let (pre_channel_value, post_channel_value) = get_pre_and_post(9_000, 4_000, 2_000);
13027+
assert_eq!(pre_channel_value, 9_000);
13028+
assert_eq!(post_channel_value, 15_000);
13029+
}
13030+
{
13031+
// increase, small amounts
13032+
let (pre_channel_value, post_channel_value) = get_pre_and_post(9_000, 0, 6_000);
13033+
assert_eq!(pre_channel_value, 9_000);
13034+
assert_eq!(post_channel_value, 15_000);
13035+
}
13036+
{
13037+
// decrease, small amounts
13038+
let (pre_channel_value, post_channel_value) = get_pre_and_post(15_000, -6_000, 0);
13039+
assert_eq!(pre_channel_value, 15_000);
13040+
assert_eq!(post_channel_value, 9_000);
13041+
}
13042+
{
13043+
// decrease, small amounts
13044+
let (pre_channel_value, post_channel_value) = get_pre_and_post(15_000, -4_000, -2_000);
13045+
assert_eq!(pre_channel_value, 15_000);
13046+
assert_eq!(post_channel_value, 9_000);
13047+
}
13048+
{
13049+
// increase and decrease
13050+
let (pre_channel_value, post_channel_value) = get_pre_and_post(15_000, 4_000, -2_000);
13051+
assert_eq!(pre_channel_value, 15_000);
13052+
assert_eq!(post_channel_value, 17_000);
13053+
}
13054+
let base2: u64 = 2;
13055+
let huge63i3 = (base2.pow(63) - 3) as i64;
13056+
assert_eq!(huge63i3, 9223372036854775805);
13057+
assert_eq!(-huge63i3, -9223372036854775805);
13058+
{
13059+
// increase, large amount
13060+
let (pre_channel_value, post_channel_value) = get_pre_and_post(9_000, huge63i3, 3);
13061+
assert_eq!(pre_channel_value, 9_000);
13062+
assert_eq!(post_channel_value, 9223372036854784807);
13063+
}
13064+
{
13065+
// increase, large amounts
13066+
let (pre_channel_value, post_channel_value) = get_pre_and_post(9_000, huge63i3, huge63i3);
13067+
assert_eq!(pre_channel_value, 9_000);
13068+
assert_eq!(post_channel_value, 9223372036854784807);
13069+
}
13070+
}
1279313071
}

0 commit comments

Comments
 (0)