fix: prevent RN migration from overwriting local channel monitors#495
fix: prevent RN migration from overwriting local channel monitors#495
Conversation
This comment was marked as outdated.
This comment was marked as outdated.
Safest recovery path for already-affected usersThe safest approach is to accept stale monitors and cooperative-close the affected channels, rather than wiping data or force-closing with revoked commitments. Why this is the safest option
How it works
Required changesrust-lightning ( The stale check and // Instead of returning Err(DecodeError::DangerousValue) when monitor is stale:
if args.accept_stale_channel_monitors {
log_warn!(logger,
"Accepting stale monitor for channel {} (monitor update_id {} vs manager update_id {}). \
Cooperative close recommended.",
channel_id, monitor_update_id, manager_update_id
);
stale_channels.push(channel_id);
// Continue deserialization instead of returning error
} else {
return Err(DecodeError::DangerousValue); // existing behavior
}ldk-node ( Expose the flag via the builder API: pub fn set_accept_stale_channel_monitors(&mut self, accept: bool) -> &mut Self {
self.accept_stale_monitors = accept;
self
}After the node starts, auto-close stale channels: for channel_id in stale_channels {
node.close_channel(&channel_id, counterparty_node_id)?;
}Swift side (after ldk-node change): On the first startup failure due to stale monitors, retry with the flag enabled: // In LightningService.setup() or WalletViewModel.start()
// If node fails with stale monitor error, rebuild with:
builder.setAcceptStaleChannelMonitors(true)
// Node starts → stale channels are auto-closed cooperativelyCombine with migration-aware write (prevention)For defense-in-depth, also add the migration-aware write from the previous comment to |
Impact on displayed balanceThe balance values ( When the node fails to start due to the stale monitor:
Result: The user sees a plausible-looking but completely frozen balance — the last known values from before the app update. It's not zero, so it doesn't immediately look broken. But:
|
Correction: ldk-node migration-aware write already existsThe migration-aware write protection already exists in ldk-node — but was added after the app's pinned version. Timeline
The current
What this means
|
Update: ldk-node fix confirmed in
|
…ludes the migration-aware write protection
d7a9d96 to
8ab0069
Compare
|
@piotr-iohk replaced by #498 for a cleaner commit history |
Description
This PR prevents old RN channel monitors from overwriting newer local ones during orphaned channel recovery, which caused LDK to refuse to start and display stale balances.
Root cause: PR #462 introduced orphaned channel monitor recovery that re-fetches all RN monitors from the remote backup. The ldk-node version at the time (
c5698d0, post-rc.32) blindly wrote migration monitors to storage viasetChannelDataMigration(), overwriting existing ones — including monitors that had advanced far beyond the old RN version. PR #480 fixed the channel manager overwrite but not the monitor overwrite.Why it triggered for this user:
isChannelRecoveryCheckedis a new UserDefaults key introduced by PR #462. On first launch of the new version, it defaults tofalse, causingfetchOrphanedChannelMonitorsIfNeeded()to run for ALL existing RN-migrated users — not just those with genuinely orphaned channels. Every RN-migrated user with active channels who updated to the version with PR #462 is affected.Fix: The migration-aware write protection already exists in ldk-node
v0.7.0-rc.33(commite48948f).apply_channel_data_migration()now comparesupdate_idbefore writing and skips if the existing monitor is newer or equal. This PR bumps ldk-node to include this fix. No Swift-side filtering is needed.Recovery for already-affected users: Requires a separate ldk-node change —
set_accept_stale_channel_monitors(true)to allow the node to start with stale monitors and cooperative-close the affected channels (see PR comments for details).Balance impact: When the node fails to start,
@AppStoragebalance values (totalBalanceSats,totalOnchainSats,totalLightningSats) are never updated — the user sees a plausible-looking but completely frozen balance from the last successful session.Logs from the bug
bitkit_logs_2026-03-17_14-16-30.zip
Linked Issues/Tasks
Related PRs:
Screenshot / Video
N/A - backend logic change