Skip to content

feat: add network graph reset to recovery - #600

Merged
jvsena42 merged 8 commits into
masterfrom
feat/manual-rgs-wipe
Jun 19, 2026
Merged

feat: add network graph reset to recovery#600
jvsena42 merged 8 commits into
masterfrom
feat/manual-rgs-wipe

Conversation

@jvsena42

@jvsena42 jvsena42 commented Jun 17, 2026

Copy link
Copy Markdown
Member

This PR adds a manual "Reset Network Graph" option to the Recovery Mode screen as a safe recovery path for a rare "route not found" issue.

In an observed support case (on the React Native app), a wallet with healthy channels and outbound liquidity could not route payments ("route not found") because its local Lightning network graph had decayed (far fewer channels than a full snapshot). The exact root cause is not yet confirmed — the leading hypothesis is an interaction between LDK pruning stale channels and Rapid Gossip Sync only applying forward deltas without ever re-downloading a full snapshot, but that is not conclusive. The issue appears to be infrequent.

What is clear is that the decayed-graph state breaks routing, and that wiping the cached graph reliably resolves it by forcing a fresh full download. This PR adds that as a manual, low-risk recovery path so support and users can fix an affected wallet without reinstalling, independent of the eventual root-cause fix. This is the iOS port of synonymdev/bitkit-android#1020.

Description

  • Adds a "Reset Network Graph" action to Recovery Mode that clears the cached network graph (the local cache file and the VSS backup copy) so a fresh full snapshot is downloaded on the next launch.
  • After a successful reset, the app automatically restarts (~3s later): it cleanly stops the LDK node, then recreates the app scene so startup re-downloads the graph from scratch. The user no longer has to restart manually. Since iOS apps cannot relaunch their own process, this reuses the existing session-bump mechanism (the same one used after a wallet wipe) rather than killing the process.
  • Before clearing, it waits for any in-progress startup to settle and stops the node whenever one actually exists — covering the .stopping, .errorStarting, and .starting states as well — so a node can't survive the restart and re-persist the graph after it's cleared.
  • Extracts the existing graph-clear logic (local cache + VSS delete) into a shared helper reused by both the automatic startup cleanup and the new manual action.
  • The button is enabled only when a wallet exists, is non-destructive to funds, and is behind a confirmation dialog. A success or error toast is shown, the loading spinner is kept through the restart, and the action is clearly logged for support.
  • The secondary button variant now renders a loading spinner (it previously only greyed out), so the in-progress reset is visible to the user.

This is a workaround, not a root-cause fix: it does not change the gossip/pruning behaviour and does not try to detect or auto-trigger the reset.

Linked Issues/Tasks

Ports synonymdev/bitkit-android#1020.

Screenshot / Video

with-wallet.mov
16:32:20.762  Resetting network graph (manual)
16:32:21.220  Node stopped
16:32:21.224  Deleted network graph cache at: …/regtest/wallet0/ldk/network_graph_cache
16:32:21.930  VSS 'deleteKey' success for 'network_graph': true
16:32:21.932  Cleared network graph from VSS

16:32:25.243  Using gossip source rgs url: …/rgs/snapshot       (fresh setup after restart)
16:32:29.394  No network graph found, creating empty graph      (confirms the wipe took)

16:32:51.101  Searching for a route … overriding the network graph of 0 nodes and 0 channels with 1 first hops
16:32:56.853  Successfully sent payment of 114000msat (fee 1000 msat) … preimage "08df9431…"
no-wallet.mov

QA Notes

Manual Tests

  • 1. Recovery → tap Reset Network Graph → confirm: spinner shows on the button, success toast appears, then the app automatically restarts after a few seconds.
  • 2. After the auto-restart → perform a Lightning payment: payment succeeds.
  • 3. regression: Recovery with no wallet: Reset Network Graph button is disabled.

Automated Checks

  • Local: xcodebuild (Debug, iPhone 17 simulator) build passes; SwiftFormat reports no changes.
  • CI: standard build and test checks run by the PR bot.

@jvsena42 jvsena42 self-assigned this Jun 17, 2026
@jvsena42

This comment was marked as resolved.

@jvsena42
jvsena42 marked this pull request as ready for review June 17, 2026 14:39

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a82c16990a

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread Bitkit/ViewModels/WalletViewModel.swift Outdated
@greptile-apps

greptile-apps Bot commented Jun 17, 2026

Copy link
Copy Markdown

Greptile Summary

This PR ports the Android "Reset Network Graph" recovery action to iOS, giving users a manual way to clear a decayed LDK network graph that causes "route not found" failures without reinstalling the app.

  • Adds a Reset Network Graph button to RecoveryScreen (guarded by locked + walletExists, behind a confirmation dialog) that stops the LDK node, deletes the local graph cache and its VSS backup, shows a success toast, and automatically restarts the app via the existing session.bump() mechanism.
  • Refactors the duplicated VSS + local delete logic into a shared clearNetworkGraph() helper reused by RecoveryScreen, WalletViewModel.runLegacyNetworkGraphCleanupIfNeeded, and LdkDebugScreen.
  • Extends SecondaryButtonView to render a ProgressView spinner when isLoading is set, and threads isLoading through CustomButton to the secondary variant.

Confidence Score: 5/5

The change is non-destructive to funds and follows the established wipe/restart pattern; the new button is gated behind wallet existence and a confirmation dialog.

The core flow — stop node, delete local graph, delete VSS entry, restart — is logically correct for all normal node states. The shared clearNetworkGraph() helper is a clean extraction with no new logic. SecondaryButtonView correctly gates double-taps via isLoading. No new correctness issues were found beyond what prior threads already captured.

WalletViewModel.swift — the resetNetworkGraph() path through waitForNodeToRun for edge-case lifecycle states was flagged in a prior review thread and is worth verifying before merge.

Important Files Changed

Filename Overview
Bitkit/ViewModels/WalletViewModel.swift Extracts clearNetworkGraph() helper and adds resetNetworkGraph() with node-stop logic; waitForNodeToRun does not await an in-flight .stopping state before clearing, which was flagged in a prior thread.
Bitkit/Views/Recovery/RecoveryScreen.swift Adds Reset Network Graph button with confirmation alert, loading state, success/error toasts, and session.bump() restart; button is correctly guarded by locked and walletExists.
Bitkit/Components/Button/SecondaryButtonView.swift Adds isLoading support: shows a ProgressView spinner and hides icon/title while loading, matching PrimaryButtonView behavior.
Bitkit/Components/Button/Button.swift Threads isLoading through to SecondaryButtonView; no logic changes.
Bitkit/Views/Settings/LdkDebugScreen.swift Replaces duplicated VSS + local delete logic with wallet.clearNetworkGraph(); error path now only logs, which matches prior behavior.
Bitkit/Services/LightningService.swift Adds hasNode computed property so callers can check node existence without a blocking LDK call.
Bitkit/Resources/Localization/en.lproj/Localizable.strings Adds seven new localisation keys for the Reset Network Graph flow.
changelog.d/next/600.fixed.md Adds changelog entry for the new Reset Network Graph recovery option.

Reviews (3): Last reviewed commit: "Merge branch 'master' into feat/manual-r..." | Re-trigger Greptile

Comment thread Bitkit/ViewModels/WalletViewModel.swift
Comment thread Bitkit/Views/Recovery/RecoveryScreen.swift
@jvsena42
jvsena42 marked this pull request as draft June 17, 2026 14:49
@jvsena42
jvsena42 marked this pull request as ready for review June 17, 2026 16:41

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1d3654d3df

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread Bitkit/ViewModels/WalletViewModel.swift Outdated
@jvsena42
jvsena42 requested review from ovitrif and pwltr June 18, 2026 12:25
ovitrif
ovitrif previously approved these changes Jun 18, 2026

@ovitrif ovitrif left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tAck
I verified the Recovery reset network graph flow with exported iPhone logs: the local network_graph_cache was removed, VSS network_graph was deleted successfully, and the node restarted from an empty graph.

@ovitrif ovitrif added this to the 2.4.0 milestone Jun 18, 2026
@pwltr

pwltr commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Originally the recovery was added from necessity because we had several issues with the node crashing the app or freezing the UI, and we needed a place to run functions without the node running. Currently it only contains the bare minimum to recover from such a broken state. This feature doesn't seem like it fits in that same category and belongs in either dev settings (the same function is already exposed in LdkDebugScreen, if you decide to keep it please reuse the logic) or in the normal UI where it makes sense (SendFailure screen or advanced settings).

Related to that there seems to be some edge cases where the node would indeed also start on RecoveryScreen, ie. launching into recovery mode while offline, then coming back online starts the node. Probably why you needed to guard against a running node with try await stopLightningNode(). Tracked in #603.

@jvsena42

Copy link
Copy Markdown
Member Author

Originally the recovery was added from necessity because we had several issues with the node crashing the app or freezing the UI, and we needed a place to run functions without the node running. Currently it only contains the bare minimum to recover from such a broken state. This feature doesn't seem like it fits in that same category and belongs in either dev settings (the same function is already exposed in LdkDebugScreen, if you decide to keep it please reuse the logic) or in the normal UI where it makes sense (SendFailure screen or advanced settings).

Related to that there seems to be some edge cases where the node would indeed also start on RecoveryScreen, ie. launching into recovery mode while offline, then coming back online starts the node. Probably why you needed to guard against a running node with try await stopLightningNode(). Tracked in #603.

I created a specific button because I want to investigate further the source of the bug before integrating the wipe to the try again flow. see https://synonymworkspace.slack.com/archives/C0AMQ60FA4R/p1781263588886129

And implemented on recovery screen because I didn't want to make a normal user enable the developer screen

@ovitrif

ovitrif commented Jun 19, 2026

Copy link
Copy Markdown
Collaborator

IMHO it's ok to keep it on recovery screen, it does signal intent of recovery for users; which know less about historical reasons.

It also warrants less work now because this shape of the solution is already merged in Android.

Hope this helps 🙏🏻

@ovitrif

ovitrif commented Jun 19, 2026

Copy link
Copy Markdown
Collaborator

I also know we agreed to a different shape before it got implemented, but that's ok IMHO, ultimate decision is for the author of the changes proposed in PRs. I should've probably signaled the mismatch vs. previous discussions while reviewing the Android PR, that's on me.

@jvsena42

Copy link
Copy Markdown
Member Author

(the same function is already exposed in LdkDebugScreen, if you decide to keep it please reuse the logic)

The logic implemented in the view model has a guard for VSS wipe failure. I'll set as draft to reuse the same logic in both places

@jvsena42
jvsena42 marked this pull request as draft June 19, 2026 11:31
@jvsena42

Copy link
Copy Markdown
Member Author

I also know we agreed to a different shape before it got implemented, but that's ok IMHO, ultimate decision is for the author of the changes proposed in PRs. I should've probably signaled the mismatch vs. previous discussions while reviewing the Android PR, that's on me.

I should have make more clear in the description why Implemented as a new button instead of an automated check of inside "try again" flow

@jvsena42

Copy link
Copy Markdown
Member Author

test after 0092285

Simulator.Screen.Recording.-.iPhone.17.-.2026-06-19.at.10.39.09.mov
  Graph delete (via debug screen → shared helper)

  13:38:33.493  Network graph cache not found at:
  …/regtest/wallet0/ldk/network_graph_cache   (no local file to
  delete this run)
  13:38:33.494  VSS 'deleteKey' call for 'network_graph'
  13:38:33.717  VSS 'deleteKey' success for 'network_graph': true
  13:38:33.718  Cleared network graph from VSS          →
  WalletViewModel.clearNetworkGraph()

  Payment (~25s later)

  13:38:58.360  Searching for a route … overriding the network graph
  of 0 nodes and 0 channels with 1 first hops
  13:39:04.604  PaymentSent { amount_msat: 23000, fee_paid_msat:
  1000, preimage: 69809b10… }
  13:39:04.826  Successfully sent payment of 23000msat (fee 1000
  msat) … preimage "69809b10…"  ✅

OBS: the node started from an already-empty graph after a earlier recovery resets

@jvsena42
jvsena42 marked this pull request as ready for review June 19, 2026 13:54

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: bada900adc

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread Bitkit/ViewModels/WalletViewModel.swift

@pwltr pwltr left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tACK 273e733

@jvsena42
jvsena42 enabled auto-merge June 19, 2026 14:29
@jvsena42
jvsena42 merged commit 9218194 into master Jun 19, 2026
11 checks passed
@jvsena42
jvsena42 deleted the feat/manual-rgs-wipe branch June 19, 2026 15:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants