Skip to content

lnd+htlcswitch: keep on-chain intercepts held until the htlc expires#10893

Open
kovin-muun wants to merge 3 commits into
lightningnetwork:masterfrom
kovin-muun:fix-onchain-intercept-autofail
Open

lnd+htlcswitch: keep on-chain intercepts held until the htlc expires#10893
kovin-muun wants to merge 3 commits into
lightningnetwork:masterfrom
kovin-muun:fix-onchain-intercept-autofail

Conversation

@kovin-muun

@kovin-muun kovin-muun commented Jun 10, 2026

Copy link
Copy Markdown

Change Description

Bug: HTLCs offered to the interceptor through the on-chain resolution flow (witness_beacon.go) are created without an AutoFailHeight, and the auto-fail sweep in htlcswitch treats the zero value as a deadline that has already passed.

Consequence: therefore, every on-chain intercept is evicted from the held set on the first new block after being offered. Any later Settle from the interceptor fails with fwd not found, the preimage never reaches the witness
beacon, and the htlc is eventually claimed by the counterparty via the timeout path even though the interceptor held the preimage.

This PR fixes the witness beacon so that now sets AutoFailHeight to the htlc's on-chain expiry (RefundTimeout), (past it a settle can no longer reliably win the race against the remote's timeout claim).

This PR also changes the default behavior of the auto-fail sweep to skips held forwards with a non-positive AutoFailHeight instead of treating them as already expired. On-chain intercepts cannot be failed back anyway
(FailWithCode returns ErrCannotFail), but the sweep deleted them from the held set regardless.

Fixes #10892

Steps to Test

Regression test triggering the bug (fails on master, passes with this
change):

go test ./htlcswitch/ -run TestHeldHtlcSetAutoFailNoDeadline

End-to-end on regtest (recipe from the issue):

  1. Three nodes A -> B -> C; B runs with requireinterceptor=true and an interceptor client connected.
  2. Pay an invoice from A to C and have the interceptor hold the intercepted HTLC at B.
  3. Force-close the A–B channel and let the close confirm; the contest resolver re-offers the HTLC to the interceptor through the on-chain flow.
  4. Mine a block. On master, B logs Cannot fail packet: cannot fail in the on-chain flow and the held entry is evicted; with this change the forward stays held.
  5. Send Settle with the correct preimage from the interceptor. On master it fails with fwd not found; with this change the preimage reaches the witness beacon and B claims the HTLC on-chain.

The auto-fail sweep treated an unset AutoFailHeight (zero value) as a
deadline that has already been reached, popping the forward on the
first block after it was held. Skip forwards with a non-positive
auto-fail height instead, so that a forward without a deadline stays
available to the interceptor until it is resolved.

This protects on-chain intercepted forwards, which are created without
an auto-fail height and cannot be failed back at all: FailWithCode
returns ErrCannotFail, but the sweep deleted the entry from the held
set regardless, leaving any later settle from the interceptor to fail
with "fwd not found".
Set AutoFailHeight on the packets that the witness beacon offers to the
htlc interceptor. An on-chain intercept can only be settled, so the
right deadline is the htlc's on-chain expiry: past RefundTimeout a
settle can no longer reliably win the race against the remote's timeout
claim, while before it the forward must remain available so that a
settle at any time hands the preimage to the witness beacon for the
on-chain claim.

Previously the field was left at zero and the auto-fail sweep evicted
every on-chain intercept from the held set on the first new block after
it was offered (~10 minutes). A settle arriving after that failed with
"fwd not found" - additionally tearing down the interceptor stream -
and the htlc was eventually claimed by the counterparty via the timeout
path, losing funds that the interceptor held the preimage for.

Fixes lightningnetwork#10892
@gemini-code-assist

Copy link
Copy Markdown

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request addresses a critical issue where on-chain intercepted HTLCs were being prematurely evicted from the held set. By incorrectly treating a zero-value auto-fail height as an expired deadline, the system would tear down interceptor streams on the first block following an on-chain offer. The changes ensure that these intercepts are maintained until their actual on-chain expiry, allowing the interceptor sufficient time to provide a settlement preimage.

Highlights

  • HTLC Auto-Fail Logic: Updated the auto-fail sweep in htlcswitch to ignore forwards with a non-positive AutoFailHeight, preventing premature eviction of on-chain intercepts.
  • Witness Beacon Deadline: Configured the witness beacon to set the AutoFailHeight to the HTLC's on-chain expiry (RefundTimeout), ensuring intercepts remain available until they can no longer be settled.
  • Regression Testing: Added a new test case, TestHeldHtlcSetAutoFailNoDeadline, to verify that forwards without an explicit deadline are not incorrectly evicted.
New Features

🧠 You can now enable Memory (public preview) to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@github-actions github-actions Bot added the severity-critical Requires expert review - security/consensus critical label Jun 10, 2026

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request addresses a bug where HTLCs offered to the interceptor via the on-chain resolution flow were prematurely evicted from the held set on the first new block because their auto-fail height was not set. The fix ensures that forwards with a zero or negative auto-fail height are not auto-failed in popAutoFails, and sets the AutoFailHeight to the HTLC's refund timeout in witness_beacon.go. A corresponding unit test has been added to prevent regressions, and the release notes have been updated. No review comments were provided, and the implementation aligns with the repository's style guidelines.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

@github-actions

Copy link
Copy Markdown

PR Severity: CRITICAL. Automated classification. 3 files (excl. tests), 25 lines changed. Critical: htlcswitch/held_htlc_set.go (HTLC forwarding state machine). Medium: witness_beacon.go (root-level Go). Low: docs/release-notes/release-notes-0.22.0.md. No bump applied. <!-- pr-severity-bot -->

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

severity-critical Requires expert review - security/consensus critical

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[bug]: on-chain intercepts evicted after one block due to unset AutoFailHeight

1 participant