feat(lite): M5a — wallet persistence after sync/send/shield

Verified against the SDXL Rust source that the backend auto-saves only on
new-address / import / rescan; it does NOT save after sync, send, or shield, and
litelib_shutdown merely sets a flag. So without intervention a first sync
(~30 min) and any sent transaction are lost on restart.

The controller now triggers the backend `save` at exactly the right points:
- after the detached `sync` completes — and BEFORE syncDone_ is set, so a
  syncComplete() observer always sees a fully persisted wallet;
- after a successful send / shield (the doSend/doShield cores; skipped on
  failure so a failed broadcast doesn't write);
- a guarded best-effort flush in the destructor, only when syncDone_ and no
  broadcast is in flight, so shutdown never blocks on the wallet lock held by an
  uninterruptible scan or in-progress proving;
- plus a public saveWallet() for explicit/periodic saves.

Wallet-file crash recovery (.dat / .dat.bak rotation) is already handled inside
the backend.

Tests: testLiteWalletControllerM5Persistence proves saves fire after
sync/send/shield and explicit saveWallet(), and do NOT fire on a failed send or
with no wallet open (fake gains a save counter). Plan doc updated; M5b
(packaging/CI/signing/rollout) remains.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-05 12:48:44 -05:00
parent a677c09984
commit 5d317f6be3
5 changed files with 130 additions and 3 deletions

View File

@@ -35,6 +35,7 @@ inline bool g_liteFakeShutdownCalled = false;
inline std::atomic<bool> g_liteFakeSyncBlock{false}; // when true, the "sync" command blocks
inline std::atomic<bool> g_liteFakeBadBalance{false}; // when true, "balance" returns invalid JSON
inline std::atomic<bool> g_liteFakeSendFails{false}; // when true, "send"/"shield" return {"error":..}
inline std::atomic<long> g_liteFakeSaveCount{0}; // counts "save" commands (persistence checks)
inline void resetLiteFakeCounters()
{
@@ -121,6 +122,10 @@ inline char* liteFakeExecute(const char* command, const char* args)
// import (shielded key) / timport (transparent WIF): do_import_* pretty-print JSON.
if (std::strcmp(c, "import") == 0 || std::strcmp(c, "timport") == 0)
return liteFakeDup("{\"result\":\"success\",\"address\":\"zs1imported\"}");
if (std::strcmp(c, "save") == 0) {
++g_liteFakeSaveCount;
return liteFakeDup("{\"result\":\"success\"}");
}
}
// Default for any other/unknown command.
return liteFakeDup("{\"version\":\"sdxl-fake\"}");