fix(lite): honor the backend sync result with retry and a failed state

A failed backend sync was silently swallowed: the reply was never
inspected, so a sync that errored out left the wallet showing a
"syncing" state that never completed and never reported a problem.

- The sync and rescan replies are now parsed. On failure the partial
  progress is saved and the sync is retried with exponential back-off
  (5s doubling up to 60s, six attempts in all), then an explicit failed
  state is published: done, sync not complete, and the backend's error
  text carried through.
- A failed rescan is retried as a plain sync, because a rescan has
  already cleared the wallet and re-running it would throw away the
  partial progress just saved.
- A terminal error, such as a reorg deeper than the wallet can unwind,
  stops the retries immediately instead of waiting out the attempts.
- Recovery is observed, not guessed: once a later refresh's server-bound
  reads all succeed, the failed state clears itself.

Behaviour change: a sync failure now surfaces to the user as a failed
state with a message, instead of a frozen "syncing", and the client
retries on its own with back-off.

Tests: a parser case for the success, failure, and terminal replies, and
fake-backend controller cases for retrying after a failure, giving up
after the attempt cap, a terminal reorg stopping at once, a failed
rescan retrying as sync, cancel stopping the back-off, and the failed
state self-healing on a good refresh.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-10-12 21:42:23 -05:00
co-authored by Claude Opus 4.8
parent 3f7af2d028
commit 1ea584311e
8 changed files with 355 additions and 21 deletions
+12
View File
@@ -166,4 +166,16 @@ LiteTransactionsParseResult parseLiteTransactionsResponse(const nlohmann::json&
LiteSyncStatusParseResult parseLiteSyncStatusResponse(const std::string& jsonText);
LiteSyncStatusParseResult parseLiteSyncStatusResponse(const nlohmann::json& value);
// A backend `sync` / `rescan` reply. Success is exactly a JSON object with result == "success" (both do_sync Ok paths
// and do_rescan emit it). Anything else is the failure text of do_sync/do_rescan (plain text, NO "Error:" prefix), e.g.
// "Error getting latest block ...", "Server's latest block(..) is behind ours(..)", "Reorg has exceeded 100 blocks. Aborting."
struct LiteSyncOpResult {
bool ok = false;
bool terminal = false; // retrying cannot help (reorg deeper than the wallet can unwind)
std::string error; // trimmed, <= 300 chars; empty when ok
};
LiteSyncOpResult parseLiteSyncOpResponse(const std::string& text);
// Wait before sync-retry number `attempt` (1-based): baseMs, x2 per attempt, capped at 60000 ms.
int liteSyncRetryDelayMs(int attempt, int baseMs);
} // namespace dragonx::wallet