Skip to content

Initial ChannelMonitor persistence can remain pending after ChannelManager reload #4765

Open

Description

@comwanga

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:

  1. Open a channel with the persister returning InProgress for the initial monitor on one side.
  2. Serialize that node's ChannelManager.
  3. Complete the persistence afterwards, or never deliver the completion.
  4. reload_node! with that manager and the monitor, and expect ChannelPending (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

I used AI tools (Claude Code) while investigating this and writing the tests.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions