') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); })(); Update `channel_reestablish` for splicing by jkczyz · Pull Request #3886 · lightningdevkit/rust-lightning · GitHub
Skip to content

Update channel_reestablish for splicing - #3886

Merged
wpaulino merged 13 commits into
lightningdevkit:mainfrom
jkczyz:2025-06-channel-reestablish
Sep 5, 2025
Merged

Update channel_reestablish for splicing#3886
wpaulino merged 13 commits into
lightningdevkit:mainfrom
jkczyz:2025-06-channel-reestablish

Conversation

@jkczyz

@jkczyzjkczyz commented Jun 24, 2025

Copy link
Copy Markdown
Contributor

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

ldk-reviews-bot commented Jun 24, 2025

Copy link
Copy Markdown

👋 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
Copy Markdown

🔔 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.

@jkczyzjkczyz assigned jkczyz and unassigned jkczyzJun 26, 2025
@jkczyz
jkczyzforce-pushed the 2025-06-channel-reestablish branch 2 times, most recently from 06c0dfc to f000b76CompareJune 27, 2025 17:04
@jkczyz
jkczyz marked this pull request as ready for review June 27, 2025 17:12
@jkczyz

Copy link
Copy Markdown
ContributorAuthor

@wpaulino Ready for review now.

@codecov

codecovBot commented Jun 27, 2025

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 61.25000% with 93 lines in your changes missing coverage. Please review.
✅ Project coverage is 88.74%. Comparing base (a9bbb24) to head (a3a3a55).
⚠️ Report is 77 commits behind head on main.

Files with missing linesPatch %Lines
lightning/src/ln/channel.rs52.08%67 Missing and 2 partials ⚠️
lightning/src/ln/channelmanager.rs62.50%8 Missing and 4 partials ⚠️
lightning/src/ln/msgs.rs80.64%12 Missing ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #3886 +/- ##
========================================
Coverage 88.74% 88.74% ========================================
Files 176 176 Lines 128845 129485 +640 Branches 128845 129485 +640 ========================================
+ Hits 114340 114912 +572 - Misses 11912 11962 +50 - Partials 2593 2611 +18 
FlagCoverage Δ
fuzzing21.99% <9.04%> (+0.06%)⬆️
tests88.57% <61.25%> (+<0.01%)⬆️

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
Copy Markdown

🔔 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
Copy Markdown

🔔 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
Copy Markdown

🔔 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.

@jkczyzjkczyz self-assigned this Jul 3, 2025
@jkczyzjkczyz moved this to Goal: Merge in Weekly GoalsJul 3, 2025
@ldk-reviews-bot

Copy link
Copy Markdown

🔔 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
Copy Markdown

🔔 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
jkczyzforce-pushed the 2025-06-channel-reestablish branch from f000b76 to e2ea3bfCompareJuly 7, 2025 21:02
@jkczyz

Copy link
Copy Markdown
ContributorAuthor

Rebased.

@wpaulinowpaulino left a comment

Copy link
Copy Markdown
Contributor

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.

Comment threadlightning-types/src/features.rs Outdated
Comment threadlightning/src/ln/channel.rs Outdated
Comment threadlightning/src/ln/channel.rs Outdated
Comment threadlightning/src/ln/channel.rs Outdated
Comment threadlightning/src/ln/channel.rs Outdated
Comment threadlightning/src/ln/channel.rs Outdated
Comment threadlightning/src/ln/channel.rs Outdated
Comment threadlightning/src/ln/channel.rs
Comment threadlightning/src/ln/channel.rs Outdated
Comment threadlightning/src/ln/channelmanager.rs
@ldk-reviews-bot

Copy link
Copy Markdown

👋 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
jkczyzforce-pushed the 2025-06-channel-reestablish branch from e2ea3bf to 69be701CompareJuly 9, 2025 00:17

@jkczyzjkczyz left a comment

Copy link
Copy Markdown
ContributorAuthor

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.

Comment threadlightning/src/ln/channel.rs Outdated
Comment threadlightning/src/ln/channel.rs Outdated
Comment threadlightning/src/ln/channel.rs Outdated
Comment threadlightning/src/ln/channel.rs Outdated
Comment threadlightning/src/ln/channel.rs Outdated
Comment threadlightning/src/ln/channel.rs Outdated
Comment threadlightning/src/ln/channel.rs
Comment threadlightning/src/ln/channel.rs Outdated
Comment threadlightning/src/ln/channelmanager.rs
@jkczyz
jkczyzforce-pushed the 2025-06-channel-reestablish branch 2 times, most recently from f7b9785 to b0291f4CompareJuly 9, 2025 21:49
@jkczyz

Copy link
Copy Markdown
ContributorAuthor

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 TheBlueMattJuly 9, 2025 22:20
@jkczyz

Copy link
Copy Markdown
ContributorAuthor

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,
_ => returnErr(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 =
ifself.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
}elseifself.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){
returnErr(ChannelError::close("Peer sent a reconnect channel_ready with a different point".to_owned()));
}
returnOk(None);
}

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.
During channel reestablishment, both commitment_signed and tx_signatures
messages may be retransmitted. However, commitment_signed must come
first, so re-arrange the ordering. This moves tx_abort as well to keep
the interactive tx messages together.
@jkczyz
jkczyzforce-pushed the 2025-06-channel-reestablish branch from dc7379d to cce9421CompareAugust 28, 2025 23:26
@jkczyz
jkczyzforce-pushed the 2025-06-channel-reestablish branch from cce9421 to 845360bCompareAugust 28, 2025 23:37
Comment threadlightning/src/ln/channel.rs Outdated
})
} else if msg.next_local_commitment_number == next_counterparty_commitment_number - 1 {
debug_assert!(commitment_update.is_none());
debug_assert!(tx_signatures.is_none());

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I believe this is reachable if a counterparty sends a bogus message? If we're just trying to test for it we should #[cfg(test)] it, in general we try to avoid reachable debug_asserts as well.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Yeah, see #3886 (comment)

@jkczyz
jkczyzforce-pushed the 2025-06-channel-reestablish branch from 845360b to 27f70b0CompareAugust 29, 2025 21:50

@TheBlueMattTheBlueMatt left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Some nits and one more issue I think.

Comment threadlightning/src/ln/channel.rs Outdated
}
}

// FIXME: Is this correct?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Looks right to me. Once we fix #4036 basically we can assume we're connected to the peer and in normal operation, which means an interactive signing session or awaiting-ready implies if we're sending a CS its an initial one, and otherwise we want to make sure we get our ready out first. Probably good to leave a comment describing that reasoning.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Replaced the FIXME accordingly.

Comment threadlightning/src/ln/channel.rs Outdated
data:
"No active signing session. The associated funding transaction may have already been broadcast.".as_bytes().to_vec() }))
}
let channel_ready_order = if commitment_update.is_some() || tx_signatures.is_some() {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

If we're not sending any signatures does it matter? Can we not just always set SignaturesFirst?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Done. Also, pulled out a new commit introducing ChannelReadyOrder since it's applicable for v2 channel establishment. Addressed this comment there.

commitment_order: self.context.resend_order.clone(),
shutdown_msg, announcement_sigs,
tx_signatures: None,
tx_signatures,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

We shouldn't send tx_signatures if is_monitor_update_in_progress, right? Probably need to check it in the interactive signing session stuff above, but also here cause we're specifically in a block because of it sometimes.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Introduced a separate commit for the interactive signing session case since that preexist this PR. No additional logic is needed here since tx_signatures is initialized there.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 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
jkczyzforce-pushed the 2025-06-channel-reestablish branch from 27f70b0 to 51406e6CompareSeptember 2, 2025 23:14
Comment threadlightning/src/ln/channel.rs
@ldk-reviews-bot

Copy link
Copy Markdown

🔔 2nd 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.

jkczyzand others added 9 commits September 3, 2025 11:20
When handling channel_reestablish, the order in which channel_ready is
sent depends on whether or not the initial commitment_signed /
tx_signatures are being retransmitted. When they are, then channel_ready
should come after them. Otherwise, channel_ready should come before any
commitment_signed.
The ChannelMonitor must be successfully persisted before transmitting
any messages.
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.
Note that the spec is in flux. Instead, next_funding_txid is replaced
with next_funding, which contains both a txid and retransmit_flags. The
latter is used instead of next_commitment_number to determine whether
commitment_signed should be retransmitted.
This commit updates FundedChannel::channel_reestablish accordingly.
Co-authored-by: Wilmer Paulino <wilmer@wilmerpaulino.com>
Co-authored-by: Jeffrey Czyz <jkczyz@gmail.com>
During channel_reestablish handling, next_local_commitment_number can
never be zero. Remove the unnecessary check conditioning on next_funding
not being set. Additionally, remove the spec requirement in the comment
that follows since it does not exist.
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
jkczyzforce-pushed the 2025-06-channel-reestablish branch from 51406e6 to a3a3a55CompareSeptember 3, 2025 18:20

@TheBlueMattTheBlueMatt left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🎉

// signatures should be sent before channel_ready.
let channel_ready_order = if self.interactive_tx_signing_session.is_some() {
ChannelReadyOrder::SignaturesFirst
} else if matches!(self.context.channel_state, ChannelState::AwaitingChannelReady(_)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Do we actually need to check this? We keep the signing session around until we receive channel_ready.

Comment threadlightning/src/ln/channel.rs Outdated
if session.holder_tx_signatures().is_none() {
log_debug!(logger, "Waiting for funding transaction signatures to be provided");
None
} else if self.context.channel_state.is_monitor_update_in_progress() {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This shouldn't be necessary since the signing event isn't given to the user until after the monitor update completes.

ChannelError::Close(
(
message.clone(),
ClosureReason::HolderForceClosed { message, broadcasted_latest_txn: Some(false) },

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Should be ClosureReason::ProcessingError

ChannelError::Close(
(
message.clone(),
ClosureReason::HolderForceClosed { message, broadcasted_latest_txn: Some(false) },

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Same here

@wpaulino
wpaulino merged commit ecce859 into lightningdevkit:mainSep 5, 2025
24 checks passed
@github-project-automationgithub-project-automationBot moved this from Goal: Merge to Done in Weekly GoalsSep 5, 2025
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

4 participants

@jkczyz@ldk-reviews-bot@TheBlueMatt@wpaulino