feat(lite): M2b-3 — background refresh worker + App::update hook

- LiteWalletController owns a background std::thread worker that, once a wallet is ready,
  refreshes every ~4s and publishes a copyable LiteWalletAppRefreshModel under a mutex.
  Worker auto-starts on lifecycle-ready and is stopped+joined in the destructor. status_
  is written only on the main thread; walletOpen_/syncStarted_ are atomic.
- App::update() calls takeRefreshedModel() and applies it into state_ on the main thread
  (WalletState is non-copyable, so the model crosses the thread boundary, not the state),
  so the existing Balance/Receive/Transactions tabs populate from lite data.
- refreshWalletState() refactored onto refreshModel() (pure, worker-safe).
- testLiteWalletControllerWorkerProducesModel verifies the worker publishes a populated
  model (stable across repeated runs). Builds clean in all configs.

Real-backend smoke (lite_smoke --refresh now runs real output through the parsers) found
two integration bugs, documented in the plan for follow-up:
- syncstatus parser requires synced_blocks/total_blocks but the real idle response is
  {"syncing":"false"} (string), so it fails to parse when not actively syncing.
- the first data query (balance/list) blocks on a full chain sync, which would hang the
  worker's shutdown join — needs a cancel/timeout path.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-04 22:38:34 -05:00
parent 012341b1a4
commit a8b5d2f6a3
7 changed files with 190 additions and 15 deletions

View File

@@ -15,6 +15,7 @@
#include "wallet/lite_client_bridge.h"
#include "wallet/lite_connection_service.h"
#include "wallet/lite_result_parsers.h"
#include <cstdio>
#include <string>
@@ -22,12 +23,17 @@
int main(int argc, char** argv)
{
using namespace dragonx::wallet;
std::setvbuf(stdout, nullptr, _IONBF, 0); // unbuffered so output survives a timeout kill
std::string server = "https://lite.dragonx.is";
bool doCreate = false;
bool doRefresh = false;
bool doFull = false;
for (int i = 1; i < argc; ++i) {
const std::string arg = argv[i];
if (arg == "--create") doCreate = true;
else if (arg == "--refresh") doRefresh = true;
else if (arg == "--full") doFull = true;
else server = arg;
}
@@ -61,6 +67,49 @@ int main(int argc, char** argv)
}
}
if (doRefresh) {
// Run each refresh command through the real parser to verify the LIVE backend's
// JSON shapes match what the gateway expects. Prints flags/counts only (no secrets,
// addresses, or amounts).
std::printf("[lite-smoke] --- refresh shape check (real backend output -> parsers) ---\n");
{
auto r = bridge.execute("info", "");
auto p = parseLiteInfoResponse(r.value);
std::printf("[lite-smoke] info bridge_ok=%d parse_ok=%d err=%s\n",
r.ok, p.ok, liteResultParserErrorName(p.error));
}
{
auto r = bridge.execute("syncstatus", "");
auto p = parseLiteSyncStatusResponse(r.value);
std::printf("[lite-smoke] syncstatus bridge_ok=%d parse_ok=%d err=%s synced=%llu/%llu\n",
r.ok, p.ok, liteResultParserErrorName(p.error),
(unsigned long long)p.syncStatus.syncedBlocks, (unsigned long long)p.syncStatus.totalBlocks);
std::printf("[lite-smoke] syncstatus RAW = %.200s\n", r.value.c_str());
}
// The data commands below trigger a blocking full-chain sync on a fresh wallet;
// gate them behind --full so the shape check stays fast by default.
if (!doFull) { bridge.shutdown(); std::printf("[lite-smoke] (skipping balance/addresses/list; pass --full)\n"); return 0; }
{
auto r = bridge.execute("balance", "");
auto p = parseLiteBalanceResponse(r.value);
std::printf("[lite-smoke] balance bridge_ok=%d parse_ok=%d err=%s\n",
r.ok, p.ok, liteResultParserErrorName(p.error));
}
{
auto r = bridge.execute("addresses", "");
auto p = parseLiteAddressesResponse(r.value);
std::printf("[lite-smoke] addresses bridge_ok=%d parse_ok=%d err=%s z=%zu t=%zu\n",
r.ok, p.ok, liteResultParserErrorName(p.error),
p.addresses.zAddresses.size(), p.addresses.tAddresses.size());
}
{
auto r = bridge.execute("list", "");
auto p = parseLiteTransactionsResponse(r.value);
std::printf("[lite-smoke] list bridge_ok=%d parse_ok=%d err=%s count=%zu\n",
r.ok, p.ok, liteResultParserErrorName(p.error), p.transactions.transactions.size());
}
}
bridge.shutdown();
std::printf("[lite-smoke] done (real litelib_* symbols are callable; results above are live)\n");
return 0;