Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Splicing] Partial, handle splice_init & splice_ack messages #3407

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
365 changes: 345 additions & 20 deletions lightning/src/ln/channel.rs

Large diffs are not rendered by default.

181 changes: 172 additions & 9 deletions lightning/src/ln/channelmanager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ use crate::ln::inbound_payment;
use crate::ln::types::ChannelId;
use crate::types::payment::{PaymentHash, PaymentPreimage, PaymentSecret};
use crate::ln::channel::{self, Channel, ChannelError, ChannelUpdateStatus, FundedChannel, ShutdownResult, UpdateFulfillCommitFetch, OutboundV1Channel, InboundV1Channel, WithChannelContext};
#[cfg(any(dual_funding, splicing))]
#[cfg(dual_funding)]
use crate::ln::channel::PendingV2Channel;
use crate::ln::channel_state::ChannelDetails;
use crate::types::features::{Bolt12InvoiceFeatures, ChannelFeatures, ChannelTypeFeatures, InitFeatures, NodeFeatures};
Expand Down Expand Up @@ -4206,6 +4206,65 @@ where
}
}

/// Initiate a splice, to change the channel capacity of an existing funded channel.
/// After completion of splicing, the funding transaction will be replaced by a new one, spending the old funding transaction,
/// with optional extra inputs (splice-in) and/or extra outputs (splice-out or change).
/// TODO(splicing): Implementation is currently incomplete.
/// Note: Currently only splice-in is supported (increase in channel capacity), splice-out is not.
/// - our_funding_contribution_satoshis: the amount contributed by us to the channel. This will increase our channel balance.
/// - our_funding_inputs: the funding inputs provided by us. If our contribution is positive, our funding inputs must cover at least that amount.
/// - witness_weight: The witness weight for contributed inputs.
#[cfg(splicing)]
pub fn splice_channel(
&self, channel_id: &ChannelId, counterparty_node_id: &PublicKey, our_funding_contribution_satoshis: i64,
our_funding_inputs: Vec<(TxIn, Transaction)>, witness_weight: Weight,
funding_feerate_per_kw: u32, locktime: u32,
) -> Result<(), APIError> {
let per_peer_state = self.per_peer_state.read().unwrap();

let peer_state_mutex = per_peer_state.get(counterparty_node_id)
.ok_or_else(|| APIError::ChannelUnavailable { err: format!("Can't find a peer matching the passed counterparty node_id {}", counterparty_node_id) })?;

let mut peer_state_lock = peer_state_mutex.lock().unwrap();
let peer_state = &mut *peer_state_lock;

// Look for the channel
match peer_state.channel_by_id.entry(*channel_id) {
hash_map::Entry::Occupied(mut chan_phase_entry) => {
if let Some(chan) = chan_phase_entry.get_mut().as_funded_mut() {
let msg = chan.splice_channel(our_funding_contribution_satoshis, our_funding_inputs, witness_weight, funding_feerate_per_kw, locktime)
.map_err(|err| APIError::APIMisuseError {
err: format!(
"Cannot initiate Splicing, {}, channel ID {}", err, channel_id
)
})?;

peer_state.pending_msg_events.push(events::MessageSendEvent::SendSpliceInit {
node_id: *counterparty_node_id,
msg,
});

Ok(())
} else {
Err(APIError::ChannelUnavailable {
err: format!(
"Channel with id {} is not funded, cannot splice it",
channel_id
)
})
}
},
hash_map::Entry::Vacant(_) => {
return Err(APIError::ChannelUnavailable {
err: format!(
"Channel with id {} not found for the passed counterparty node_id {}",
channel_id, counterparty_node_id,
)
});
},
}
}

fn can_forward_htlc_to_outgoing_channel(
&self, chan: &mut FundedChannel<SP>, msg: &msgs::UpdateAddHTLC, next_packet: &NextPacketDetails
) -> Result<(), (&'static str, u16)> {
Expand Down Expand Up @@ -9308,6 +9367,94 @@ This indicates a bug inside LDK. Please report this error at https://github.com/
Ok(NotifyOption::SkipPersistHandleEvents)
}

/// Handle incoming splice request, transition channel to splice-pending (unless some check fails).
#[cfg(splicing)]
fn internal_splice_init(&self, counterparty_node_id: &PublicKey, msg: &msgs::SpliceInit) -> Result<(), MsgHandleErrInternal> {
// TODO(splicing): if we accept splicing, quiescence

let per_peer_state = self.per_peer_state.read().unwrap();
let peer_state_mutex = per_peer_state.get(counterparty_node_id)
.ok_or_else(|| {
debug_assert!(false);
MsgHandleErrInternal::send_err_msg_no_close(format!("Can't find a peer matching the passed counterparty node_id {}", counterparty_node_id), msg.channel_id)
})?;
let mut peer_state_lock = peer_state_mutex.lock().unwrap();
let peer_state = &mut *peer_state_lock;

// Look for the channel
match peer_state.channel_by_id.entry(msg.channel_id) {
hash_map::Entry::Vacant(_) => return Err(MsgHandleErrInternal::send_err_msg_no_close(format!(
"Got a message for a channel from the wrong node! No such channel for the passed counterparty_node_id {}, channel_id {}",
counterparty_node_id, msg.channel_id,
), msg.channel_id)),
hash_map::Entry::Occupied(mut chan_entry) => {
if let Some(chan) = chan_entry.get_mut().as_funded_mut() {
match chan.splice_init(msg) {
Ok(splice_ack_msg) => {
peer_state.pending_msg_events.push(events::MessageSendEvent::SendSpliceAck {
node_id: *counterparty_node_id,
msg: splice_ack_msg,
});
},
Err(err) => {
return Err(MsgHandleErrInternal::from_chan_no_close(err, msg.channel_id));
}
}
} else {
return Err(MsgHandleErrInternal::send_err_msg_no_close("Channel is not funded, cannot be spliced".to_owned(), msg.channel_id));
}
},
};

// TODO(splicing):
// Change channel, change phase (remove and add)
// Create new post-splice channel
// etc.

Ok(())
}

/// Handle incoming splice request ack, transition channel to splice-pending (unless some check fails).
#[cfg(splicing)]
fn internal_splice_ack(&self, counterparty_node_id: &PublicKey, msg: &msgs::SpliceAck) -> Result<(), MsgHandleErrInternal> {
let per_peer_state = self.per_peer_state.read().unwrap();
let peer_state_mutex = per_peer_state.get(counterparty_node_id)
.ok_or_else(|| {
debug_assert!(false);
MsgHandleErrInternal::send_err_msg_no_close(format!("Can't find a peer matching the passed counterparty node_id {}", counterparty_node_id), msg.channel_id)
})?;
let mut peer_state_lock = peer_state_mutex.lock().unwrap();
let peer_state = &mut *peer_state_lock;

// Look for the channel
match peer_state.channel_by_id.entry(msg.channel_id) {
hash_map::Entry::Vacant(_) => return Err(MsgHandleErrInternal::send_err_msg_no_close(format!(
"Got a message for a channel from the wrong node! No such channel for the passed counterparty_node_id {}",
counterparty_node_id
), msg.channel_id)),
hash_map::Entry::Occupied(mut chan) => {
if let Some(chan) = chan.get_mut().as_funded_mut() {
match chan.splice_ack(msg) {
Ok(_) => {}
Err(err) => {
return Err(MsgHandleErrInternal::from_chan_no_close(err, msg.channel_id));
}
}
} else {
return Err(MsgHandleErrInternal::send_err_msg_no_close("Channel is not funded, cannot splice".to_owned(), msg.channel_id));
}
},
};

// TODO(splicing):
// Change channel, change phase (remove and add)
// Create new post-splice channel
// Start splice funding transaction negotiation
// etc.

Err(MsgHandleErrInternal::send_err_msg_no_close("TODO(splicing): Splicing is not implemented (splice_ack)".to_owned(), msg.channel_id))
}

/// Process pending events from the [`chain::Watch`], returning whether any events were processed.
fn process_pending_monitor_events(&self) -> bool {
debug_assert!(self.total_consistency_lock.try_write().is_err()); // Caller holds read lock
Expand Down Expand Up @@ -11409,28 +11556,44 @@ where
fn handle_stfu(&self, counterparty_node_id: PublicKey, msg: &msgs::Stfu) {
let _: Result<(), _> = handle_error!(self, Err(MsgHandleErrInternal::send_err_msg_no_close(
"Quiescence not supported".to_owned(),
msg.channel_id.clone())), counterparty_node_id);
msg.channel_id)), counterparty_node_id);
}

/// TODO(splicing): Implement persisting
#[cfg(splicing)]
fn handle_splice_init(&self, counterparty_node_id: PublicKey, msg: &msgs::SpliceInit) {
let _: Result<(), _> = handle_error!(self, Err(MsgHandleErrInternal::send_err_msg_no_close(
"Splicing not supported".to_owned(),
msg.channel_id.clone())), counterparty_node_id);
let _persistence_guard = PersistenceNotifierGuard::optionally_notify(self, || {
let res = self.internal_splice_init(&counterparty_node_id, msg);
let persist = match &res {
Err(e) if e.closes_channel() => NotifyOption::DoPersist,
Err(_) => NotifyOption::SkipPersistHandleEvents,
Ok(()) => NotifyOption::SkipPersistNoEvents,
};
optout21 marked this conversation as resolved.
Show resolved Hide resolved
let _ = handle_error!(self, res, counterparty_node_id);
persist
});
}

/// TODO(splicing): Implement persisting
#[cfg(splicing)]
fn handle_splice_ack(&self, counterparty_node_id: PublicKey, msg: &msgs::SpliceAck) {
let _: Result<(), _> = handle_error!(self, Err(MsgHandleErrInternal::send_err_msg_no_close(
"Splicing not supported (splice_ack)".to_owned(),
msg.channel_id.clone())), counterparty_node_id);
let _persistence_guard = PersistenceNotifierGuard::optionally_notify(self, || {
let res = self.internal_splice_ack(&counterparty_node_id, msg);
let persist = match &res {
Err(e) if e.closes_channel() => NotifyOption::DoPersist,
Err(_) => NotifyOption::SkipPersistHandleEvents,
Ok(()) => NotifyOption::SkipPersistNoEvents,
};
let _ = handle_error!(self, res, counterparty_node_id);
persist
});
}

#[cfg(splicing)]
fn handle_splice_locked(&self, counterparty_node_id: PublicKey, msg: &msgs::SpliceLocked) {
let _: Result<(), _> = handle_error!(self, Err(MsgHandleErrInternal::send_err_msg_no_close(
"Splicing not supported (splice_locked)".to_owned(),
msg.channel_id.clone())), counterparty_node_id);
msg.channel_id)), counterparty_node_id);
}

fn handle_shutdown(&self, counterparty_node_id: PublicKey, msg: &msgs::Shutdown) {
Expand Down
4 changes: 2 additions & 2 deletions lightning/src/ln/dual_funding_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ fn do_test_v2_channel_establishment(
let logger_a = test_utils::TestLogger::with_id("node a".to_owned());

// Create a funding input for the new channel along with its previous transaction.
let initiator_funding_inputs: Vec<_> = create_dual_funding_utxos_with_prev_txs(
let (initiator_funding_inputs, total_weight) = create_dual_funding_utxos_with_prev_txs(
&nodes[0],
&[session.initiator_input_value_satoshis],
)
Expand All @@ -66,7 +66,7 @@ fn do_test_v2_channel_establishment(
let funding_satoshis = calculate_our_funding_satoshis(
true,
&initiator_funding_inputs[..],
Weight::from_wu(P2WPKH_WITNESS_WEIGHT),
total_weight,
funding_feerate,
MIN_CHAN_DUST_LIMIT_SATOSHIS,
)
Expand Down
17 changes: 11 additions & 6 deletions lightning/src/ln/functional_test_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ use crate::util::test_utils;
use crate::util::test_utils::{TestChainMonitor, TestScorer, TestKeysInterface};
use crate::util::ser::{ReadableArgs, Writeable};

use bitcoin::WPubkeyHash;
use bitcoin::{Weight, WPubkeyHash};
use bitcoin::amount::Amount;
use bitcoin::block::{Block, Header, Version as BlockVersion};
use bitcoin::locktime::absolute::{LockTime, LOCK_TIME_THRESHOLD};
Expand All @@ -60,6 +60,7 @@ use core::mem;
use core::ops::Deref;
use crate::io;
use crate::prelude::*;
use crate::sign::P2WPKH_WITNESS_WEIGHT;
use crate::sync::{Arc, Mutex, LockTestExt, RwLock};

pub const CHAN_CONFIRM_DEPTH: u32 = 10;
Expand Down Expand Up @@ -801,7 +802,7 @@ macro_rules! get_event_msg {
assert_eq!(*node_id, $node_id);
(*msg).clone()
},
_ => panic!("Unexpected event"),
_ => panic!("Unexpected event {:?}", events[0]),
}
}
}
Expand Down Expand Up @@ -1243,9 +1244,11 @@ fn internal_create_funding_transaction<'a, 'b, 'c>(node: &Node<'a, 'b, 'c>,
}
}

/// Create test inputs for a funding transaction.
/// Return the inputs (with prev tx), and the total witness weight for these inputs
pub fn create_dual_funding_utxos_with_prev_txs(
node: &Node<'_, '_, '_>, utxo_values_in_satoshis: &[u64],
) -> Vec<(TxIn, Transaction)> {
) -> (Vec<(TxIn, Transaction)>, Weight) {
// Ensure we have unique transactions per node by using the locktime.
let tx = Transaction {
version: TxVersion::TWO,
Expand All @@ -1258,9 +1261,9 @@ pub fn create_dual_funding_utxos_with_prev_txs(
}).collect()
};

let mut result = vec![];
let mut inputs = vec![];
for i in 0..utxo_values_in_satoshis.len() {
result.push(
inputs.push(
(TxIn {
previous_output: OutPoint {
txid: tx.compute_txid(),
Expand All @@ -1271,7 +1274,9 @@ pub fn create_dual_funding_utxos_with_prev_txs(
witness: Witness::new(),
}, tx.clone()));
}
result
let total_weight = Weight::from_wu(utxo_values_in_satoshis.len() as u64 * P2WPKH_WITNESS_WEIGHT);

(inputs, total_weight)
}

pub fn sign_funding_transaction<'a, 'b, 'c>(node_a: &Node<'a, 'b, 'c>, node_b: &Node<'a, 'b, 'c>, channel_value: u64, expected_temporary_channel_id: ChannelId) -> Transaction {
Expand Down
Loading
Loading