Repository navigation
Description
With async monitor persistence, a channel can be left waiting on its initial ChannelMonitor persistence after a restart, even though that monitor is on disk and was loaded.
What happens
The initial monitor is handed to the persister through watch_channel, which returns InProgress. The write completes, but the ChannelManager that ends up on disk was serialized before the completion was processed, or the completion was never delivered at all (e.g. the node went down right after the write). On restart we load that manager together with the persisted monitor.
ChannelManager::read reconciles in-flight ChannelMonitorUpdates against the loaded monitors and queues BackgroundEvent::MonitorUpdatesComplete for those that made it to disk. The initial persistence is not a ChannelMonitorUpdate though: the channel is still at update id 0 and has nothing in in_flight_monitor_updates, so nothing is queued for it, and no MonitorEvent::Completed is generated for it after the restart either. The channel stays in MONITOR_UPDATE_IN_PROGRESS.
#1678 handles the neighbouring case where the monitor did not make it to disk (the channel is dropped). This is the case where it did.
Expected
If a channel is awaiting a monitor update, its latest update id is 0, and we loaded its monitor, then the initial persistence evidently completed. The channel should resume on startup the same way it would have if the completion had been delivered.
Actual
After the reload the channel does not resume:
- As the funder: no
ChannelPending, and the funding transaction is never broadcast. - As the acceptor: no
ChannelPending, and the channel does not become usable on its own.
It is not always permanent. On the acceptor side I saw the channel recover once the funding had confirmed and the peer reconnected: the stuck node sent channel_ready again, and the monitor update for the first HTLC the peer sent then released the channel. I have not seen funds at risk, the channel is just unusable until something like that happens.
Reproduction
I have a functional test for this in chanmon_update_fail_tests.rs and can share it. In short:
- Open a channel with the persister returning
InProgressfor the initial monitor on one side. - Serialize that node's
ChannelManager. - Complete the persistence afterwards, or never deliver the completion.
reload_node!with that manager and the monitor, and expectChannelPending(plus the funding broadcast for the funder).
Step 4 fails for an inbound channel, an outbound channel and a 0conf inbound channel, with LDK_TEST_REBUILD_MGR_FROM_MONITORS set to both 0 and 1, on:
The control where the completion is processed before the manager is serialized passes. I have not tested dual-funded channels.
I ran into this through lightningdevkit/ldk-node#1081, where a node using an async KVStore is stopped while the initial monitor write is in flight and then rebuilt from its store.
Possible fix
Locally I have a small change to the per-channel reconciliation in ChannelManager::read: if the channel is_awaiting_monitor_update() and get_latest_monitor_update_id() == 0, push a BackgroundEvent::MonitorUpdatesComplete for it with the loaded monitor's latest update id. With that the tests above pass, and the lightning lib tests pass in both reload modes on both revisions.
I am not sure that is the right condition or the right place for it, so I would like to hear what you think before opening a PR. LDK Node is on the 0.3 branch, so whether this can be backported matters for it.
Related
- Confirmed channel can remain pending after async monitor completion is aborted during shutdown ldk-node#1081
- Prevent ChannelMonitor persistence cancellation during shutdown ldk-node#1229 (draft) stops LDK Node from cancelling or refusing monitor persistence tasks on shutdown. That is a separate problem. It does not change the reload behaviour described here.
I used AI tools (Claude Code) while investigating this and writing the tests.
Activity