#11182·lnd

[bug]: Force-close recovery can remain in waiting_close after restart despite sufficient confirmations

Author: ripslineCreated Sep 8, 2026Updated Sep 17, 2026
Labelsbugneeds triage

Pre-Submission Checklist

  • I have searched the existing issues and believe this is a new bug.
  • I am not asking a question about how to use lnd, but reporting a bug (otherwise open a discussion).

LND Version

v0.21.2-beta Commit: 29257ecc8892af0c882fa627b66a556190c84fd9

LND Configuration

Relevant settings: bitcoin.signet=true bitcoin.node=bitcoind db.backend=sqlite db.use-native-sql=true

Private Taproot channels. No pending HTLCs in the controlled restart test. Peer connections use Tor.

Backend Version

Bitcoin Core 29.3

Backend Configuration

Public Signet, pruning enabled, no transaction index.

The closing block remained available locally. Bitcoin Core stayed running during the controlled LND stop/start.

OS/Distribution

Debian 13 amd64, native systemd services.

Bug Details & Steps to Reproduce

After a force close and graceful LND restart, the closing transaction had sufficient confirmations, but the channel remained in waiting_close_channels and its main local commitment output was not registered for sweeping.

Observed sequence on a two-node Signet setup:

  1. Force-close a private Taproot channel.
  2. Wait until LND recognizes the first confirmation at height H.
  3. Gracefully stop LND before its three-confirmation notification is delivered. Leave Bitcoin Core running.
  4. Let Core reach H+4, then start and unlock LND. Wait for synchronization.
  5. After additional blocks, inspect lncli pendingchannels and lncli wallet pendingsweeps.

At H+6, the channel still appeared in waiting_close_channels with close_height=H and blocks_til_close_confirmed=0. Anchor sweep candidates were present, but the main local commitment output was absent.

A separate force close on the same node, without restarting during confirmation, advanced to pending_force_closing_channels after three confirmations and registered its main output for sweeping. That registration survived the subsequent restart.

The live failure may depend on startup registration order. The attached component test controls that order deterministically. We have not demonstrated permanent loss of funds.

Possibly related to #8120, #9532, and #10351. I could not establish whether these share the same underlying cause. Please redirect this report if the attached reproduction belongs under an existing issue.

Expected Behavior

LND should recognize the historical confirmation after restart and advance the channel into force-close recovery once its confirmation threshold is met.

Sweeping may still wait for the output's normal maturity.

Debug Information

The observed restart sequence, summarized without host or transaction identifiers:

  • A six-confirmation subscription starts its lookup at H+4.
  • Historical spend lookup finds the closing transaction at H.
  • The three-confirmation watcher uses the later hint/search and misses that historical confirmation.

Suspected mechanism: TxNotifier.RegisterConf shares searches for the same transaction/script. A later-starting subscriber can register first, and an earlier hint from a subsequent subscriber does not trigger an expanded lookup. An empty lookup can also advance the cached hint.

The attached test adds one file to the pinned LND commit and changes no production code. Across five runs:

  • No-restart control: PASS
  • Three-confirmation subscriber first: PASS
  • Six-confirmation subscriber first, initial scan completed: FAIL
  • Six-confirmation subscriber first, initial scan pending: FAIL

The test intentionally exits nonzero on the affected commit. It uses synthetic data, models historical lookup, and uses LND's existing in-memory mock hint cache. It does not launch full daemons or use SQLite.

Separately, the same ordering failure was reproduced with actual Core historical lookup and a reopened SQLite height-hint cache. The correct hint survived reopening, and disabling cache queries did not resolve the ordering case. We have not established that this is SQLite-specific.

The attached ZIP contains the test-only patch, instructions, and local test output.

Could you confirm whether this matches a known notifier issue and advise on a supported recovery path for an already-stuck channel?

lnd-confirmation-order-repro.zip

Environment

Two Signet nodes with Tor peer connections. A node-management TUI submitted the force-close RPC; the controlled stop/start used systemctl.

The attached reproduction runs directly against LND's notifier code without the TUI or node-management code. Tested with Go 1.26.8.