Skip to content

fix(watchdog): restart a wallet whose API stops answering - #3443

Open
Crypto2099 wants to merge 3 commits into
masterfrom
fix/wallet-health-check
Open

Crypto2099 wants to merge 3 commits into
masterfrom
fix/wallet-health-check

Conversation

@Crypto2099

Copy link
Copy Markdown
Collaborator

Restarts cardano-wallet when its API stops answering while its process stays alive. Until now the watchdog learned about a running wallet only from its exit, so a wallet whose server had ended left every request failing until the user restarted it from Diagnostics.

Behavior:

  • Every 15 s the watchdog sends GET /v2/network/information to a ready wallet on a new connection, with a 10 s timeout. Any HTTP status counts as an answer.
  • After 4 consecutive failed probes (45 to 70 s after answers stop) the wallet is stopped with a 15 s bound and started again on the same port, and the renderer reconnects on the new wallet_ready.
  • Before the wallet's first answer, timeouts are ignored, because the first request waits for the wallet's first connection to the node; refused connections count from the first probe. A wallet that has not answered after 5 minutes is acted on too.
  • After the machine sleeps or the watchdog is starved for more than three intervals, the failure count starts again.
  • A wallet running with tls_dir is probed over mutual TLS with the generated client certificate. A certificate failure turns the check off for that wallet process and never restarts it.
  • Restarts for no response have their own limit: at most 5 restarts in any hour, counted on a sliding window, and each restart in the window doubles the failure threshold for the next wallet. At the limit the wallet is left stopped, wallet_unrecoverable is emitted and the existing retry is offered. A restart or retry requested by the user clears the count.
  • A new wallet_unresponsive event carries the failed probe count, time without an answer, whether the wallet ever answered, the last error, the restart number and whether a restart follows. It does not use wallet_restarting, whose attempt counts crashes.
  • The check is configured by an optional wallet.health section; absent means off. The Nix generator emits the section with defaults for every network.

Stop path:

  • stop_wallet returns how the process ended and watchdog.log records its exit code on every stop.
  • The default bound rises from 10 s to 60 s for requested stops (quit, restarts the user asks for, Mithril, update install), so a wallet that is still closing its databases is not killed. A restart for no response and the stop that follows a node exit, which precedes an automatic restart, use the 15 s bound. The wait of a second launch for a stopping instance is 150 s by default as a result.

Electron: wallet_unresponsive is recorded in WatchdogManager, BackendStore mirrors it, and Diagnostics shows "Wallet restarts after no response" and "Last wallet no-response" in the table and in the copied summary. The rows appear only after a wallet has stopped answering. The state snapshot written to the support logs gains walletUnresponsiveRestarts and lastWalletUnresponsive.

Dependencies: rustls 0.23.46, tokio-rustls 0.26.6, rustls-webpki 0.103.16 and subtle 2.6.1, with default features off so aws-lc-rs is not built. ring 0.17.14 and rustls-pki-types 1.15.1 were already in the lockfile through rcgen. Cargo.lock gains 4 packages (141 to 145).

The probe client, monitor and restart budget live in watchdog/src/health/ and know nothing about the wallet.

Screenshots

None. The two Diagnostics rows are plain text rows in the existing table and appear only after a wallet has stopped answering.

Verification

  • The checks relevant to this change pass locally
  • Anything verified by hand is described above

Run with nix build .#checks.x86_64-linux.<name>: watchdog-clippy, watchdog-test, treefmt, compile, lint, jest, i18n, stylelint, cucumber-unit and storybook all pass.

Tests

  • A bug fix includes a test that fails without the fix
  • New behaviour is covered by tests, or the reason it cannot be is stated

The monitor and budget are unit-tested with synthetic time. The probe is tested against in-process servers: plain HTTP answering 200, 404 and 500, a listener that never writes, a closed port, and a tokio-rustls server that requires a client certificate, with the generated certificates answering and a certificate from another CA rejected in both directions. mock-wallet gains modes that close its listener, mute it, ignore stdin and run healthy on the second process. watchdog/tests/integration.rs covers restart after a closed listener, a kill of a hung server, the stop after a node exit using the short bound, the restart limit and its clearing by a retry, a healthy wallet, the startup grace, refusals before the first answer, clearing by a user restart and the check being off without its section. watchdog/tests/ipc.rs runs the closed-listener case without a platform gate. Jest covers the WatchdogManager and BackendStore state.

Code quality

  • Code is typed, and introduces no new @ts-ignore
  • React components are split up enough to avoid unnecessary re-renders
  • Code that only works in the main process is separated from components
  • yarn.lock is updated if dependencies changed

Review

  • PR is assigned to its author
  • PR carries the labels that apply to it
  • PR is current with the target branch and has no conflicts
  • The description above summarises every change in the PR

The watchdog learned about a running wallet only from its exit, so a wallet whose server had ended while its process stayed alive left every request failing until the user restarted it. A ready wallet is now asked for GET /v2/network/information on a new connection every 15 seconds, and after 4 consecutive failures the watchdog stops it and starts a new one on the same port. The check is configured by an optional wallet.health section (absent means off), and the Nix generator emits it for every network.

Any HTTP status counts as an answer, because a 404 or a 500 still shows that the server serves requests. A wallet started with tls_dir is probed over mutual TLS with the generated client certificate, using rustls on the ring provider that rcgen already builds, and the generated CA is the only trusted root. A certificate failure in either direction is a configuration fault, so it turns the check off for that wallet process instead of restarting it. Each probe opens a new connection, because a kept-alive connection can keep answering after the loop that accepts new connections is gone.

The first request after a wallet starts waits for the wallet's first connection to the node, so before the first answer timeouts are ignored and refused connections count; a wallet that has not answered after 5 minutes is acted on as well. After the machine sleeps or the process is starved, the failure count starts again.

A restart for no response stops the wallet, emits wallet_unresponsive and does not emit wallet_restarting, whose attempt counts crashes. These restarts have a separate limit: at most 5 restarts in any hour, counted on a sliding window, and each restart in the window doubles the failure threshold for the next wallet. At the limit the wallet is left stopped and wallet_unrecoverable is emitted, which shows the existing retry. A restart or retry requested by the user clears the count.

stop_wallet now returns how the process ended and writes its exit code to the log, so a crash on a requested stop is recorded. The default bound rises from 10 to 60 seconds for requested stops (quit, restarts the user asks for, Mithril and update install), because a wallet that closes its databases while queries are running can take longer than 10 seconds and a kill during that costs time on the next start. A restart for no response and the stop that follows a node exit, which precedes an automatic restart, use the 15-second bound. The wait of a second launch for a stopping instance follows, and is 150 seconds by default.

Electron records wallet_unresponsive and Diagnostics shows the number of restarts and the time and last error of the latest one, in the table and in the copied summary. The new strings are in en-US, and in ja-JP as untranslated entries.

The probe client, monitor and restart budget are in a health module that does not depend on the wallet. rustls 0.23.46, tokio-rustls 0.26.6, rustls-webpki 0.103.16 and subtle 2.6.1 are added with default features off.
Windows reports a connection attempt to a closed local port only after its SYN retransmissions, about two seconds, where Linux reports it at once. The test used the 400 ms timeout of the other probe tests, so on Windows the timeout fired first, and the probe returned Timeout. It now uses the 10-second timeout the watchdog runs the probe with by default.
@Crypto2099
Crypto2099 force-pushed the fix/wallet-health-check branch from a96f19c to decff87 Compare November 3, 2026 22:42
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.

1 participant