Skip to content

Commit 18b95d9

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

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};
@@ -1427,6 +1426,30 @@ impl UnfundedChannelContext {
14271426
}
14281427
}
14291428

1429+
/// Info about a pending splice, used in the pre-splice channel
1430+
#[cfg(splicing)]
1431+
#[derive(Clone)]
1432+
struct PendingSpliceInfoPre {
1433+
pub our_funding_contribution: i64,
1434+
}
1435+
1436+
#[cfg(splicing)]
1437+
impl PendingSpliceInfoPre {
1438+
#[inline]
1439+
fn add_checked(base: u64, delta: i64) -> u64 {
1440+
if delta >= 0 {
1441+
base.saturating_add(delta as u64)
1442+
} else {
1443+
base.saturating_sub(delta.abs() as u64)
1444+
}
1445+
}
1446+
1447+
/// Compute the post-splice channel value from the pre-splice values and the peer contributions
1448+
pub fn compute_post_value(pre_channel_value: u64, our_funding_contribution: i64, their_funding_contribution: i64) -> u64 {
1449+
Self::add_checked(pre_channel_value, our_funding_contribution.saturating_add(their_funding_contribution))
1450+
}
1451+
}
1452+
14301453
/// Contains everything about the channel including state, and various flags.
14311454
pub(super) struct ChannelContext<SP: Deref> where SP::Target: SignerProvider {
14321455
config: LegacyChannelConfig,
@@ -1462,6 +1485,10 @@ pub(super) struct ChannelContext<SP: Deref> where SP::Target: SignerProvider {
14621485
secp_ctx: Secp256k1<secp256k1::All>,
14631486
channel_value_satoshis: u64,
14641487

1488+
/// Info about an in-progress, pending splice (if any), on the pre-splice channel
1489+
#[cfg(splicing)]
1490+
pending_splice_pre: Option<PendingSpliceInfoPre>,
1491+
14651492
latest_monitor_update_id: u64,
14661493

14671494
holder_signer: ChannelSignerType<SP>,
@@ -2516,6 +2543,9 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
25162543
is_manual_broadcast: false,
25172544

25182545
next_funding_txid: None,
2546+
2547+
#[cfg(splicing)]
2548+
pending_splice_pre: None,
25192549
};
25202550

25212551
Ok(channel_context)
@@ -2746,6 +2776,9 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
27462776
local_initiated_shutdown: None,
27472777
is_manual_broadcast: false,
27482778
next_funding_txid: None,
2779+
2780+
#[cfg(splicing)]
2781+
pending_splice_pre: None,
27492782
})
27502783
}
27512784

@@ -3928,6 +3961,33 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
39283961
(context.holder_selected_channel_reserve_satoshis, context.counterparty_selected_channel_reserve_satoshis)
39293962
}
39303963

3964+
/// Check that a balance value meets the channel reserve requirements or violates them (below reserve).
3965+
/// The channel value is an input as opposed to using from self, so that this can be used in case of splicing
3966+
/// to checks with new channel value (before being comitted to it).
3967+
#[cfg(splicing)]
3968+
pub fn check_balance_meets_reserve_requirements(&self, balance: u64, channel_value: u64) -> Result<(), ChannelError> {
3969+
if balance == 0 {
3970+
return Ok(());
3971+
}
3972+
let holder_selected_channel_reserve_satoshis = get_v2_channel_reserve_satoshis(
3973+
channel_value, self.holder_dust_limit_satoshis);
3974+
if balance < holder_selected_channel_reserve_satoshis {
3975+
return Err(ChannelError::Warn(format!(
3976+
"Balance below reserve mandated by holder, {} vs {}",
3977+
balance, holder_selected_channel_reserve_satoshis,
3978+
)));
3979+
}
3980+
let counterparty_selected_channel_reserve_satoshis = get_v2_channel_reserve_satoshis(
3981+
channel_value, self.counterparty_dust_limit_satoshis);
3982+
if balance < counterparty_selected_channel_reserve_satoshis {
3983+
return Err(ChannelError::Warn(format!(
3984+
"Balance below reserve mandated by counterparty, {} vs {}",
3985+
balance, counterparty_selected_channel_reserve_satoshis,
3986+
)));
3987+
}
3988+
Ok(())
3989+
}
3990+
39313991
/// Get the commitment tx fee for the local's (i.e. our) next commitment transaction based on the
39323992
/// number of pending HTLCs that are on track to be in our next commitment tx.
39333993
///
@@ -4390,6 +4450,38 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
43904450
self.channel_transaction_parameters = channel_transaction_parameters;
43914451
self.get_initial_counterparty_commitment_signature(logger)
43924452
}
4453+
4454+
/// Get the splice message that can be sent during splice initiation.
4455+
#[cfg(splicing)]
4456+
pub fn get_splice_init(&self, our_funding_contribution_satoshis: i64,
4457+
funding_feerate_perkw: u32, locktime: u32,
4458+
) -> msgs::SpliceInit {
4459+
// Reuse the existing funding pubkey, in spite of the channel value changing
4460+
// (though at this point we don't know the new value yet, due tue the optional counterparty contribution)
4461+
// Note that channel_keys_id is supposed NOT to change
4462+
let funding_pubkey = self.get_holder_pubkeys().funding_pubkey.clone();
4463+
msgs::SpliceInit {
4464+
channel_id: self.channel_id,
4465+
funding_contribution_satoshis: our_funding_contribution_satoshis,
4466+
funding_feerate_perkw,
4467+
locktime,
4468+
funding_pubkey,
4469+
require_confirmed_inputs: None,
4470+
}
4471+
}
4472+
4473+
/// Get the splice_ack message that can be sent in response to splice initiation.
4474+
#[cfg(splicing)]
4475+
pub fn get_splice_ack(&self, our_funding_contribution_satoshis: i64) -> msgs::SpliceAck {
4476+
// Reuse the existing funding pubkey, in spite of the channel value changing
4477+
let funding_pubkey = self.get_holder_pubkeys().funding_pubkey;
4478+
msgs::SpliceAck {
4479+
channel_id: self.channel_id,
4480+
funding_contribution_satoshis: our_funding_contribution_satoshis,
4481+
funding_pubkey,
4482+
require_confirmed_inputs: None,
4483+
}
4484+
}
43934485
}
43944486

43954487
// Internal utility functions for channels
@@ -8132,6 +8224,124 @@ impl<SP: Deref> FundedChannel<SP> where
81328224
}
81338225
}
81348226

8227+
/// Initiate splicing
8228+
#[cfg(splicing)]
8229+
pub fn splice_channel(&mut self, our_funding_contribution_satoshis: i64,
8230+
funding_feerate_perkw: u32, locktime: u32,
8231+
) -> Result<msgs::SpliceInit, ChannelError> {
8232+
// Check if a splice has been initiated already.
8233+
// Note: this could be handled more nicely, and support multiple outstanding splice's, the incoming splice_ack matters anyways.
8234+
if let Some(splice_info) = &self.context.pending_splice_pre {
8235+
return Err(ChannelError::Warn(format!(
8236+
"Channel has already a splice pending, contribution {}", splice_info.our_funding_contribution
8237+
)));
8238+
}
8239+
8240+
if !matches!(self.context.channel_state, ChannelState::ChannelReady(_)) {
8241+
return Err(ChannelError::Warn(format!("Cannot initiate splicing, as channel is not Ready")));
8242+
}
8243+
8244+
let pre_channel_value = self.context.get_value_satoshis();
8245+
// Sanity check: capacity cannot decrease below 0
8246+
if (pre_channel_value as i64).saturating_add(our_funding_contribution_satoshis) < 0 {
8247+
return Err(ChannelError::Warn(format!(
8248+
"Post-splicing channel value cannot be negative. It was {} + {}",
8249+
pre_channel_value, our_funding_contribution_satoshis
8250+
)));
8251+
}
8252+
8253+
if our_funding_contribution_satoshis < 0 {
8254+
return Err(ChannelError::Warn(format!(
8255+
"TODO(splicing): Splice-out not supported, only splice in, contribution {}",
8256+
our_funding_contribution_satoshis,
8257+
)));
8258+
}
8259+
8260+
// Note: post-splice channel value is not yet known at this point, counterpary contribution is not known
8261+
// (Cannot test for miminum required post-splice channel value)
8262+
8263+
self.context.pending_splice_pre = Some(PendingSpliceInfoPre {
8264+
our_funding_contribution: our_funding_contribution_satoshis,
8265+
});
8266+
8267+
let msg = self.context.get_splice_init(our_funding_contribution_satoshis, funding_feerate_perkw, locktime);
8268+
Ok(msg)
8269+
}
8270+
8271+
/// Handle splice_init
8272+
#[cfg(splicing)]
8273+
pub fn splice_init(&mut self, msg: &msgs::SpliceInit) -> Result<msgs::SpliceAck, ChannelError> {
8274+
let their_funding_contribution_satoshis = msg.funding_contribution_satoshis;
8275+
// TODO(splicing): Currently not possible to contribute on the splicing-acceptor side
8276+
let our_funding_contribution_satoshis = 0i64;
8277+
8278+
// Check if a splice has been initiated already.
8279+
// Note: this could be handled more nicely, and support multiple outstanding splice's, the incoming splice_ack matters anyways.
8280+
if let Some(splice_info) = &self.context.pending_splice_pre {
8281+
return Err(ChannelError::Warn(format!(
8282+
"Channel has already a splice pending, contribution {}", splice_info.our_funding_contribution,
8283+
)));
8284+
}
8285+
8286+
if !matches!(self.context.channel_state, ChannelState::ChannelReady(_)) {
8287+
return Err(ChannelError::Warn(format!("Splicing requested on a channel that is not Ready")));
8288+
}
8289+
8290+
let pre_channel_value = self.context.get_value_satoshis();
8291+
// Sanity check: capacity cannot decrease below 0
8292+
if (pre_channel_value as i64)
8293+
.saturating_add(their_funding_contribution_satoshis)
8294+
.saturating_add(our_funding_contribution_satoshis) < 0
8295+
{
8296+
return Err(ChannelError::Warn(format!(
8297+
"Post-splicing channel value cannot be negative. It was {} + {} + {}",
8298+
pre_channel_value, their_funding_contribution_satoshis, our_funding_contribution_satoshis,
8299+
)));
8300+
}
8301+
8302+
if their_funding_contribution_satoshis.saturating_add(our_funding_contribution_satoshis) < 0 {
8303+
return Err(ChannelError::Warn(format!(
8304+
"Splice-out not supported, only splice in, relative {} + {}",
8305+
their_funding_contribution_satoshis, our_funding_contribution_satoshis,
8306+
)));
8307+
}
8308+
8309+
let post_channel_value = PendingSpliceInfoPre::compute_post_value(pre_channel_value, their_funding_contribution_satoshis, our_funding_contribution_satoshis);
8310+
let post_balance = PendingSpliceInfoPre::add_checked(self.context.value_to_self_msat, our_funding_contribution_satoshis);
8311+
// Early check for reserve requirement, assuming maximum balance of full channel value
8312+
// This will also be checked later at tx_complete
8313+
let _res = self.context.check_balance_meets_reserve_requirements(post_balance, post_channel_value)?;
8314+
8315+
// TODO(splicing): Store msg.funding_pubkey
8316+
// TODO(splicing): Apply start of splice (splice_start)
8317+
8318+
let splice_ack_msg = self.context.get_splice_ack(our_funding_contribution_satoshis);
8319+
// TODO(splicing): start interactive funding negotiation
8320+
Ok(splice_ack_msg)
8321+
}
8322+
8323+
/// Handle splice_ack
8324+
#[cfg(splicing)]
8325+
pub fn splice_ack(&mut self, msg: &msgs::SpliceAck) -> Result<(), ChannelError> {
8326+
let their_funding_contribution_satoshis = msg.funding_contribution_satoshis;
8327+
8328+
// check if splice is pending
8329+
let pending_splice = if let Some(pending_splice) = &self.context.pending_splice_pre {
8330+
pending_splice
8331+
} else {
8332+
return Err(ChannelError::Warn(format!("Channel is not in pending splice")));
8333+
};
8334+
8335+
let our_funding_contribution = pending_splice.our_funding_contribution;
8336+
8337+
let pre_channel_value = self.context.get_value_satoshis();
8338+
let post_channel_value = PendingSpliceInfoPre::compute_post_value(pre_channel_value, our_funding_contribution, their_funding_contribution_satoshis);
8339+
let post_balance = PendingSpliceInfoPre::add_checked(self.context.value_to_self_msat, our_funding_contribution);
8340+
// Early check for reserve requirement, assuming maximum balance of full channel value
8341+
// This will also be checked later at tx_complete
8342+
let _res = self.context.check_balance_meets_reserve_requirements(post_balance, post_channel_value)?;
8343+
Ok(())
8344+
}
81358345

81368346
// Send stuff to our remote peers:
81378347

@@ -10531,6 +10741,9 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, u32, &'c Ch
1053110741
// during a signing session, but have not received `tx_signatures` we MUST set `next_funding_txid`
1053210742
// to the txid of that interactive transaction, else we MUST NOT set it.
1053310743
next_funding_txid: None,
10744+
10745+
#[cfg(splicing)]
10746+
pending_splice_pre: None,
1053410747
},
1053510748
interactive_tx_signing_session: None,
1053610749
holder_commitment_point,
@@ -12316,4 +12529,69 @@ mod tests {
1231612529
assert_eq!(node_a_chan.context.channel_state, ChannelState::AwaitingChannelReady(AwaitingChannelReadyFlags::THEIR_CHANNEL_READY));
1231712530
assert!(node_a_chan.check_get_channel_ready(0, &&logger).is_some());
1231812531
}
12532+
12533+
#[cfg(all(test, splicing))]
12534+
fn get_pre_and_post(pre_channel_value: u64, our_funding_contribution: i64, their_funding_contribution: i64) -> (u64, u64) {
12535+
use crate::ln::channel::PendingSpliceInfoPre;
12536+
12537+
let post_channel_value = PendingSpliceInfoPre::compute_post_value(pre_channel_value, our_funding_contribution, their_funding_contribution);
12538+
(pre_channel_value, post_channel_value)
12539+
}
12540+
12541+
#[cfg(all(test, splicing))]
12542+
#[test]
12543+
fn test_splice_compute_post_value() {
12544+
{
12545+
// increase, small amounts
12546+
let (pre_channel_value, post_channel_value) = get_pre_and_post(9_000, 6_000, 0);
12547+
assert_eq!(pre_channel_value, 9_000);
12548+
assert_eq!(post_channel_value, 15_000);
12549+
}
12550+
{
12551+
// increase, small amounts
12552+
let (pre_channel_value, post_channel_value) = get_pre_and_post(9_000, 4_000, 2_000);
12553+
assert_eq!(pre_channel_value, 9_000);
12554+
assert_eq!(post_channel_value, 15_000);
12555+
}
12556+
{
12557+
// increase, small amounts
12558+
let (pre_channel_value, post_channel_value) = get_pre_and_post(9_000, 0, 6_000);
12559+
assert_eq!(pre_channel_value, 9_000);
12560+
assert_eq!(post_channel_value, 15_000);
12561+
}
12562+
{
12563+
// decrease, small amounts
12564+
let (pre_channel_value, post_channel_value) = get_pre_and_post(15_000, -6_000, 0);
12565+
assert_eq!(pre_channel_value, 15_000);
12566+
assert_eq!(post_channel_value, 9_000);
12567+
}
12568+
{
12569+
// decrease, small amounts
12570+
let (pre_channel_value, post_channel_value) = get_pre_and_post(15_000, -4_000, -2_000);
12571+
assert_eq!(pre_channel_value, 15_000);
12572+
assert_eq!(post_channel_value, 9_000);
12573+
}
12574+
{
12575+
// increase and decrease
12576+
let (pre_channel_value, post_channel_value) = get_pre_and_post(15_000, 4_000, -2_000);
12577+
assert_eq!(pre_channel_value, 15_000);
12578+
assert_eq!(post_channel_value, 17_000);
12579+
}
12580+
let base2: u64 = 2;
12581+
let huge63i3 = (base2.pow(63) - 3) as i64;
12582+
assert_eq!(huge63i3, 9223372036854775805);
12583+
assert_eq!(-huge63i3, -9223372036854775805);
12584+
{
12585+
// increase, large amount
12586+
let (pre_channel_value, post_channel_value) = get_pre_and_post(9_000, huge63i3, 3);
12587+
assert_eq!(pre_channel_value, 9_000);
12588+
assert_eq!(post_channel_value, 9223372036854784807);
12589+
}
12590+
{
12591+
// increase, large amounts
12592+
let (pre_channel_value, post_channel_value) = get_pre_and_post(9_000, huge63i3, huge63i3);
12593+
assert_eq!(pre_channel_value, 9_000);
12594+
assert_eq!(post_channel_value, 9223372036854784807);
12595+
}
12596+
}
1231912597
}

0 commit comments

Comments
 (0)