Skip to content

Conversation

jkczyz
Copy link
Contributor

@jkczyz jkczyz commented Jun 24, 2025

The splicing spec extends the channel_reestablish message with two more TLVs indicating which funding txid the sender has sent/received either explicitly via splice_locked or implicitly via channel_ready. This allows peers to detect if a splice_locked was lost during disconnection and must be retransmitted.

To this end, the spec updates the channel_reestablish logic to support splicing.

@ldk-reviews-bot
Copy link

ldk-reviews-bot commented Jun 24, 2025

👋 Thanks for assigning @TheBlueMatt as a reviewer!
I'll wait for their review and will help manage the review process.
Once they submit their review, I'll check if a second reviewer would be helpful.

@ldk-reviews-bot
Copy link

🔔 1st Reminder

Hey @wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@jkczyz jkczyz assigned jkczyz and unassigned jkczyz Jun 26, 2025
@jkczyz jkczyz force-pushed the 2025-06-channel-reestablish branch 2 times, most recently from 06c0dfc to f000b76 Compare June 27, 2025 17:04
@jkczyz jkczyz marked this pull request as ready for review June 27, 2025 17:12
@jkczyz
Copy link
Contributor Author

jkczyz commented Jun 27, 2025

@wpaulino Ready for review now.

Copy link

codecov bot commented Jun 27, 2025

Codecov Report

❌ Patch coverage is 87.98077% with 25 lines in your changes missing coverage. Please review.
✅ Project coverage is 88.76%. Comparing base (96f9242) to head (7c948e4).
⚠️ Report is 66 commits behind head on main.

Files with missing lines Patch % Lines
lightning/src/ln/channel.rs 92.00% 10 Missing and 2 partials ⚠️
lightning/src/ln/msgs.rs 76.00% 12 Missing ⚠️
lightning/src/ln/channelmanager.rs 83.33% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3886      +/-   ##
==========================================
+ Coverage   88.61%   88.76%   +0.15%     
==========================================
  Files         174      176       +2     
  Lines      127640   128622     +982     
  Branches   127640   128622     +982     
==========================================
+ Hits       113113   114177    +1064     
+ Misses      12046    11860     -186     
- Partials     2481     2585     +104     
Flag Coverage Δ
fuzzing 21.84% <38.09%> (?)
tests 88.60% <87.98%> (-0.02%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@ldk-reviews-bot
Copy link

🔔 2nd Reminder

Hey @wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot
Copy link

🔔 3rd Reminder

Hey @wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot
Copy link

🔔 4th Reminder

Hey @wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@jkczyz jkczyz self-assigned this Jul 3, 2025
@jkczyz jkczyz moved this to Goal: Merge in Weekly Goals Jul 3, 2025
@ldk-reviews-bot
Copy link

🔔 5th Reminder

Hey @wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot
Copy link

🔔 6th Reminder

Hey @wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@jkczyz jkczyz force-pushed the 2025-06-channel-reestablish branch from f000b76 to e2ea3bf Compare July 7, 2025 21:02
@jkczyz
Copy link
Contributor Author

jkczyz commented Jul 7, 2025

Rebased.

Copy link
Contributor

@wpaulino wpaulino left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We may need to double check our incoming channel_ready handling to make sure we can handle receiving one long after channel_ready was already exchanged due to the new logic surrounding your_last_funding_locked_txid.

@ldk-reviews-bot
Copy link

👋 The first review has been submitted!

Do you think this PR is ready for a second reviewer? If so, click here to assign a second reviewer.

@jkczyz jkczyz force-pushed the 2025-06-channel-reestablish branch from e2ea3bf to 69be701 Compare July 9, 2025 00:17
Copy link
Contributor Author

@jkczyz jkczyz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We may need to double check our incoming channel_ready handling to make sure we can handle receiving one long after channel_ready was already exchanged due to the new logic surrounding your_last_funding_locked_txid.

ACK. Also need to address https://github.com/lightningdevkit/rust-lightning/pull/3736/files#r2133028859.

@jkczyz jkczyz force-pushed the 2025-06-channel-reestablish branch 2 times, most recently from f7b9785 to b0291f4 Compare July 9, 2025 21:49
@jkczyz
Copy link
Contributor Author

jkczyz commented Jul 9, 2025

Also need to address https://github.com/lightningdevkit/rust-lightning/pull/3736/files#r2133028859.

Latest push adds some commits to address this.

@jkczyz jkczyz requested a review from TheBlueMatt July 9, 2025 22:20
@jkczyz
Copy link
Contributor Author

jkczyz commented Jul 9, 2025

We may need to double check our incoming channel_ready handling to make sure we can handle receiving one long after channel_ready was already exchanged due to the new logic surrounding your_last_funding_locked_txid.

Looks like we have some logic below. I think the catch-all case still holds? We should only receive a channel_ready if we didn't set your_last_funding_locked_txid.

// If we reconnected before sending our `channel_ready` they may still resend theirs.
ChannelState::ChannelReady(_) => check_reconnection = true,
_ => return Err(ChannelError::close("Peer sent a channel_ready at a strange time".to_owned())),
}
if check_reconnection {
// They probably disconnected/reconnected and re-sent the channel_ready, which is
// required, or they're sending a fresh SCID alias.
let expected_point =
if self.context.cur_counterparty_commitment_transaction_number == INITIAL_COMMITMENT_NUMBER - 1 {
// If they haven't ever sent an updated point, the point they send should match
// the current one.
self.context.counterparty_cur_commitment_point
} else if self.context.cur_counterparty_commitment_transaction_number == INITIAL_COMMITMENT_NUMBER - 2 {
// If we've advanced the commitment number once, the second commitment point is
// at `counterparty_prev_commitment_point`, which is not yet revoked.
debug_assert!(self.context.counterparty_prev_commitment_point.is_some());
self.context.counterparty_prev_commitment_point
} else {
// If they have sent updated points, channel_ready is always supposed to match
// their "first" point, which we re-derive here.
Some(PublicKey::from_secret_key(&self.context.secp_ctx, &SecretKey::from_slice(
&self.context.commitment_secrets.get_secret(INITIAL_COMMITMENT_NUMBER - 1).expect("We should have all prev secrets available")
).expect("We already advanced, so previous secret keys should have been validated already")))
};
if expected_point != Some(msg.next_per_commitment_point) {
return Err(ChannelError::close("Peer sent a reconnect channel_ready with a different point".to_owned()));
}
return Ok(None);
}

@jkczyz jkczyz force-pushed the 2025-06-channel-reestablish branch from 08065aa to 2f88511 Compare August 12, 2025 17:38
@jkczyz
Copy link
Contributor Author

jkczyz commented Aug 12, 2025

Squashed fixups for now. Working on updating this for the most recent spec changes.

@jkczyz jkczyz force-pushed the 2025-06-channel-reestablish branch 2 times, most recently from 4e76094 to f5c9271 Compare August 19, 2025 17:05
@jkczyz jkczyz requested review from TheBlueMatt and wpaulino August 19, 2025 17:07
@jkczyz
Copy link
Contributor Author

jkczyz commented Aug 19, 2025

@wpaulino @TheBlueMatt Let me know if you want me to squash and get rid of the commits that are reverted now that the spec has been simplified.

@TheBlueMatt
Copy link
Collaborator

Feel free to squash, simplify, and rebase IMO

jkczyz and others added 4 commits August 19, 2025 14:47
While splicing is not yet fully supported, checking if the feature has
been negotiated is needed for changes to the channel_reestablish logic.
The splicing spec extends the channel_reestablish message with two more
TLVs indicating which funding txid the sender has sent/received either
explicitly via splice_locked or implicitly via channel_ready. This
allows peers to detect if a splice_locked was lost during disconnection
and must be retransmitted. This commit updates channel_reestablish with
the TLVs. Subsequent commits will implement the spec requirements.
The previous commit extended the channel_reestablish message with
your_last_funding_locked_txid and my_current_funding_locked_txid for use
as described there. This commit sets those fields to the funding txid
most recently sent/received accordingly.
The splicing spec updates the logic pertaining to next_funding_txid when
handling a channel_reestablish message. Specifically:

A receiving node:
  - if `next_funding_txid` is set:
    - if `next_funding_txid` matches the latest interactive funding transaction
      or the current channel funding transaction:
      - if `next_commitment_number` is equal to the commitment number of the
        `commitment_signed` message it sent for this funding transaction:
        - MUST retransmit its `commitment_signed` for that funding transaction.
      - if it has already received `commitment_signed` and it should sign first,
        as specified in the [`tx_signatures` requirements](#the-tx_signatures-message):
        - MUST send its `tx_signatures` for that funding transaction.
      - if it has already received `tx_signatures` for that funding transaction:
        - MUST send its `tx_signatures` for that funding transaction.
    - if it also sets `next_funding_txid` in its own `channel_reestablish`, but the
      values don't match:
      - MUST send an `error` and fail the channel.
    - otherwise:
      - MUST send `tx_abort` to let the sending node know that they can forget
        this funding transaction.

This commit updates FundedChannel::channel_reestablish accordingly.

Co-authored-by: Wilmer Paulino <[email protected]>
Co-authored-by: Jeffrey Czyz <[email protected]>
@jkczyz jkczyz force-pushed the 2025-06-channel-reestablish branch from f5c9271 to 719ade4 Compare August 19, 2025 20:37
@jkczyz
Copy link
Contributor Author

jkczyz commented Aug 19, 2025

Feel free to squash, simplify, and rebase IMO

Done.

@@ -1215,6 +1215,7 @@ pub(super) struct ReestablishResponses {
pub shutdown_msg: Option<msgs::Shutdown>,
pub tx_signatures: Option<msgs::TxSignatures>,
pub tx_abort: Option<msgs::TxAbort>,
pub implicit_splice_locked: Option<msgs::SpliceLocked>,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bleh, let's please note rely on ChannelManager to pipe channel.rs logic around in a loop. We need to be exposing Channel-internal to ChannelManager less, not more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May just need to rename this as there is some update logic that needs to be executed in ChannelManager::internal_splice_locked. Will re-visit to see if there's a better alternative.

} else {
session.holder_tx_signatures().clone()
}
if !session.has_received_commitment_signed() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this unreachable per the comment in maybe_get_next_funding_txid - "Since we have a signing_session, this implies we've sent an initial commitment_signed"? Or do we switch to interactive signing once we send our CS, before our peer sends their CS (and should we?).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or do we switch to interactive signing once we send our CS, before our peer sends their CS (and should we?).

Based on offline discussion with @wpaulino this statement is true.

// - MUST retransmit its `commitment_signed` for that funding transaction.
if msg.next_local_commitment_number == next_counterparty_commitment_number {
// `next_counterparty_commitment_number` is guaranteed to always be the
// commitment number of the `commitment_signed` message we sent for this
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems wrong re: #4014.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@wpaulino said he will take a look when reviewing. Seems we'll need to follow-up in the spec meeting to potentially add a retransmit flag to retransmit commitment_signed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could either of you comment on the spec PR as to what you had in mind with retransmit_flags? Might be worth having t-bast think on it before the spec meeting.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated to next_funding_txid to be next_funding with a txid and retransmit_flags as discussed on the spec.

// - if it has already received `tx_signatures` for that funding transaction:
// - MUST send its `tx_signatures` for that funding transaction.
if (session.has_received_commitment_signed() && session.holder_sends_tx_signatures_first())
|| self.context.channel_state.is_their_tx_signatures_sent()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't we need to check has_received_commitment_signed() even in the case where they already sent their tx signatures? Or can the state machine not advance in that case?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on offline discussion self.context.channel_state.is_their_tx_signatures_sent() implies session.has_received_commitment_signed().

// on reestablish and tell our peer to just forget about it.
// Our peer is doing something strange, but it doesn't warrant closing the channel.
(None, None, Some(msgs::TxAbort {
// The `next_funding_txid` does not match the latest interactive funding
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the else block for where maybe_get_next_funding_txid returned None, not where the txid doesn't match, so the comment is confusing. But I'm also a bit less clear on the second case of maybe_get_next_funding_txid - if we have an interactive signing session, but they did send their tx signatures (and we received them), we'll end up here, but it seems its much too late to TxAbort.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From offline discussion we'll drop the else block and use of maybe_get_next_funding_txid in the if block. Instead, we'll get the latter from the signing session.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

})
} else if msg.next_local_commitment_number == next_counterparty_commitment_number - 1 {
// We've made an update so we must have exchanged `tx_signatures`, implying that
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might indicate we're sending our CS for the interactive signing session, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Related to #3886 (comment), so will depend on how that is resolved.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The proposed spec change did not include a flag for re-transmitting tx_signatures, so kept this as is for now.

Copy link
Contributor Author

@jkczyz jkczyz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Discussed recent review comments offline. Left some notes about what was decided. @TheBlueMatt @wpaulino Feel free to expand on anything there that isn't clear. I'll address anything that can be resolved and not blocked on discussion at the spec meeting.

} else {
session.holder_tx_signatures().clone()
}
if !session.has_received_commitment_signed() {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or do we switch to interactive signing once we send our CS, before our peer sends their CS (and should we?).

Based on offline discussion with @wpaulino this statement is true.

// - MUST retransmit its `commitment_signed` for that funding transaction.
if msg.next_local_commitment_number == next_counterparty_commitment_number {
// `next_counterparty_commitment_number` is guaranteed to always be the
// commitment number of the `commitment_signed` message we sent for this
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@wpaulino said he will take a look when reviewing. Seems we'll need to follow-up in the spec meeting to potentially add a retransmit flag to retransmit commitment_signed.

// - if it has already received `tx_signatures` for that funding transaction:
// - MUST send its `tx_signatures` for that funding transaction.
if (session.has_received_commitment_signed() && session.holder_sends_tx_signatures_first())
|| self.context.channel_state.is_their_tx_signatures_sent()
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on offline discussion self.context.channel_state.is_their_tx_signatures_sent() implies session.has_received_commitment_signed().

// on reestablish and tell our peer to just forget about it.
// Our peer is doing something strange, but it doesn't warrant closing the channel.
(None, None, Some(msgs::TxAbort {
// The `next_funding_txid` does not match the latest interactive funding
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From offline discussion we'll drop the else block and use of maybe_get_next_funding_txid in the if block. Instead, we'll get the latter from the signing session.

})
} else if msg.next_local_commitment_number == next_counterparty_commitment_number - 1 {
// We've made an update so we must have exchanged `tx_signatures`, implying that
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Related to #3886 (comment), so will depend on how that is resolved.

@@ -1215,6 +1215,7 @@ pub(super) struct ReestablishResponses {
pub shutdown_msg: Option<msgs::Shutdown>,
pub tx_signatures: Option<msgs::TxSignatures>,
pub tx_abort: Option<msgs::TxAbort>,
pub implicit_splice_locked: Option<msgs::SpliceLocked>,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May just need to rename this as there is some update logic that needs to be executed in ChannelManager::internal_splice_locked. Will re-visit to see if there's a better alternative.

@ldk-reviews-bot
Copy link

🔔 1st Reminder

Hey @wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot
Copy link

🔔 2nd Reminder

Hey @wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

jkczyz added 8 commits August 25, 2025 13:02
The splicing spec updates the logic pertaining to next_commitment_number
when sending a channel_reestablish message. Specifically:

The sending node:
  - if it has sent `commitment_signed` for an interactive transaction construction but
    it has not received `tx_signatures`:
    - MUST set `next_funding_txid` to the txid of that interactive transaction.
    - if it has not received `commitment_signed` for that interactive transaction:
      - MUST set `next_commitment_number` to the commitment number of the `commitment_signed` it sent.
When a splice transaction is promoted (i.e., when splice_locked has been
exchanged), announcement_signatures must be sent. However, if we try to
send a channel_announcement before they are received, then the
signatures will be incorrect. To avoid this, clear the counterparty's
announcement_signatures upon promoting a FundingScope.
When handling a counterparties channel_reestablish, the spec dictates
that a splice_locked may be implied by my_current_funding_locked.
Compare that against any pending splices and handle an implicit
splice_locked message when applicable.
During channel reestablishment, announcement_signatures may need to be
retransmitted. The splicing spec allows doing so without retransmitting
splice_locked first, which could normally trigger retransmitting
announcement_signatures. Instead, my_current_funding_locked lets the
sender request retransmitting it.
The previous commit allowed requesting retransmission of
announcement_signatures during channel reestablishment. This commit
handles such requests.
@jkczyz jkczyz force-pushed the 2025-06-channel-reestablish branch from 719ade4 to 7c948e4 Compare August 26, 2025 20:14
@jkczyz jkczyz requested a review from TheBlueMatt August 26, 2025 20:16
@ldk-reviews-bot
Copy link

🔔 3rd Reminder

Hey @TheBlueMatt @wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: Goal: Merge
Development

Successfully merging this pull request may close these issues.

4 participants