Skip to content

Commit ab39d51

Browse files
committed
Improve error handling, persistence; misc review changes
1 parent 2a7f768 commit ab39d51

File tree

4 files changed

+108
-106
lines changed

4 files changed

+108
-106
lines changed

lightning/src/ln/channel.rs

Lines changed: 44 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -8378,74 +8378,66 @@ impl<SP: Deref> FundedChannel<SP> where
83788378
}
83798379
}
83808380

8381-
/// Initiate splicing
8381+
/// Initiate splicing.
8382+
/// - our_funding_inputs: the inputs we contribute to the new funding transaction.
8383+
/// Includes the witness weight for this input (e.g. P2WPKH_WITNESS_WEIGHT=109 for typical P2WPKH inputs).
83828384
#[cfg(splicing)]
83838385
pub fn splice_channel(&mut self, our_funding_contribution_satoshis: i64,
8384-
our_funding_inputs: Vec<(TxIn, Transaction, Weight)>, funding_feerate_perkw: u32, locktime: u32,
8385-
) -> Result<msgs::SpliceInit, ChannelError> {
8386+
_our_funding_inputs: &Vec<(TxIn, Transaction, Weight)>,
8387+
funding_feerate_per_kw: u32, locktime: u32,
8388+
) -> Result<msgs::SpliceInit, APIError> {
83868389
// Check if a splice has been initiated already.
8387-
// Note: this could be handled more nicely, and support multiple outstanding splice's, the incoming splice_ack matters anyways.
8390+
// Note: only a single outstanding splice is supported (per spec)
83888391
if let Some(splice_info) = &self.pending_splice_pre {
8389-
return Err(ChannelError::Warn(format!(
8390-
"Channel has already a splice pending, contribution {}", splice_info.our_funding_contribution
8391-
)));
8392+
return Err(APIError::APIMisuseError { err: format!(
8393+
"Channel {} cannot be spliced, as it has already a splice pending (contribution {})",
8394+
self.context.channel_id(), splice_info.our_funding_contribution
8395+
)});
83928396
}
83938397

8394-
if !matches!(self.context.channel_state, ChannelState::ChannelReady(_)) {
8395-
return Err(ChannelError::Warn(format!("Cannot initiate splicing, as channel is not Ready")));
8398+
if !self.context.is_live() {
8399+
return Err(APIError::APIMisuseError { err: format!(
8400+
"Channel {} cannot be spliced, as channel is not live",
8401+
self.context.channel_id()
8402+
)});
83968403
}
83978404

8398-
let pre_channel_value = self.funding.get_value_satoshis();
8399-
// Sanity check: capacity cannot decrease below 0
8400-
if (pre_channel_value as i64).saturating_add(our_funding_contribution_satoshis) < 0 {
8401-
return Err(ChannelError::Warn(format!(
8402-
"Post-splicing channel value cannot be negative. It was {} + {}",
8403-
pre_channel_value, our_funding_contribution_satoshis
8404-
)));
8405-
}
8405+
// TODO(splicing): check for quiescence
84068406

84078407
if our_funding_contribution_satoshis < 0 {
8408-
return Err(ChannelError::Warn(format!(
8409-
"TODO(splicing): Splice-out not supported, only splice in, contribution {}",
8410-
our_funding_contribution_satoshis,
8411-
)));
8408+
return Err(APIError::APIMisuseError { err: format!(
8409+
"TODO(splicing): Splice-out not supported, only splice in; channel ID {}, contribution {}",
8410+
self.context.channel_id(), our_funding_contribution_satoshis,
8411+
)});
84128412
}
84138413

8414-
// Note: post-splice channel value is not yet known at this point, counterpary contribution is not known
8414+
// TODO(splicing): Once splice-out is supported, check that channel balance does not go below 0
8415+
// (or below channel reserve)
8416+
8417+
// Note: post-splice channel value is not yet known at this point, counterparty contribution is not known
84158418
// (Cannot test for miminum required post-splice channel value)
84168419

8417-
// Check that inputs are sufficient to cover our contribution
8418-
let sum_input: i64 = our_funding_inputs.into_iter().map(
8419-
|(txin, tx, _)| tx.output.get(txin.previous_output.vout as usize).map(|tx| tx.value.to_sat() as i64).unwrap_or(0)
8420-
).sum();
8421-
if sum_input < our_funding_contribution_satoshis {
8422-
return Err(ChannelError::Warn(format!(
8423-
"Provided inputs are insufficient for our contribution, {} {}",
8424-
sum_input, our_funding_contribution_satoshis,
8425-
)));
8426-
}
84278420

84288421
self.pending_splice_pre = Some(PendingSplice {
84298422
our_funding_contribution: our_funding_contribution_satoshis,
84308423
});
84318424

8432-
let msg = self.get_splice_init(our_funding_contribution_satoshis, funding_feerate_perkw, locktime);
8425+
let msg = self.get_splice_init(our_funding_contribution_satoshis, funding_feerate_per_kw, locktime);
84338426
Ok(msg)
84348427
}
84358428

84368429
/// Get the splice message that can be sent during splice initiation.
84378430
#[cfg(splicing)]
84388431
pub fn get_splice_init(&self, our_funding_contribution_satoshis: i64,
8439-
funding_feerate_perkw: u32, locktime: u32,
8432+
funding_feerate_per_kw: u32, locktime: u32,
84408433
) -> msgs::SpliceInit {
8441-
// Reuse the existing funding pubkey, in spite of the channel value changing
8442-
// (though at this point we don't know the new value yet, due tue the optional counterparty contribution)
8434+
// TODO(splicing): The exisiting pubkey is reused, but a new one should be generated. See #3542.
84438435
// Note that channel_keys_id is supposed NOT to change
84448436
let funding_pubkey = self.funding.get_holder_pubkeys().funding_pubkey.clone();
84458437
msgs::SpliceInit {
84468438
channel_id: self.context.channel_id,
84478439
funding_contribution_satoshis: our_funding_contribution_satoshis,
8448-
funding_feerate_perkw,
8440+
funding_feerate_per_kw,
84498441
locktime,
84508442
funding_pubkey,
84518443
require_confirmed_inputs: None,
@@ -8467,20 +8459,11 @@ impl<SP: Deref> FundedChannel<SP> where
84678459
)));
84688460
}
84698461

8470-
if !matches!(self.context.channel_state, ChannelState::ChannelReady(_)) {
8471-
return Err(ChannelError::Warn(format!("Splicing requested on a channel that is not Ready")));
8472-
}
8473-
8474-
let pre_channel_value = self.funding.get_value_satoshis();
8475-
// Sanity check: capacity cannot decrease below 0
8476-
if (pre_channel_value as i64)
8477-
.saturating_add(their_funding_contribution_satoshis)
8478-
.saturating_add(our_funding_contribution_satoshis) < 0
8479-
{
8480-
return Err(ChannelError::Warn(format!(
8481-
"Post-splicing channel value cannot be negative. It was {} + {} + {}",
8482-
pre_channel_value, their_funding_contribution_satoshis, our_funding_contribution_satoshis,
8483-
)));
8462+
// - If it has received shutdown:
8463+
// MUST send a warning and close the connection or send an error
8464+
// and fail the channel.
8465+
if !self.context.is_live() {
8466+
return Err(ChannelError::Warn(format!("Splicing requested on a channel that is not live")));
84848467
}
84858468

84868469
if their_funding_contribution_satoshis.saturating_add(our_funding_contribution_satoshis) < 0 {
@@ -8490,11 +8473,12 @@ impl<SP: Deref> FundedChannel<SP> where
84908473
)));
84918474
}
84928475

8493-
let post_channel_value = PendingSplice::compute_post_value(pre_channel_value, their_funding_contribution_satoshis, our_funding_contribution_satoshis);
8494-
let post_balance = PendingSplice::add_checked(self.funding.value_to_self_msat, our_funding_contribution_satoshis);
8495-
// Early check for reserve requirement, assuming maximum balance of full channel value
8496-
// This will also be checked later at tx_complete
8497-
let _res = self.context.check_balance_meets_reserve_requirements(post_balance, post_channel_value)?;
8476+
// TODO(splicing): Once splice acceptor can contribute, check that inputs are sufficient,
8477+
// similarly to the check in `splice_channel`.
8478+
8479+
// Note on channel reserve requirement pre-check: as the splice acceptor does not contribute,
8480+
// it can't go below reserve, therefore no pre-check is done here.
8481+
// TODO(splicing): Once splice acceptor can contribute, add reserve pre-check, similar to the one in `splice_ack`.
84988482

84998483
// TODO(splicing): Store msg.funding_pubkey
85008484
// TODO(splicing): Apply start of splice (splice_start)
@@ -8507,7 +8491,8 @@ impl<SP: Deref> FundedChannel<SP> where
85078491
/// Get the splice_ack message that can be sent in response to splice initiation.
85088492
#[cfg(splicing)]
85098493
pub fn get_splice_ack(&self, our_funding_contribution_satoshis: i64) -> msgs::SpliceAck {
8510-
// Reuse the existing funding pubkey, in spite of the channel value changing
8494+
// TODO(splicing): The exisiting pubkey is reused, but a new one should be generated. See #3542.
8495+
// Note that channel_keys_id is supposed NOT to change
85118496
let funding_pubkey = self.funding.get_holder_pubkeys().funding_pubkey;
85128497
msgs::SpliceAck {
85138498
channel_id: self.context.channel_id,
@@ -12907,15 +12892,15 @@ mod tests {
1290712892
);
1290812893
}
1290912894

12910-
#[cfg(all(test, splicing))]
12895+
#[cfg(splicing)]
1291112896
fn get_pre_and_post(pre_channel_value: u64, our_funding_contribution: i64, their_funding_contribution: i64) -> (u64, u64) {
1291212897
use crate::ln::channel::PendingSplice;
1291312898

1291412899
let post_channel_value = PendingSplice::compute_post_value(pre_channel_value, our_funding_contribution, their_funding_contribution);
1291512900
(pre_channel_value, post_channel_value)
1291612901
}
1291712902

12918-
#[cfg(all(test, splicing))]
12903+
#[cfg(splicing)]
1291912904
#[test]
1292012905
fn test_splice_compute_post_value() {
1292112906
{

lightning/src/ln/channelmanager.rs

Lines changed: 59 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@ use bitcoin::hash_types::{BlockHash, Txid};
3030

3131
use bitcoin::secp256k1::{SecretKey,PublicKey};
3232
use bitcoin::secp256k1::Secp256k1;
33-
use bitcoin::{secp256k1, Sequence, TxIn};
33+
use bitcoin::{secp256k1, Sequence};
3434
#[cfg(splicing)]
35-
use bitcoin::Weight;
35+
use bitcoin::{TxIn, Weight};
3636

3737
use crate::events::FundingInfo;
3838
use crate::blinded_path::message::{AsyncPaymentsContext, MessageContext, OffersContext};
@@ -4283,36 +4283,33 @@ where
42834283
}
42844284
}
42854285

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

4300-
let peer_state_mutex = per_peer_state.get(counterparty_node_id)
4301-
.ok_or_else(|| APIError::ChannelUnavailable { err: format!("Can't find a peer matching the passed counterparty node_id {}", counterparty_node_id) })?;
4295+
let peer_state_mutex = match per_peer_state.get(counterparty_node_id)
4296+
.ok_or_else(|| APIError::ChannelUnavailable { err: format!("Can't find a peer matching the passed counterparty node_id {}", counterparty_node_id) }) {
4297+
Ok(p) => p,
4298+
Err(e) => return Err(e),
4299+
};
43024300

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

43064304
// Look for the channel
43074305
match peer_state.channel_by_id.entry(*channel_id) {
43084306
hash_map::Entry::Occupied(mut chan_phase_entry) => {
4307+
let locktime = locktime.unwrap_or(self.current_best_block().height);
43094308
if let Some(chan) = chan_phase_entry.get_mut().as_funded_mut() {
4310-
let msg = chan.splice_channel(our_funding_contribution_satoshis, our_funding_inputs, funding_feerate_perkw, locktime)
4311-
.map_err(|err| APIError::APIMisuseError {
4312-
err: format!(
4313-
"Cannot initiate Splicing, {}, channel ID {}", err, channel_id
4314-
)
4315-
})?;
4309+
let msg = match chan.splice_channel(our_funding_contribution_satoshis, our_funding_inputs, funding_feerate_per_kw, locktime) {
4310+
Ok(m) => m,
4311+
Err(e) => return Err(e),
4312+
};
43164313

43174314
peer_state.pending_msg_events.push(events::MessageSendEvent::SendSpliceInit {
43184315
node_id: *counterparty_node_id,
@@ -4330,16 +4327,47 @@ where
43304327
}
43314328
},
43324329
hash_map::Entry::Vacant(_) => {
4333-
return Err(APIError::ChannelUnavailable {
4330+
Err(APIError::ChannelUnavailable {
43344331
err: format!(
43354332
"Channel with id {} not found for the passed counterparty node_id {}",
43364333
channel_id, counterparty_node_id,
43374334
)
4338-
});
4335+
})
43394336
},
43404337
}
43414338
}
43424339

4340+
/// Initiate a splice, to change the channel capacity of an existing funded channel.
4341+
/// After completion of splicing, the funding transaction will be replaced by a new one, spending the old funding transaction,
4342+
/// with optional extra inputs (splice-in) and/or extra outputs (splice-out or change).
4343+
/// TODO(splicing): Implementation is currently incomplete.
4344+
///
4345+
/// Note: Currently only splice-in is supported (increase in channel capacity), splice-out is not.
4346+
///
4347+
/// - our_funding_contribution_satoshis: the amount contributed by us to the channel. This will increase our channel balance.
4348+
/// - our_funding_inputs: the funding inputs provided by us. If our contribution is positive, our funding inputs must cover at least that amount.
4349+
/// Includes the witness weight for this input (e.g. P2WPKH_WITNESS_WEIGHT=109 for typical P2WPKH inputs).
4350+
/// - locktime: Optional locktime for the new funding transaction. If None, set to the current block height.
4351+
#[cfg(splicing)]
4352+
pub fn splice_channel(
4353+
&self, channel_id: &ChannelId, counterparty_node_id: &PublicKey, our_funding_contribution_satoshis: i64,
4354+
our_funding_inputs: Vec<(TxIn, Transaction, Weight)>,
4355+
funding_feerate_per_kw: u32, locktime: Option<u32>,
4356+
) -> Result<(), APIError> {
4357+
let mut res = Ok(());
4358+
PersistenceNotifierGuard::optionally_notify(self, || {
4359+
let result = self.internal_splice_channel(
4360+
channel_id, counterparty_node_id, our_funding_contribution_satoshis, &our_funding_inputs, funding_feerate_per_kw, locktime
4361+
);
4362+
res = result;
4363+
match res {
4364+
Ok(_) => NotifyOption::SkipPersistHandleEvents,
4365+
Err(_) => NotifyOption::SkipPersistNoEvents,
4366+
}
4367+
});
4368+
res
4369+
}
4370+
43434371
fn can_forward_htlc_to_outgoing_channel(
43444372
&self, chan: &mut FundedChannel<SP>, msg: &msgs::UpdateAddHTLC, next_packet: &NextPacketDetails
43454373
) -> Result<(), (&'static str, u16)> {
@@ -9540,17 +9568,11 @@ This indicates a bug inside LDK. Please report this error at https://github.com/
95409568
), msg.channel_id)),
95419569
hash_map::Entry::Occupied(mut chan_entry) => {
95429570
if let Some(chan) = chan_entry.get_mut().as_funded_mut() {
9543-
match chan.splice_init(msg) {
9544-
Ok(splice_ack_msg) => {
9545-
peer_state.pending_msg_events.push(events::MessageSendEvent::SendSpliceAck {
9546-
node_id: *counterparty_node_id,
9547-
msg: splice_ack_msg,
9548-
});
9549-
},
9550-
Err(err) => {
9551-
return Err(MsgHandleErrInternal::from_chan_no_close(err, msg.channel_id));
9552-
}
9553-
}
9571+
let splice_ack_msg = try_channel_entry!(self, peer_state, chan.splice_init(msg), chan_entry);
9572+
peer_state.pending_msg_events.push(events::MessageSendEvent::SendSpliceAck {
9573+
node_id: *counterparty_node_id,
9574+
msg: splice_ack_msg,
9575+
});
95549576
} else {
95559577
return Err(MsgHandleErrInternal::send_err_msg_no_close("Channel is not funded, cannot be spliced".to_owned(), msg.channel_id));
95569578
}
@@ -9583,14 +9605,9 @@ This indicates a bug inside LDK. Please report this error at https://github.com/
95839605
"Got a message for a channel from the wrong node! No such channel for the passed counterparty_node_id {}",
95849606
counterparty_node_id
95859607
), msg.channel_id)),
9586-
hash_map::Entry::Occupied(mut chan) => {
9587-
if let Some(chan) = chan.get_mut().as_funded_mut() {
9588-
match chan.splice_ack(msg) {
9589-
Ok(_) => {}
9590-
Err(err) => {
9591-
return Err(MsgHandleErrInternal::from_chan_no_close(err, msg.channel_id));
9592-
}
9593-
}
9608+
hash_map::Entry::Occupied(mut chan_entry) => {
9609+
if let Some(chan) = chan_entry.get_mut().as_funded_mut() {
9610+
try_channel_entry!(self, peer_state, chan.splice_ack(msg), chan_entry);
95949611
} else {
95959612
return Err(MsgHandleErrInternal::send_err_msg_no_close("Channel is not funded, cannot splice".to_owned(), msg.channel_id));
95969613
}
@@ -11887,7 +11904,7 @@ where
1188711904
let persist = match &res {
1188811905
Err(e) if e.closes_channel() => NotifyOption::DoPersist,
1188911906
Err(_) => NotifyOption::SkipPersistHandleEvents,
11890-
Ok(()) => NotifyOption::SkipPersistNoEvents,
11907+
Ok(()) => NotifyOption::SkipPersistHandleEvents,
1189111908
};
1189211909
let _ = handle_error!(self, res, counterparty_node_id);
1189311910
persist
@@ -11901,7 +11918,7 @@ where
1190111918
let persist = match &res {
1190211919
Err(e) if e.closes_channel() => NotifyOption::DoPersist,
1190311920
Err(_) => NotifyOption::SkipPersistHandleEvents,
11904-
Ok(()) => NotifyOption::SkipPersistNoEvents,
11921+
Ok(()) => NotifyOption::SkipPersistHandleEvents,
1190511922
};
1190611923
let _ = handle_error!(self, res, counterparty_node_id);
1190711924
persist

lightning/src/ln/functional_tests_splice.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ fn test_v1_splice_in() {
7272
splice_in_sats as i64,
7373
funding_inputs,
7474
funding_feerate_per_kw,
75-
0, // locktime
75+
None, // locktime
7676
)
7777
.unwrap();
7878
// Extract the splice message from node0 to node1
@@ -82,7 +82,7 @@ fn test_v1_splice_in() {
8282
acceptor_node.node.get_our_node_id()
8383
);
8484
assert_eq!(splice_init_msg.funding_contribution_satoshis, splice_in_sats as i64);
85-
assert_eq!(splice_init_msg.funding_feerate_perkw, funding_feerate_per_kw);
85+
assert_eq!(splice_init_msg.funding_feerate_per_kw, funding_feerate_per_kw);
8686
assert_eq!(splice_init_msg.funding_pubkey.to_string(), expected_initiator_funding_key);
8787
assert!(splice_init_msg.require_confirmed_inputs.is_none());
8888

lightning/src/ln/msgs.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -470,7 +470,7 @@ pub struct SpliceInit {
470470
/// or remove from its channel balance (splice-out).
471471
pub funding_contribution_satoshis: i64,
472472
/// The feerate for the new funding transaction, set by the splice initiator
473-
pub funding_feerate_perkw: u32,
473+
pub funding_feerate_per_kw: u32,
474474
/// The locktime for the new funding transaction
475475
pub locktime: u32,
476476
/// The key of the sender (splice initiator) controlling the new funding transaction
@@ -2239,7 +2239,7 @@ impl_writeable_msg!(Stfu, {
22392239
impl_writeable_msg!(SpliceInit, {
22402240
channel_id,
22412241
funding_contribution_satoshis,
2242-
funding_feerate_perkw,
2242+
funding_feerate_per_kw,
22432243
locktime,
22442244
funding_pubkey,
22452245
}, {
@@ -4099,7 +4099,7 @@ mod tests {
40994099
let splice_init = msgs::SpliceInit {
41004100
channel_id: ChannelId::from_bytes([2; 32]),
41014101
funding_contribution_satoshis: -123456,
4102-
funding_feerate_perkw: 2000,
4102+
funding_feerate_per_kw: 2000,
41034103
locktime: 0,
41044104
funding_pubkey: pubkey_1,
41054105
require_confirmed_inputs: Some(()),

0 commit comments

Comments
 (0)