Skip to content

recover force-closed channel funds lost during RN migration #799

@jvsena42

Description

@jvsena42

Summary

A channel was force-closed during migration from RN due to the bug fixed in #760. The channel monitor was silently dropped by .mapNotNull() in fetchRNRemoteLdkData(), causing LDK to not recognize the channel on restart. The counterparty force-closed the channel, and the client's to_local output was never swept because the monitor data was missing.

376,523 sats are still sitting unclaimed on-chain.

Channel Details

Field Value
Channel ID 7c9f614b82e0917924e2925284bc6ba9f8932055e57225ffeb804cde35b96c08
Funding Txid 086cb935de4c80ebff2572e5552093f8a96bbc845292e2247991e0824b619f7c
Funding Output 0
Channel Capacity 441,826 sats
Counterparty 03816141f1dce7782ec32b66a300783b1d436b19777e7c686ed00115bd4b88ff4b (Blocktank LSP lnd3)

Force Close Transaction

Field Value
Close Txid b41cb9e5e562014ea2535c193496c099a59086c22ddafaae32c0fd9ba1b4c0d3
Block Height 934,477

Outputs

# Value Status Purpose
0 330 sats Spent (block 934,493) Anchor
1 330 sats Spent (block 934,493) Anchor
2 64,046 sats Spent (block 934,623) LSP's to_remote (swept by lnd3)
3 376,523 sats Unspent Client's to_local — never claimed

Root Cause

MigrationService.fetchRNRemoteLdkData() (before #760 fix):

}.mapNotNull { it.await() }

If any channel monitor retrieval failed, it was silently dropped. The monitor was lost, LDK sent a bogus ChannelReestablish, and the counterparty force-closed.

Proposed Solution

The RN remote backup is not wiped after migration — cleanupAfterMigration() only clears local DataStore preferences. Channel monitors should still be on the RN backup server.

One-time recovery check for all affected users

On app startup (post-migration), perform a one-time check against the RN remote backup:

  1. List channel monitors on the RN backup server via rnBackupClient.listFiles(fileGroup = "ldk")
  2. Compare with the monitors LDK Node currently knows about
  3. If there are orphaned monitors on the remote that LDK doesn't have, retrieve them
  4. For each recovered monitor, check if the corresponding funding output has an unswept to_local output on-chain
  5. If claimable funds are found, feed the monitor to LDK to reconstruct state and sweep

This would recover funds for any user affected by the #760 bug, not just this specific case. The check should be gated behind a flag so it only runs once.

Related

Metadata

Metadata

Assignees

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