Skip to content

Commit 2eb392c

Browse files
committed
New splice_channel() for initiating splicing, handle splice_init and splice_ack messages, but fail afterwards
1 parent 03587f6 commit 2eb392c

File tree

5 files changed

+779
-11
lines changed

5 files changed

+779
-11
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};
@@ -1541,6 +1540,30 @@ impl UnfundedChannelContext {
15411540
}
15421541
}
15431542

1543+
/// Info about a pending splice, used in the pre-splice channel
1544+
#[cfg(splicing)]
1545+
#[derive(Clone)]
1546+
struct PendingSpliceInfoPre {
1547+
pub our_funding_contribution: i64,
1548+
}
1549+
1550+
#[cfg(splicing)]
1551+
impl PendingSpliceInfoPre {
1552+
#[inline]
1553+
fn add_checked(base: u64, delta: i64) -> u64 {
1554+
if delta >= 0 {
1555+
base.saturating_add(delta as u64)
1556+
} else {
1557+
base.saturating_sub(delta.abs() as u64)
1558+
}
1559+
}
1560+
1561+
/// Compute the post-splice channel value from the pre-splice values and the peer contributions
1562+
pub fn compute_post_value(pre_channel_value: u64, our_funding_contribution: i64, their_funding_contribution: i64) -> u64 {
1563+
Self::add_checked(pre_channel_value, our_funding_contribution.saturating_add(their_funding_contribution))
1564+
}
1565+
}
1566+
15441567
/// Contains everything about the channel including state, and various flags.
15451568
pub(super) struct ChannelContext<SP: Deref> where SP::Target: SignerProvider {
15461569
config: LegacyChannelConfig,
@@ -1576,6 +1599,10 @@ pub(super) struct ChannelContext<SP: Deref> where SP::Target: SignerProvider {
15761599
secp_ctx: Secp256k1<secp256k1::All>,
15771600
channel_value_satoshis: u64,
15781601

1602+
/// Info about an in-progress, pending splice (if any), on the pre-splice channel
1603+
#[cfg(splicing)]
1604+
pending_splice_pre: Option<PendingSpliceInfoPre>,
1605+
15791606
latest_monitor_update_id: u64,
15801607

15811608
holder_signer: ChannelSignerType<SP>,
@@ -2638,6 +2665,9 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
26382665
is_manual_broadcast: false,
26392666

26402667
next_funding_txid: None,
2668+
2669+
#[cfg(splicing)]
2670+
pending_splice_pre: None,
26412671
};
26422672

26432673
Ok(channel_context)
@@ -2865,6 +2895,9 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
28652895
local_initiated_shutdown: None,
28662896
is_manual_broadcast: false,
28672897
next_funding_txid: None,
2898+
2899+
#[cfg(splicing)]
2900+
pending_splice_pre: None,
28682901
})
28692902
}
28702903

@@ -4047,6 +4080,33 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
40474080
(context.holder_selected_channel_reserve_satoshis, context.counterparty_selected_channel_reserve_satoshis)
40484081
}
40494082

4083+
/// Check that a balance value meets the channel reserve requirements or violates them (below reserve).
4084+
/// The channel value is an input as opposed to using from self, so that this can be used in case of splicing
4085+
/// to checks with new channel value (before being comitted to it).
4086+
#[cfg(splicing)]
4087+
pub fn check_balance_meets_reserve_requirements(&self, balance: u64, channel_value: u64) -> Result<(), ChannelError> {
4088+
if balance == 0 {
4089+
return Ok(());
4090+
}
4091+
let holder_selected_channel_reserve_satoshis = get_v2_channel_reserve_satoshis(
4092+
channel_value, self.holder_dust_limit_satoshis);
4093+
if balance < holder_selected_channel_reserve_satoshis {
4094+
return Err(ChannelError::Warn(format!(
4095+
"Balance below reserve mandated by holder, {} vs {}",
4096+
balance, holder_selected_channel_reserve_satoshis,
4097+
)));
4098+
}
4099+
let counterparty_selected_channel_reserve_satoshis = get_v2_channel_reserve_satoshis(
4100+
channel_value, self.counterparty_dust_limit_satoshis);
4101+
if balance < counterparty_selected_channel_reserve_satoshis {
4102+
return Err(ChannelError::Warn(format!(
4103+
"Balance below reserve mandated by counterparty, {} vs {}",
4104+
balance, counterparty_selected_channel_reserve_satoshis,
4105+
)));
4106+
}
4107+
Ok(())
4108+
}
4109+
40504110
/// Get the commitment tx fee for the local's (i.e. our) next commitment transaction based on the
40514111
/// number of pending HTLCs that are on track to be in our next commitment tx.
40524112
///
@@ -4513,6 +4573,38 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
45134573
self.channel_transaction_parameters = channel_transaction_parameters;
45144574
self.get_initial_counterparty_commitment_signature(logger)
45154575
}
4576+
4577+
/// Get the splice message that can be sent during splice initiation.
4578+
#[cfg(splicing)]
4579+
pub fn get_splice_init(&self, our_funding_contribution_satoshis: i64,
4580+
funding_feerate_perkw: u32, locktime: u32,
4581+
) -> msgs::SpliceInit {
4582+
// Reuse the existing funding pubkey, in spite of the channel value changing
4583+
// (though at this point we don't know the new value yet, due tue the optional counterparty contribution)
4584+
// Note that channel_keys_id is supposed NOT to change
4585+
let funding_pubkey = self.get_holder_pubkeys().funding_pubkey.clone();
4586+
msgs::SpliceInit {
4587+
channel_id: self.channel_id,
4588+
funding_contribution_satoshis: our_funding_contribution_satoshis,
4589+
funding_feerate_perkw,
4590+
locktime,
4591+
funding_pubkey,
4592+
require_confirmed_inputs: None,
4593+
}
4594+
}
4595+
4596+
/// Get the splice_ack message that can be sent in response to splice initiation.
4597+
#[cfg(splicing)]
4598+
pub fn get_splice_ack(&self, our_funding_contribution_satoshis: i64) -> msgs::SpliceAck {
4599+
// Reuse the existing funding pubkey, in spite of the channel value changing
4600+
let funding_pubkey = self.get_holder_pubkeys().funding_pubkey;
4601+
msgs::SpliceAck {
4602+
channel_id: self.channel_id,
4603+
funding_contribution_satoshis: our_funding_contribution_satoshis,
4604+
funding_pubkey,
4605+
require_confirmed_inputs: None,
4606+
}
4607+
}
45164608
}
45174609

45184610
// Internal utility functions for channels
@@ -8220,6 +8312,124 @@ impl<SP: Deref> FundedChannel<SP> where
82208312
}
82218313
}
82228314

8315+
/// Initiate splicing
8316+
#[cfg(splicing)]
8317+
pub fn splice_channel(&mut self, our_funding_contribution_satoshis: i64,
8318+
funding_feerate_perkw: u32, locktime: u32,
8319+
) -> Result<msgs::SpliceInit, ChannelError> {
8320+
// Check if a splice has been initiated already.
8321+
// Note: this could be handled more nicely, and support multiple outstanding splice's, the incoming splice_ack matters anyways.
8322+
if let Some(splice_info) = &self.context.pending_splice_pre {
8323+
return Err(ChannelError::Warn(format!(
8324+
"Channel has already a splice pending, contribution {}", splice_info.our_funding_contribution
8325+
)));
8326+
}
8327+
8328+
if !matches!(self.context.channel_state, ChannelState::ChannelReady(_)) {
8329+
return Err(ChannelError::Warn(format!("Cannot initiate splicing, as channel is not Ready")));
8330+
}
8331+
8332+
let pre_channel_value = self.context.get_value_satoshis();
8333+
// Sanity check: capacity cannot decrease below 0
8334+
if (pre_channel_value as i64).saturating_add(our_funding_contribution_satoshis) < 0 {
8335+
return Err(ChannelError::Warn(format!(
8336+
"Post-splicing channel value cannot be negative. It was {} + {}",
8337+
pre_channel_value, our_funding_contribution_satoshis
8338+
)));
8339+
}
8340+
8341+
if our_funding_contribution_satoshis < 0 {
8342+
return Err(ChannelError::Warn(format!(
8343+
"TODO(splicing): Splice-out not supported, only splice in, contribution {}",
8344+
our_funding_contribution_satoshis,
8345+
)));
8346+
}
8347+
8348+
// Note: post-splice channel value is not yet known at this point, counterpary contribution is not known
8349+
// (Cannot test for miminum required post-splice channel value)
8350+
8351+
self.context.pending_splice_pre = Some(PendingSpliceInfoPre {
8352+
our_funding_contribution: our_funding_contribution_satoshis,
8353+
});
8354+
8355+
let msg = self.context.get_splice_init(our_funding_contribution_satoshis, funding_feerate_perkw, locktime);
8356+
Ok(msg)
8357+
}
8358+
8359+
/// Handle splice_init
8360+
#[cfg(splicing)]
8361+
pub fn splice_init(&mut self, msg: &msgs::SpliceInit) -> Result<msgs::SpliceAck, ChannelError> {
8362+
let their_funding_contribution_satoshis = msg.funding_contribution_satoshis;
8363+
// TODO(splicing): Currently not possible to contribute on the splicing-acceptor side
8364+
let our_funding_contribution_satoshis = 0i64;
8365+
8366+
// Check if a splice has been initiated already.
8367+
// Note: this could be handled more nicely, and support multiple outstanding splice's, the incoming splice_ack matters anyways.
8368+
if let Some(splice_info) = &self.context.pending_splice_pre {
8369+
return Err(ChannelError::Warn(format!(
8370+
"Channel has already a splice pending, contribution {}", splice_info.our_funding_contribution,
8371+
)));
8372+
}
8373+
8374+
if !matches!(self.context.channel_state, ChannelState::ChannelReady(_)) {
8375+
return Err(ChannelError::Warn(format!("Splicing requested on a channel that is not Ready")));
8376+
}
8377+
8378+
let pre_channel_value = self.context.get_value_satoshis();
8379+
// Sanity check: capacity cannot decrease below 0
8380+
if (pre_channel_value as i64)
8381+
.saturating_add(their_funding_contribution_satoshis)
8382+
.saturating_add(our_funding_contribution_satoshis) < 0
8383+
{
8384+
return Err(ChannelError::Warn(format!(
8385+
"Post-splicing channel value cannot be negative. It was {} + {} + {}",
8386+
pre_channel_value, their_funding_contribution_satoshis, our_funding_contribution_satoshis,
8387+
)));
8388+
}
8389+
8390+
if their_funding_contribution_satoshis.saturating_add(our_funding_contribution_satoshis) < 0 {
8391+
return Err(ChannelError::Warn(format!(
8392+
"Splice-out not supported, only splice in, relative {} + {}",
8393+
their_funding_contribution_satoshis, our_funding_contribution_satoshis,
8394+
)));
8395+
}
8396+
8397+
let post_channel_value = PendingSpliceInfoPre::compute_post_value(pre_channel_value, their_funding_contribution_satoshis, our_funding_contribution_satoshis);
8398+
let post_balance = PendingSpliceInfoPre::add_checked(self.context.value_to_self_msat, our_funding_contribution_satoshis);
8399+
// Early check for reserve requirement, assuming maximum balance of full channel value
8400+
// This will also be checked later at tx_complete
8401+
let _res = self.context.check_balance_meets_reserve_requirements(post_balance, post_channel_value)?;
8402+
8403+
// TODO(splicing): Store msg.funding_pubkey
8404+
// TODO(splicing): Apply start of splice (splice_start)
8405+
8406+
let splice_ack_msg = self.context.get_splice_ack(our_funding_contribution_satoshis);
8407+
// TODO(splicing): start interactive funding negotiation
8408+
Ok(splice_ack_msg)
8409+
}
8410+
8411+
/// Handle splice_ack
8412+
#[cfg(splicing)]
8413+
pub fn splice_ack(&mut self, msg: &msgs::SpliceAck) -> Result<(), ChannelError> {
8414+
let their_funding_contribution_satoshis = msg.funding_contribution_satoshis;
8415+
8416+
// check if splice is pending
8417+
let pending_splice = if let Some(pending_splice) = &self.context.pending_splice_pre {
8418+
pending_splice
8419+
} else {
8420+
return Err(ChannelError::Warn(format!("Channel is not in pending splice")));
8421+
};
8422+
8423+
let our_funding_contribution = pending_splice.our_funding_contribution;
8424+
8425+
let pre_channel_value = self.context.get_value_satoshis();
8426+
let post_channel_value = PendingSpliceInfoPre::compute_post_value(pre_channel_value, our_funding_contribution, their_funding_contribution_satoshis);
8427+
let post_balance = PendingSpliceInfoPre::add_checked(self.context.value_to_self_msat, our_funding_contribution);
8428+
// Early check for reserve requirement, assuming maximum balance of full channel value
8429+
// This will also be checked later at tx_complete
8430+
let _res = self.context.check_balance_meets_reserve_requirements(post_balance, post_channel_value)?;
8431+
Ok(())
8432+
}
82238433

82248434
// Send stuff to our remote peers:
82258435

@@ -10559,6 +10769,9 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, u32, &'c Ch
1055910769
// during a signing session, but have not received `tx_signatures` we MUST set `next_funding_txid`
1056010770
// to the txid of that interactive transaction, else we MUST NOT set it.
1056110771
next_funding_txid: None,
10772+
10773+
#[cfg(splicing)]
10774+
pending_splice_pre: None,
1056210775
},
1056310776
interactive_tx_signing_session: None,
1056410777
holder_commitment_point,
@@ -12344,4 +12557,69 @@ mod tests {
1234412557
assert_eq!(node_a_chan.context.channel_state, ChannelState::AwaitingChannelReady(AwaitingChannelReadyFlags::THEIR_CHANNEL_READY));
1234512558
assert!(node_a_chan.check_get_channel_ready(0, &&logger).is_some());
1234612559
}
12560+
12561+
#[cfg(all(test, splicing))]
12562+
fn get_pre_and_post(pre_channel_value: u64, our_funding_contribution: i64, their_funding_contribution: i64) -> (u64, u64) {
12563+
use crate::ln::channel::PendingSpliceInfoPre;
12564+
12565+
let post_channel_value = PendingSpliceInfoPre::compute_post_value(pre_channel_value, our_funding_contribution, their_funding_contribution);
12566+
(pre_channel_value, post_channel_value)
12567+
}
12568+
12569+
#[cfg(all(test, splicing))]
12570+
#[test]
12571+
fn test_splice_compute_post_value() {
12572+
{
12573+
// increase, small amounts
12574+
let (pre_channel_value, post_channel_value) = get_pre_and_post(9_000, 6_000, 0);
12575+
assert_eq!(pre_channel_value, 9_000);
12576+
assert_eq!(post_channel_value, 15_000);
12577+
}
12578+
{
12579+
// increase, small amounts
12580+
let (pre_channel_value, post_channel_value) = get_pre_and_post(9_000, 4_000, 2_000);
12581+
assert_eq!(pre_channel_value, 9_000);
12582+
assert_eq!(post_channel_value, 15_000);
12583+
}
12584+
{
12585+
// increase, small amounts
12586+
let (pre_channel_value, post_channel_value) = get_pre_and_post(9_000, 0, 6_000);
12587+
assert_eq!(pre_channel_value, 9_000);
12588+
assert_eq!(post_channel_value, 15_000);
12589+
}
12590+
{
12591+
// decrease, small amounts
12592+
let (pre_channel_value, post_channel_value) = get_pre_and_post(15_000, -6_000, 0);
12593+
assert_eq!(pre_channel_value, 15_000);
12594+
assert_eq!(post_channel_value, 9_000);
12595+
}
12596+
{
12597+
// decrease, small amounts
12598+
let (pre_channel_value, post_channel_value) = get_pre_and_post(15_000, -4_000, -2_000);
12599+
assert_eq!(pre_channel_value, 15_000);
12600+
assert_eq!(post_channel_value, 9_000);
12601+
}
12602+
{
12603+
// increase and decrease
12604+
let (pre_channel_value, post_channel_value) = get_pre_and_post(15_000, 4_000, -2_000);
12605+
assert_eq!(pre_channel_value, 15_000);
12606+
assert_eq!(post_channel_value, 17_000);
12607+
}
12608+
let base2: u64 = 2;
12609+
let huge63i3 = (base2.pow(63) - 3) as i64;
12610+
assert_eq!(huge63i3, 9223372036854775805);
12611+
assert_eq!(-huge63i3, -9223372036854775805);
12612+
{
12613+
// increase, large amount
12614+
let (pre_channel_value, post_channel_value) = get_pre_and_post(9_000, huge63i3, 3);
12615+
assert_eq!(pre_channel_value, 9_000);
12616+
assert_eq!(post_channel_value, 9223372036854784807);
12617+
}
12618+
{
12619+
// increase, large amounts
12620+
let (pre_channel_value, post_channel_value) = get_pre_and_post(9_000, huge63i3, huge63i3);
12621+
assert_eq!(pre_channel_value, 9_000);
12622+
assert_eq!(post_channel_value, 9223372036854784807);
12623+
}
12624+
}
1234712625
}

0 commit comments

Comments
 (0)