Skip to content

Commit c967b50

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

File tree

5 files changed

+793
-12
lines changed

5 files changed

+793
-12
lines changed

lightning/src/ln/channel.rs

Lines changed: 289 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};
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 PendingSplice {
1433+
pub our_funding_contribution: i64,
1434+
}
1435+
1436+
#[cfg(splicing)]
1437+
impl PendingSplice {
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,
@@ -3828,6 +3851,33 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
38283851
(context.holder_selected_channel_reserve_satoshis, context.counterparty_selected_channel_reserve_satoshis)
38293852
}
38303853

3854+
/// Check that a balance value meets the channel reserve requirements or violates them (below reserve).
3855+
/// The channel value is an input as opposed to using from self, so that this can be used in case of splicing
3856+
/// to checks with new channel value (before being comitted to it).
3857+
#[cfg(splicing)]
3858+
pub fn check_balance_meets_reserve_requirements(&self, balance: u64, channel_value: u64) -> Result<(), ChannelError> {
3859+
if balance == 0 {
3860+
return Ok(());
3861+
}
3862+
let holder_selected_channel_reserve_satoshis = get_v2_channel_reserve_satoshis(
3863+
channel_value, self.holder_dust_limit_satoshis);
3864+
if balance < holder_selected_channel_reserve_satoshis {
3865+
return Err(ChannelError::Warn(format!(
3866+
"Balance below reserve mandated by holder, {} vs {}",
3867+
balance, holder_selected_channel_reserve_satoshis,
3868+
)));
3869+
}
3870+
let counterparty_selected_channel_reserve_satoshis = get_v2_channel_reserve_satoshis(
3871+
channel_value, self.counterparty_dust_limit_satoshis);
3872+
if balance < counterparty_selected_channel_reserve_satoshis {
3873+
return Err(ChannelError::Warn(format!(
3874+
"Balance below reserve mandated by counterparty, {} vs {}",
3875+
balance, counterparty_selected_channel_reserve_satoshis,
3876+
)));
3877+
}
3878+
Ok(())
3879+
}
3880+
38313881
/// Get the commitment tx fee for the local's (i.e. our) next commitment transaction based on the
38323882
/// number of pending HTLCs that are on track to be in our next commitment tx.
38333883
///
@@ -4290,6 +4340,38 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
42904340
self.channel_transaction_parameters = channel_transaction_parameters;
42914341
self.get_initial_counterparty_commitment_signature(logger)
42924342
}
4343+
4344+
/// Get the splice message that can be sent during splice initiation.
4345+
#[cfg(splicing)]
4346+
pub fn get_splice_init(&self, our_funding_contribution_satoshis: i64,
4347+
funding_feerate_perkw: u32, locktime: u32,
4348+
) -> msgs::SpliceInit {
4349+
// Reuse the existing funding pubkey, in spite of the channel value changing
4350+
// (though at this point we don't know the new value yet, due tue the optional counterparty contribution)
4351+
// Note that channel_keys_id is supposed NOT to change
4352+
let funding_pubkey = self.get_holder_pubkeys().funding_pubkey.clone();
4353+
msgs::SpliceInit {
4354+
channel_id: self.channel_id,
4355+
funding_contribution_satoshis: our_funding_contribution_satoshis,
4356+
funding_feerate_perkw,
4357+
locktime,
4358+
funding_pubkey,
4359+
require_confirmed_inputs: None,
4360+
}
4361+
}
4362+
4363+
/// Get the splice_ack message that can be sent in response to splice initiation.
4364+
#[cfg(splicing)]
4365+
pub fn get_splice_ack(&self, our_funding_contribution_satoshis: i64) -> msgs::SpliceAck {
4366+
// Reuse the existing funding pubkey, in spite of the channel value changing
4367+
let funding_pubkey = self.get_holder_pubkeys().funding_pubkey;
4368+
msgs::SpliceAck {
4369+
channel_id: self.channel_id,
4370+
funding_contribution_satoshis: our_funding_contribution_satoshis,
4371+
funding_pubkey,
4372+
require_confirmed_inputs: None,
4373+
}
4374+
}
42934375
}
42944376

42954377
// Internal utility functions for channels
@@ -4414,6 +4496,9 @@ pub(super) struct FundedChannel<SP: Deref> where SP::Target: SignerProvider {
44144496
pub context: ChannelContext<SP>,
44154497
pub interactive_tx_signing_session: Option<InteractiveTxSigningSession>,
44164498
holder_commitment_point: HolderCommitmentPoint,
4499+
/// Info about an in-progress, pending splice (if any), on the pre-splice channel
4500+
#[cfg(splicing)]
4501+
pending_splice_pre: Option<PendingSplice>,
44174502
}
44184503

44194504
#[cfg(any(test, fuzzing))]
@@ -8028,6 +8113,135 @@ impl<SP: Deref> FundedChannel<SP> where
80288113
}
80298114
}
80308115

8116+
/// Initiate splicing
8117+
#[cfg(splicing)]
8118+
pub fn splice_channel(&mut self, our_funding_contribution_satoshis: i64,
8119+
our_funding_inputs: Vec<(TxIn, Transaction)>, funding_feerate_perkw: u32, locktime: u32,
8120+
) -> Result<msgs::SpliceInit, ChannelError> {
8121+
// Check if a splice has been initiated already.
8122+
// Note: this could be handled more nicely, and support multiple outstanding splice's, the incoming splice_ack matters anyways.
8123+
if let Some(splice_info) = &self.pending_splice_pre {
8124+
return Err(ChannelError::Warn(format!(
8125+
"Channel has already a splice pending, contribution {}", splice_info.our_funding_contribution
8126+
)));
8127+
}
8128+
8129+
if !matches!(self.context.channel_state, ChannelState::ChannelReady(_)) {
8130+
return Err(ChannelError::Warn(format!("Cannot initiate splicing, as channel is not Ready")));
8131+
}
8132+
8133+
let pre_channel_value = self.context.get_value_satoshis();
8134+
// Sanity check: capacity cannot decrease below 0
8135+
if (pre_channel_value as i64).saturating_add(our_funding_contribution_satoshis) < 0 {
8136+
return Err(ChannelError::Warn(format!(
8137+
"Post-splicing channel value cannot be negative. It was {} + {}",
8138+
pre_channel_value, our_funding_contribution_satoshis
8139+
)));
8140+
}
8141+
8142+
if our_funding_contribution_satoshis < 0 {
8143+
return Err(ChannelError::Warn(format!(
8144+
"TODO(splicing): Splice-out not supported, only splice in, contribution {}",
8145+
our_funding_contribution_satoshis,
8146+
)));
8147+
}
8148+
8149+
// Note: post-splice channel value is not yet known at this point, counterpary contribution is not known
8150+
// (Cannot test for miminum required post-splice channel value)
8151+
8152+
// Check that inputs are sufficient to cover our contribution
8153+
let sum_input: i64 = our_funding_inputs.into_iter().map(
8154+
|(txin, tx)| tx.output.get(txin.previous_output.vout as usize).map(|tx| tx.value.to_sat() as i64).unwrap_or(0)
8155+
).sum();
8156+
if sum_input < our_funding_contribution_satoshis {
8157+
return Err(ChannelError::Warn(format!(
8158+
"Provided inputs are insufficient for our contribution, {} {}",
8159+
sum_input, our_funding_contribution_satoshis,
8160+
)));
8161+
}
8162+
8163+
self.pending_splice_pre = Some(PendingSplice {
8164+
our_funding_contribution: our_funding_contribution_satoshis,
8165+
});
8166+
8167+
let msg = self.context.get_splice_init(our_funding_contribution_satoshis, funding_feerate_perkw, locktime);
8168+
Ok(msg)
8169+
}
8170+
8171+
/// Handle splice_init
8172+
#[cfg(splicing)]
8173+
pub fn splice_init(&mut self, msg: &msgs::SpliceInit) -> Result<msgs::SpliceAck, ChannelError> {
8174+
let their_funding_contribution_satoshis = msg.funding_contribution_satoshis;
8175+
// TODO(splicing): Currently not possible to contribute on the splicing-acceptor side
8176+
let our_funding_contribution_satoshis = 0i64;
8177+
8178+
// Check if a splice has been initiated already.
8179+
// Note: this could be handled more nicely, and support multiple outstanding splice's, the incoming splice_ack matters anyways.
8180+
if let Some(splice_info) = &self.pending_splice_pre {
8181+
return Err(ChannelError::Warn(format!(
8182+
"Channel has already a splice pending, contribution {}", splice_info.our_funding_contribution,
8183+
)));
8184+
}
8185+
8186+
if !matches!(self.context.channel_state, ChannelState::ChannelReady(_)) {
8187+
return Err(ChannelError::Warn(format!("Splicing requested on a channel that is not Ready")));
8188+
}
8189+
8190+
let pre_channel_value = self.context.get_value_satoshis();
8191+
// Sanity check: capacity cannot decrease below 0
8192+
if (pre_channel_value as i64)
8193+
.saturating_add(their_funding_contribution_satoshis)
8194+
.saturating_add(our_funding_contribution_satoshis) < 0
8195+
{
8196+
return Err(ChannelError::Warn(format!(
8197+
"Post-splicing channel value cannot be negative. It was {} + {} + {}",
8198+
pre_channel_value, their_funding_contribution_satoshis, our_funding_contribution_satoshis,
8199+
)));
8200+
}
8201+
8202+
if their_funding_contribution_satoshis.saturating_add(our_funding_contribution_satoshis) < 0 {
8203+
return Err(ChannelError::Warn(format!(
8204+
"Splice-out not supported, only splice in, relative {} + {}",
8205+
their_funding_contribution_satoshis, our_funding_contribution_satoshis,
8206+
)));
8207+
}
8208+
8209+
let post_channel_value = PendingSplice::compute_post_value(pre_channel_value, their_funding_contribution_satoshis, our_funding_contribution_satoshis);
8210+
let post_balance = PendingSplice::add_checked(self.context.value_to_self_msat, our_funding_contribution_satoshis);
8211+
// Early check for reserve requirement, assuming maximum balance of full channel value
8212+
// This will also be checked later at tx_complete
8213+
let _res = self.context.check_balance_meets_reserve_requirements(post_balance, post_channel_value)?;
8214+
8215+
// TODO(splicing): Store msg.funding_pubkey
8216+
// TODO(splicing): Apply start of splice (splice_start)
8217+
8218+
let splice_ack_msg = self.context.get_splice_ack(our_funding_contribution_satoshis);
8219+
// TODO(splicing): start interactive funding negotiation
8220+
Ok(splice_ack_msg)
8221+
}
8222+
8223+
/// Handle splice_ack
8224+
#[cfg(splicing)]
8225+
pub fn splice_ack(&mut self, msg: &msgs::SpliceAck) -> Result<(), ChannelError> {
8226+
let their_funding_contribution_satoshis = msg.funding_contribution_satoshis;
8227+
8228+
// check if splice is pending
8229+
let pending_splice = if let Some(pending_splice) = &self.pending_splice_pre {
8230+
pending_splice
8231+
} else {
8232+
return Err(ChannelError::Warn(format!("Channel is not in pending splice")));
8233+
};
8234+
8235+
let our_funding_contribution = pending_splice.our_funding_contribution;
8236+
8237+
let pre_channel_value = self.context.get_value_satoshis();
8238+
let post_channel_value = PendingSplice::compute_post_value(pre_channel_value, our_funding_contribution, their_funding_contribution_satoshis);
8239+
let post_balance = PendingSplice::add_checked(self.context.value_to_self_msat, our_funding_contribution);
8240+
// Early check for reserve requirement, assuming maximum balance of full channel value
8241+
// This will also be checked later at tx_complete
8242+
let _res = self.context.check_balance_meets_reserve_requirements(post_balance, post_channel_value)?;
8243+
Ok(())
8244+
}
80318245

80328246
// Send stuff to our remote peers:
80338247

@@ -8722,6 +8936,8 @@ impl<SP: Deref> OutboundV1Channel<SP> where SP::Target: SignerProvider {
87228936
context: self.context,
87238937
interactive_tx_signing_session: None,
87248938
holder_commitment_point,
8939+
#[cfg(splicing)]
8940+
pending_splice_pre: None,
87258941
};
87268942

87278943
let need_channel_ready = channel.check_get_channel_ready(0, logger).is_some()
@@ -8987,6 +9203,8 @@ impl<SP: Deref> InboundV1Channel<SP> where SP::Target: SignerProvider {
89879203
context: self.context,
89889204
interactive_tx_signing_session: None,
89899205
holder_commitment_point,
9206+
#[cfg(splicing)]
9207+
pending_splice_pre: None,
89909208
};
89919209
let need_channel_ready = channel.check_get_channel_ready(0, logger).is_some()
89929210
|| channel.context.signer_pending_channel_ready;
@@ -9348,6 +9566,8 @@ impl<SP: Deref> PendingV2Channel<SP> where SP::Target: SignerProvider {
93489566
context: self.context,
93499567
interactive_tx_signing_session: Some(signing_session),
93509568
holder_commitment_point,
9569+
#[cfg(splicing)]
9570+
pending_splice_pre: None,
93519571
};
93529572

93539573
Ok(channel)
@@ -10428,6 +10648,8 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, u32, &'c Ch
1042810648
},
1042910649
interactive_tx_signing_session: None,
1043010650
holder_commitment_point,
10651+
#[cfg(splicing)]
10652+
pending_splice_pre: None,
1043110653
})
1043210654
}
1043310655
}
@@ -12210,4 +12432,69 @@ mod tests {
1221012432
assert_eq!(node_a_chan.context.channel_state, ChannelState::AwaitingChannelReady(AwaitingChannelReadyFlags::THEIR_CHANNEL_READY));
1221112433
assert!(node_a_chan.check_get_channel_ready(0, &&logger).is_some());
1221212434
}
12435+
12436+
#[cfg(all(test, splicing))]
12437+
fn get_pre_and_post(pre_channel_value: u64, our_funding_contribution: i64, their_funding_contribution: i64) -> (u64, u64) {
12438+
use crate::ln::channel::PendingSplice;
12439+
12440+
let post_channel_value = PendingSplice::compute_post_value(pre_channel_value, our_funding_contribution, their_funding_contribution);
12441+
(pre_channel_value, post_channel_value)
12442+
}
12443+
12444+
#[cfg(all(test, splicing))]
12445+
#[test]
12446+
fn test_splice_compute_post_value() {
12447+
{
12448+
// increase, small amounts
12449+
let (pre_channel_value, post_channel_value) = get_pre_and_post(9_000, 6_000, 0);
12450+
assert_eq!(pre_channel_value, 9_000);
12451+
assert_eq!(post_channel_value, 15_000);
12452+
}
12453+
{
12454+
// increase, small amounts
12455+
let (pre_channel_value, post_channel_value) = get_pre_and_post(9_000, 4_000, 2_000);
12456+
assert_eq!(pre_channel_value, 9_000);
12457+
assert_eq!(post_channel_value, 15_000);
12458+
}
12459+
{
12460+
// increase, small amounts
12461+
let (pre_channel_value, post_channel_value) = get_pre_and_post(9_000, 0, 6_000);
12462+
assert_eq!(pre_channel_value, 9_000);
12463+
assert_eq!(post_channel_value, 15_000);
12464+
}
12465+
{
12466+
// decrease, small amounts
12467+
let (pre_channel_value, post_channel_value) = get_pre_and_post(15_000, -6_000, 0);
12468+
assert_eq!(pre_channel_value, 15_000);
12469+
assert_eq!(post_channel_value, 9_000);
12470+
}
12471+
{
12472+
// decrease, small amounts
12473+
let (pre_channel_value, post_channel_value) = get_pre_and_post(15_000, -4_000, -2_000);
12474+
assert_eq!(pre_channel_value, 15_000);
12475+
assert_eq!(post_channel_value, 9_000);
12476+
}
12477+
{
12478+
// increase and decrease
12479+
let (pre_channel_value, post_channel_value) = get_pre_and_post(15_000, 4_000, -2_000);
12480+
assert_eq!(pre_channel_value, 15_000);
12481+
assert_eq!(post_channel_value, 17_000);
12482+
}
12483+
let base2: u64 = 2;
12484+
let huge63i3 = (base2.pow(63) - 3) as i64;
12485+
assert_eq!(huge63i3, 9223372036854775805);
12486+
assert_eq!(-huge63i3, -9223372036854775805);
12487+
{
12488+
// increase, large amount
12489+
let (pre_channel_value, post_channel_value) = get_pre_and_post(9_000, huge63i3, 3);
12490+
assert_eq!(pre_channel_value, 9_000);
12491+
assert_eq!(post_channel_value, 9223372036854784807);
12492+
}
12493+
{
12494+
// increase, large amounts
12495+
let (pre_channel_value, post_channel_value) = get_pre_and_post(9_000, huge63i3, huge63i3);
12496+
assert_eq!(pre_channel_value, 9_000);
12497+
assert_eq!(post_channel_value, 9223372036854784807);
12498+
}
12499+
}
1221312500
}

0 commit comments

Comments
 (0)