Rework ChannelManager::funding_transaction_signed - #4336

Merged
TheBlueMatt merged 3 commits into
lightningdevkit:mainfrom
wpaulino:funding-transaction-signed-rework
Jan 30, 2026
Merged

Rework ChannelManager::funding_transaction_signed#4336
TheBlueMatt merged 3 commits into
lightningdevkit:mainfrom
wpaulino:funding-transaction-signed-rework

Conversation

@wpaulino

Copy link
Copy Markdown
Contributor

Alternative version to #4257

@wpaulinowpaulino added this to the 0.3 milestone Jan 22, 2026
@wpaulinowpaulino self-assigned this Jan 22, 2026
@ldk-reviews-bot

ldk-reviews-bot commented Jan 22, 2026

Copy link
Copy Markdown

👋 Thanks for assigning @jkczyz 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.

@codecov

codecovBot commented Jan 22, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 72.00957% with 117 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.07%. Comparing base (8cdc86a) to head (be67c67).
⚠️ Report is 37 commits behind head on main.

Files with missing linesPatch %Lines
lightning/src/ln/channel.rs72.72%54 Missing and 6 partials ⚠️
lightning/src/ln/channelmanager.rs71.42%46 Missing and 10 partials ⚠️
lightning/src/ln/interactivetxs.rs50.00%1 Missing ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #4336 +/- ##
==========================================
- Coverage 86.53% 86.07% -0.47% 
==========================================
Files 158 156 -2 Lines 103190 102804 -386 Branches 103190 102804 -386 ==========================================
- Hits 89300 88490 -810 - Misses 11469 11803 +334 - Partials 2421 2511 +90 
FlagCoverage Δ
fuzzing?
tests86.07% <72.00%> (+0.24%)⬆️

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.

@wpaulino
wpaulinoforce-pushed the funding-transaction-signed-rework branch from c0193fe to 0fc923cCompareJanuary 22, 2026 19:59
Comment threadlightning/src/ln/channel.rs Outdated
Comment on lines +11265 to 11274
// We should never be sending a `commitment_signed` in response to their
// `tx_signatures`.
debug_assert!(commitment_signed.is_none());

if let Some(tx_signatures) = tx_signatures {
peer_state.pending_msg_events.push(MessageSendEvent::SendTxSignatures {
node_id: *counterparty_node_id,
msg: tx_signatures,
});
}

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.

I'm a little confused by this. Why would we send our tx_signatures if we aren't sending our commitment_signed? Didn't we want to send both at once?

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've already sent it by this point, we're sending our tx_signatures here in response to theirs

Comment on lines +18998 to +18999
// We may need to regenerate [`Event::FundingTransactionReadyForSigning`] for channels that
// still need their holder `tx_signatures`.

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.

Does it make sense to start persisting this?

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.

Matt pointed out we shouldn't #4257 (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.

@TheBlueMatt Is the goal eventually to not write events that (a) can be re-constructed from ChannelMonitor / FundedChannel state or (b) aren't applicable across a restart? Presumable this falls into that category.

Seems there will at least be some events that aren't tied to a particular channel, so we may still need to persist some events?

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @TheBlueMatt! 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.

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

Hmm, seems like the last commit should really get have test coverage? I guess just our dual-funding coverage isn't that great?

Also, presumably this needs documentation for how to cancel a splice instead of signing and a test that does so?

Comment threadlightning/src/ln/channelmanager.rs Outdated
@wpaulino

Copy link
Copy Markdown
ContributorAuthor

Hmm, seems like the last commit should really get have test coverage? I guess just our dual-funding coverage isn't that great?

Yeah things aren't really hooked up yet to do proper testing.

Also, presumably this needs documentation for how to cancel a splice instead of signing and a test that does so?

Planned for a separate PR, no need for it to happen here.

@wpaulino
wpaulinoforce-pushed the funding-transaction-signed-rework branch 2 times, most recently from 734ad69 to 04a7560CompareJanuary 27, 2026 00:34
TheBlueMatt
TheBlueMatt previously approved these changes Jan 28, 2026

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

Question somewhat orthogonal to this PR - do we need a monitor update before sending our tx-sigs? Once we send it, its possible for our counterparty to respond with a commitment sigs and then we can't cancel. If we restart without chanman persistence and the user tries to cancel at that point we'll end up getting force-closed on.

I continue to be somewhat annoyed at how many expects there are in the splicing logic, and would feel a lot better if we had a full_stack_target seed that spliced so that at least the fuzzer could explore it a tiny bit (though of course a larger refactor to reduce them would be nice too).

Comment threadlightning/src/ln/channel.rs
Comment threadlightning/src/ln/channel.rs
@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @jkczyz! 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.

Comment on lines +18998 to +18999
// We may need to regenerate [`Event::FundingTransactionReadyForSigning`] for channels that
// still need their holder `tx_signatures`.

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.

@TheBlueMatt Is the goal eventually to not write events that (a) can be re-constructed from ChannelMonitor / FundedChannel state or (b) aren't applicable across a restart? Presumable this falls into that category.

Seems there will at least be some events that aren't tied to a particular channel, so we may still need to persist some events?

Comment threadlightning/src/ln/channelmanager.rs Outdated
);
}
}
return NotifyOption::DoPersist;

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.

Is this independent of any events being generated? (i.e., some stated in the channel was made, but is_connected was false?

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 still want to persist the funding signatures regardless of channel connectivity

Comment on lines +11264 to +11268

// We should never be sending a `commitment_signed` in response to their
// `tx_signatures`.
debug_assert!(commitment_signed.is_none());

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.

Would it make sense to return an error here?

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.

If we actually do, they'll force close on us, so we get the same result.

let funding_tx_opt = self.maybe_finalize_funding_tx();
let holder_tx_signatures = (self.holder_sends_tx_signatures_first
|| self.has_received_tx_signatures())
let holder_tx_signatures = (self.has_received_commitment_signed

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.

Is has_received_commitment_signed implied from has_received_tx_signatures?

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.

Yes, we force close when receiving tx_signatures without the commitment_signed first. I'd prefer keeping the code as is for clarity though.

Comment threadlightning/src/ln/channel.rs Outdated
Comment threadlightning/src/ln/channel.rs Outdated
Previously, we'd emit a FundingTransactionReadyForSigning event once the
initial commitment_signed is exchanged for a splicing/dual-funding
attempt and require users to call back with their signed inputs using
ChannelManager::funding_transaction_signed. While this approach worked
in practice, it prevents us from abandoning a splice if we cannot or no
longer wish to sign as the splice has already been committed to by this
point.
This commit reworks the API such that this is now possible. After
exchanging tx_complete, we will no longer immediately send our initial
commitment_signed. We will now emit the
FundingTransactionReadyForSigning event and wait for the user to call
back before releasing both our initial commitment_signed and our
tx_signatures. As a result, the event is now persisted, as there is only
one possible path in which it is generated. Note that we continue to
only emit the event if a local contribution to negotiated transaction
was made.
Future work will expose a cancellation API such that we can abandon
splice attempts safely (we can just force close the channel with
dual-funding).
This is crucial to enable the splice cancellation use case. When we
process the initial commitment signed from our counterparty, we queue a
monitor update that cannot be undone. To give the user a chance to abort
the splice negotiation before it's committed to, we buffer the message
until a successful call to `Channel::funding_transaction_signed` and
process it then.
Note that this is currently only done for splice and RBF attempts, as
if we want to abort a dual funding negotiation, we can just force close
the channel as it hasn't been funded yet.
Now that we require users to first call
`ChannelManager::funding_transaction_signed` before releasing any
signatures, it's possible that it is called before we receive the
initial commitment signed from our counterparty, which would transition
the channel to funded. Because of this, we need to support the API call
while the channel is still in the unfunded phase.
Note that this commit is mostly a code move of
`FundedChannel::funding_transaction_signed` to
`Channel::funding_transaction_signed` that doesn't alter the signing
logic.
@wpaulino
wpaulinoforce-pushed the funding-transaction-signed-rework branch from f2dd6f2 to be67c67CompareJanuary 29, 2026 18:03
@TheBlueMatt
TheBlueMatt merged commit fcc3d33 into lightningdevkit:mainJan 30, 2026
21 checks passed
@wpaulino
wpaulino deleted the funding-transaction-signed-rework branch January 30, 2026 02:08
@jkczyzjkczyz mentioned this pull request Mar 19, 2026
50 tasks
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@wpaulino@ldk-reviews-bot@TheBlueMatt@jkczyz
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Rework ChannelManager::funding_transaction_signed - #4336

Merged
TheBlueMatt merged 3 commits into
lightningdevkit:mainfrom
wpaulino:funding-transaction-signed-rework
Jan 30, 2026
Merged

Rework ChannelManager::funding_transaction_signed#4336
TheBlueMatt merged 3 commits into
lightningdevkit:mainfrom
wpaulino:funding-transaction-signed-rework

Conversation

@wpaulino

Copy link
Copy Markdown
Contributor

Alternative version to #4257

@wpaulinowpaulino added this to the 0.3 milestone Jan 22, 2026
@wpaulinowpaulino self-assigned this Jan 22, 2026
@ldk-reviews-bot

ldk-reviews-bot commented Jan 22, 2026

Copy link
Copy Markdown

👋 Thanks for assigning @jkczyz 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.

@codecov

codecovBot commented Jan 22, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 72.00957% with 117 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.07%. Comparing base (8cdc86a) to head (be67c67).
⚠️ Report is 37 commits behind head on main.

Files with missing linesPatch %Lines
lightning/src/ln/channel.rs72.72%54 Missing and 6 partials ⚠️
lightning/src/ln/channelmanager.rs71.42%46 Missing and 10 partials ⚠️
lightning/src/ln/interactivetxs.rs50.00%1 Missing ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #4336 +/- ##
==========================================
- Coverage 86.53% 86.07% -0.47% 
==========================================
Files 158 156 -2 Lines 103190 102804 -386 Branches 103190 102804 -386 ==========================================
- Hits 89300 88490 -810 - Misses 11469 11803 +334 - Partials 2421 2511 +90 
FlagCoverage Δ
fuzzing?
tests86.07% <72.00%> (+0.24%)⬆️

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.

@wpaulino
wpaulinoforce-pushed the funding-transaction-signed-rework branch from c0193fe to 0fc923cCompareJanuary 22, 2026 19:59
Comment threadlightning/src/ln/channel.rs Outdated
Comment on lines +11265 to 11274
// We should never be sending a `commitment_signed` in response to their
// `tx_signatures`.
debug_assert!(commitment_signed.is_none());

if let Some(tx_signatures) = tx_signatures {
peer_state.pending_msg_events.push(MessageSendEvent::SendTxSignatures {
node_id: *counterparty_node_id,
msg: tx_signatures,
});
}

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.

I'm a little confused by this. Why would we send our tx_signatures if we aren't sending our commitment_signed? Didn't we want to send both at once?

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've already sent it by this point, we're sending our tx_signatures here in response to theirs

Comment on lines +18998 to +18999
// We may need to regenerate [`Event::FundingTransactionReadyForSigning`] for channels that
// still need their holder `tx_signatures`.

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.

Does it make sense to start persisting this?

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.

Matt pointed out we shouldn't #4257 (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.

@TheBlueMatt Is the goal eventually to not write events that (a) can be re-constructed from ChannelMonitor / FundedChannel state or (b) aren't applicable across a restart? Presumable this falls into that category.

Seems there will at least be some events that aren't tied to a particular channel, so we may still need to persist some events?

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @TheBlueMatt! 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.

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

Hmm, seems like the last commit should really get have test coverage? I guess just our dual-funding coverage isn't that great?

Also, presumably this needs documentation for how to cancel a splice instead of signing and a test that does so?

Comment threadlightning/src/ln/channelmanager.rs Outdated
@wpaulino

Copy link
Copy Markdown
ContributorAuthor

Hmm, seems like the last commit should really get have test coverage? I guess just our dual-funding coverage isn't that great?

Yeah things aren't really hooked up yet to do proper testing.

Also, presumably this needs documentation for how to cancel a splice instead of signing and a test that does so?

Planned for a separate PR, no need for it to happen here.

@wpaulino
wpaulinoforce-pushed the funding-transaction-signed-rework branch 2 times, most recently from 734ad69 to 04a7560CompareJanuary 27, 2026 00:34
TheBlueMatt
TheBlueMatt previously approved these changes Jan 28, 2026

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

Question somewhat orthogonal to this PR - do we need a monitor update before sending our tx-sigs? Once we send it, its possible for our counterparty to respond with a commitment sigs and then we can't cancel. If we restart without chanman persistence and the user tries to cancel at that point we'll end up getting force-closed on.

I continue to be somewhat annoyed at how many expects there are in the splicing logic, and would feel a lot better if we had a full_stack_target seed that spliced so that at least the fuzzer could explore it a tiny bit (though of course a larger refactor to reduce them would be nice too).

Comment threadlightning/src/ln/channel.rs
Comment threadlightning/src/ln/channel.rs
@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @jkczyz! 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.

Comment on lines +18998 to +18999
// We may need to regenerate [`Event::FundingTransactionReadyForSigning`] for channels that
// still need their holder `tx_signatures`.

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.

@TheBlueMatt Is the goal eventually to not write events that (a) can be re-constructed from ChannelMonitor / FundedChannel state or (b) aren't applicable across a restart? Presumable this falls into that category.

Seems there will at least be some events that aren't tied to a particular channel, so we may still need to persist some events?

Comment threadlightning/src/ln/channelmanager.rs Outdated
);
}
}
return NotifyOption::DoPersist;

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.

Is this independent of any events being generated? (i.e., some stated in the channel was made, but is_connected was false?

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 still want to persist the funding signatures regardless of channel connectivity

Comment on lines +11264 to +11268

// We should never be sending a `commitment_signed` in response to their
// `tx_signatures`.
debug_assert!(commitment_signed.is_none());

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.

Would it make sense to return an error here?

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.

If we actually do, they'll force close on us, so we get the same result.

let funding_tx_opt = self.maybe_finalize_funding_tx();
let holder_tx_signatures = (self.holder_sends_tx_signatures_first
|| self.has_received_tx_signatures())
let holder_tx_signatures = (self.has_received_commitment_signed

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.

Is has_received_commitment_signed implied from has_received_tx_signatures?

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.

Yes, we force close when receiving tx_signatures without the commitment_signed first. I'd prefer keeping the code as is for clarity though.

Comment threadlightning/src/ln/channel.rs Outdated
Comment threadlightning/src/ln/channel.rs Outdated
Previously, we'd emit a FundingTransactionReadyForSigning event once the
initial commitment_signed is exchanged for a splicing/dual-funding
attempt and require users to call back with their signed inputs using
ChannelManager::funding_transaction_signed. While this approach worked
in practice, it prevents us from abandoning a splice if we cannot or no
longer wish to sign as the splice has already been committed to by this
point.
This commit reworks the API such that this is now possible. After
exchanging tx_complete, we will no longer immediately send our initial
commitment_signed. We will now emit the
FundingTransactionReadyForSigning event and wait for the user to call
back before releasing both our initial commitment_signed and our
tx_signatures. As a result, the event is now persisted, as there is only
one possible path in which it is generated. Note that we continue to
only emit the event if a local contribution to negotiated transaction
was made.
Future work will expose a cancellation API such that we can abandon
splice attempts safely (we can just force close the channel with
dual-funding).
This is crucial to enable the splice cancellation use case. When we
process the initial commitment signed from our counterparty, we queue a
monitor update that cannot be undone. To give the user a chance to abort
the splice negotiation before it's committed to, we buffer the message
until a successful call to `Channel::funding_transaction_signed` and
process it then.
Note that this is currently only done for splice and RBF attempts, as
if we want to abort a dual funding negotiation, we can just force close
the channel as it hasn't been funded yet.
Now that we require users to first call
`ChannelManager::funding_transaction_signed` before releasing any
signatures, it's possible that it is called before we receive the
initial commitment signed from our counterparty, which would transition
the channel to funded. Because of this, we need to support the API call
while the channel is still in the unfunded phase.
Note that this commit is mostly a code move of
`FundedChannel::funding_transaction_signed` to
`Channel::funding_transaction_signed` that doesn't alter the signing
logic.
@wpaulino
wpaulinoforce-pushed the funding-transaction-signed-rework branch from f2dd6f2 to be67c67CompareJanuary 29, 2026 18:03
@TheBlueMatt
TheBlueMatt merged commit fcc3d33 into lightningdevkit:mainJan 30, 2026
21 checks passed
@wpaulino
wpaulino deleted the funding-transaction-signed-rework branch January 30, 2026 02:08
@jkczyzjkczyz mentioned this pull request Mar 19, 2026
50 tasks
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@wpaulino@ldk-reviews-bot@TheBlueMatt@jkczyz
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Rework ChannelManager::funding_transaction_signed - #4336

Merged
TheBlueMatt merged 3 commits into
lightningdevkit:mainfrom
wpaulino:funding-transaction-signed-rework
Jan 30, 2026
Merged

Rework ChannelManager::funding_transaction_signed#4336
TheBlueMatt merged 3 commits into
lightningdevkit:mainfrom
wpaulino:funding-transaction-signed-rework

Conversation

@wpaulino

Copy link
Copy Markdown
Contributor

Alternative version to #4257

@wpaulinowpaulino added this to the 0.3 milestone Jan 22, 2026
@wpaulinowpaulino self-assigned this Jan 22, 2026
@ldk-reviews-bot

ldk-reviews-bot commented Jan 22, 2026

Copy link
Copy Markdown

👋 Thanks for assigning @jkczyz 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.

@codecov

codecovBot commented Jan 22, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 72.00957% with 117 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.07%. Comparing base (8cdc86a) to head (be67c67).
⚠️ Report is 37 commits behind head on main.

Files with missing linesPatch %Lines
lightning/src/ln/channel.rs72.72%54 Missing and 6 partials ⚠️
lightning/src/ln/channelmanager.rs71.42%46 Missing and 10 partials ⚠️
lightning/src/ln/interactivetxs.rs50.00%1 Missing ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #4336 +/- ##
==========================================
- Coverage 86.53% 86.07% -0.47% 
==========================================
Files 158 156 -2 Lines 103190 102804 -386 Branches 103190 102804 -386 ==========================================
- Hits 89300 88490 -810 - Misses 11469 11803 +334 - Partials 2421 2511 +90 
FlagCoverage Δ
fuzzing?
tests86.07% <72.00%> (+0.24%)⬆️

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.

@wpaulino
wpaulinoforce-pushed the funding-transaction-signed-rework branch from c0193fe to 0fc923cCompareJanuary 22, 2026 19:59
Comment threadlightning/src/ln/channel.rs Outdated
Comment on lines +11265 to 11274
// We should never be sending a `commitment_signed` in response to their
// `tx_signatures`.
debug_assert!(commitment_signed.is_none());

if let Some(tx_signatures) = tx_signatures {
peer_state.pending_msg_events.push(MessageSendEvent::SendTxSignatures {
node_id: *counterparty_node_id,
msg: tx_signatures,
});
}

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.

I'm a little confused by this. Why would we send our tx_signatures if we aren't sending our commitment_signed? Didn't we want to send both at once?

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've already sent it by this point, we're sending our tx_signatures here in response to theirs

Comment on lines +18998 to +18999
// We may need to regenerate [`Event::FundingTransactionReadyForSigning`] for channels that
// still need their holder `tx_signatures`.

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.

Does it make sense to start persisting this?

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.

Matt pointed out we shouldn't #4257 (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.

@TheBlueMatt Is the goal eventually to not write events that (a) can be re-constructed from ChannelMonitor / FundedChannel state or (b) aren't applicable across a restart? Presumable this falls into that category.

Seems there will at least be some events that aren't tied to a particular channel, so we may still need to persist some events?

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @TheBlueMatt! 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.

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

Hmm, seems like the last commit should really get have test coverage? I guess just our dual-funding coverage isn't that great?

Also, presumably this needs documentation for how to cancel a splice instead of signing and a test that does so?

Comment threadlightning/src/ln/channelmanager.rs Outdated
@wpaulino

Copy link
Copy Markdown
ContributorAuthor

Hmm, seems like the last commit should really get have test coverage? I guess just our dual-funding coverage isn't that great?

Yeah things aren't really hooked up yet to do proper testing.

Also, presumably this needs documentation for how to cancel a splice instead of signing and a test that does so?

Planned for a separate PR, no need for it to happen here.

@wpaulino
wpaulinoforce-pushed the funding-transaction-signed-rework branch 2 times, most recently from 734ad69 to 04a7560CompareJanuary 27, 2026 00:34
TheBlueMatt
TheBlueMatt previously approved these changes Jan 28, 2026

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

Question somewhat orthogonal to this PR - do we need a monitor update before sending our tx-sigs? Once we send it, its possible for our counterparty to respond with a commitment sigs and then we can't cancel. If we restart without chanman persistence and the user tries to cancel at that point we'll end up getting force-closed on.

I continue to be somewhat annoyed at how many expects there are in the splicing logic, and would feel a lot better if we had a full_stack_target seed that spliced so that at least the fuzzer could explore it a tiny bit (though of course a larger refactor to reduce them would be nice too).

Comment threadlightning/src/ln/channel.rs
Comment threadlightning/src/ln/channel.rs
@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @jkczyz! 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.

Comment on lines +18998 to +18999
// We may need to regenerate [`Event::FundingTransactionReadyForSigning`] for channels that
// still need their holder `tx_signatures`.

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.

@TheBlueMatt Is the goal eventually to not write events that (a) can be re-constructed from ChannelMonitor / FundedChannel state or (b) aren't applicable across a restart? Presumable this falls into that category.

Seems there will at least be some events that aren't tied to a particular channel, so we may still need to persist some events?

Comment threadlightning/src/ln/channelmanager.rs Outdated
);
}
}
return NotifyOption::DoPersist;

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.

Is this independent of any events being generated? (i.e., some stated in the channel was made, but is_connected was false?

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 still want to persist the funding signatures regardless of channel connectivity

Comment on lines +11264 to +11268

// We should never be sending a `commitment_signed` in response to their
// `tx_signatures`.
debug_assert!(commitment_signed.is_none());

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.

Would it make sense to return an error here?

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.

If we actually do, they'll force close on us, so we get the same result.

let funding_tx_opt = self.maybe_finalize_funding_tx();
let holder_tx_signatures = (self.holder_sends_tx_signatures_first
|| self.has_received_tx_signatures())
let holder_tx_signatures = (self.has_received_commitment_signed

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.

Is has_received_commitment_signed implied from has_received_tx_signatures?

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.

Yes, we force close when receiving tx_signatures without the commitment_signed first. I'd prefer keeping the code as is for clarity though.

Comment threadlightning/src/ln/channel.rs Outdated
Comment threadlightning/src/ln/channel.rs Outdated
Previously, we'd emit a FundingTransactionReadyForSigning event once the
initial commitment_signed is exchanged for a splicing/dual-funding
attempt and require users to call back with their signed inputs using
ChannelManager::funding_transaction_signed. While this approach worked
in practice, it prevents us from abandoning a splice if we cannot or no
longer wish to sign as the splice has already been committed to by this
point.
This commit reworks the API such that this is now possible. After
exchanging tx_complete, we will no longer immediately send our initial
commitment_signed. We will now emit the
FundingTransactionReadyForSigning event and wait for the user to call
back before releasing both our initial commitment_signed and our
tx_signatures. As a result, the event is now persisted, as there is only
one possible path in which it is generated. Note that we continue to
only emit the event if a local contribution to negotiated transaction
was made.
Future work will expose a cancellation API such that we can abandon
splice attempts safely (we can just force close the channel with
dual-funding).
This is crucial to enable the splice cancellation use case. When we
process the initial commitment signed from our counterparty, we queue a
monitor update that cannot be undone. To give the user a chance to abort
the splice negotiation before it's committed to, we buffer the message
until a successful call to `Channel::funding_transaction_signed` and
process it then.
Note that this is currently only done for splice and RBF attempts, as
if we want to abort a dual funding negotiation, we can just force close
the channel as it hasn't been funded yet.
Now that we require users to first call
`ChannelManager::funding_transaction_signed` before releasing any
signatures, it's possible that it is called before we receive the
initial commitment signed from our counterparty, which would transition
the channel to funded. Because of this, we need to support the API call
while the channel is still in the unfunded phase.
Note that this commit is mostly a code move of
`FundedChannel::funding_transaction_signed` to
`Channel::funding_transaction_signed` that doesn't alter the signing
logic.
@wpaulino
wpaulinoforce-pushed the funding-transaction-signed-rework branch from f2dd6f2 to be67c67CompareJanuary 29, 2026 18:03
@TheBlueMatt
TheBlueMatt merged commit fcc3d33 into lightningdevkit:mainJan 30, 2026
21 checks passed
@wpaulino
wpaulino deleted the funding-transaction-signed-rework branch January 30, 2026 02:08
@jkczyzjkczyz mentioned this pull request Mar 19, 2026
50 tasks
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@wpaulino@ldk-reviews-bot@TheBlueMatt@jkczyz
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Rework ChannelManager::funding_transaction_signed - #4336

Merged
TheBlueMatt merged 3 commits into
lightningdevkit:mainfrom
wpaulino:funding-transaction-signed-rework
Jan 30, 2026
Merged

Rework ChannelManager::funding_transaction_signed#4336
TheBlueMatt merged 3 commits into
lightningdevkit:mainfrom
wpaulino:funding-transaction-signed-rework

Conversation

@wpaulino

Copy link
Copy Markdown
Contributor

Alternative version to #4257

@wpaulinowpaulino added this to the 0.3 milestone Jan 22, 2026
@wpaulinowpaulino self-assigned this Jan 22, 2026
@ldk-reviews-bot

ldk-reviews-bot commented Jan 22, 2026

Copy link
Copy Markdown

👋 Thanks for assigning @jkczyz 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.

@codecov

codecovBot commented Jan 22, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 72.00957% with 117 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.07%. Comparing base (8cdc86a) to head (be67c67).
⚠️ Report is 37 commits behind head on main.

Files with missing linesPatch %Lines
lightning/src/ln/channel.rs72.72%54 Missing and 6 partials ⚠️
lightning/src/ln/channelmanager.rs71.42%46 Missing and 10 partials ⚠️
lightning/src/ln/interactivetxs.rs50.00%1 Missing ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #4336 +/- ##
==========================================
- Coverage 86.53% 86.07% -0.47% 
==========================================
Files 158 156 -2 Lines 103190 102804 -386 Branches 103190 102804 -386 ==========================================
- Hits 89300 88490 -810 - Misses 11469 11803 +334 - Partials 2421 2511 +90 
FlagCoverage Δ
fuzzing?
tests86.07% <72.00%> (+0.24%)⬆️

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.

@wpaulino
wpaulinoforce-pushed the funding-transaction-signed-rework branch from c0193fe to 0fc923cCompareJanuary 22, 2026 19:59
Comment threadlightning/src/ln/channel.rs Outdated
Comment on lines +11265 to 11274
// We should never be sending a `commitment_signed` in response to their
// `tx_signatures`.
debug_assert!(commitment_signed.is_none());

if let Some(tx_signatures) = tx_signatures {
peer_state.pending_msg_events.push(MessageSendEvent::SendTxSignatures {
node_id: *counterparty_node_id,
msg: tx_signatures,
});
}

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.

I'm a little confused by this. Why would we send our tx_signatures if we aren't sending our commitment_signed? Didn't we want to send both at once?

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've already sent it by this point, we're sending our tx_signatures here in response to theirs

Comment on lines +18998 to +18999
// We may need to regenerate [`Event::FundingTransactionReadyForSigning`] for channels that
// still need their holder `tx_signatures`.

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.

Does it make sense to start persisting this?

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.

Matt pointed out we shouldn't #4257 (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.

@TheBlueMatt Is the goal eventually to not write events that (a) can be re-constructed from ChannelMonitor / FundedChannel state or (b) aren't applicable across a restart? Presumable this falls into that category.

Seems there will at least be some events that aren't tied to a particular channel, so we may still need to persist some events?

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @TheBlueMatt! 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.

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

Hmm, seems like the last commit should really get have test coverage? I guess just our dual-funding coverage isn't that great?

Also, presumably this needs documentation for how to cancel a splice instead of signing and a test that does so?

Comment threadlightning/src/ln/channelmanager.rs Outdated
@wpaulino

Copy link
Copy Markdown
ContributorAuthor

Hmm, seems like the last commit should really get have test coverage? I guess just our dual-funding coverage isn't that great?

Yeah things aren't really hooked up yet to do proper testing.

Also, presumably this needs documentation for how to cancel a splice instead of signing and a test that does so?

Planned for a separate PR, no need for it to happen here.

@wpaulino
wpaulinoforce-pushed the funding-transaction-signed-rework branch 2 times, most recently from 734ad69 to 04a7560CompareJanuary 27, 2026 00:34
TheBlueMatt
TheBlueMatt previously approved these changes Jan 28, 2026

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

Question somewhat orthogonal to this PR - do we need a monitor update before sending our tx-sigs? Once we send it, its possible for our counterparty to respond with a commitment sigs and then we can't cancel. If we restart without chanman persistence and the user tries to cancel at that point we'll end up getting force-closed on.

I continue to be somewhat annoyed at how many expects there are in the splicing logic, and would feel a lot better if we had a full_stack_target seed that spliced so that at least the fuzzer could explore it a tiny bit (though of course a larger refactor to reduce them would be nice too).

Comment threadlightning/src/ln/channel.rs
Comment threadlightning/src/ln/channel.rs
@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @jkczyz! 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.

Comment on lines +18998 to +18999
// We may need to regenerate [`Event::FundingTransactionReadyForSigning`] for channels that
// still need their holder `tx_signatures`.

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.

@TheBlueMatt Is the goal eventually to not write events that (a) can be re-constructed from ChannelMonitor / FundedChannel state or (b) aren't applicable across a restart? Presumable this falls into that category.

Seems there will at least be some events that aren't tied to a particular channel, so we may still need to persist some events?

Comment threadlightning/src/ln/channelmanager.rs Outdated
);
}
}
return NotifyOption::DoPersist;

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.

Is this independent of any events being generated? (i.e., some stated in the channel was made, but is_connected was false?

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 still want to persist the funding signatures regardless of channel connectivity

Comment on lines +11264 to +11268

// We should never be sending a `commitment_signed` in response to their
// `tx_signatures`.
debug_assert!(commitment_signed.is_none());

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.

Would it make sense to return an error here?

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.

If we actually do, they'll force close on us, so we get the same result.

let funding_tx_opt = self.maybe_finalize_funding_tx();
let holder_tx_signatures = (self.holder_sends_tx_signatures_first
|| self.has_received_tx_signatures())
let holder_tx_signatures = (self.has_received_commitment_signed

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.

Is has_received_commitment_signed implied from has_received_tx_signatures?

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.

Yes, we force close when receiving tx_signatures without the commitment_signed first. I'd prefer keeping the code as is for clarity though.

Comment threadlightning/src/ln/channel.rs Outdated
Comment threadlightning/src/ln/channel.rs Outdated
Previously, we'd emit a FundingTransactionReadyForSigning event once the
initial commitment_signed is exchanged for a splicing/dual-funding
attempt and require users to call back with their signed inputs using
ChannelManager::funding_transaction_signed. While this approach worked
in practice, it prevents us from abandoning a splice if we cannot or no
longer wish to sign as the splice has already been committed to by this
point.
This commit reworks the API such that this is now possible. After
exchanging tx_complete, we will no longer immediately send our initial
commitment_signed. We will now emit the
FundingTransactionReadyForSigning event and wait for the user to call
back before releasing both our initial commitment_signed and our
tx_signatures. As a result, the event is now persisted, as there is only
one possible path in which it is generated. Note that we continue to
only emit the event if a local contribution to negotiated transaction
was made.
Future work will expose a cancellation API such that we can abandon
splice attempts safely (we can just force close the channel with
dual-funding).
This is crucial to enable the splice cancellation use case. When we
process the initial commitment signed from our counterparty, we queue a
monitor update that cannot be undone. To give the user a chance to abort
the splice negotiation before it's committed to, we buffer the message
until a successful call to `Channel::funding_transaction_signed` and
process it then.
Note that this is currently only done for splice and RBF attempts, as
if we want to abort a dual funding negotiation, we can just force close
the channel as it hasn't been funded yet.
Now that we require users to first call
`ChannelManager::funding_transaction_signed` before releasing any
signatures, it's possible that it is called before we receive the
initial commitment signed from our counterparty, which would transition
the channel to funded. Because of this, we need to support the API call
while the channel is still in the unfunded phase.
Note that this commit is mostly a code move of
`FundedChannel::funding_transaction_signed` to
`Channel::funding_transaction_signed` that doesn't alter the signing
logic.
@wpaulino
wpaulinoforce-pushed the funding-transaction-signed-rework branch from f2dd6f2 to be67c67CompareJanuary 29, 2026 18:03
@TheBlueMatt
TheBlueMatt merged commit fcc3d33 into lightningdevkit:mainJan 30, 2026
21 checks passed
@wpaulino
wpaulino deleted the funding-transaction-signed-rework branch January 30, 2026 02:08
@jkczyzjkczyz mentioned this pull request Mar 19, 2026
50 tasks
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@wpaulino@ldk-reviews-bot@TheBlueMatt@jkczyz
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Rework ChannelManager::funding_transaction_signed - #4336

Merged
TheBlueMatt merged 3 commits into
lightningdevkit:mainfrom
wpaulino:funding-transaction-signed-rework
Jan 30, 2026
Merged

Rework ChannelManager::funding_transaction_signed#4336
TheBlueMatt merged 3 commits into
lightningdevkit:mainfrom
wpaulino:funding-transaction-signed-rework

Conversation

@wpaulino

Copy link
Copy Markdown
Contributor

Alternative version to #4257

@wpaulinowpaulino added this to the 0.3 milestone Jan 22, 2026
@wpaulinowpaulino self-assigned this Jan 22, 2026
@ldk-reviews-bot

ldk-reviews-bot commented Jan 22, 2026

Copy link
Copy Markdown

👋 Thanks for assigning @jkczyz 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.

@codecov

codecovBot commented Jan 22, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 72.00957% with 117 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.07%. Comparing base (8cdc86a) to head (be67c67).
⚠️ Report is 37 commits behind head on main.

Files with missing linesPatch %Lines
lightning/src/ln/channel.rs72.72%54 Missing and 6 partials ⚠️
lightning/src/ln/channelmanager.rs71.42%46 Missing and 10 partials ⚠️
lightning/src/ln/interactivetxs.rs50.00%1 Missing ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #4336 +/- ##
==========================================
- Coverage 86.53% 86.07% -0.47% 
==========================================
Files 158 156 -2 Lines 103190 102804 -386 Branches 103190 102804 -386 ==========================================
- Hits 89300 88490 -810 - Misses 11469 11803 +334 - Partials 2421 2511 +90 
FlagCoverage Δ
fuzzing?
tests86.07% <72.00%> (+0.24%)⬆️

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.

@wpaulino
wpaulinoforce-pushed the funding-transaction-signed-rework branch from c0193fe to 0fc923cCompareJanuary 22, 2026 19:59
Comment threadlightning/src/ln/channel.rs Outdated
Comment on lines +11265 to 11274
// We should never be sending a `commitment_signed` in response to their
// `tx_signatures`.
debug_assert!(commitment_signed.is_none());

if let Some(tx_signatures) = tx_signatures {
peer_state.pending_msg_events.push(MessageSendEvent::SendTxSignatures {
node_id: *counterparty_node_id,
msg: tx_signatures,
});
}

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.

I'm a little confused by this. Why would we send our tx_signatures if we aren't sending our commitment_signed? Didn't we want to send both at once?

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've already sent it by this point, we're sending our tx_signatures here in response to theirs

Comment on lines +18998 to +18999
// We may need to regenerate [`Event::FundingTransactionReadyForSigning`] for channels that
// still need their holder `tx_signatures`.

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.

Does it make sense to start persisting this?

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.

Matt pointed out we shouldn't #4257 (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.

@TheBlueMatt Is the goal eventually to not write events that (a) can be re-constructed from ChannelMonitor / FundedChannel state or (b) aren't applicable across a restart? Presumable this falls into that category.

Seems there will at least be some events that aren't tied to a particular channel, so we may still need to persist some events?

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @TheBlueMatt! 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.

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

Hmm, seems like the last commit should really get have test coverage? I guess just our dual-funding coverage isn't that great?

Also, presumably this needs documentation for how to cancel a splice instead of signing and a test that does so?

Comment threadlightning/src/ln/channelmanager.rs Outdated
@wpaulino

Copy link
Copy Markdown
ContributorAuthor

Hmm, seems like the last commit should really get have test coverage? I guess just our dual-funding coverage isn't that great?

Yeah things aren't really hooked up yet to do proper testing.

Also, presumably this needs documentation for how to cancel a splice instead of signing and a test that does so?

Planned for a separate PR, no need for it to happen here.

@wpaulino
wpaulinoforce-pushed the funding-transaction-signed-rework branch 2 times, most recently from 734ad69 to 04a7560CompareJanuary 27, 2026 00:34
TheBlueMatt
TheBlueMatt previously approved these changes Jan 28, 2026

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

Question somewhat orthogonal to this PR - do we need a monitor update before sending our tx-sigs? Once we send it, its possible for our counterparty to respond with a commitment sigs and then we can't cancel. If we restart without chanman persistence and the user tries to cancel at that point we'll end up getting force-closed on.

I continue to be somewhat annoyed at how many expects there are in the splicing logic, and would feel a lot better if we had a full_stack_target seed that spliced so that at least the fuzzer could explore it a tiny bit (though of course a larger refactor to reduce them would be nice too).

Comment threadlightning/src/ln/channel.rs
Comment threadlightning/src/ln/channel.rs
@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @jkczyz! 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.

Comment on lines +18998 to +18999
// We may need to regenerate [`Event::FundingTransactionReadyForSigning`] for channels that
// still need their holder `tx_signatures`.

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.

@TheBlueMatt Is the goal eventually to not write events that (a) can be re-constructed from ChannelMonitor / FundedChannel state or (b) aren't applicable across a restart? Presumable this falls into that category.

Seems there will at least be some events that aren't tied to a particular channel, so we may still need to persist some events?

Comment threadlightning/src/ln/channelmanager.rs Outdated
);
}
}
return NotifyOption::DoPersist;

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.

Is this independent of any events being generated? (i.e., some stated in the channel was made, but is_connected was false?

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 still want to persist the funding signatures regardless of channel connectivity

Comment on lines +11264 to +11268

// We should never be sending a `commitment_signed` in response to their
// `tx_signatures`.
debug_assert!(commitment_signed.is_none());

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.

Would it make sense to return an error here?

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.

If we actually do, they'll force close on us, so we get the same result.

let funding_tx_opt = self.maybe_finalize_funding_tx();
let holder_tx_signatures = (self.holder_sends_tx_signatures_first
|| self.has_received_tx_signatures())
let holder_tx_signatures = (self.has_received_commitment_signed

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.

Is has_received_commitment_signed implied from has_received_tx_signatures?

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.

Yes, we force close when receiving tx_signatures without the commitment_signed first. I'd prefer keeping the code as is for clarity though.

Comment threadlightning/src/ln/channel.rs Outdated
Comment threadlightning/src/ln/channel.rs Outdated
Previously, we'd emit a FundingTransactionReadyForSigning event once the
initial commitment_signed is exchanged for a splicing/dual-funding
attempt and require users to call back with their signed inputs using
ChannelManager::funding_transaction_signed. While this approach worked
in practice, it prevents us from abandoning a splice if we cannot or no
longer wish to sign as the splice has already been committed to by this
point.
This commit reworks the API such that this is now possible. After
exchanging tx_complete, we will no longer immediately send our initial
commitment_signed. We will now emit the
FundingTransactionReadyForSigning event and wait for the user to call
back before releasing both our initial commitment_signed and our
tx_signatures. As a result, the event is now persisted, as there is only
one possible path in which it is generated. Note that we continue to
only emit the event if a local contribution to negotiated transaction
was made.
Future work will expose a cancellation API such that we can abandon
splice attempts safely (we can just force close the channel with
dual-funding).
This is crucial to enable the splice cancellation use case. When we
process the initial commitment signed from our counterparty, we queue a
monitor update that cannot be undone. To give the user a chance to abort
the splice negotiation before it's committed to, we buffer the message
until a successful call to `Channel::funding_transaction_signed` and
process it then.
Note that this is currently only done for splice and RBF attempts, as
if we want to abort a dual funding negotiation, we can just force close
the channel as it hasn't been funded yet.
Now that we require users to first call
`ChannelManager::funding_transaction_signed` before releasing any
signatures, it's possible that it is called before we receive the
initial commitment signed from our counterparty, which would transition
the channel to funded. Because of this, we need to support the API call
while the channel is still in the unfunded phase.
Note that this commit is mostly a code move of
`FundedChannel::funding_transaction_signed` to
`Channel::funding_transaction_signed` that doesn't alter the signing
logic.
@wpaulino
wpaulinoforce-pushed the funding-transaction-signed-rework branch from f2dd6f2 to be67c67CompareJanuary 29, 2026 18:03
@TheBlueMatt
TheBlueMatt merged commit fcc3d33 into lightningdevkit:mainJan 30, 2026
21 checks passed
@wpaulino
wpaulino deleted the funding-transaction-signed-rework branch January 30, 2026 02:08
@jkczyzjkczyz mentioned this pull request Mar 19, 2026
50 tasks
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@wpaulino@ldk-reviews-bot@TheBlueMatt@jkczyz
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Rework ChannelManager::funding_transaction_signed - #4336

Merged
TheBlueMatt merged 3 commits into
lightningdevkit:mainfrom
wpaulino:funding-transaction-signed-rework
Jan 30, 2026
Merged

Rework ChannelManager::funding_transaction_signed#4336
TheBlueMatt merged 3 commits into
lightningdevkit:mainfrom
wpaulino:funding-transaction-signed-rework

Conversation

@wpaulino

Copy link
Copy Markdown
Contributor

Alternative version to #4257

@wpaulinowpaulino added this to the 0.3 milestone Jan 22, 2026
@wpaulinowpaulino self-assigned this Jan 22, 2026
@ldk-reviews-bot

ldk-reviews-bot commented Jan 22, 2026

Copy link
Copy Markdown

👋 Thanks for assigning @jkczyz 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.

@codecov

codecovBot commented Jan 22, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 72.00957% with 117 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.07%. Comparing base (8cdc86a) to head (be67c67).
⚠️ Report is 37 commits behind head on main.

Files with missing linesPatch %Lines
lightning/src/ln/channel.rs72.72%54 Missing and 6 partials ⚠️
lightning/src/ln/channelmanager.rs71.42%46 Missing and 10 partials ⚠️
lightning/src/ln/interactivetxs.rs50.00%1 Missing ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #4336 +/- ##
==========================================
- Coverage 86.53% 86.07% -0.47% 
==========================================
Files 158 156 -2 Lines 103190 102804 -386 Branches 103190 102804 -386 ==========================================
- Hits 89300 88490 -810 - Misses 11469 11803 +334 - Partials 2421 2511 +90 
FlagCoverage Δ
fuzzing?
tests86.07% <72.00%> (+0.24%)⬆️

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.

@wpaulino
wpaulinoforce-pushed the funding-transaction-signed-rework branch from c0193fe to 0fc923cCompareJanuary 22, 2026 19:59
Comment threadlightning/src/ln/channel.rs Outdated
Comment on lines +11265 to 11274
// We should never be sending a `commitment_signed` in response to their
// `tx_signatures`.
debug_assert!(commitment_signed.is_none());

if let Some(tx_signatures) = tx_signatures {
peer_state.pending_msg_events.push(MessageSendEvent::SendTxSignatures {
node_id: *counterparty_node_id,
msg: tx_signatures,
});
}

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.

I'm a little confused by this. Why would we send our tx_signatures if we aren't sending our commitment_signed? Didn't we want to send both at once?

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've already sent it by this point, we're sending our tx_signatures here in response to theirs

Comment on lines +18998 to +18999
// We may need to regenerate [`Event::FundingTransactionReadyForSigning`] for channels that
// still need their holder `tx_signatures`.

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.

Does it make sense to start persisting this?

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.

Matt pointed out we shouldn't #4257 (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.

@TheBlueMatt Is the goal eventually to not write events that (a) can be re-constructed from ChannelMonitor / FundedChannel state or (b) aren't applicable across a restart? Presumable this falls into that category.

Seems there will at least be some events that aren't tied to a particular channel, so we may still need to persist some events?

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @TheBlueMatt! 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.

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

Hmm, seems like the last commit should really get have test coverage? I guess just our dual-funding coverage isn't that great?

Also, presumably this needs documentation for how to cancel a splice instead of signing and a test that does so?

Comment threadlightning/src/ln/channelmanager.rs Outdated
@wpaulino

Copy link
Copy Markdown
ContributorAuthor

Hmm, seems like the last commit should really get have test coverage? I guess just our dual-funding coverage isn't that great?

Yeah things aren't really hooked up yet to do proper testing.

Also, presumably this needs documentation for how to cancel a splice instead of signing and a test that does so?

Planned for a separate PR, no need for it to happen here.

@wpaulino
wpaulinoforce-pushed the funding-transaction-signed-rework branch 2 times, most recently from 734ad69 to 04a7560CompareJanuary 27, 2026 00:34
TheBlueMatt
TheBlueMatt previously approved these changes Jan 28, 2026

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

Question somewhat orthogonal to this PR - do we need a monitor update before sending our tx-sigs? Once we send it, its possible for our counterparty to respond with a commitment sigs and then we can't cancel. If we restart without chanman persistence and the user tries to cancel at that point we'll end up getting force-closed on.

I continue to be somewhat annoyed at how many expects there are in the splicing logic, and would feel a lot better if we had a full_stack_target seed that spliced so that at least the fuzzer could explore it a tiny bit (though of course a larger refactor to reduce them would be nice too).

Comment threadlightning/src/ln/channel.rs
Comment threadlightning/src/ln/channel.rs
@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @jkczyz! 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.

Comment on lines +18998 to +18999
// We may need to regenerate [`Event::FundingTransactionReadyForSigning`] for channels that
// still need their holder `tx_signatures`.

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.

@TheBlueMatt Is the goal eventually to not write events that (a) can be re-constructed from ChannelMonitor / FundedChannel state or (b) aren't applicable across a restart? Presumable this falls into that category.

Seems there will at least be some events that aren't tied to a particular channel, so we may still need to persist some events?

Comment threadlightning/src/ln/channelmanager.rs Outdated
);
}
}
return NotifyOption::DoPersist;

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.

Is this independent of any events being generated? (i.e., some stated in the channel was made, but is_connected was false?

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 still want to persist the funding signatures regardless of channel connectivity

Comment on lines +11264 to +11268

// We should never be sending a `commitment_signed` in response to their
// `tx_signatures`.
debug_assert!(commitment_signed.is_none());

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.

Would it make sense to return an error here?

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.

If we actually do, they'll force close on us, so we get the same result.

let funding_tx_opt = self.maybe_finalize_funding_tx();
let holder_tx_signatures = (self.holder_sends_tx_signatures_first
|| self.has_received_tx_signatures())
let holder_tx_signatures = (self.has_received_commitment_signed

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.

Is has_received_commitment_signed implied from has_received_tx_signatures?

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.

Yes, we force close when receiving tx_signatures without the commitment_signed first. I'd prefer keeping the code as is for clarity though.

Comment threadlightning/src/ln/channel.rs Outdated
Comment threadlightning/src/ln/channel.rs Outdated
Previously, we'd emit a FundingTransactionReadyForSigning event once the
initial commitment_signed is exchanged for a splicing/dual-funding
attempt and require users to call back with their signed inputs using
ChannelManager::funding_transaction_signed. While this approach worked
in practice, it prevents us from abandoning a splice if we cannot or no
longer wish to sign as the splice has already been committed to by this
point.
This commit reworks the API such that this is now possible. After
exchanging tx_complete, we will no longer immediately send our initial
commitment_signed. We will now emit the
FundingTransactionReadyForSigning event and wait for the user to call
back before releasing both our initial commitment_signed and our
tx_signatures. As a result, the event is now persisted, as there is only
one possible path in which it is generated. Note that we continue to
only emit the event if a local contribution to negotiated transaction
was made.
Future work will expose a cancellation API such that we can abandon
splice attempts safely (we can just force close the channel with
dual-funding).
This is crucial to enable the splice cancellation use case. When we
process the initial commitment signed from our counterparty, we queue a
monitor update that cannot be undone. To give the user a chance to abort
the splice negotiation before it's committed to, we buffer the message
until a successful call to `Channel::funding_transaction_signed` and
process it then.
Note that this is currently only done for splice and RBF attempts, as
if we want to abort a dual funding negotiation, we can just force close
the channel as it hasn't been funded yet.
Now that we require users to first call
`ChannelManager::funding_transaction_signed` before releasing any
signatures, it's possible that it is called before we receive the
initial commitment signed from our counterparty, which would transition
the channel to funded. Because of this, we need to support the API call
while the channel is still in the unfunded phase.
Note that this commit is mostly a code move of
`FundedChannel::funding_transaction_signed` to
`Channel::funding_transaction_signed` that doesn't alter the signing
logic.
@wpaulino
wpaulinoforce-pushed the funding-transaction-signed-rework branch from f2dd6f2 to be67c67CompareJanuary 29, 2026 18:03
@TheBlueMatt
TheBlueMatt merged commit fcc3d33 into lightningdevkit:mainJan 30, 2026
21 checks passed
@wpaulino
wpaulino deleted the funding-transaction-signed-rework branch January 30, 2026 02:08
@jkczyzjkczyz mentioned this pull request Mar 19, 2026
50 tasks
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@wpaulino@ldk-reviews-bot@TheBlueMatt@jkczyz
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Rework ChannelManager::funding_transaction_signed - #4336

Merged
TheBlueMatt merged 3 commits into
lightningdevkit:mainfrom
wpaulino:funding-transaction-signed-rework
Jan 30, 2026
Merged

Rework ChannelManager::funding_transaction_signed#4336
TheBlueMatt merged 3 commits into
lightningdevkit:mainfrom
wpaulino:funding-transaction-signed-rework

Conversation

@wpaulino

Copy link
Copy Markdown
Contributor

Alternative version to #4257

@wpaulinowpaulino added this to the 0.3 milestone Jan 22, 2026
@wpaulinowpaulino self-assigned this Jan 22, 2026
@ldk-reviews-bot

ldk-reviews-bot commented Jan 22, 2026

Copy link
Copy Markdown

👋 Thanks for assigning @jkczyz 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.

@codecov

codecovBot commented Jan 22, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 72.00957% with 117 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.07%. Comparing base (8cdc86a) to head (be67c67).
⚠️ Report is 37 commits behind head on main.

Files with missing linesPatch %Lines
lightning/src/ln/channel.rs72.72%54 Missing and 6 partials ⚠️
lightning/src/ln/channelmanager.rs71.42%46 Missing and 10 partials ⚠️
lightning/src/ln/interactivetxs.rs50.00%1 Missing ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #4336 +/- ##
==========================================
- Coverage 86.53% 86.07% -0.47% 
==========================================
Files 158 156 -2 Lines 103190 102804 -386 Branches 103190 102804 -386 ==========================================
- Hits 89300 88490 -810 - Misses 11469 11803 +334 - Partials 2421 2511 +90 
FlagCoverage Δ
fuzzing?
tests86.07% <72.00%> (+0.24%)⬆️

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.

@wpaulino
wpaulinoforce-pushed the funding-transaction-signed-rework branch from c0193fe to 0fc923cCompareJanuary 22, 2026 19:59
Comment threadlightning/src/ln/channel.rs Outdated
Comment on lines +11265 to 11274
// We should never be sending a `commitment_signed` in response to their
// `tx_signatures`.
debug_assert!(commitment_signed.is_none());

if let Some(tx_signatures) = tx_signatures {
peer_state.pending_msg_events.push(MessageSendEvent::SendTxSignatures {
node_id: *counterparty_node_id,
msg: tx_signatures,
});
}

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.

I'm a little confused by this. Why would we send our tx_signatures if we aren't sending our commitment_signed? Didn't we want to send both at once?

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've already sent it by this point, we're sending our tx_signatures here in response to theirs

Comment on lines +18998 to +18999
// We may need to regenerate [`Event::FundingTransactionReadyForSigning`] for channels that
// still need their holder `tx_signatures`.

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.

Does it make sense to start persisting this?

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.

Matt pointed out we shouldn't #4257 (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.

@TheBlueMatt Is the goal eventually to not write events that (a) can be re-constructed from ChannelMonitor / FundedChannel state or (b) aren't applicable across a restart? Presumable this falls into that category.

Seems there will at least be some events that aren't tied to a particular channel, so we may still need to persist some events?

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @TheBlueMatt! 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.

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

Hmm, seems like the last commit should really get have test coverage? I guess just our dual-funding coverage isn't that great?

Also, presumably this needs documentation for how to cancel a splice instead of signing and a test that does so?

Comment threadlightning/src/ln/channelmanager.rs Outdated
@wpaulino

Copy link
Copy Markdown
ContributorAuthor

Hmm, seems like the last commit should really get have test coverage? I guess just our dual-funding coverage isn't that great?

Yeah things aren't really hooked up yet to do proper testing.

Also, presumably this needs documentation for how to cancel a splice instead of signing and a test that does so?

Planned for a separate PR, no need for it to happen here.

@wpaulino
wpaulinoforce-pushed the funding-transaction-signed-rework branch 2 times, most recently from 734ad69 to 04a7560CompareJanuary 27, 2026 00:34
TheBlueMatt
TheBlueMatt previously approved these changes Jan 28, 2026

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

Question somewhat orthogonal to this PR - do we need a monitor update before sending our tx-sigs? Once we send it, its possible for our counterparty to respond with a commitment sigs and then we can't cancel. If we restart without chanman persistence and the user tries to cancel at that point we'll end up getting force-closed on.

I continue to be somewhat annoyed at how many expects there are in the splicing logic, and would feel a lot better if we had a full_stack_target seed that spliced so that at least the fuzzer could explore it a tiny bit (though of course a larger refactor to reduce them would be nice too).

Comment threadlightning/src/ln/channel.rs
Comment threadlightning/src/ln/channel.rs
@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @jkczyz! 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.

Comment on lines +18998 to +18999
// We may need to regenerate [`Event::FundingTransactionReadyForSigning`] for channels that
// still need their holder `tx_signatures`.

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.

@TheBlueMatt Is the goal eventually to not write events that (a) can be re-constructed from ChannelMonitor / FundedChannel state or (b) aren't applicable across a restart? Presumable this falls into that category.

Seems there will at least be some events that aren't tied to a particular channel, so we may still need to persist some events?

Comment threadlightning/src/ln/channelmanager.rs Outdated
);
}
}
return NotifyOption::DoPersist;

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.

Is this independent of any events being generated? (i.e., some stated in the channel was made, but is_connected was false?

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 still want to persist the funding signatures regardless of channel connectivity

Comment on lines +11264 to +11268

// We should never be sending a `commitment_signed` in response to their
// `tx_signatures`.
debug_assert!(commitment_signed.is_none());

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.

Would it make sense to return an error here?

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.

If we actually do, they'll force close on us, so we get the same result.

let funding_tx_opt = self.maybe_finalize_funding_tx();
let holder_tx_signatures = (self.holder_sends_tx_signatures_first
|| self.has_received_tx_signatures())
let holder_tx_signatures = (self.has_received_commitment_signed

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.

Is has_received_commitment_signed implied from has_received_tx_signatures?

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.

Yes, we force close when receiving tx_signatures without the commitment_signed first. I'd prefer keeping the code as is for clarity though.

Comment threadlightning/src/ln/channel.rs Outdated
Comment threadlightning/src/ln/channel.rs Outdated
Previously, we'd emit a FundingTransactionReadyForSigning event once the
initial commitment_signed is exchanged for a splicing/dual-funding
attempt and require users to call back with their signed inputs using
ChannelManager::funding_transaction_signed. While this approach worked
in practice, it prevents us from abandoning a splice if we cannot or no
longer wish to sign as the splice has already been committed to by this
point.
This commit reworks the API such that this is now possible. After
exchanging tx_complete, we will no longer immediately send our initial
commitment_signed. We will now emit the
FundingTransactionReadyForSigning event and wait for the user to call
back before releasing both our initial commitment_signed and our
tx_signatures. As a result, the event is now persisted, as there is only
one possible path in which it is generated. Note that we continue to
only emit the event if a local contribution to negotiated transaction
was made.
Future work will expose a cancellation API such that we can abandon
splice attempts safely (we can just force close the channel with
dual-funding).
This is crucial to enable the splice cancellation use case. When we
process the initial commitment signed from our counterparty, we queue a
monitor update that cannot be undone. To give the user a chance to abort
the splice negotiation before it's committed to, we buffer the message
until a successful call to `Channel::funding_transaction_signed` and
process it then.
Note that this is currently only done for splice and RBF attempts, as
if we want to abort a dual funding negotiation, we can just force close
the channel as it hasn't been funded yet.
Now that we require users to first call
`ChannelManager::funding_transaction_signed` before releasing any
signatures, it's possible that it is called before we receive the
initial commitment signed from our counterparty, which would transition
the channel to funded. Because of this, we need to support the API call
while the channel is still in the unfunded phase.
Note that this commit is mostly a code move of
`FundedChannel::funding_transaction_signed` to
`Channel::funding_transaction_signed` that doesn't alter the signing
logic.
@wpaulino
wpaulinoforce-pushed the funding-transaction-signed-rework branch from f2dd6f2 to be67c67CompareJanuary 29, 2026 18:03
@TheBlueMatt
TheBlueMatt merged commit fcc3d33 into lightningdevkit:mainJan 30, 2026
21 checks passed
@wpaulino
wpaulino deleted the funding-transaction-signed-rework branch January 30, 2026 02:08
@jkczyzjkczyz mentioned this pull request Mar 19, 2026
50 tasks
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@wpaulino@ldk-reviews-bot@TheBlueMatt@jkczyz
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Rework ChannelManager::funding_transaction_signed - #4336

Merged
TheBlueMatt merged 3 commits into
lightningdevkit:mainfrom
wpaulino:funding-transaction-signed-rework
Jan 30, 2026
Merged

Rework ChannelManager::funding_transaction_signed#4336
TheBlueMatt merged 3 commits into
lightningdevkit:mainfrom
wpaulino:funding-transaction-signed-rework

Conversation

@wpaulino

Copy link
Copy Markdown
Contributor

Alternative version to #4257

@wpaulinowpaulino added this to the 0.3 milestone Jan 22, 2026
@wpaulinowpaulino self-assigned this Jan 22, 2026
@ldk-reviews-bot

ldk-reviews-bot commented Jan 22, 2026

Copy link
Copy Markdown

👋 Thanks for assigning @jkczyz 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.

@codecov

codecovBot commented Jan 22, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 72.00957% with 117 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.07%. Comparing base (8cdc86a) to head (be67c67).
⚠️ Report is 37 commits behind head on main.

Files with missing linesPatch %Lines
lightning/src/ln/channel.rs72.72%54 Missing and 6 partials ⚠️
lightning/src/ln/channelmanager.rs71.42%46 Missing and 10 partials ⚠️
lightning/src/ln/interactivetxs.rs50.00%1 Missing ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #4336 +/- ##
==========================================
- Coverage 86.53% 86.07% -0.47% 
==========================================
Files 158 156 -2 Lines 103190 102804 -386 Branches 103190 102804 -386 ==========================================
- Hits 89300 88490 -810 - Misses 11469 11803 +334 - Partials 2421 2511 +90 
FlagCoverage Δ
fuzzing?
tests86.07% <72.00%> (+0.24%)⬆️

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.

@wpaulino
wpaulinoforce-pushed the funding-transaction-signed-rework branch from c0193fe to 0fc923cCompareJanuary 22, 2026 19:59
Comment threadlightning/src/ln/channel.rs Outdated
Comment on lines +11265 to 11274
// We should never be sending a `commitment_signed` in response to their
// `tx_signatures`.
debug_assert!(commitment_signed.is_none());

if let Some(tx_signatures) = tx_signatures {
peer_state.pending_msg_events.push(MessageSendEvent::SendTxSignatures {
node_id: *counterparty_node_id,
msg: tx_signatures,
});
}

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.

I'm a little confused by this. Why would we send our tx_signatures if we aren't sending our commitment_signed? Didn't we want to send both at once?

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've already sent it by this point, we're sending our tx_signatures here in response to theirs

Comment on lines +18998 to +18999
// We may need to regenerate [`Event::FundingTransactionReadyForSigning`] for channels that
// still need their holder `tx_signatures`.

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.

Does it make sense to start persisting this?

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.

Matt pointed out we shouldn't #4257 (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.

@TheBlueMatt Is the goal eventually to not write events that (a) can be re-constructed from ChannelMonitor / FundedChannel state or (b) aren't applicable across a restart? Presumable this falls into that category.

Seems there will at least be some events that aren't tied to a particular channel, so we may still need to persist some events?

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @TheBlueMatt! 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.

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

Hmm, seems like the last commit should really get have test coverage? I guess just our dual-funding coverage isn't that great?

Also, presumably this needs documentation for how to cancel a splice instead of signing and a test that does so?

Comment threadlightning/src/ln/channelmanager.rs Outdated
@wpaulino

Copy link
Copy Markdown
ContributorAuthor

Hmm, seems like the last commit should really get have test coverage? I guess just our dual-funding coverage isn't that great?

Yeah things aren't really hooked up yet to do proper testing.

Also, presumably this needs documentation for how to cancel a splice instead of signing and a test that does so?

Planned for a separate PR, no need for it to happen here.

@wpaulino
wpaulinoforce-pushed the funding-transaction-signed-rework branch 2 times, most recently from 734ad69 to 04a7560CompareJanuary 27, 2026 00:34
TheBlueMatt
TheBlueMatt previously approved these changes Jan 28, 2026

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

Question somewhat orthogonal to this PR - do we need a monitor update before sending our tx-sigs? Once we send it, its possible for our counterparty to respond with a commitment sigs and then we can't cancel. If we restart without chanman persistence and the user tries to cancel at that point we'll end up getting force-closed on.

I continue to be somewhat annoyed at how many expects there are in the splicing logic, and would feel a lot better if we had a full_stack_target seed that spliced so that at least the fuzzer could explore it a tiny bit (though of course a larger refactor to reduce them would be nice too).

Comment threadlightning/src/ln/channel.rs
Comment threadlightning/src/ln/channel.rs
@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @jkczyz! 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.

Comment on lines +18998 to +18999
// We may need to regenerate [`Event::FundingTransactionReadyForSigning`] for channels that
// still need their holder `tx_signatures`.

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.

@TheBlueMatt Is the goal eventually to not write events that (a) can be re-constructed from ChannelMonitor / FundedChannel state or (b) aren't applicable across a restart? Presumable this falls into that category.

Seems there will at least be some events that aren't tied to a particular channel, so we may still need to persist some events?

Comment threadlightning/src/ln/channelmanager.rs Outdated
);
}
}
return NotifyOption::DoPersist;

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.

Is this independent of any events being generated? (i.e., some stated in the channel was made, but is_connected was false?

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 still want to persist the funding signatures regardless of channel connectivity

Comment on lines +11264 to +11268

// We should never be sending a `commitment_signed` in response to their
// `tx_signatures`.
debug_assert!(commitment_signed.is_none());

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.

Would it make sense to return an error here?

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.

If we actually do, they'll force close on us, so we get the same result.

let funding_tx_opt = self.maybe_finalize_funding_tx();
let holder_tx_signatures = (self.holder_sends_tx_signatures_first
|| self.has_received_tx_signatures())
let holder_tx_signatures = (self.has_received_commitment_signed

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.

Is has_received_commitment_signed implied from has_received_tx_signatures?

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.

Yes, we force close when receiving tx_signatures without the commitment_signed first. I'd prefer keeping the code as is for clarity though.

Comment threadlightning/src/ln/channel.rs Outdated
Comment threadlightning/src/ln/channel.rs Outdated
Previously, we'd emit a FundingTransactionReadyForSigning event once the
initial commitment_signed is exchanged for a splicing/dual-funding
attempt and require users to call back with their signed inputs using
ChannelManager::funding_transaction_signed. While this approach worked
in practice, it prevents us from abandoning a splice if we cannot or no
longer wish to sign as the splice has already been committed to by this
point.
This commit reworks the API such that this is now possible. After
exchanging tx_complete, we will no longer immediately send our initial
commitment_signed. We will now emit the
FundingTransactionReadyForSigning event and wait for the user to call
back before releasing both our initial commitment_signed and our
tx_signatures. As a result, the event is now persisted, as there is only
one possible path in which it is generated. Note that we continue to
only emit the event if a local contribution to negotiated transaction
was made.
Future work will expose a cancellation API such that we can abandon
splice attempts safely (we can just force close the channel with
dual-funding).
This is crucial to enable the splice cancellation use case. When we
process the initial commitment signed from our counterparty, we queue a
monitor update that cannot be undone. To give the user a chance to abort
the splice negotiation before it's committed to, we buffer the message
until a successful call to `Channel::funding_transaction_signed` and
process it then.
Note that this is currently only done for splice and RBF attempts, as
if we want to abort a dual funding negotiation, we can just force close
the channel as it hasn't been funded yet.
Now that we require users to first call
`ChannelManager::funding_transaction_signed` before releasing any
signatures, it's possible that it is called before we receive the
initial commitment signed from our counterparty, which would transition
the channel to funded. Because of this, we need to support the API call
while the channel is still in the unfunded phase.
Note that this commit is mostly a code move of
`FundedChannel::funding_transaction_signed` to
`Channel::funding_transaction_signed` that doesn't alter the signing
logic.
@wpaulino
wpaulinoforce-pushed the funding-transaction-signed-rework branch from f2dd6f2 to be67c67CompareJanuary 29, 2026 18:03
@TheBlueMatt
TheBlueMatt merged commit fcc3d33 into lightningdevkit:mainJan 30, 2026
21 checks passed
@wpaulino
wpaulino deleted the funding-transaction-signed-rework branch January 30, 2026 02:08
@jkczyzjkczyz mentioned this pull request Mar 19, 2026
50 tasks
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

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