Skip to content

Commit 4bc0e46

Browse files
jkczyzclaude
andcommitted
Emit SplicePending event when splice funding is negotiated
Once a splice has been negotiated and its funding transaction has been broadcast, emit a SplicePending event. Once this occurs, the inputs contributed to the splice cannot be reused except by an RBF attempt. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
1 parent 06dc0d8 commit 4bc0e46

File tree

4 files changed

+114
-27
lines changed

4 files changed

+114
-27
lines changed

lightning/src/ln/channel.rs

Lines changed: 47 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6718,6 +6718,15 @@ type BestBlockUpdatedRes = (
67186718
Option<msgs::AnnouncementSignatures>,
67196719
);
67206720

6721+
/// Information about a splice funding negotiation that has been completed.
6722+
pub struct SpliceFundingNegotiated {
6723+
/// The outpoint of the channel's splice funding transaction.
6724+
pub funding_txo: bitcoin::OutPoint,
6725+
6726+
/// The features that this channel will operate with.
6727+
pub channel_type: ChannelTypeFeatures,
6728+
}
6729+
67216730
pub struct SpliceFundingPromotion {
67226731
pub funding_txo: OutPoint,
67236732
pub monitor_update: Option<ChannelMonitorUpdate>,
@@ -8641,30 +8650,49 @@ where
86418650
}
86428651
}
86438652

8644-
fn on_tx_signatures_exchange(&mut self, funding_tx: Transaction) {
8653+
fn on_tx_signatures_exchange(
8654+
&mut self, funding_tx: Transaction,
8655+
) -> Option<SpliceFundingNegotiated> {
86458656
debug_assert!(!self.context.channel_state.is_monitor_update_in_progress());
86468657
debug_assert!(!self.context.channel_state.is_awaiting_remote_revoke());
86478658

86488659
if let Some(pending_splice) = self.pending_splice.as_mut() {
8660+
self.context.channel_state.clear_quiescent();
86498661
if let Some(FundingNegotiation::AwaitingSignatures { mut funding }) =
86508662
pending_splice.funding_negotiation.take()
86518663
{
86528664
funding.funding_transaction = Some(funding_tx);
8665+
8666+
let funding_txo =
8667+
funding.get_funding_txo().expect("funding outpoint should be set");
8668+
let channel_type = funding.get_channel_type().clone();
8669+
86538670
pending_splice.negotiated_candidates.push(funding);
8671+
8672+
let splice_negotiated = SpliceFundingNegotiated {
8673+
funding_txo: funding_txo.into_bitcoin_outpoint(),
8674+
channel_type,
8675+
};
8676+
8677+
Some(splice_negotiated)
86548678
} else {
86558679
debug_assert!(false);
8680+
None
86568681
}
8657-
self.context.channel_state.clear_quiescent();
86588682
} else {
86598683
self.funding.funding_transaction = Some(funding_tx);
86608684
self.context.channel_state =
86618685
ChannelState::AwaitingChannelReady(AwaitingChannelReadyFlags::new());
8686+
None
86628687
}
86638688
}
86648689

86658690
pub fn funding_transaction_signed(
86668691
&mut self, funding_txid_signed: Txid, witnesses: Vec<Witness>,
8667-
) -> Result<(Option<msgs::TxSignatures>, Option<Transaction>), APIError> {
8692+
) -> Result<
8693+
(Option<msgs::TxSignatures>, Option<Transaction>, Option<SpliceFundingNegotiated>),
8694+
APIError,
8695+
> {
86688696
let signing_session =
86698697
if let Some(signing_session) = self.context.interactive_tx_signing_session.as_mut() {
86708698
if let Some(pending_splice) = self.pending_splice.as_ref() {
@@ -8720,17 +8748,22 @@ where
87208748
.provide_holder_witnesses(tx_signatures, &self.context.secp_ctx)
87218749
.map_err(|err| APIError::APIMisuseError { err })?;
87228750

8723-
if let Some(funding_tx) = funding_tx_opt.clone() {
8751+
let splice_negotiated_opt = if let Some(funding_tx) = funding_tx_opt.clone() {
87248752
debug_assert!(tx_signatures_opt.is_some());
8725-
self.on_tx_signatures_exchange(funding_tx);
8726-
}
8753+
self.on_tx_signatures_exchange(funding_tx)
8754+
} else {
8755+
None
8756+
};
87278757

8728-
Ok((tx_signatures_opt, funding_tx_opt))
8758+
Ok((tx_signatures_opt, funding_tx_opt, splice_negotiated_opt))
87298759
}
87308760

87318761
pub fn tx_signatures(
87328762
&mut self, msg: &msgs::TxSignatures,
8733-
) -> Result<(Option<msgs::TxSignatures>, Option<Transaction>), ChannelError> {
8763+
) -> Result<
8764+
(Option<msgs::TxSignatures>, Option<Transaction>, Option<SpliceFundingNegotiated>),
8765+
ChannelError,
8766+
> {
87348767
let signing_session = if let Some(signing_session) =
87358768
self.context.interactive_tx_signing_session.as_mut()
87368769
{
@@ -8776,11 +8809,13 @@ where
87768809
let (holder_tx_signatures_opt, funding_tx_opt) =
87778810
signing_session.received_tx_signatures(msg).map_err(|msg| ChannelError::Warn(msg))?;
87788811

8779-
if let Some(funding_tx) = funding_tx_opt.clone() {
8780-
self.on_tx_signatures_exchange(funding_tx);
8781-
}
8812+
let splice_negotiated_opt = if let Some(funding_tx) = funding_tx_opt.clone() {
8813+
self.on_tx_signatures_exchange(funding_tx)
8814+
} else {
8815+
None
8816+
};
87828817

8783-
Ok((holder_tx_signatures_opt, funding_tx_opt))
8818+
Ok((holder_tx_signatures_opt, funding_tx_opt, splice_negotiated_opt))
87848819
}
87858820

87868821
/// Queues up an outbound update fee by placing it in the holding cell. You should call

lightning/src/ln/channelmanager.rs

Lines changed: 48 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6296,10 +6296,22 @@ where
62966296
.filter(|witness| !witness.is_empty())
62976297
.collect();
62986298
match chan.funding_transaction_signed(txid, witnesses) {
6299-
Ok((Some(tx_signatures), funding_tx_opt)) => {
6299+
Ok((Some(tx_signatures), funding_tx_opt, splice_negotiated_opt)) => {
63006300
if let Some(funding_tx) = funding_tx_opt {
63016301
self.broadcast_interactive_funding(chan, &funding_tx);
63026302
}
6303+
if let Some(splice_negotiated) = splice_negotiated_opt {
6304+
self.pending_events.lock().unwrap().push_back((
6305+
events::Event::SplicePending {
6306+
channel_id: *channel_id,
6307+
counterparty_node_id: *counterparty_node_id,
6308+
user_channel_id: chan.context.get_user_id(),
6309+
funding_txo: splice_negotiated.funding_txo,
6310+
channel_type: splice_negotiated.channel_type,
6311+
},
6312+
None,
6313+
));
6314+
}
63036315
peer_state.pending_msg_events.push(
63046316
MessageSendEvent::SendTxSignatures {
63056317
node_id: *counterparty_node_id,
@@ -6312,7 +6324,9 @@ where
63126324
result = Err(err);
63136325
return NotifyOption::SkipPersistNoEvents;
63146326
},
6315-
_ => {
6327+
Ok((None, funding_tx_opt, splice_negotiated_opt)) => {
6328+
debug_assert!(funding_tx_opt.is_none());
6329+
debug_assert!(splice_negotiated_opt.is_none());
63166330
return NotifyOption::SkipPersistNoEvents;
63176331
},
63186332
}
@@ -9410,18 +9424,32 @@ This indicates a bug inside LDK. Please report this error at https://github.com/
94109424
} else {
94119425
let txid = signing_session.unsigned_tx().compute_txid();
94129426
match channel.funding_transaction_signed(txid, vec![]) {
9413-
Ok((Some(tx_signatures), funding_tx_opt)) => {
9427+
Ok((Some(tx_signatures), funding_tx_opt, splice_negotiated_opt)) => {
94149428
if let Some(funding_tx) = funding_tx_opt {
94159429
self.broadcast_interactive_funding(channel, &funding_tx);
94169430
}
9431+
9432+
if let Some(splice_negotiated) = splice_negotiated_opt {
9433+
self.pending_events.lock().unwrap().push_back((
9434+
events::Event::SplicePending {
9435+
channel_id: channel.context.channel_id(),
9436+
counterparty_node_id,
9437+
user_channel_id: channel.context.get_user_id(),
9438+
funding_txo: splice_negotiated.funding_txo,
9439+
channel_type: splice_negotiated.channel_type,
9440+
},
9441+
None,
9442+
));
9443+
}
9444+
94179445
if channel.context.is_connected() {
94189446
pending_msg_events.push(MessageSendEvent::SendTxSignatures {
94199447
node_id: counterparty_node_id,
94209448
msg: tx_signatures,
94219449
});
94229450
}
94239451
},
9424-
Ok((None, _)) => {
9452+
Ok((None, _, _)) => {
94259453
debug_assert!(false, "If our tx_signatures is empty, then we should send it first!");
94269454
},
94279455
Err(err) => {
@@ -10370,7 +10398,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/
1037010398
hash_map::Entry::Occupied(mut chan_entry) => {
1037110399
match chan_entry.get_mut().as_funded_mut() {
1037210400
Some(chan) => {
10373-
let (tx_signatures_opt, funding_tx_opt) = try_channel_entry!(self, peer_state, chan.tx_signatures(msg), chan_entry);
10401+
let (tx_signatures_opt, funding_tx_opt, splice_negotiated_opt) = try_channel_entry!(self, peer_state, chan.tx_signatures(msg), chan_entry);
1037410402
if let Some(tx_signatures) = tx_signatures_opt {
1037510403
peer_state.pending_msg_events.push(MessageSendEvent::SendTxSignatures {
1037610404
node_id: *counterparty_node_id,
@@ -10384,6 +10412,18 @@ This indicates a bug inside LDK. Please report this error at https://github.com/
1038410412
emit_channel_pending_event!(pending_events, chan);
1038510413
}
1038610414
}
10415+
if let Some(splice_negotiated) = splice_negotiated_opt {
10416+
self.pending_events.lock().unwrap().push_back((
10417+
events::Event::SplicePending {
10418+
channel_id: msg.channel_id,
10419+
counterparty_node_id: *counterparty_node_id,
10420+
user_channel_id: chan.context.get_user_id(),
10421+
funding_txo: splice_negotiated.funding_txo,
10422+
channel_type: splice_negotiated.channel_type,
10423+
},
10424+
None,
10425+
));
10426+
}
1038710427
},
1038810428
None => {
1038910429
let msg = "Got an unexpected tx_signatures message";
@@ -14559,16 +14599,9 @@ where
1455914599
fn handle_channel_reestablish(
1456014600
&self, counterparty_node_id: PublicKey, msg: &msgs::ChannelReestablish,
1456114601
) {
14562-
let _persistence_guard = PersistenceNotifierGuard::optionally_notify(self, || {
14563-
let res = self.internal_channel_reestablish(&counterparty_node_id, msg);
14564-
let persist = match &res {
14565-
Err(e) if e.closes_channel() => NotifyOption::DoPersist,
14566-
Err(_) => NotifyOption::SkipPersistHandleEvents,
14567-
Ok(persist) => *persist,
14568-
};
14569-
let _ = handle_error!(self, res, counterparty_node_id);
14570-
persist
14571-
});
14602+
let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self);
14603+
let res = self.internal_channel_reestablish(&counterparty_node_id, msg);
14604+
let _ = handle_error!(self, res, counterparty_node_id);
1457214605
}
1457314606

1457414607
#[rustfmt::skip]

lightning/src/ln/functional_test_utils.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3067,6 +3067,21 @@ pub fn expect_channel_ready_event<'a, 'b, 'c, 'd>(
30673067
}
30683068
}
30693069

3070+
#[cfg(any(test, ldk_bench, feature = "_test_utils"))]
3071+
pub fn expect_splice_pending_event<'a, 'b, 'c, 'd>(
3072+
node: &'a Node<'b, 'c, 'd>, expected_counterparty_node_id: &PublicKey,
3073+
) -> ChannelId {
3074+
let events = node.node.get_and_clear_pending_events();
3075+
assert_eq!(events.len(), 1);
3076+
match &events[0] {
3077+
crate::events::Event::SplicePending { channel_id, counterparty_node_id, .. } => {
3078+
assert_eq!(*expected_counterparty_node_id, *counterparty_node_id);
3079+
*channel_id
3080+
},
3081+
_ => panic!("Unexpected event"),
3082+
}
3083+
}
3084+
30703085
pub fn expect_probe_successful_events(
30713086
node: &Node, mut probe_results: Vec<(PaymentHash, PaymentId)>,
30723087
) {

lightning/src/ln/splicing_tests.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,10 @@ fn splice_channel<'a, 'b, 'c, 'd>(
248248
assert_eq!(initiator_txn, acceptor_txn);
249249
initiator_txn.remove(0)
250250
};
251+
252+
expect_splice_pending_event(initiator, &node_id_acceptor);
253+
expect_splice_pending_event(acceptor, &node_id_initiator);
254+
251255
splice_tx
252256
}
253257

0 commit comments

Comments
 (0)