Skip to content

Commit 009ba76

Browse files
committed
Improve error handling, persistence; misc review changes
1 parent fad5dc9 commit 009ba76

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
@@ -8377,74 +8377,66 @@ impl<SP: Deref> FundedChannel<SP> where
83778377
}
83788378
}
83798379

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

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

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

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

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

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

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

8431-
let msg = self.get_splice_init(our_funding_contribution_satoshis, funding_feerate_perkw, locktime);
8424+
let msg = self.get_splice_init(our_funding_contribution_satoshis, funding_feerate_per_kw, locktime);
84328425
Ok(msg)
84338426
}
84348427

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

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

84858468
if their_funding_contribution_satoshis.saturating_add(our_funding_contribution_satoshis) < 0 {
@@ -8489,11 +8472,12 @@ impl<SP: Deref> FundedChannel<SP> where
84898472
)));
84908473
}
84918474

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

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

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

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

12917-
#[cfg(all(test, splicing))]
12902+
#[cfg(splicing)]
1291812903
#[test]
1291912904
fn test_splice_compute_post_value() {
1292012905
{

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};
@@ -4280,36 +4280,33 @@ where
42804280
}
42814281
}
42824282

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

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

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

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

43144311
peer_state.pending_msg_events.push(events::MessageSendEvent::SendSpliceInit {
43154312
node_id: *counterparty_node_id,
@@ -4327,16 +4324,47 @@ where
43274324
}
43284325
},
43294326
hash_map::Entry::Vacant(_) => {
4330-
return Err(APIError::ChannelUnavailable {
4327+
Err(APIError::ChannelUnavailable {
43314328
err: format!(
43324329
"Channel with id {} not found for the passed counterparty node_id {}",
43334330
channel_id, counterparty_node_id,
43344331
)
4335-
});
4332+
})
43364333
},
43374334
}
43384335
}
43394336

4337+
/// Initiate a splice, to change the channel capacity of an existing funded channel.
4338+
/// After completion of splicing, the funding transaction will be replaced by a new one, spending the old funding transaction,
4339+
/// with optional extra inputs (splice-in) and/or extra outputs (splice-out or change).
4340+
/// TODO(splicing): Implementation is currently incomplete.
4341+
///
4342+
/// Note: Currently only splice-in is supported (increase in channel capacity), splice-out is not.
4343+
///
4344+
/// - our_funding_contribution_satoshis: the amount contributed by us to the channel. This will increase our channel balance.
4345+
/// - our_funding_inputs: the funding inputs provided by us. If our contribution is positive, our funding inputs must cover at least that amount.
4346+
/// Includes the witness weight for this input (e.g. P2WPKH_WITNESS_WEIGHT=109 for typical P2WPKH inputs).
4347+
/// - locktime: Optional locktime for the new funding transaction. If None, set to the current block height.
4348+
#[cfg(splicing)]
4349+
pub fn splice_channel(
4350+
&self, channel_id: &ChannelId, counterparty_node_id: &PublicKey, our_funding_contribution_satoshis: i64,
4351+
our_funding_inputs: Vec<(TxIn, Transaction, Weight)>,
4352+
funding_feerate_per_kw: u32, locktime: Option<u32>,
4353+
) -> Result<(), APIError> {
4354+
let mut res = Ok(());
4355+
PersistenceNotifierGuard::optionally_notify(self, || {
4356+
let result = self.internal_splice_channel(
4357+
channel_id, counterparty_node_id, our_funding_contribution_satoshis, &our_funding_inputs, funding_feerate_per_kw, locktime
4358+
);
4359+
res = result;
4360+
match res {
4361+
Ok(_) => NotifyOption::SkipPersistHandleEvents,
4362+
Err(_) => NotifyOption::SkipPersistNoEvents,
4363+
}
4364+
});
4365+
res
4366+
}
4367+
43404368
fn can_forward_htlc_to_outgoing_channel(
43414369
&self, chan: &mut FundedChannel<SP>, msg: &msgs::UpdateAddHTLC, next_packet: &NextPacketDetails
43424370
) -> Result<(), (&'static str, u16)> {
@@ -9536,17 +9564,11 @@ This indicates a bug inside LDK. Please report this error at https://github.com/
95369564
), msg.channel_id)),
95379565
hash_map::Entry::Occupied(mut chan_entry) => {
95389566
if let Some(chan) = chan_entry.get_mut().as_funded_mut() {
9539-
match chan.splice_init(msg) {
9540-
Ok(splice_ack_msg) => {
9541-
peer_state.pending_msg_events.push(events::MessageSendEvent::SendSpliceAck {
9542-
node_id: *counterparty_node_id,
9543-
msg: splice_ack_msg,
9544-
});
9545-
},
9546-
Err(err) => {
9547-
return Err(MsgHandleErrInternal::from_chan_no_close(err, msg.channel_id));
9548-
}
9549-
}
9567+
let splice_ack_msg = try_channel_entry!(self, peer_state, chan.splice_init(msg), chan_entry);
9568+
peer_state.pending_msg_events.push(events::MessageSendEvent::SendSpliceAck {
9569+
node_id: *counterparty_node_id,
9570+
msg: splice_ack_msg,
9571+
});
95509572
} else {
95519573
return Err(MsgHandleErrInternal::send_err_msg_no_close("Channel is not funded, cannot be spliced".to_owned(), msg.channel_id));
95529574
}
@@ -9579,14 +9601,9 @@ This indicates a bug inside LDK. Please report this error at https://github.com/
95799601
"Got a message for a channel from the wrong node! No such channel for the passed counterparty_node_id {}",
95809602
counterparty_node_id
95819603
), msg.channel_id)),
9582-
hash_map::Entry::Occupied(mut chan) => {
9583-
if let Some(chan) = chan.get_mut().as_funded_mut() {
9584-
match chan.splice_ack(msg) {
9585-
Ok(_) => {}
9586-
Err(err) => {
9587-
return Err(MsgHandleErrInternal::from_chan_no_close(err, msg.channel_id));
9588-
}
9589-
}
9604+
hash_map::Entry::Occupied(mut chan_entry) => {
9605+
if let Some(chan) = chan_entry.get_mut().as_funded_mut() {
9606+
try_channel_entry!(self, peer_state, chan.splice_ack(msg), chan_entry);
95909607
} else {
95919608
return Err(MsgHandleErrInternal::send_err_msg_no_close("Channel is not funded, cannot splice".to_owned(), msg.channel_id));
95929609
}
@@ -11883,7 +11900,7 @@ where
1188311900
let persist = match &res {
1188411901
Err(e) if e.closes_channel() => NotifyOption::DoPersist,
1188511902
Err(_) => NotifyOption::SkipPersistHandleEvents,
11886-
Ok(()) => NotifyOption::SkipPersistNoEvents,
11903+
Ok(()) => NotifyOption::SkipPersistHandleEvents,
1188711904
};
1188811905
let _ = handle_error!(self, res, counterparty_node_id);
1188911906
persist
@@ -11897,7 +11914,7 @@ where
1189711914
let persist = match &res {
1189811915
Err(e) if e.closes_channel() => NotifyOption::DoPersist,
1189911916
Err(_) => NotifyOption::SkipPersistHandleEvents,
11900-
Ok(()) => NotifyOption::SkipPersistNoEvents,
11917+
Ok(()) => NotifyOption::SkipPersistHandleEvents,
1190111918
};
1190211919
let _ = handle_error!(self, res, counterparty_node_id);
1190311920
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)