lightninglabs / lightninglabs/taproot-assets

fsm chain adapter drops the notifier's error channel

Open
#2,286 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug supply commit
Dominant language
Go
Stars
525
Forks
150
Avg merge
2d 15h
Merged PRs (30d)
31

Description

(Fable-generated. 'watcher-sites' is a development branch.)

## Summary

`lndservices.LndFsmDaemonAdapters.RegisterConfirmationsNtfn` (and
`RegisterSpendNtfn`) discards the error channel that lndclient returns
alongside the event channel:

spendDetail, _, err := l.lnd.ChainNotifier.RegisterConfirmationsNtfn(...)

lndclient's stream goroutine, on any `Recv` error (an lnd restart, a
dropped connection), sends the error on that channel and returns
without closing the event channel. The adapter wraps the event
channel in a `chainntnfs.ConfirmationEvent` whose `Confirmed` channel
therefore never closes and never delivers. protofsm's daemon-event
goroutine selects only on `Confirmed` and the state machine's
context, so the wait blocks until the machine is stopped. No error is
logged, no re-registration happens, and the machine stays "running"
so `fetchStateMachine` keeps handing out the stalled instance.

## Where

- `lndservices/daemon_adapters.go`, `RegisterConfirmationsNtfn`
(~line 167) and `RegisterSpendNtfn` (~line 190): the second return
value is discarded.
- lndclient `chainnotifier_client.go`: on stream error the goroutine
does `errChan <- err; return`, leaving `confChan` open.
- lnd `protofsm/state_machine.go`, the `RegisterConf` case: the
launched goroutine selects on `confEvent.Confirmed` and `ctx.Done()`
only; `!ok` (a closed channel) returns without re-registering.

## Exposure

Every protofsm-driven machine that waits on the adapter is affected:
the supply-commit machine on the legacy confirmation path, and the
supply verifier. On `watcher-sites` the verifier's chain-clock re-arm
makes this the machine's sole outstanding event for up to
`chainntnfs.MaxNumConfs` (144) sequential registrations while a
commitment pull keeps failing, so a single severed stream inside that
window stalls the verifier for that asset group until the daemon
restarts. (`main` retried with an in-machine `time.Sleep` and one
initial registration, so the window there is shorter but not absent.)

## Reproduction

1. Run a verifier node syncing a group whose commitment pull fails
for a while (e.g. the issuer's universe server is briefly
unreachable), so the verifier re-arms on the spend tx's next
confirmation.
2. Restart lnd (or otherwise sever the chain-notifier stream) while
the verifier waits on that registration.
3. Mine blocks. The verifier never wakes: no log line, no re-arm, no
sync. Restarting tapd recovers it.

## Possible fixes

- Adapter-level resubscription: consume the error channel in the
adapter, and on error re-register with the same parameters (the
height hint makes this safe) and keep forwarding events to the same
`Confirmed`/`Spend` channel, with backoff and a log line. This keeps
the `chainntnfs.*Event` contract that protofsm expects and fixes
every caller at once.
- At minimum, close the event channel on error and log it, so
protofsm's `!ok` branch returns rather than blocks, and give the
affected state machines an explicit re-registration path on that
return. This is more invasive per machine.
- Add an adapter unit test with a stream that errors after
registration, asserting that a later confirmation still reaches the
consumer.

## Provenance

Pre-existing on `main` (the adapter has discarded the error channel
since it was introduced). Found on 2026-09-07 during the review of
the `watcher-sites` supply-verifier changes, which widen the window
in which it can bite.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with lndservices/daemon_adapters.go at RegisterConfirmationsNtfn and RegisterSpendNtfn, then trace the error and event channels in chainnotifier_client.go and the RegisterConf case in protofsm/state_machine.go. Reproduce a stream error and verify with an adapter test that the consumer does not remain blocked and a later confirmation reaches it.

Written by the indexing model from the issue text.

Assessment

Tech stack
go
Domain
backend, distributed-systems
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.