Skip to content

Commit 508c1b6

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

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};
@@ -1187,6 +1186,30 @@ impl UnfundedChannelContext {
11871186
}
11881187
}
11891188

1189+
/// Info about a pending splice, used in the pre-splice channel
1190+
#[cfg(splicing)]
1191+
#[derive(Clone)]
1192+
struct PendingSpliceInfoPre {
1193+
pub our_funding_contribution: i64,
1194+
}
1195+
1196+
#[cfg(splicing)]
1197+
impl PendingSpliceInfoPre {
1198+
#[inline]
1199+
fn add_checked(base: u64, delta: i64) -> u64 {
1200+
if delta >= 0 {
1201+
base.saturating_add(delta as u64)
1202+
} else {
1203+
base.saturating_sub(delta.abs() as u64)
1204+
}
1205+
}
1206+
1207+
/// Compute the post-splice channel value from the pre-splice values and the peer contributions
1208+
pub fn compute_post_value(pre_channel_value: u64, our_funding_contribution: i64, their_funding_contribution: i64) -> u64 {
1209+
Self::add_checked(pre_channel_value, our_funding_contribution.saturating_add(their_funding_contribution))
1210+
}
1211+
}
1212+
11901213
/// Contains everything about the channel including state, and various flags.
11911214
pub(super) struct ChannelContext<SP: Deref> where SP::Target: SignerProvider {
11921215
config: LegacyChannelConfig,
@@ -1222,6 +1245,10 @@ pub(super) struct ChannelContext<SP: Deref> where SP::Target: SignerProvider {
12221245
secp_ctx: Secp256k1<secp256k1::All>,
12231246
channel_value_satoshis: u64,
12241247

1248+
/// Info about an in-progress, pending splice (if any), on the pre-splice channel
1249+
#[cfg(splicing)]
1250+
pending_splice_pre: Option<PendingSpliceInfoPre>,
1251+
12251252
latest_monitor_update_id: u64,
12261253

12271254
holder_signer: ChannelSignerType<SP>,
@@ -2343,6 +2370,9 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
23432370
is_manual_broadcast: false,
23442371

23452372
next_funding_txid: None,
2373+
2374+
#[cfg(splicing)]
2375+
pending_splice_pre: None,
23462376
};
23472377

23482378
Ok(channel_context)
@@ -2573,6 +2603,9 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
25732603
local_initiated_shutdown: None,
25742604
is_manual_broadcast: false,
25752605
next_funding_txid: None,
2606+
2607+
#[cfg(splicing)]
2608+
pending_splice_pre: None,
25762609
})
25772610
}
25782611

@@ -3755,6 +3788,33 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
37553788
(context.holder_selected_channel_reserve_satoshis, context.counterparty_selected_channel_reserve_satoshis)
37563789
}
37573790

3791+
/// Check that a balance value meets the channel reserve requirements or violates them (below reserve).
3792+
/// The channel value is an input as opposed to using from self, so that this can be used in case of splicing
3793+
/// to checks with new channel value (before being comitted to it).
3794+
#[cfg(splicing)]
3795+
pub fn check_balance_meets_reserve_requirements(&self, balance: u64, channel_value: u64) -> Result<(), ChannelError> {
3796+
if balance == 0 {
3797+
return Ok(());
3798+
}
3799+
let holder_selected_channel_reserve_satoshis = get_v2_channel_reserve_satoshis(
3800+
channel_value, self.holder_dust_limit_satoshis);
3801+
if balance < holder_selected_channel_reserve_satoshis {
3802+
return Err(ChannelError::Warn(format!(
3803+
"Balance below reserve mandated by holder, {} vs {}",
3804+
balance, holder_selected_channel_reserve_satoshis,
3805+
)));
3806+
}
3807+
let counterparty_selected_channel_reserve_satoshis = get_v2_channel_reserve_satoshis(
3808+
channel_value, self.counterparty_dust_limit_satoshis);
3809+
if balance < counterparty_selected_channel_reserve_satoshis {
3810+
return Err(ChannelError::Warn(format!(
3811+
"Balance below reserve mandated by counterparty, {} vs {}",
3812+
balance, counterparty_selected_channel_reserve_satoshis,
3813+
)));
3814+
}
3815+
Ok(())
3816+
}
3817+
37583818
/// Get the commitment tx fee for the local's (i.e. our) next commitment transaction based on the
37593819
/// number of pending HTLCs that are on track to be in our next commitment tx.
37603820
///
@@ -4217,6 +4277,38 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
42174277
self.channel_transaction_parameters = channel_transaction_parameters;
42184278
self.get_initial_counterparty_commitment_signature(logger)
42194279
}
4280+
4281+
/// Get the splice message that can be sent during splice initiation.
4282+
#[cfg(splicing)]
4283+
pub fn get_splice_init(&self, our_funding_contribution_satoshis: i64,
4284+
funding_feerate_perkw: u32, locktime: u32,
4285+
) -> msgs::SpliceInit {
4286+
// Reuse the existing funding pubkey, in spite of the channel value changing
4287+
// (though at this point we don't know the new value yet, due tue the optional counterparty contribution)
4288+
// Note that channel_keys_id is supposed NOT to change
4289+
let funding_pubkey = self.get_holder_pubkeys().funding_pubkey.clone();
4290+
msgs::SpliceInit {
4291+
channel_id: self.channel_id,
4292+
funding_contribution_satoshis: our_funding_contribution_satoshis,
4293+
funding_feerate_perkw,
4294+
locktime,
4295+
funding_pubkey,
4296+
require_confirmed_inputs: None,
4297+
}
4298+
}
4299+
4300+
/// Get the splice_ack message that can be sent in response to splice initiation.
4301+
#[cfg(splicing)]
4302+
pub fn get_splice_ack(&self, our_funding_contribution_satoshis: i64) -> msgs::SpliceAck {
4303+
// Reuse the existing funding pubkey, in spite of the channel value changing
4304+
let funding_pubkey = self.get_holder_pubkeys().funding_pubkey;
4305+
msgs::SpliceAck {
4306+
channel_id: self.channel_id,
4307+
funding_contribution_satoshis: our_funding_contribution_satoshis,
4308+
funding_pubkey,
4309+
require_confirmed_inputs: None,
4310+
}
4311+
}
42204312
}
42214313

42224314
// Internal utility functions for channels
@@ -7956,6 +8048,124 @@ impl<SP: Deref> Channel<SP> where
79568048
}
79578049
}
79588050

8051+
/// Initiate splicing
8052+
#[cfg(splicing)]
8053+
pub fn splice_channel(&mut self, our_funding_contribution_satoshis: i64,
8054+
funding_feerate_perkw: u32, locktime: u32,
8055+
) -> Result<msgs::SpliceInit, ChannelError> {
8056+
// Check if a splice has been initiated already.
8057+
// Note: this could be handled more nicely, and support multiple outstanding splice's, the incoming splice_ack matters anyways.
8058+
if let Some(splice_info) = &self.context.pending_splice_pre {
8059+
return Err(ChannelError::Warn(format!(
8060+
"Channel has already a splice pending, contribution {}", splice_info.our_funding_contribution
8061+
)));
8062+
}
8063+
8064+
if !matches!(self.context.channel_state, ChannelState::ChannelReady(_)) {
8065+
return Err(ChannelError::Warn(format!("Cannot initiate splicing, as channel is not Ready")));
8066+
}
8067+
8068+
let pre_channel_value = self.context.get_value_satoshis();
8069+
// Sanity check: capacity cannot decrease below 0
8070+
if (pre_channel_value as i64).saturating_add(our_funding_contribution_satoshis) < 0 {
8071+
return Err(ChannelError::Warn(format!(
8072+
"Post-splicing channel value cannot be negative. It was {} + {}",
8073+
pre_channel_value, our_funding_contribution_satoshis
8074+
)));
8075+
}
8076+
8077+
if our_funding_contribution_satoshis < 0 {
8078+
return Err(ChannelError::Warn(format!(
8079+
"TODO(splicing): Splice-out not supported, only splice in, contribution {}",
8080+
our_funding_contribution_satoshis,
8081+
)));
8082+
}
8083+
8084+
// Note: post-splice channel value is not yet known at this point, counterpary contribution is not known
8085+
// (Cannot test for miminum required post-splice channel value)
8086+
8087+
self.context.pending_splice_pre = Some(PendingSpliceInfoPre {
8088+
our_funding_contribution: our_funding_contribution_satoshis,
8089+
});
8090+
8091+
let msg = self.context.get_splice_init(our_funding_contribution_satoshis, funding_feerate_perkw, locktime);
8092+
Ok(msg)
8093+
}
8094+
8095+
/// Handle splice_init
8096+
#[cfg(splicing)]
8097+
pub fn splice_init(&mut self, msg: &msgs::SpliceInit) -> Result<msgs::SpliceAck, ChannelError> {
8098+
let their_funding_contribution_satoshis = msg.funding_contribution_satoshis;
8099+
// TODO(splicing): Currently not possible to contribute on the splicing-acceptor side
8100+
let our_funding_contribution_satoshis = 0i64;
8101+
8102+
// Check if a splice has been initiated already.
8103+
// Note: this could be handled more nicely, and support multiple outstanding splice's, the incoming splice_ack matters anyways.
8104+
if let Some(splice_info) = &self.context.pending_splice_pre {
8105+
return Err(ChannelError::Warn(format!(
8106+
"Channel has already a splice pending, contribution {}", splice_info.our_funding_contribution,
8107+
)));
8108+
}
8109+
8110+
if !matches!(self.context.channel_state, ChannelState::ChannelReady(_)) {
8111+
return Err(ChannelError::Warn(format!("Splicing requested on a channel that is not Ready")));
8112+
}
8113+
8114+
let pre_channel_value = self.context.get_value_satoshis();
8115+
// Sanity check: capacity cannot decrease below 0
8116+
if (pre_channel_value as i64)
8117+
.saturating_add(their_funding_contribution_satoshis)
8118+
.saturating_add(our_funding_contribution_satoshis) < 0
8119+
{
8120+
return Err(ChannelError::Warn(format!(
8121+
"Post-splicing channel value cannot be negative. It was {} + {} + {}",
8122+
pre_channel_value, their_funding_contribution_satoshis, our_funding_contribution_satoshis,
8123+
)));
8124+
}
8125+
8126+
if their_funding_contribution_satoshis.saturating_add(our_funding_contribution_satoshis) < 0 {
8127+
return Err(ChannelError::Warn(format!(
8128+
"Splice-out not supported, only splice in, relative {} + {}",
8129+
their_funding_contribution_satoshis, our_funding_contribution_satoshis,
8130+
)));
8131+
}
8132+
8133+
let post_channel_value = PendingSpliceInfoPre::compute_post_value(pre_channel_value, their_funding_contribution_satoshis, our_funding_contribution_satoshis);
8134+
let post_balance = PendingSpliceInfoPre::add_checked(self.context.value_to_self_msat, our_funding_contribution_satoshis);
8135+
// Early check for reserve requirement, assuming maximum balance of full channel value
8136+
// This will also be checked later at tx_complete
8137+
let _res = self.context.check_balance_meets_reserve_requirements(post_balance, post_channel_value)?;
8138+
8139+
// TODO(splicing): Store msg.funding_pubkey
8140+
// TODO(splicing): Apply start of splice (splice_start)
8141+
8142+
let splice_ack_msg = self.context.get_splice_ack(our_funding_contribution_satoshis);
8143+
// TODO(splicing): start interactive funding negotiation
8144+
Ok(splice_ack_msg)
8145+
}
8146+
8147+
/// Handle splice_ack
8148+
#[cfg(splicing)]
8149+
pub fn splice_ack(&mut self, msg: &msgs::SpliceAck) -> Result<(), ChannelError> {
8150+
let their_funding_contribution_satoshis = msg.funding_contribution_satoshis;
8151+
8152+
// check if splice is pending
8153+
let pending_splice = if let Some(pending_splice) = &self.context.pending_splice_pre {
8154+
pending_splice
8155+
} else {
8156+
return Err(ChannelError::Warn(format!("Channel is not in pending splice")));
8157+
};
8158+
8159+
let our_funding_contribution = pending_splice.our_funding_contribution;
8160+
8161+
let pre_channel_value = self.context.get_value_satoshis();
8162+
let post_channel_value = PendingSpliceInfoPre::compute_post_value(pre_channel_value, our_funding_contribution, their_funding_contribution_satoshis);
8163+
let post_balance = PendingSpliceInfoPre::add_checked(self.context.value_to_self_msat, our_funding_contribution);
8164+
// Early check for reserve requirement, assuming maximum balance of full channel value
8165+
// This will also be checked later at tx_complete
8166+
let _res = self.context.check_balance_meets_reserve_requirements(post_balance, post_channel_value)?;
8167+
Ok(())
8168+
}
79598169

79608170
// Send stuff to our remote peers:
79618171

@@ -10373,6 +10583,9 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, u32, &'c Ch
1037310583
// during a signing session, but have not received `tx_signatures` we MUST set `next_funding_txid`
1037410584
// to the txid of that interactive transaction, else we MUST NOT set it.
1037510585
next_funding_txid: None,
10586+
10587+
#[cfg(splicing)]
10588+
pending_splice_pre: None,
1037610589
},
1037710590
interactive_tx_signing_session: None,
1037810591
holder_commitment_point,
@@ -12158,4 +12371,69 @@ mod tests {
1215812371
assert_eq!(node_a_chan.context.channel_state, ChannelState::AwaitingChannelReady(AwaitingChannelReadyFlags::THEIR_CHANNEL_READY));
1215912372
assert!(node_a_chan.check_get_channel_ready(0, &&logger).is_some());
1216012373
}
12374+
12375+
#[cfg(all(test, splicing))]
12376+
fn get_pre_and_post(pre_channel_value: u64, our_funding_contribution: i64, their_funding_contribution: i64) -> (u64, u64) {
12377+
use crate::ln::channel::PendingSpliceInfoPre;
12378+
12379+
let post_channel_value = PendingSpliceInfoPre::compute_post_value(pre_channel_value, our_funding_contribution, their_funding_contribution);
12380+
(pre_channel_value, post_channel_value)
12381+
}
12382+
12383+
#[cfg(all(test, splicing))]
12384+
#[test]
12385+
fn test_splice_compute_post_value() {
12386+
{
12387+
// increase, small amounts
12388+
let (pre_channel_value, post_channel_value) = get_pre_and_post(9_000, 6_000, 0);
12389+
assert_eq!(pre_channel_value, 9_000);
12390+
assert_eq!(post_channel_value, 15_000);
12391+
}
12392+
{
12393+
// increase, small amounts
12394+
let (pre_channel_value, post_channel_value) = get_pre_and_post(9_000, 4_000, 2_000);
12395+
assert_eq!(pre_channel_value, 9_000);
12396+
assert_eq!(post_channel_value, 15_000);
12397+
}
12398+
{
12399+
// increase, small amounts
12400+
let (pre_channel_value, post_channel_value) = get_pre_and_post(9_000, 0, 6_000);
12401+
assert_eq!(pre_channel_value, 9_000);
12402+
assert_eq!(post_channel_value, 15_000);
12403+
}
12404+
{
12405+
// decrease, small amounts
12406+
let (pre_channel_value, post_channel_value) = get_pre_and_post(15_000, -6_000, 0);
12407+
assert_eq!(pre_channel_value, 15_000);
12408+
assert_eq!(post_channel_value, 9_000);
12409+
}
12410+
{
12411+
// decrease, small amounts
12412+
let (pre_channel_value, post_channel_value) = get_pre_and_post(15_000, -4_000, -2_000);
12413+
assert_eq!(pre_channel_value, 15_000);
12414+
assert_eq!(post_channel_value, 9_000);
12415+
}
12416+
{
12417+
// increase and decrease
12418+
let (pre_channel_value, post_channel_value) = get_pre_and_post(15_000, 4_000, -2_000);
12419+
assert_eq!(pre_channel_value, 15_000);
12420+
assert_eq!(post_channel_value, 17_000);
12421+
}
12422+
let base2: u64 = 2;
12423+
let huge63i3 = (base2.pow(63) - 3) as i64;
12424+
assert_eq!(huge63i3, 9223372036854775805);
12425+
assert_eq!(-huge63i3, -9223372036854775805);
12426+
{
12427+
// increase, large amount
12428+
let (pre_channel_value, post_channel_value) = get_pre_and_post(9_000, huge63i3, 3);
12429+
assert_eq!(pre_channel_value, 9_000);
12430+
assert_eq!(post_channel_value, 9223372036854784807);
12431+
}
12432+
{
12433+
// increase, large amounts
12434+
let (pre_channel_value, post_channel_value) = get_pre_and_post(9_000, huge63i3, huge63i3);
12435+
assert_eq!(pre_channel_value, 9_000);
12436+
assert_eq!(post_channel_value, 9223372036854784807);
12437+
}
12438+
}
1216112439
}

0 commit comments

Comments
 (0)