lightninglabs / lightninglabs/taproot-assets
fsm chain adapter drops the notifier's error channel
Nobody has claimed this yet.
- 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
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- 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