Skip to content

feat: add network graph reset to recovery - #1020

Merged
ovitrif merged 9 commits into
masterfrom
feat/manual-rgs-wipe
Jun 17, 2026
Merged

feat: add network graph reset to recovery#1020
ovitrif merged 9 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, 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 100% confirmed and is still being investigated — 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 exists and 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.

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 (~5s later): it cleanly stops the LDK node and the foreground node service, then relaunches into a fresh process so the graph is re-downloaded from scratch. The user no longer has to restart manually.
  • Extracts the existing graph-clear logic (local cache + VSS delete) into a shared helper reused by both the automatic startup reset and the new manual action.
  • The button is shown 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 state is kept through the restart, and the action is clearly logged for support.

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.

Preview

wallet-exist-fg-service-disabled.webm
  Reset + clean shutdown (session 12-04-27)
  - 12:04:50 node stopped cleanly — ldk_node: Stopped all background tasks / Stopped background processing of events ✅  (your lightningRepo.stop() worked)
  - 12:05:04 Resetting network graph (manual) → Resetting network graph cache…
  - 12:05:06 VSS 'deleteObject' success for 'network_graph' - key was deleted → Cleared network graph from VSS ✅

  Restart + re-base (new session 12-05-11, ~5s later)
  - 12:05:12 app relaunched, Using gossip source: RGS server …/rgs/snapshot ✅
  - 12:05:16 No network graph found, creating empty graph ✅ (confirms the wipe  took)
  - 12:05:19 Startup complete, node Running, 1 ready channel 

  Payment (session 12-05-11)
  - 12:05:33 payInvoice → router: "…overriding the network graph of 0 nodes and 0 channels with 1 first hop"
  - 12:05:37 PaymentSent → Successfully sent payment of 55000msat (fee 1000 msat) → PaymentSuccessful ✅
wallet-exists-fg-service-enabled.webm
  Manual reset + clean stop (session 12-56-15)
  - 12:57:31.162 Resetting network graph (manual) → Stopping node…
  - 12:57:31.6 Stopped background processing → Resetting network graph cache…
  - 12:57:33.651 VSS 'deleteObject' success for 'network_graph' - key was deleted → Cleared network graph from VSS ✅
  
  Restart + re-base (new session 12-57-39, ~5s later)
  - 12:57:40 relaunched, gossip source set
  - 12:57:43.775 No network graph found, creating empty graph ✅  (confirms the cache file was truly deleted — not just emptied)
  - 12:57:46.495 Startup complete
  
  Payment (session 12-57-39)
  - 12:57:52.332 payInvoice → route over "network graph of 0 nodes and 0 channels with 1 first hop"
  - 12:57:56.296 PaymentSent → Successfully sent payment of 596000msat (fee 1000 msat) → PaymentSuccessful ✅
no-wallet.webm

QA Notes

Manual Tests

  • 1. Enter Recovery Mode → tap Reset Network Graph → confirm: success toast shows, then the app automatically restarts after a few seconds.
  • 2. After the auto-restart → perform a lightning payment
  • 3. regression: Recovery Mode with no wallet: Reset Network Graph button is hidden/disabled.

Automated Checks

  • Unit test added: covers resetNetworkGraph() clearing the local cache and deleting the VSS network_graph copy in app/src/test/java/to/bitkit/repositories/LightningRepoTest.kt.
  • Local: just compile, just test, and just lint pass.
  • On-device (regtest dev build): verified the full flow — manual reset → clean node stop → local cache + VSS delete → auto-restart → fresh empty graph → Lightning payment succeeds.

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

Copy link
Copy Markdown
Member Author

OBS: logs are reporting staging SRG server more Tham 2 weeks old

12:05:19.720 ⚠️ Failed to update network graph with RGS data: "Rapid Gossip Sync data is more than two weeks old"

@jvsena42
jvsena42 marked this pull request as ready for review June 17, 2026 13:03
@greptile-apps

greptile-apps Bot commented Jun 17, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds a "Reset Network Graph" option to the Recovery Mode screen. It extracts the existing graph-clear logic (local cache delete + VSS deleteObject) into a shared clearNetworkGraph() helper used by both the automatic startup reset and the new manual trigger, wires up a confirmation dialog and loading state in the ViewModel, and hard-restarts the app via Runtime.getRuntime().exit(0) after clearing the cache.

  • LightningRepo.resetNetworkGraph() — new public entry-point that stops the LDK node (if running) and delegates to the extracted clearNetworkGraph() helper; VSS delete failures are logged but swallowed so they do not block the flow.
  • RecoveryViewModel — new resetNetworkGraph() coroutine that shows a confirmation dialog, calls the repo method, displays a success/error toast, waits 5 s, calls lightningRepo.stop() (redundant — the node was already stopped inside resetNetworkGraph()), then calls relaunchApp().
  • Context.relaunchApp() — new extension that stops the foreground service and hard-exits; getLaunchIntentForPackage can return null, causing startActivity(null) to throw before exit(0) is reached and leaving the app in a dead state.

Confidence Score: 4/5

The core graph-reset and VSS-delete logic is sound and non-destructive to funds; the main risk is in relaunchApp() where a null launch intent can leave the app alive but broken after the node has already been stopped.

The null-safety gap in relaunchApp() means a failure path leaves the app with its node stopped, graph wiped, and no restart — requiring a manual force-close. This is a recovery screen so the blast radius is contained, but it directly undermines the feature’s reliability.

app/src/main/java/to/bitkit/ext/Context.kt (null launch intent) and app/src/main/java/to/bitkit/ui/screens/recovery/RecoveryViewModel.kt (redundant stop call / stale lifecycle state)

Important Files Changed

Filename Overview
app/src/main/java/to/bitkit/ext/Context.kt Adds relaunchApp() helper — stops foreground service, fires launch intent, and hard-exits; has a null-safety gap: if getLaunchIntentForPackage returns null, startActivity(null) throws before exit(0) runs
app/src/main/java/to/bitkit/repositories/LightningRepo.kt Extracts graph-clear logic into clearNetworkGraph() and exposes new resetNetworkGraph() public method; direct lightningService.stop() call bypasses the state machine, leaving _lightningState stale after a manual reset
app/src/main/java/to/bitkit/ui/screens/recovery/RecoveryViewModel.kt Adds resetNetworkGraph() flow with confirmation dialog, loading state, toast, and relaunch; redundant lightningRepo.stop() call after resetNetworkGraph() already stopped the node internally
app/src/main/java/to/bitkit/ui/screens/recovery/RecoveryModeScreen.kt Wires up the new Reset Network Graph button and confirmation dialog; button is correctly gated on walletExists, changes are straightforward UI additions
app/src/test/java/to/bitkit/repositories/LightningRepoTest.kt Adds a unit test for the happy path of resetNetworkGraph(); does not cover the failure path or the case where the node is running (stop is expected to be called)

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    actor User
    participant RecoveryScreen
    participant RecoveryViewModel
    participant LightningRepo
    participant LightningService
    participant VssBackupClient
    participant Context

    User->>RecoveryScreen: Tap Reset Network Graph
    RecoveryScreen->>RecoveryViewModel: showGraphResetConfirmation()
    RecoveryScreen-->>User: Show confirmation dialog

    User->>RecoveryScreen: Confirm
    RecoveryScreen->>RecoveryViewModel: resetNetworkGraph()
    RecoveryViewModel->>RecoveryViewModel: "isResettingGraph = true"

    RecoveryViewModel->>LightningRepo: resetNetworkGraph()
    alt "node != null"
        LightningRepo->>LightningService: stop()
    end
    LightningRepo->>LightningService: "resetNetworkGraph(walletIndex=0)"
    LightningRepo->>VssBackupClient: setup(0)
    LightningRepo->>VssBackupClient: deleteObject(network_graph)
    LightningRepo-->>RecoveryViewModel: Result.success

    RecoveryViewModel->>RecoveryViewModel: show success toast
    RecoveryViewModel->>RecoveryViewModel: delay(5s)
    RecoveryViewModel->>LightningRepo: stop() already stopped
    RecoveryViewModel->>Context: relaunchApp()
    Context->>Context: stopService(LightningNodeService)
    Context->>Context: startActivity(launchIntent) null risk
    Context->>Context: Runtime.exit(0)
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    actor User
    participant RecoveryScreen
    participant RecoveryViewModel
    participant LightningRepo
    participant LightningService
    participant VssBackupClient
    participant Context

    User->>RecoveryScreen: Tap Reset Network Graph
    RecoveryScreen->>RecoveryViewModel: showGraphResetConfirmation()
    RecoveryScreen-->>User: Show confirmation dialog

    User->>RecoveryScreen: Confirm
    RecoveryScreen->>RecoveryViewModel: resetNetworkGraph()
    RecoveryViewModel->>RecoveryViewModel: "isResettingGraph = true"

    RecoveryViewModel->>LightningRepo: resetNetworkGraph()
    alt "node != null"
        LightningRepo->>LightningService: stop()
    end
    LightningRepo->>LightningService: "resetNetworkGraph(walletIndex=0)"
    LightningRepo->>VssBackupClient: setup(0)
    LightningRepo->>VssBackupClient: deleteObject(network_graph)
    LightningRepo-->>RecoveryViewModel: Result.success

    RecoveryViewModel->>RecoveryViewModel: show success toast
    RecoveryViewModel->>RecoveryViewModel: delay(5s)
    RecoveryViewModel->>LightningRepo: stop() already stopped
    RecoveryViewModel->>Context: relaunchApp()
    Context->>Context: stopService(LightningNodeService)
    Context->>Context: startActivity(launchIntent) null risk
    Context->>Context: Runtime.exit(0)
Loading

Reviews (1): Last reviewed commit: "feat: restart automatically after succes..." | Re-trigger Greptile

Comment thread app/src/main/java/to/bitkit/ext/Context.kt

@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: 26bddf9f5d

ℹ️ 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 app/src/main/java/to/bitkit/repositories/LightningRepo.kt

@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.

LGTM

Tested on same mainnet wallet I use for other app tests.
Did a payment to bitkit iOS (CJIT funding) before resetting graph via the new recovery option.

Did the reset → App restarted.

Attempted then to pay:

  • Phoenix LN Invoice 🔴 1st attempt failed
  • WoS LN Invoice 🟢 suceeded
  • new Phoenix LN Invoice 🟢 succeeded

All as fast as I could, right after the app opened.

My main concerns around how long it might take to resync graph are mitigated.

@ovitrif
ovitrif merged commit 60ad136 into master Jun 17, 2026
17 checks passed
@ovitrif
ovitrif deleted the feat/manual-rgs-wipe branch June 17, 2026 15:12
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.

2 participants