Splice with mempool fuzz fixes - #4708

Closed
wpaulino wants to merge 6 commits into
lightningdevkit:mainfrom
wpaulino:splice-with-mempool-fuzz-fixes
Closed

Splice with mempool fuzz fixes#4708
wpaulino wants to merge 6 commits into
lightningdevkit:mainfrom
wpaulino:splice-with-mempool-fuzz-fixes

Conversation

@wpaulino

Copy link
Copy Markdown
Contributor

This PR includes a few more bug fixes that were discovered by the chanmon_consistency_target after #4657.

Fixes#4696.

@wpaulinowpaulino added this to the 0.3 milestone Jun 17, 2026
@wpaulino
wpaulino requested a review from jkczyzJune 17, 2026 18:38
@wpaulinowpaulino self-assigned this Jun 17, 2026
@ldk-reviews-bot

ldk-reviews-bot commented Jun 17, 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.

@ldk-claude-review-bot

ldk-claude-review-bot commented Jun 17, 2026

Copy link
Copy Markdown
Collaborator

I have re-verified the key changes against the actual code. Everything is consistent with my prior review:

  • validate_tx_init_rbf reordering (channel.rs:13390-13438) behaves as expected; the no-candidate + negotiation-in-progress edge only changes the abort message, both paths abort.
  • The fuzz allowlist addition for "contribution no longer valid at quiescence" correctly matches the still-WarnAndDisconnect re-validation path at channel.rs:14656.
  • has_local_contribution gating, the splice_funding_failed_for! macro refactor, the new AbortReason variants, and the from_chan_no_close.clone() change are all consistent and correct.

No issues found.

@wpaulino
wpaulinoforce-pushed the splice-with-mempool-fuzz-fixes branch from bc23128 to a120ac1CompareJune 17, 2026 21:23
@joostjager

joostjager commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

I updated #4699 to hopefully no longer trigger false positives. Removed the splice state assert after event handling, because it wasn't reliable.

Still there seem a lingering problem (with #4699 applied to this PR) using byte string 0080a1ffa1ffa1ffa1ffa1ffa1ffa1ffa1ffa1ffa1ffa1ffa080a1ff. The failure is tx_abort left splice negotiation state behind: QueuedAction: node 0 rejects node 1's tx_init_rbf with Insufficient RBF feerate and sends tx_abort; node 1 logs the counterparty abort, then a queued splice action on node 0 still progresses far enough to deliver another stfu before the echoed tx_abort is processed, leaving QueuedAction behind when the harness checks tx_abort cleanup.

@joostjager

Copy link
Copy Markdown
Contributor

The one above also seems to be a false positive. The queued contribution is not stale state left behind by the aborted tx_init_rbf. It is a newer local splice attempt on node 0 that starts progressing before node 0 processes node 1’s echoed tx_abort.

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

@ldk-reviews-bot

Copy link
Copy Markdown

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

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 3rd 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 +7185 to +7187
/// Whether the holder contributed local inputs or outputs to the negotiated splice.
pub has_local_contribution: bool,

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.

Can't we simply return None instead of Some(SpliceFundingNegotiated{ .. })?

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.

No because there's a case where we rely on SpliceFundingNegotiated being set to free the holding cell after quiescence terminates.

Comment on lines -13136 to -13140
if self.holder_commitment_point.current_point().is_none() {
return Err(ChannelError::WarnAndDisconnect(format!(
"Channel {} commitment point needs to be advanced once before spliced",
self.context.channel_id(),
)));

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.

Wasn't this performed early because it indicates the channel was created before we persisted the current commitment point? (a7ba4dd) Any specific reason to move it later?

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.

I don't see why the ordering should matter as long as we're still failing because of it. I just wanted to keep the WarnAndDisconnect paths together, and since we can use tx_abort to fail this specific case, we must be quiescent first to do so.

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

Copy link
Copy Markdown

👋 The first review has been submitted!

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

@jkczyzjkczyz left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM

@joostjager Did you have any other concerns left from your earlier comments?

There's no need to inform users of negotiated splices when they're not
contributing as it just produces noise. Once they do start contributing,
they cannot stop, so we always emit the event going forward. Note that
we still emit `Event::ChannelReady` with the new locked funding outpoint
for each locked splice, so users can still learn that a splice occurred
that way.
Previously, this could result in an acceptor not receiving a
`Event::SpliceNegotiationFailed` for a splice in which they reused the
same contribution (except for the feerate change). Our API should
guarantee that users should always see `SpliceNegotiated` and
`SpliceNegotiationFailed` events for splices that they contribute to.
While a contribution may be valid at the time the splice is requested,
quiescence still needs to happen, which can affect the balances of the
channel as it fully settles all pending state. After doing so, it's
possible that the contribution is no longer valid. Since quiescence
itself doesn't have a terminal message, we see a `WarnAndDisconnect`
event happen.
We keep some `WarnAndDisconnect` cases as mandated by the spec, but
otherwise prefer sending `tx_abort` to terminate quiescence and avoid
reconnection loops.
Send `tx_abort` to terminate quiescence and avoid reconnection loops.
This mirrors what we do for counterparty `splice_init` messages, making
sure we don't accept RBFs once a channel has requested shutdown.
@wpaulino
wpaulinoforce-pushed the splice-with-mempool-fuzz-fixes branch from a120ac1 to 19c358eCompareJune 29, 2026 18:03
@joostjager

Copy link
Copy Markdown
Contributor

LGTM

@joostjager Did you have any other concerns left from your earlier comments?

No, nothing left

@codecov

codecovBot commented Jun 30, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 75.37313% with 33 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.94%. Comparing base (1ff2247) to head (19c358e).
⚠️ Report is 11 commits behind head on main.

Files with missing linesPatch %Lines
lightning/src/ln/channel.rs77.02%17 Missing ⚠️
lightning/src/ln/channelmanager.rs71.42%14 Missing and 2 partials ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #4708 +/- ##
==========================================
- Coverage 86.95% 86.94% -0.01% 
==========================================
Files 161 161 Lines 111659 111680 +21 Branches 111659 111680 +21 ==========================================
+ Hits 97090 97104 +14 - Misses 12060 12068 +8 + Partials 2509 2508 -1 
FlagCoverage Δ
fuzzing-fake-hashes8.43% <0.00%> (-0.01%)⬇️
fuzzing-real-hashes32.42% <45.52%> (+<0.01%)⬆️
tests86.27% <75.37%> (-0.01%)⬇️

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

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

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

@ldk-reviews-bot

Copy link
Copy Markdown

Hi @wpaulino,

Thanks for your contributions to rust-lightning!

After too many struggles with bugs, outages, contributor bans, and, finally, a multi-week CI ban, the rust-lightning project is moving off of GitHub for day-to-day development.

You can still file issues and access the git tree here, but PRs will now take place exclusively at https://git.rust-bitcoin.org/. As such, this PR has been migrated to https://git.rust-bitcoin.org/lightningdevkit/rust-lightning/pulls/4708

If you log in using GitHub (or otherwise link your GitHub account from https://git.rust-bitcoin.org/user/settings/security), ownership of your PRs, issues, and comments will automatically transfer. To push updates to this PR, you'll need to use git push git@gitea-ssh.bitcoin.ninja:lightningdevkit/rust-lightning YOUR_LOCAL_COMMIT_OR_BRANCH:wpaulino/splice-with-mempool-fuzz-fixes. This may require a permissions change - if it doesn't work initially just leave a comment and we'll get you access.

ldk-reviews-bot pushed a commit that referenced this pull request Jul 5, 2026
from wpaulino/splice-with-mempool-fuzz-fixes into main
Reviewed-on: https://git.rust-bitcoin.org/lightningdevkit/rust-lightning/pulls/4708
Reviewed-by: jkczyz <jkczyz@noreply.gitea.bitcoin.ninja>
Reviewed-by: Matt Corallo <matt@noreply.gitea.bitcoin.ninja>
@wpaulino
wpaulino deleted the splice-with-mempool-fuzz-fixes branch August 5, 2026 17:50
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Splice negotiation failure handling issues

5 participants

@wpaulino@ldk-reviews-bot@ldk-claude-review-bot@joostjager@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

Splice with mempool fuzz fixes - #4708

Closed
wpaulino wants to merge 6 commits into
lightningdevkit:mainfrom
wpaulino:splice-with-mempool-fuzz-fixes
Closed

Splice with mempool fuzz fixes#4708
wpaulino wants to merge 6 commits into
lightningdevkit:mainfrom
wpaulino:splice-with-mempool-fuzz-fixes

Conversation

@wpaulino

Copy link
Copy Markdown
Contributor

This PR includes a few more bug fixes that were discovered by the chanmon_consistency_target after #4657.

Fixes#4696.

@wpaulinowpaulino added this to the 0.3 milestone Jun 17, 2026
@wpaulino
wpaulino requested a review from jkczyzJune 17, 2026 18:38
@wpaulinowpaulino self-assigned this Jun 17, 2026
@ldk-reviews-bot

ldk-reviews-bot commented Jun 17, 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.

@ldk-claude-review-bot

ldk-claude-review-bot commented Jun 17, 2026

Copy link
Copy Markdown
Collaborator

I have re-verified the key changes against the actual code. Everything is consistent with my prior review:

  • validate_tx_init_rbf reordering (channel.rs:13390-13438) behaves as expected; the no-candidate + negotiation-in-progress edge only changes the abort message, both paths abort.
  • The fuzz allowlist addition for "contribution no longer valid at quiescence" correctly matches the still-WarnAndDisconnect re-validation path at channel.rs:14656.
  • has_local_contribution gating, the splice_funding_failed_for! macro refactor, the new AbortReason variants, and the from_chan_no_close.clone() change are all consistent and correct.

No issues found.

@wpaulino
wpaulinoforce-pushed the splice-with-mempool-fuzz-fixes branch from bc23128 to a120ac1CompareJune 17, 2026 21:23
@joostjager

joostjager commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

I updated #4699 to hopefully no longer trigger false positives. Removed the splice state assert after event handling, because it wasn't reliable.

Still there seem a lingering problem (with #4699 applied to this PR) using byte string 0080a1ffa1ffa1ffa1ffa1ffa1ffa1ffa1ffa1ffa1ffa1ffa080a1ff. The failure is tx_abort left splice negotiation state behind: QueuedAction: node 0 rejects node 1's tx_init_rbf with Insufficient RBF feerate and sends tx_abort; node 1 logs the counterparty abort, then a queued splice action on node 0 still progresses far enough to deliver another stfu before the echoed tx_abort is processed, leaving QueuedAction behind when the harness checks tx_abort cleanup.

@joostjager

Copy link
Copy Markdown
Contributor

The one above also seems to be a false positive. The queued contribution is not stale state left behind by the aborted tx_init_rbf. It is a newer local splice attempt on node 0 that starts progressing before node 0 processes node 1’s echoed tx_abort.

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

@ldk-reviews-bot

Copy link
Copy Markdown

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

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 3rd 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 +7185 to +7187
/// Whether the holder contributed local inputs or outputs to the negotiated splice.
pub has_local_contribution: bool,

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.

Can't we simply return None instead of Some(SpliceFundingNegotiated{ .. })?

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.

No because there's a case where we rely on SpliceFundingNegotiated being set to free the holding cell after quiescence terminates.

Comment on lines -13136 to -13140
if self.holder_commitment_point.current_point().is_none() {
return Err(ChannelError::WarnAndDisconnect(format!(
"Channel {} commitment point needs to be advanced once before spliced",
self.context.channel_id(),
)));

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.

Wasn't this performed early because it indicates the channel was created before we persisted the current commitment point? (a7ba4dd) Any specific reason to move it later?

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.

I don't see why the ordering should matter as long as we're still failing because of it. I just wanted to keep the WarnAndDisconnect paths together, and since we can use tx_abort to fail this specific case, we must be quiescent first to do so.

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

Copy link
Copy Markdown

👋 The first review has been submitted!

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

@jkczyzjkczyz left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM

@joostjager Did you have any other concerns left from your earlier comments?

There's no need to inform users of negotiated splices when they're not
contributing as it just produces noise. Once they do start contributing,
they cannot stop, so we always emit the event going forward. Note that
we still emit `Event::ChannelReady` with the new locked funding outpoint
for each locked splice, so users can still learn that a splice occurred
that way.
Previously, this could result in an acceptor not receiving a
`Event::SpliceNegotiationFailed` for a splice in which they reused the
same contribution (except for the feerate change). Our API should
guarantee that users should always see `SpliceNegotiated` and
`SpliceNegotiationFailed` events for splices that they contribute to.
While a contribution may be valid at the time the splice is requested,
quiescence still needs to happen, which can affect the balances of the
channel as it fully settles all pending state. After doing so, it's
possible that the contribution is no longer valid. Since quiescence
itself doesn't have a terminal message, we see a `WarnAndDisconnect`
event happen.
We keep some `WarnAndDisconnect` cases as mandated by the spec, but
otherwise prefer sending `tx_abort` to terminate quiescence and avoid
reconnection loops.
Send `tx_abort` to terminate quiescence and avoid reconnection loops.
This mirrors what we do for counterparty `splice_init` messages, making
sure we don't accept RBFs once a channel has requested shutdown.
@wpaulino
wpaulinoforce-pushed the splice-with-mempool-fuzz-fixes branch from a120ac1 to 19c358eCompareJune 29, 2026 18:03
@joostjager

Copy link
Copy Markdown
Contributor

LGTM

@joostjager Did you have any other concerns left from your earlier comments?

No, nothing left

@codecov

codecovBot commented Jun 30, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 75.37313% with 33 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.94%. Comparing base (1ff2247) to head (19c358e).
⚠️ Report is 11 commits behind head on main.

Files with missing linesPatch %Lines
lightning/src/ln/channel.rs77.02%17 Missing ⚠️
lightning/src/ln/channelmanager.rs71.42%14 Missing and 2 partials ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #4708 +/- ##
==========================================
- Coverage 86.95% 86.94% -0.01% 
==========================================
Files 161 161 Lines 111659 111680 +21 Branches 111659 111680 +21 ==========================================
+ Hits 97090 97104 +14 - Misses 12060 12068 +8 + Partials 2509 2508 -1 
FlagCoverage Δ
fuzzing-fake-hashes8.43% <0.00%> (-0.01%)⬇️
fuzzing-real-hashes32.42% <45.52%> (+<0.01%)⬆️
tests86.27% <75.37%> (-0.01%)⬇️

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

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

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

@ldk-reviews-bot

Copy link
Copy Markdown

Hi @wpaulino,

Thanks for your contributions to rust-lightning!

After too many struggles with bugs, outages, contributor bans, and, finally, a multi-week CI ban, the rust-lightning project is moving off of GitHub for day-to-day development.

You can still file issues and access the git tree here, but PRs will now take place exclusively at https://git.rust-bitcoin.org/. As such, this PR has been migrated to https://git.rust-bitcoin.org/lightningdevkit/rust-lightning/pulls/4708

If you log in using GitHub (or otherwise link your GitHub account from https://git.rust-bitcoin.org/user/settings/security), ownership of your PRs, issues, and comments will automatically transfer. To push updates to this PR, you'll need to use git push git@gitea-ssh.bitcoin.ninja:lightningdevkit/rust-lightning YOUR_LOCAL_COMMIT_OR_BRANCH:wpaulino/splice-with-mempool-fuzz-fixes. This may require a permissions change - if it doesn't work initially just leave a comment and we'll get you access.

ldk-reviews-bot pushed a commit that referenced this pull request Jul 5, 2026
from wpaulino/splice-with-mempool-fuzz-fixes into main
Reviewed-on: https://git.rust-bitcoin.org/lightningdevkit/rust-lightning/pulls/4708
Reviewed-by: jkczyz <jkczyz@noreply.gitea.bitcoin.ninja>
Reviewed-by: Matt Corallo <matt@noreply.gitea.bitcoin.ninja>
@wpaulino
wpaulino deleted the splice-with-mempool-fuzz-fixes branch August 5, 2026 17:50
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Splice negotiation failure handling issues

5 participants

@wpaulino@ldk-reviews-bot@ldk-claude-review-bot@joostjager@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

Splice with mempool fuzz fixes - #4708

Closed
wpaulino wants to merge 6 commits into
lightningdevkit:mainfrom
wpaulino:splice-with-mempool-fuzz-fixes
Closed

Splice with mempool fuzz fixes#4708
wpaulino wants to merge 6 commits into
lightningdevkit:mainfrom
wpaulino:splice-with-mempool-fuzz-fixes

Conversation

@wpaulino

Copy link
Copy Markdown
Contributor

This PR includes a few more bug fixes that were discovered by the chanmon_consistency_target after #4657.

Fixes#4696.

@wpaulinowpaulino added this to the 0.3 milestone Jun 17, 2026
@wpaulino
wpaulino requested a review from jkczyzJune 17, 2026 18:38
@wpaulinowpaulino self-assigned this Jun 17, 2026
@ldk-reviews-bot

ldk-reviews-bot commented Jun 17, 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.

@ldk-claude-review-bot

ldk-claude-review-bot commented Jun 17, 2026

Copy link
Copy Markdown
Collaborator

I have re-verified the key changes against the actual code. Everything is consistent with my prior review:

  • validate_tx_init_rbf reordering (channel.rs:13390-13438) behaves as expected; the no-candidate + negotiation-in-progress edge only changes the abort message, both paths abort.
  • The fuzz allowlist addition for "contribution no longer valid at quiescence" correctly matches the still-WarnAndDisconnect re-validation path at channel.rs:14656.
  • has_local_contribution gating, the splice_funding_failed_for! macro refactor, the new AbortReason variants, and the from_chan_no_close.clone() change are all consistent and correct.

No issues found.

@wpaulino
wpaulinoforce-pushed the splice-with-mempool-fuzz-fixes branch from bc23128 to a120ac1CompareJune 17, 2026 21:23
@joostjager

joostjager commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

I updated #4699 to hopefully no longer trigger false positives. Removed the splice state assert after event handling, because it wasn't reliable.

Still there seem a lingering problem (with #4699 applied to this PR) using byte string 0080a1ffa1ffa1ffa1ffa1ffa1ffa1ffa1ffa1ffa1ffa1ffa080a1ff. The failure is tx_abort left splice negotiation state behind: QueuedAction: node 0 rejects node 1's tx_init_rbf with Insufficient RBF feerate and sends tx_abort; node 1 logs the counterparty abort, then a queued splice action on node 0 still progresses far enough to deliver another stfu before the echoed tx_abort is processed, leaving QueuedAction behind when the harness checks tx_abort cleanup.

@joostjager

Copy link
Copy Markdown
Contributor

The one above also seems to be a false positive. The queued contribution is not stale state left behind by the aborted tx_init_rbf. It is a newer local splice attempt on node 0 that starts progressing before node 0 processes node 1’s echoed tx_abort.

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

@ldk-reviews-bot

Copy link
Copy Markdown

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

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 3rd 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 +7185 to +7187
/// Whether the holder contributed local inputs or outputs to the negotiated splice.
pub has_local_contribution: bool,

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.

Can't we simply return None instead of Some(SpliceFundingNegotiated{ .. })?

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.

No because there's a case where we rely on SpliceFundingNegotiated being set to free the holding cell after quiescence terminates.

Comment on lines -13136 to -13140
if self.holder_commitment_point.current_point().is_none() {
return Err(ChannelError::WarnAndDisconnect(format!(
"Channel {} commitment point needs to be advanced once before spliced",
self.context.channel_id(),
)));

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.

Wasn't this performed early because it indicates the channel was created before we persisted the current commitment point? (a7ba4dd) Any specific reason to move it later?

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.

I don't see why the ordering should matter as long as we're still failing because of it. I just wanted to keep the WarnAndDisconnect paths together, and since we can use tx_abort to fail this specific case, we must be quiescent first to do so.

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

Copy link
Copy Markdown

👋 The first review has been submitted!

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

@jkczyzjkczyz left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM

@joostjager Did you have any other concerns left from your earlier comments?

There's no need to inform users of negotiated splices when they're not
contributing as it just produces noise. Once they do start contributing,
they cannot stop, so we always emit the event going forward. Note that
we still emit `Event::ChannelReady` with the new locked funding outpoint
for each locked splice, so users can still learn that a splice occurred
that way.
Previously, this could result in an acceptor not receiving a
`Event::SpliceNegotiationFailed` for a splice in which they reused the
same contribution (except for the feerate change). Our API should
guarantee that users should always see `SpliceNegotiated` and
`SpliceNegotiationFailed` events for splices that they contribute to.
While a contribution may be valid at the time the splice is requested,
quiescence still needs to happen, which can affect the balances of the
channel as it fully settles all pending state. After doing so, it's
possible that the contribution is no longer valid. Since quiescence
itself doesn't have a terminal message, we see a `WarnAndDisconnect`
event happen.
We keep some `WarnAndDisconnect` cases as mandated by the spec, but
otherwise prefer sending `tx_abort` to terminate quiescence and avoid
reconnection loops.
Send `tx_abort` to terminate quiescence and avoid reconnection loops.
This mirrors what we do for counterparty `splice_init` messages, making
sure we don't accept RBFs once a channel has requested shutdown.
@wpaulino
wpaulinoforce-pushed the splice-with-mempool-fuzz-fixes branch from a120ac1 to 19c358eCompareJune 29, 2026 18:03
@joostjager

Copy link
Copy Markdown
Contributor

LGTM

@joostjager Did you have any other concerns left from your earlier comments?

No, nothing left

@codecov

codecovBot commented Jun 30, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 75.37313% with 33 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.94%. Comparing base (1ff2247) to head (19c358e).
⚠️ Report is 11 commits behind head on main.

Files with missing linesPatch %Lines
lightning/src/ln/channel.rs77.02%17 Missing ⚠️
lightning/src/ln/channelmanager.rs71.42%14 Missing and 2 partials ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #4708 +/- ##
==========================================
- Coverage 86.95% 86.94% -0.01% 
==========================================
Files 161 161 Lines 111659 111680 +21 Branches 111659 111680 +21 ==========================================
+ Hits 97090 97104 +14 - Misses 12060 12068 +8 + Partials 2509 2508 -1 
FlagCoverage Δ
fuzzing-fake-hashes8.43% <0.00%> (-0.01%)⬇️
fuzzing-real-hashes32.42% <45.52%> (+<0.01%)⬆️
tests86.27% <75.37%> (-0.01%)⬇️

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

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

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

@ldk-reviews-bot

Copy link
Copy Markdown

Hi @wpaulino,

Thanks for your contributions to rust-lightning!

After too many struggles with bugs, outages, contributor bans, and, finally, a multi-week CI ban, the rust-lightning project is moving off of GitHub for day-to-day development.

You can still file issues and access the git tree here, but PRs will now take place exclusively at https://git.rust-bitcoin.org/. As such, this PR has been migrated to https://git.rust-bitcoin.org/lightningdevkit/rust-lightning/pulls/4708

If you log in using GitHub (or otherwise link your GitHub account from https://git.rust-bitcoin.org/user/settings/security), ownership of your PRs, issues, and comments will automatically transfer. To push updates to this PR, you'll need to use git push git@gitea-ssh.bitcoin.ninja:lightningdevkit/rust-lightning YOUR_LOCAL_COMMIT_OR_BRANCH:wpaulino/splice-with-mempool-fuzz-fixes. This may require a permissions change - if it doesn't work initially just leave a comment and we'll get you access.

ldk-reviews-bot pushed a commit that referenced this pull request Jul 5, 2026
from wpaulino/splice-with-mempool-fuzz-fixes into main
Reviewed-on: https://git.rust-bitcoin.org/lightningdevkit/rust-lightning/pulls/4708
Reviewed-by: jkczyz <jkczyz@noreply.gitea.bitcoin.ninja>
Reviewed-by: Matt Corallo <matt@noreply.gitea.bitcoin.ninja>
@wpaulino
wpaulino deleted the splice-with-mempool-fuzz-fixes branch August 5, 2026 17:50
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Splice negotiation failure handling issues

5 participants

@wpaulino@ldk-reviews-bot@ldk-claude-review-bot@joostjager@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

Splice with mempool fuzz fixes - #4708

Closed
wpaulino wants to merge 6 commits into
lightningdevkit:mainfrom
wpaulino:splice-with-mempool-fuzz-fixes
Closed

Splice with mempool fuzz fixes#4708
wpaulino wants to merge 6 commits into
lightningdevkit:mainfrom
wpaulino:splice-with-mempool-fuzz-fixes

Conversation

@wpaulino

Copy link
Copy Markdown
Contributor

This PR includes a few more bug fixes that were discovered by the chanmon_consistency_target after #4657.

Fixes#4696.

@wpaulinowpaulino added this to the 0.3 milestone Jun 17, 2026
@wpaulino
wpaulino requested a review from jkczyzJune 17, 2026 18:38
@wpaulinowpaulino self-assigned this Jun 17, 2026
@ldk-reviews-bot

ldk-reviews-bot commented Jun 17, 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.

@ldk-claude-review-bot

ldk-claude-review-bot commented Jun 17, 2026

Copy link
Copy Markdown
Collaborator

I have re-verified the key changes against the actual code. Everything is consistent with my prior review:

  • validate_tx_init_rbf reordering (channel.rs:13390-13438) behaves as expected; the no-candidate + negotiation-in-progress edge only changes the abort message, both paths abort.
  • The fuzz allowlist addition for "contribution no longer valid at quiescence" correctly matches the still-WarnAndDisconnect re-validation path at channel.rs:14656.
  • has_local_contribution gating, the splice_funding_failed_for! macro refactor, the new AbortReason variants, and the from_chan_no_close.clone() change are all consistent and correct.

No issues found.

@wpaulino
wpaulinoforce-pushed the splice-with-mempool-fuzz-fixes branch from bc23128 to a120ac1CompareJune 17, 2026 21:23
@joostjager

joostjager commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

I updated #4699 to hopefully no longer trigger false positives. Removed the splice state assert after event handling, because it wasn't reliable.

Still there seem a lingering problem (with #4699 applied to this PR) using byte string 0080a1ffa1ffa1ffa1ffa1ffa1ffa1ffa1ffa1ffa1ffa1ffa080a1ff. The failure is tx_abort left splice negotiation state behind: QueuedAction: node 0 rejects node 1's tx_init_rbf with Insufficient RBF feerate and sends tx_abort; node 1 logs the counterparty abort, then a queued splice action on node 0 still progresses far enough to deliver another stfu before the echoed tx_abort is processed, leaving QueuedAction behind when the harness checks tx_abort cleanup.

@joostjager

Copy link
Copy Markdown
Contributor

The one above also seems to be a false positive. The queued contribution is not stale state left behind by the aborted tx_init_rbf. It is a newer local splice attempt on node 0 that starts progressing before node 0 processes node 1’s echoed tx_abort.

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

@ldk-reviews-bot

Copy link
Copy Markdown

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

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 3rd 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 +7185 to +7187
/// Whether the holder contributed local inputs or outputs to the negotiated splice.
pub has_local_contribution: bool,

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.

Can't we simply return None instead of Some(SpliceFundingNegotiated{ .. })?

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.

No because there's a case where we rely on SpliceFundingNegotiated being set to free the holding cell after quiescence terminates.

Comment on lines -13136 to -13140
if self.holder_commitment_point.current_point().is_none() {
return Err(ChannelError::WarnAndDisconnect(format!(
"Channel {} commitment point needs to be advanced once before spliced",
self.context.channel_id(),
)));

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.

Wasn't this performed early because it indicates the channel was created before we persisted the current commitment point? (a7ba4dd) Any specific reason to move it later?

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.

I don't see why the ordering should matter as long as we're still failing because of it. I just wanted to keep the WarnAndDisconnect paths together, and since we can use tx_abort to fail this specific case, we must be quiescent first to do so.

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

Copy link
Copy Markdown

👋 The first review has been submitted!

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

@jkczyzjkczyz left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM

@joostjager Did you have any other concerns left from your earlier comments?

There's no need to inform users of negotiated splices when they're not
contributing as it just produces noise. Once they do start contributing,
they cannot stop, so we always emit the event going forward. Note that
we still emit `Event::ChannelReady` with the new locked funding outpoint
for each locked splice, so users can still learn that a splice occurred
that way.
Previously, this could result in an acceptor not receiving a
`Event::SpliceNegotiationFailed` for a splice in which they reused the
same contribution (except for the feerate change). Our API should
guarantee that users should always see `SpliceNegotiated` and
`SpliceNegotiationFailed` events for splices that they contribute to.
While a contribution may be valid at the time the splice is requested,
quiescence still needs to happen, which can affect the balances of the
channel as it fully settles all pending state. After doing so, it's
possible that the contribution is no longer valid. Since quiescence
itself doesn't have a terminal message, we see a `WarnAndDisconnect`
event happen.
We keep some `WarnAndDisconnect` cases as mandated by the spec, but
otherwise prefer sending `tx_abort` to terminate quiescence and avoid
reconnection loops.
Send `tx_abort` to terminate quiescence and avoid reconnection loops.
This mirrors what we do for counterparty `splice_init` messages, making
sure we don't accept RBFs once a channel has requested shutdown.
@wpaulino
wpaulinoforce-pushed the splice-with-mempool-fuzz-fixes branch from a120ac1 to 19c358eCompareJune 29, 2026 18:03
@joostjager

Copy link
Copy Markdown
Contributor

LGTM

@joostjager Did you have any other concerns left from your earlier comments?

No, nothing left

@codecov

codecovBot commented Jun 30, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 75.37313% with 33 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.94%. Comparing base (1ff2247) to head (19c358e).
⚠️ Report is 11 commits behind head on main.

Files with missing linesPatch %Lines
lightning/src/ln/channel.rs77.02%17 Missing ⚠️
lightning/src/ln/channelmanager.rs71.42%14 Missing and 2 partials ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #4708 +/- ##
==========================================
- Coverage 86.95% 86.94% -0.01% 
==========================================
Files 161 161 Lines 111659 111680 +21 Branches 111659 111680 +21 ==========================================
+ Hits 97090 97104 +14 - Misses 12060 12068 +8 + Partials 2509 2508 -1 
FlagCoverage Δ
fuzzing-fake-hashes8.43% <0.00%> (-0.01%)⬇️
fuzzing-real-hashes32.42% <45.52%> (+<0.01%)⬆️
tests86.27% <75.37%> (-0.01%)⬇️

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

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

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

@ldk-reviews-bot

Copy link
Copy Markdown

Hi @wpaulino,

Thanks for your contributions to rust-lightning!

After too many struggles with bugs, outages, contributor bans, and, finally, a multi-week CI ban, the rust-lightning project is moving off of GitHub for day-to-day development.

You can still file issues and access the git tree here, but PRs will now take place exclusively at https://git.rust-bitcoin.org/. As such, this PR has been migrated to https://git.rust-bitcoin.org/lightningdevkit/rust-lightning/pulls/4708

If you log in using GitHub (or otherwise link your GitHub account from https://git.rust-bitcoin.org/user/settings/security), ownership of your PRs, issues, and comments will automatically transfer. To push updates to this PR, you'll need to use git push git@gitea-ssh.bitcoin.ninja:lightningdevkit/rust-lightning YOUR_LOCAL_COMMIT_OR_BRANCH:wpaulino/splice-with-mempool-fuzz-fixes. This may require a permissions change - if it doesn't work initially just leave a comment and we'll get you access.

ldk-reviews-bot pushed a commit that referenced this pull request Jul 5, 2026
from wpaulino/splice-with-mempool-fuzz-fixes into main
Reviewed-on: https://git.rust-bitcoin.org/lightningdevkit/rust-lightning/pulls/4708
Reviewed-by: jkczyz <jkczyz@noreply.gitea.bitcoin.ninja>
Reviewed-by: Matt Corallo <matt@noreply.gitea.bitcoin.ninja>
@wpaulino
wpaulino deleted the splice-with-mempool-fuzz-fixes branch August 5, 2026 17:50
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Splice negotiation failure handling issues

5 participants

@wpaulino@ldk-reviews-bot@ldk-claude-review-bot@joostjager@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

Splice with mempool fuzz fixes - #4708

Closed
wpaulino wants to merge 6 commits into
lightningdevkit:mainfrom
wpaulino:splice-with-mempool-fuzz-fixes
Closed

Splice with mempool fuzz fixes#4708
wpaulino wants to merge 6 commits into
lightningdevkit:mainfrom
wpaulino:splice-with-mempool-fuzz-fixes

Conversation

@wpaulino

Copy link
Copy Markdown
Contributor

This PR includes a few more bug fixes that were discovered by the chanmon_consistency_target after #4657.

Fixes#4696.

@wpaulinowpaulino added this to the 0.3 milestone Jun 17, 2026
@wpaulino
wpaulino requested a review from jkczyzJune 17, 2026 18:38
@wpaulinowpaulino self-assigned this Jun 17, 2026
@ldk-reviews-bot

ldk-reviews-bot commented Jun 17, 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.

@ldk-claude-review-bot

ldk-claude-review-bot commented Jun 17, 2026

Copy link
Copy Markdown
Collaborator

I have re-verified the key changes against the actual code. Everything is consistent with my prior review:

  • validate_tx_init_rbf reordering (channel.rs:13390-13438) behaves as expected; the no-candidate + negotiation-in-progress edge only changes the abort message, both paths abort.
  • The fuzz allowlist addition for "contribution no longer valid at quiescence" correctly matches the still-WarnAndDisconnect re-validation path at channel.rs:14656.
  • has_local_contribution gating, the splice_funding_failed_for! macro refactor, the new AbortReason variants, and the from_chan_no_close.clone() change are all consistent and correct.

No issues found.

@wpaulino
wpaulinoforce-pushed the splice-with-mempool-fuzz-fixes branch from bc23128 to a120ac1CompareJune 17, 2026 21:23
@joostjager

joostjager commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

I updated #4699 to hopefully no longer trigger false positives. Removed the splice state assert after event handling, because it wasn't reliable.

Still there seem a lingering problem (with #4699 applied to this PR) using byte string 0080a1ffa1ffa1ffa1ffa1ffa1ffa1ffa1ffa1ffa1ffa1ffa080a1ff. The failure is tx_abort left splice negotiation state behind: QueuedAction: node 0 rejects node 1's tx_init_rbf with Insufficient RBF feerate and sends tx_abort; node 1 logs the counterparty abort, then a queued splice action on node 0 still progresses far enough to deliver another stfu before the echoed tx_abort is processed, leaving QueuedAction behind when the harness checks tx_abort cleanup.

@joostjager

Copy link
Copy Markdown
Contributor

The one above also seems to be a false positive. The queued contribution is not stale state left behind by the aborted tx_init_rbf. It is a newer local splice attempt on node 0 that starts progressing before node 0 processes node 1’s echoed tx_abort.

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

@ldk-reviews-bot

Copy link
Copy Markdown

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

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 3rd 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 +7185 to +7187
/// Whether the holder contributed local inputs or outputs to the negotiated splice.
pub has_local_contribution: bool,

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.

Can't we simply return None instead of Some(SpliceFundingNegotiated{ .. })?

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.

No because there's a case where we rely on SpliceFundingNegotiated being set to free the holding cell after quiescence terminates.

Comment on lines -13136 to -13140
if self.holder_commitment_point.current_point().is_none() {
return Err(ChannelError::WarnAndDisconnect(format!(
"Channel {} commitment point needs to be advanced once before spliced",
self.context.channel_id(),
)));

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.

Wasn't this performed early because it indicates the channel was created before we persisted the current commitment point? (a7ba4dd) Any specific reason to move it later?

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.

I don't see why the ordering should matter as long as we're still failing because of it. I just wanted to keep the WarnAndDisconnect paths together, and since we can use tx_abort to fail this specific case, we must be quiescent first to do so.

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

Copy link
Copy Markdown

👋 The first review has been submitted!

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

@jkczyzjkczyz left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM

@joostjager Did you have any other concerns left from your earlier comments?

There's no need to inform users of negotiated splices when they're not
contributing as it just produces noise. Once they do start contributing,
they cannot stop, so we always emit the event going forward. Note that
we still emit `Event::ChannelReady` with the new locked funding outpoint
for each locked splice, so users can still learn that a splice occurred
that way.
Previously, this could result in an acceptor not receiving a
`Event::SpliceNegotiationFailed` for a splice in which they reused the
same contribution (except for the feerate change). Our API should
guarantee that users should always see `SpliceNegotiated` and
`SpliceNegotiationFailed` events for splices that they contribute to.
While a contribution may be valid at the time the splice is requested,
quiescence still needs to happen, which can affect the balances of the
channel as it fully settles all pending state. After doing so, it's
possible that the contribution is no longer valid. Since quiescence
itself doesn't have a terminal message, we see a `WarnAndDisconnect`
event happen.
We keep some `WarnAndDisconnect` cases as mandated by the spec, but
otherwise prefer sending `tx_abort` to terminate quiescence and avoid
reconnection loops.
Send `tx_abort` to terminate quiescence and avoid reconnection loops.
This mirrors what we do for counterparty `splice_init` messages, making
sure we don't accept RBFs once a channel has requested shutdown.
@wpaulino
wpaulinoforce-pushed the splice-with-mempool-fuzz-fixes branch from a120ac1 to 19c358eCompareJune 29, 2026 18:03
@joostjager

Copy link
Copy Markdown
Contributor

LGTM

@joostjager Did you have any other concerns left from your earlier comments?

No, nothing left

@codecov

codecovBot commented Jun 30, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 75.37313% with 33 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.94%. Comparing base (1ff2247) to head (19c358e).
⚠️ Report is 11 commits behind head on main.

Files with missing linesPatch %Lines
lightning/src/ln/channel.rs77.02%17 Missing ⚠️
lightning/src/ln/channelmanager.rs71.42%14 Missing and 2 partials ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #4708 +/- ##
==========================================
- Coverage 86.95% 86.94% -0.01% 
==========================================
Files 161 161 Lines 111659 111680 +21 Branches 111659 111680 +21 ==========================================
+ Hits 97090 97104 +14 - Misses 12060 12068 +8 + Partials 2509 2508 -1 
FlagCoverage Δ
fuzzing-fake-hashes8.43% <0.00%> (-0.01%)⬇️
fuzzing-real-hashes32.42% <45.52%> (+<0.01%)⬆️
tests86.27% <75.37%> (-0.01%)⬇️

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

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

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

@ldk-reviews-bot

Copy link
Copy Markdown

Hi @wpaulino,

Thanks for your contributions to rust-lightning!

After too many struggles with bugs, outages, contributor bans, and, finally, a multi-week CI ban, the rust-lightning project is moving off of GitHub for day-to-day development.

You can still file issues and access the git tree here, but PRs will now take place exclusively at https://git.rust-bitcoin.org/. As such, this PR has been migrated to https://git.rust-bitcoin.org/lightningdevkit/rust-lightning/pulls/4708

If you log in using GitHub (or otherwise link your GitHub account from https://git.rust-bitcoin.org/user/settings/security), ownership of your PRs, issues, and comments will automatically transfer. To push updates to this PR, you'll need to use git push git@gitea-ssh.bitcoin.ninja:lightningdevkit/rust-lightning YOUR_LOCAL_COMMIT_OR_BRANCH:wpaulino/splice-with-mempool-fuzz-fixes. This may require a permissions change - if it doesn't work initially just leave a comment and we'll get you access.

ldk-reviews-bot pushed a commit that referenced this pull request Jul 5, 2026
from wpaulino/splice-with-mempool-fuzz-fixes into main
Reviewed-on: https://git.rust-bitcoin.org/lightningdevkit/rust-lightning/pulls/4708
Reviewed-by: jkczyz <jkczyz@noreply.gitea.bitcoin.ninja>
Reviewed-by: Matt Corallo <matt@noreply.gitea.bitcoin.ninja>
@wpaulino
wpaulino deleted the splice-with-mempool-fuzz-fixes branch August 5, 2026 17:50
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Splice negotiation failure handling issues

5 participants

@wpaulino@ldk-reviews-bot@ldk-claude-review-bot@joostjager@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

Splice with mempool fuzz fixes - #4708

Closed
wpaulino wants to merge 6 commits into
lightningdevkit:mainfrom
wpaulino:splice-with-mempool-fuzz-fixes
Closed

Splice with mempool fuzz fixes#4708
wpaulino wants to merge 6 commits into
lightningdevkit:mainfrom
wpaulino:splice-with-mempool-fuzz-fixes

Conversation

@wpaulino

Copy link
Copy Markdown
Contributor

This PR includes a few more bug fixes that were discovered by the chanmon_consistency_target after #4657.

Fixes#4696.

@wpaulinowpaulino added this to the 0.3 milestone Jun 17, 2026
@wpaulino
wpaulino requested a review from jkczyzJune 17, 2026 18:38
@wpaulinowpaulino self-assigned this Jun 17, 2026
@ldk-reviews-bot

ldk-reviews-bot commented Jun 17, 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.

@ldk-claude-review-bot

ldk-claude-review-bot commented Jun 17, 2026

Copy link
Copy Markdown
Collaborator

I have re-verified the key changes against the actual code. Everything is consistent with my prior review:

  • validate_tx_init_rbf reordering (channel.rs:13390-13438) behaves as expected; the no-candidate + negotiation-in-progress edge only changes the abort message, both paths abort.
  • The fuzz allowlist addition for "contribution no longer valid at quiescence" correctly matches the still-WarnAndDisconnect re-validation path at channel.rs:14656.
  • has_local_contribution gating, the splice_funding_failed_for! macro refactor, the new AbortReason variants, and the from_chan_no_close.clone() change are all consistent and correct.

No issues found.

@wpaulino
wpaulinoforce-pushed the splice-with-mempool-fuzz-fixes branch from bc23128 to a120ac1CompareJune 17, 2026 21:23
@joostjager

joostjager commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

I updated #4699 to hopefully no longer trigger false positives. Removed the splice state assert after event handling, because it wasn't reliable.

Still there seem a lingering problem (with #4699 applied to this PR) using byte string 0080a1ffa1ffa1ffa1ffa1ffa1ffa1ffa1ffa1ffa1ffa1ffa080a1ff. The failure is tx_abort left splice negotiation state behind: QueuedAction: node 0 rejects node 1's tx_init_rbf with Insufficient RBF feerate and sends tx_abort; node 1 logs the counterparty abort, then a queued splice action on node 0 still progresses far enough to deliver another stfu before the echoed tx_abort is processed, leaving QueuedAction behind when the harness checks tx_abort cleanup.

@joostjager

Copy link
Copy Markdown
Contributor

The one above also seems to be a false positive. The queued contribution is not stale state left behind by the aborted tx_init_rbf. It is a newer local splice attempt on node 0 that starts progressing before node 0 processes node 1’s echoed tx_abort.

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

@ldk-reviews-bot

Copy link
Copy Markdown

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

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 3rd 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 +7185 to +7187
/// Whether the holder contributed local inputs or outputs to the negotiated splice.
pub has_local_contribution: bool,

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.

Can't we simply return None instead of Some(SpliceFundingNegotiated{ .. })?

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.

No because there's a case where we rely on SpliceFundingNegotiated being set to free the holding cell after quiescence terminates.

Comment on lines -13136 to -13140
if self.holder_commitment_point.current_point().is_none() {
return Err(ChannelError::WarnAndDisconnect(format!(
"Channel {} commitment point needs to be advanced once before spliced",
self.context.channel_id(),
)));

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.

Wasn't this performed early because it indicates the channel was created before we persisted the current commitment point? (a7ba4dd) Any specific reason to move it later?

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.

I don't see why the ordering should matter as long as we're still failing because of it. I just wanted to keep the WarnAndDisconnect paths together, and since we can use tx_abort to fail this specific case, we must be quiescent first to do so.

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

Copy link
Copy Markdown

👋 The first review has been submitted!

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

@jkczyzjkczyz left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM

@joostjager Did you have any other concerns left from your earlier comments?

There's no need to inform users of negotiated splices when they're not
contributing as it just produces noise. Once they do start contributing,
they cannot stop, so we always emit the event going forward. Note that
we still emit `Event::ChannelReady` with the new locked funding outpoint
for each locked splice, so users can still learn that a splice occurred
that way.
Previously, this could result in an acceptor not receiving a
`Event::SpliceNegotiationFailed` for a splice in which they reused the
same contribution (except for the feerate change). Our API should
guarantee that users should always see `SpliceNegotiated` and
`SpliceNegotiationFailed` events for splices that they contribute to.
While a contribution may be valid at the time the splice is requested,
quiescence still needs to happen, which can affect the balances of the
channel as it fully settles all pending state. After doing so, it's
possible that the contribution is no longer valid. Since quiescence
itself doesn't have a terminal message, we see a `WarnAndDisconnect`
event happen.
We keep some `WarnAndDisconnect` cases as mandated by the spec, but
otherwise prefer sending `tx_abort` to terminate quiescence and avoid
reconnection loops.
Send `tx_abort` to terminate quiescence and avoid reconnection loops.
This mirrors what we do for counterparty `splice_init` messages, making
sure we don't accept RBFs once a channel has requested shutdown.
@wpaulino
wpaulinoforce-pushed the splice-with-mempool-fuzz-fixes branch from a120ac1 to 19c358eCompareJune 29, 2026 18:03
@joostjager

Copy link
Copy Markdown
Contributor

LGTM

@joostjager Did you have any other concerns left from your earlier comments?

No, nothing left

@codecov

codecovBot commented Jun 30, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 75.37313% with 33 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.94%. Comparing base (1ff2247) to head (19c358e).
⚠️ Report is 11 commits behind head on main.

Files with missing linesPatch %Lines
lightning/src/ln/channel.rs77.02%17 Missing ⚠️
lightning/src/ln/channelmanager.rs71.42%14 Missing and 2 partials ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #4708 +/- ##
==========================================
- Coverage 86.95% 86.94% -0.01% 
==========================================
Files 161 161 Lines 111659 111680 +21 Branches 111659 111680 +21 ==========================================
+ Hits 97090 97104 +14 - Misses 12060 12068 +8 + Partials 2509 2508 -1 
FlagCoverage Δ
fuzzing-fake-hashes8.43% <0.00%> (-0.01%)⬇️
fuzzing-real-hashes32.42% <45.52%> (+<0.01%)⬆️
tests86.27% <75.37%> (-0.01%)⬇️

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

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

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

@ldk-reviews-bot

Copy link
Copy Markdown

Hi @wpaulino,

Thanks for your contributions to rust-lightning!

After too many struggles with bugs, outages, contributor bans, and, finally, a multi-week CI ban, the rust-lightning project is moving off of GitHub for day-to-day development.

You can still file issues and access the git tree here, but PRs will now take place exclusively at https://git.rust-bitcoin.org/. As such, this PR has been migrated to https://git.rust-bitcoin.org/lightningdevkit/rust-lightning/pulls/4708

If you log in using GitHub (or otherwise link your GitHub account from https://git.rust-bitcoin.org/user/settings/security), ownership of your PRs, issues, and comments will automatically transfer. To push updates to this PR, you'll need to use git push git@gitea-ssh.bitcoin.ninja:lightningdevkit/rust-lightning YOUR_LOCAL_COMMIT_OR_BRANCH:wpaulino/splice-with-mempool-fuzz-fixes. This may require a permissions change - if it doesn't work initially just leave a comment and we'll get you access.

ldk-reviews-bot pushed a commit that referenced this pull request Jul 5, 2026
from wpaulino/splice-with-mempool-fuzz-fixes into main
Reviewed-on: https://git.rust-bitcoin.org/lightningdevkit/rust-lightning/pulls/4708
Reviewed-by: jkczyz <jkczyz@noreply.gitea.bitcoin.ninja>
Reviewed-by: Matt Corallo <matt@noreply.gitea.bitcoin.ninja>
@wpaulino
wpaulino deleted the splice-with-mempool-fuzz-fixes branch August 5, 2026 17:50
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Splice negotiation failure handling issues

5 participants

@wpaulino@ldk-reviews-bot@ldk-claude-review-bot@joostjager@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

Splice with mempool fuzz fixes - #4708

Closed
wpaulino wants to merge 6 commits into
lightningdevkit:mainfrom
wpaulino:splice-with-mempool-fuzz-fixes
Closed

Splice with mempool fuzz fixes#4708
wpaulino wants to merge 6 commits into
lightningdevkit:mainfrom
wpaulino:splice-with-mempool-fuzz-fixes

Conversation

@wpaulino

Copy link
Copy Markdown
Contributor

This PR includes a few more bug fixes that were discovered by the chanmon_consistency_target after #4657.

Fixes#4696.

@wpaulinowpaulino added this to the 0.3 milestone Jun 17, 2026
@wpaulino
wpaulino requested a review from jkczyzJune 17, 2026 18:38
@wpaulinowpaulino self-assigned this Jun 17, 2026
@ldk-reviews-bot

ldk-reviews-bot commented Jun 17, 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.

@ldk-claude-review-bot

ldk-claude-review-bot commented Jun 17, 2026

Copy link
Copy Markdown
Collaborator

I have re-verified the key changes against the actual code. Everything is consistent with my prior review:

  • validate_tx_init_rbf reordering (channel.rs:13390-13438) behaves as expected; the no-candidate + negotiation-in-progress edge only changes the abort message, both paths abort.
  • The fuzz allowlist addition for "contribution no longer valid at quiescence" correctly matches the still-WarnAndDisconnect re-validation path at channel.rs:14656.
  • has_local_contribution gating, the splice_funding_failed_for! macro refactor, the new AbortReason variants, and the from_chan_no_close.clone() change are all consistent and correct.

No issues found.

@wpaulino
wpaulinoforce-pushed the splice-with-mempool-fuzz-fixes branch from bc23128 to a120ac1CompareJune 17, 2026 21:23
@joostjager

joostjager commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

I updated #4699 to hopefully no longer trigger false positives. Removed the splice state assert after event handling, because it wasn't reliable.

Still there seem a lingering problem (with #4699 applied to this PR) using byte string 0080a1ffa1ffa1ffa1ffa1ffa1ffa1ffa1ffa1ffa1ffa1ffa080a1ff. The failure is tx_abort left splice negotiation state behind: QueuedAction: node 0 rejects node 1's tx_init_rbf with Insufficient RBF feerate and sends tx_abort; node 1 logs the counterparty abort, then a queued splice action on node 0 still progresses far enough to deliver another stfu before the echoed tx_abort is processed, leaving QueuedAction behind when the harness checks tx_abort cleanup.

@joostjager

Copy link
Copy Markdown
Contributor

The one above also seems to be a false positive. The queued contribution is not stale state left behind by the aborted tx_init_rbf. It is a newer local splice attempt on node 0 that starts progressing before node 0 processes node 1’s echoed tx_abort.

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

@ldk-reviews-bot

Copy link
Copy Markdown

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

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 3rd 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 +7185 to +7187
/// Whether the holder contributed local inputs or outputs to the negotiated splice.
pub has_local_contribution: bool,

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.

Can't we simply return None instead of Some(SpliceFundingNegotiated{ .. })?

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.

No because there's a case where we rely on SpliceFundingNegotiated being set to free the holding cell after quiescence terminates.

Comment on lines -13136 to -13140
if self.holder_commitment_point.current_point().is_none() {
return Err(ChannelError::WarnAndDisconnect(format!(
"Channel {} commitment point needs to be advanced once before spliced",
self.context.channel_id(),
)));

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.

Wasn't this performed early because it indicates the channel was created before we persisted the current commitment point? (a7ba4dd) Any specific reason to move it later?

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.

I don't see why the ordering should matter as long as we're still failing because of it. I just wanted to keep the WarnAndDisconnect paths together, and since we can use tx_abort to fail this specific case, we must be quiescent first to do so.

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

Copy link
Copy Markdown

👋 The first review has been submitted!

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

@jkczyzjkczyz left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM

@joostjager Did you have any other concerns left from your earlier comments?

There's no need to inform users of negotiated splices when they're not
contributing as it just produces noise. Once they do start contributing,
they cannot stop, so we always emit the event going forward. Note that
we still emit `Event::ChannelReady` with the new locked funding outpoint
for each locked splice, so users can still learn that a splice occurred
that way.
Previously, this could result in an acceptor not receiving a
`Event::SpliceNegotiationFailed` for a splice in which they reused the
same contribution (except for the feerate change). Our API should
guarantee that users should always see `SpliceNegotiated` and
`SpliceNegotiationFailed` events for splices that they contribute to.
While a contribution may be valid at the time the splice is requested,
quiescence still needs to happen, which can affect the balances of the
channel as it fully settles all pending state. After doing so, it's
possible that the contribution is no longer valid. Since quiescence
itself doesn't have a terminal message, we see a `WarnAndDisconnect`
event happen.
We keep some `WarnAndDisconnect` cases as mandated by the spec, but
otherwise prefer sending `tx_abort` to terminate quiescence and avoid
reconnection loops.
Send `tx_abort` to terminate quiescence and avoid reconnection loops.
This mirrors what we do for counterparty `splice_init` messages, making
sure we don't accept RBFs once a channel has requested shutdown.
@wpaulino
wpaulinoforce-pushed the splice-with-mempool-fuzz-fixes branch from a120ac1 to 19c358eCompareJune 29, 2026 18:03
@joostjager

Copy link
Copy Markdown
Contributor

LGTM

@joostjager Did you have any other concerns left from your earlier comments?

No, nothing left

@codecov

codecovBot commented Jun 30, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 75.37313% with 33 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.94%. Comparing base (1ff2247) to head (19c358e).
⚠️ Report is 11 commits behind head on main.

Files with missing linesPatch %Lines
lightning/src/ln/channel.rs77.02%17 Missing ⚠️
lightning/src/ln/channelmanager.rs71.42%14 Missing and 2 partials ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #4708 +/- ##
==========================================
- Coverage 86.95% 86.94% -0.01% 
==========================================
Files 161 161 Lines 111659 111680 +21 Branches 111659 111680 +21 ==========================================
+ Hits 97090 97104 +14 - Misses 12060 12068 +8 + Partials 2509 2508 -1 
FlagCoverage Δ
fuzzing-fake-hashes8.43% <0.00%> (-0.01%)⬇️
fuzzing-real-hashes32.42% <45.52%> (+<0.01%)⬆️
tests86.27% <75.37%> (-0.01%)⬇️

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

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

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

@ldk-reviews-bot

Copy link
Copy Markdown

Hi @wpaulino,

Thanks for your contributions to rust-lightning!

After too many struggles with bugs, outages, contributor bans, and, finally, a multi-week CI ban, the rust-lightning project is moving off of GitHub for day-to-day development.

You can still file issues and access the git tree here, but PRs will now take place exclusively at https://git.rust-bitcoin.org/. As such, this PR has been migrated to https://git.rust-bitcoin.org/lightningdevkit/rust-lightning/pulls/4708

If you log in using GitHub (or otherwise link your GitHub account from https://git.rust-bitcoin.org/user/settings/security), ownership of your PRs, issues, and comments will automatically transfer. To push updates to this PR, you'll need to use git push git@gitea-ssh.bitcoin.ninja:lightningdevkit/rust-lightning YOUR_LOCAL_COMMIT_OR_BRANCH:wpaulino/splice-with-mempool-fuzz-fixes. This may require a permissions change - if it doesn't work initially just leave a comment and we'll get you access.

ldk-reviews-bot pushed a commit that referenced this pull request Jul 5, 2026
from wpaulino/splice-with-mempool-fuzz-fixes into main
Reviewed-on: https://git.rust-bitcoin.org/lightningdevkit/rust-lightning/pulls/4708
Reviewed-by: jkczyz <jkczyz@noreply.gitea.bitcoin.ninja>
Reviewed-by: Matt Corallo <matt@noreply.gitea.bitcoin.ninja>
@wpaulino
wpaulino deleted the splice-with-mempool-fuzz-fixes branch August 5, 2026 17:50
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Splice negotiation failure handling issues

5 participants

@wpaulino@ldk-reviews-bot@ldk-claude-review-bot@joostjager@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

Splice with mempool fuzz fixes - #4708

Closed
wpaulino wants to merge 6 commits into
lightningdevkit:mainfrom
wpaulino:splice-with-mempool-fuzz-fixes
Closed

Splice with mempool fuzz fixes#4708
wpaulino wants to merge 6 commits into
lightningdevkit:mainfrom
wpaulino:splice-with-mempool-fuzz-fixes

Conversation

@wpaulino

Copy link
Copy Markdown
Contributor

This PR includes a few more bug fixes that were discovered by the chanmon_consistency_target after #4657.

Fixes#4696.

@wpaulinowpaulino added this to the 0.3 milestone Jun 17, 2026
@wpaulino
wpaulino requested a review from jkczyzJune 17, 2026 18:38
@wpaulinowpaulino self-assigned this Jun 17, 2026
@ldk-reviews-bot

ldk-reviews-bot commented Jun 17, 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.

@ldk-claude-review-bot

ldk-claude-review-bot commented Jun 17, 2026

Copy link
Copy Markdown
Collaborator

I have re-verified the key changes against the actual code. Everything is consistent with my prior review:

  • validate_tx_init_rbf reordering (channel.rs:13390-13438) behaves as expected; the no-candidate + negotiation-in-progress edge only changes the abort message, both paths abort.
  • The fuzz allowlist addition for "contribution no longer valid at quiescence" correctly matches the still-WarnAndDisconnect re-validation path at channel.rs:14656.
  • has_local_contribution gating, the splice_funding_failed_for! macro refactor, the new AbortReason variants, and the from_chan_no_close.clone() change are all consistent and correct.

No issues found.

@wpaulino
wpaulinoforce-pushed the splice-with-mempool-fuzz-fixes branch from bc23128 to a120ac1CompareJune 17, 2026 21:23
@joostjager

joostjager commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

I updated #4699 to hopefully no longer trigger false positives. Removed the splice state assert after event handling, because it wasn't reliable.

Still there seem a lingering problem (with #4699 applied to this PR) using byte string 0080a1ffa1ffa1ffa1ffa1ffa1ffa1ffa1ffa1ffa1ffa1ffa080a1ff. The failure is tx_abort left splice negotiation state behind: QueuedAction: node 0 rejects node 1's tx_init_rbf with Insufficient RBF feerate and sends tx_abort; node 1 logs the counterparty abort, then a queued splice action on node 0 still progresses far enough to deliver another stfu before the echoed tx_abort is processed, leaving QueuedAction behind when the harness checks tx_abort cleanup.

@joostjager

Copy link
Copy Markdown
Contributor

The one above also seems to be a false positive. The queued contribution is not stale state left behind by the aborted tx_init_rbf. It is a newer local splice attempt on node 0 that starts progressing before node 0 processes node 1’s echoed tx_abort.

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

@ldk-reviews-bot

Copy link
Copy Markdown

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

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 3rd 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 +7185 to +7187
/// Whether the holder contributed local inputs or outputs to the negotiated splice.
pub has_local_contribution: bool,

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.

Can't we simply return None instead of Some(SpliceFundingNegotiated{ .. })?

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.

No because there's a case where we rely on SpliceFundingNegotiated being set to free the holding cell after quiescence terminates.

Comment on lines -13136 to -13140
if self.holder_commitment_point.current_point().is_none() {
return Err(ChannelError::WarnAndDisconnect(format!(
"Channel {} commitment point needs to be advanced once before spliced",
self.context.channel_id(),
)));

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.

Wasn't this performed early because it indicates the channel was created before we persisted the current commitment point? (a7ba4dd) Any specific reason to move it later?

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.

I don't see why the ordering should matter as long as we're still failing because of it. I just wanted to keep the WarnAndDisconnect paths together, and since we can use tx_abort to fail this specific case, we must be quiescent first to do so.

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

Copy link
Copy Markdown

👋 The first review has been submitted!

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

@jkczyzjkczyz left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM

@joostjager Did you have any other concerns left from your earlier comments?

There's no need to inform users of negotiated splices when they're not
contributing as it just produces noise. Once they do start contributing,
they cannot stop, so we always emit the event going forward. Note that
we still emit `Event::ChannelReady` with the new locked funding outpoint
for each locked splice, so users can still learn that a splice occurred
that way.
Previously, this could result in an acceptor not receiving a
`Event::SpliceNegotiationFailed` for a splice in which they reused the
same contribution (except for the feerate change). Our API should
guarantee that users should always see `SpliceNegotiated` and
`SpliceNegotiationFailed` events for splices that they contribute to.
While a contribution may be valid at the time the splice is requested,
quiescence still needs to happen, which can affect the balances of the
channel as it fully settles all pending state. After doing so, it's
possible that the contribution is no longer valid. Since quiescence
itself doesn't have a terminal message, we see a `WarnAndDisconnect`
event happen.
We keep some `WarnAndDisconnect` cases as mandated by the spec, but
otherwise prefer sending `tx_abort` to terminate quiescence and avoid
reconnection loops.
Send `tx_abort` to terminate quiescence and avoid reconnection loops.
This mirrors what we do for counterparty `splice_init` messages, making
sure we don't accept RBFs once a channel has requested shutdown.
@wpaulino
wpaulinoforce-pushed the splice-with-mempool-fuzz-fixes branch from a120ac1 to 19c358eCompareJune 29, 2026 18:03
@joostjager

Copy link
Copy Markdown
Contributor

LGTM

@joostjager Did you have any other concerns left from your earlier comments?

No, nothing left

@codecov

codecovBot commented Jun 30, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 75.37313% with 33 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.94%. Comparing base (1ff2247) to head (19c358e).
⚠️ Report is 11 commits behind head on main.

Files with missing linesPatch %Lines
lightning/src/ln/channel.rs77.02%17 Missing ⚠️
lightning/src/ln/channelmanager.rs71.42%14 Missing and 2 partials ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #4708 +/- ##
==========================================
- Coverage 86.95% 86.94% -0.01% 
==========================================
Files 161 161 Lines 111659 111680 +21 Branches 111659 111680 +21 ==========================================
+ Hits 97090 97104 +14 - Misses 12060 12068 +8 + Partials 2509 2508 -1 
FlagCoverage Δ
fuzzing-fake-hashes8.43% <0.00%> (-0.01%)⬇️
fuzzing-real-hashes32.42% <45.52%> (+<0.01%)⬆️
tests86.27% <75.37%> (-0.01%)⬇️

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

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

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

@ldk-reviews-bot

Copy link
Copy Markdown

Hi @wpaulino,

Thanks for your contributions to rust-lightning!

After too many struggles with bugs, outages, contributor bans, and, finally, a multi-week CI ban, the rust-lightning project is moving off of GitHub for day-to-day development.

You can still file issues and access the git tree here, but PRs will now take place exclusively at https://git.rust-bitcoin.org/. As such, this PR has been migrated to https://git.rust-bitcoin.org/lightningdevkit/rust-lightning/pulls/4708

If you log in using GitHub (or otherwise link your GitHub account from https://git.rust-bitcoin.org/user/settings/security), ownership of your PRs, issues, and comments will automatically transfer. To push updates to this PR, you'll need to use git push git@gitea-ssh.bitcoin.ninja:lightningdevkit/rust-lightning YOUR_LOCAL_COMMIT_OR_BRANCH:wpaulino/splice-with-mempool-fuzz-fixes. This may require a permissions change - if it doesn't work initially just leave a comment and we'll get you access.

ldk-reviews-bot pushed a commit that referenced this pull request Jul 5, 2026
from wpaulino/splice-with-mempool-fuzz-fixes into main
Reviewed-on: https://git.rust-bitcoin.org/lightningdevkit/rust-lightning/pulls/4708
Reviewed-by: jkczyz <jkczyz@noreply.gitea.bitcoin.ninja>
Reviewed-by: Matt Corallo <matt@noreply.gitea.bitcoin.ninja>
@wpaulino
wpaulino deleted the splice-with-mempool-fuzz-fixes branch August 5, 2026 17:50
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Splice negotiation failure handling issues

5 participants

@wpaulino@ldk-reviews-bot@ldk-claude-review-bot@joostjager@jkczyz