Skip to content

Conversation

@Roasbeef
Copy link
Member

A variant of #10265, attempting to fix the itests with a more generalized path.

@gemini-code-assist
Copy link

Summary of Changes

Hello @Roasbeef, 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 significantly enhances the robustness of channel closure handling within the lnd system, particularly against blockchain reorganizations. It refactors the chainWatcher to employ a state machine for managing channel closure spends, ensuring that transactions are only considered confirmed after a sufficient number of blocks, and gracefully handling reorgs by re-registering for spend notifications. Additionally, it introduces a configurable confirmation depth for testing and centralizes the logic for dynamically scaling confirmation requirements based on channel capacity, improving security for larger channels. A new development RPC is also added to facilitate more deterministic testing of UTXO sweeping.

Highlights

  • Reorg-Resistant Channel Closure: The chainWatcher's closeObserver has been refactored to use a state machine for handling channel closure spends, making the process more robust against blockchain reorganizations by registering for confirmation and negative confirmation (reorg) notifications.
  • Configurable Confirmation Depth: A new ChannelCloseConfs option has been introduced in ChainArbitratorConfig and chainWatcherConfig to allow overriding the number of confirmations required for channel closes, primarily for testing and development purposes.
  • Scaled Confirmation Logic: The logic for determining confirmation requirements for channel closes (CloseConfsForCapacity) and funding transactions (FundingConfsForAmounts) now scales linearly with channel capacity, with a minimum of 3 confirmations for cooperative closes in production builds to enhance reorg protection.
  • New Dev RPC for Sweeper: A TriggerSweeper RPC has been added to the devrpc service, enabling tests to explicitly trigger the UTXO sweeper to broadcast pending sweep transactions, which helps in managing asynchronous confirmation notifications in integration tests.
  • Extensive Reorg Testing: Comprehensive property-based and scenario-based tests have been added for various channel closure types (cooperative, unilateral, breach) under different reorganization conditions, significantly improving test coverage for reorg handling.
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 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 counter productive. 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.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

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.

Copy link

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

Choose a reason for hiding this comment

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

Code Review

This is a substantial and well-executed pull request that refactors the chain_watcher to be more robust against chain reorganizations by implementing a state machine for handling spend confirmations. The logic for determining confirmation numbers for channel closes and funding has been centralized, and a testing override has been added. The test coverage for the new logic is excellent, with new unit tests, property-based tests, and integration tests. A new devrpc endpoint is also added to aid in testing. The changes are well-structured and commented. I have one suggestion regarding a potential resource leak.

}

spendNtfn := c.fundingSpendNtfn
defer spendNtfn.Cancel()

Choose a reason for hiding this comment

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

high

The spendNtfn variable is reassigned within the loop in the case of a reorg. The defer statement evaluates its arguments (including the method receiver) when the defer is executed, not when the call is executed. This means spendNtfn.Cancel() will be called on the initial spendNtfn value, not the one that might have been reassigned later. This can lead to a resource leak if a reorg happens and the function exits, as the latest spend notification subscription won't be cancelled.

To fix this, you can use a closure to capture the variable itself, which will be evaluated at the time the deferred function is executed.

Suggested change
defer spendNtfn.Cancel()
defer func() { spendNtfn.Cancel() }()

We have two versions: for itests, we just use one conf, but in prod,
we'll scale the number of confirmations.
This'll be useful for the set up upcoming itests.
In this commit, we add a new param that'll allow us to scale up the
number of confirmations before we act on a new close. We'll use this
later to improve the current on chain handling logic.
We wnt to add better handling, but not break any UIs or wallets. So
we'll continue to send out a notification after a single confirmation,
then send another after things are fully confirmed.
…re n is num confs

In this commit, we update the close logic to handle re-ogs up to the
final amount of confirmations. This is done generically, so we're able
to handle events such as: coop close confirm, re-org, breach confirm,
re-org, force close confirm, re-org, etc.

The upcoming set of new tests will exercise all of these cases.

We modify the block beat handling to unify the control flow. As it's
possible we get the beat, then see the spend, or the oher way around.
We'll use this for all the upcoming tests.
All the tests need to send a confirmation _after_ the spend is detected
now.
This set of new tests ensures that if have created N RBF variants of the
coop close transaction, that any of then can confirm, and be re-org'd,
with us detecting the final spend once it confirms deeploy enough.
In this commit, we add a set of generic close re-org tests. The most
important test is the property based test, they will randomly confirm
transactions, generate a re-org, then assert that eventually we dtect
the final version.
This ensures that during the RBF process, if one confirms, a re-org
occurs, then another confirms, that we'll properly detect this case.
…oses

In this commit, we add a fast-path optimization to the chain watcher's
closeObserver that immediately dispatches close events when only a single
confirmation is required (numConfs == 1). This addresses a timing issue
with integration tests that were designed around the old synchronous
blockbeat behavior, where close events were dispatched immediately upon
spend detection.

The recent async confirmation architecture (introduced in commit f6f716a)
properly handles reorgs by waiting for N confirmations before dispatching
close events. However, this created a race condition in integration tests
that mine blocks synchronously and expect immediate close notifications.
With the build tag setting numConfs to 1 for itests, the async confirmation
notification could arrive after the test already started waiting for the
close event, causing timeouts.

We introduce a new handleSpendDispatch method that checks if numConfs == 1
and, if so, immediately calls handleCommitSpend to dispatch the close event
synchronously, then returns true to skip the async state machine. This
preserves the old behavior for integration tests while maintaining the full
async reorg protection for production (where numConfs >= 3).

The implementation adds the fast-path check in both spend detection paths
(blockbeat and spend notification) to ensure consistent behavior regardless
of which detects the spend first. We also update the affected unit tests to
remove their expectation of confirmation registration, since the fast-path
bypasses that step entirely.

This approach optimizes for the integration test scenario without compromising
production safety, as the fast-path only activates when a single confirmation
is sufficient - a configuration that only exists in the controlled test
environment.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant