Re-audited this session's lite-wallet changes (originally written at medium
effort) and fixed the genuine issues found:
- walletReady (open path): litelib_initialize_existing returns the bare string
"OK", which is NOT valid JSON, so the previous `json::accept(value)` check
marked a *successful* open as not-ready. Key off a non-empty success response
instead (the bridge already maps "Error:"/null to failure). Drops the now
unused nlohmann include.
- sync progress: while the detached sync thread is still running, syncDone_ is
authoritative — don't surface the backend's transient idle syncstatus
({"syncing":"false"} -> parser progress=1.0/complete=true) as a misleading
100%/done. Force complete=false and zero the bogus 1.0 in the progress model.
- per-address balance: also exclude `pending` outputs (notes/utxos from an
unconfirmed received tx) so per-address figures match confirmed/available.
- secret wiping: the settings page left the page-local request copies
(input.request.*Request.{passphrase,seedPhrase}) unwiped, and the
validation-only fallback path wiped nothing. Replace the single-path memzero
with an RAII scrubber that wipes both the UI char buffers and the request
string copies on every return path.
- concurrency: document that concurrent bridge->execute() is intentionally
unguarded — litelib serializes wallet access internally via
Arc<RwLock<LightWallet>>, so a C++ mutex is unnecessary and would defeat the
sync/syncstatus concurrency the design relies on. syncLaunched_ -> atomic.
Tests: fake backend now returns the real init shapes (seed object for
create/restore, bare "OK" for open) and a new open-path case guards the
walletReady regression. Removed an unreliable alloc==freed leak assert from the
thread-bearing controller test (kept in the thread-free bridge test). Also fixed
a stray CMake indent and removed ~220MB of untracked build/debug scratch.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- LiteWalletController::newAddress(shielded) runs the backend "new" command ("zs"/"R" ->
do_new_address), parses the ["addr"] response, and returns the new address; the next
refresh lists it. Fast (local derivation), safe on the UI thread.
- fake_lite_backend returns ["zs1fakenew"]/["R1fakenew"] for "new" by args.
- testLiteWalletControllerNewAddress covers shielded/transparent + no-wallet error.
Also confirmed (no code needed): the sync-progress indicator already works for lite —
balance_tab reads state.sync.* which M2b-3 populates. Per-address balances landed in M2.
Remaining M3 is pure UI wiring (receive_tab button -> newAddress, loading/empty states),
which isn't verifiable without a GUI session.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
applyLiteRefreshModelToWalletState now derives each address's balance by summing its unspent
notes/utxos (excluding spent and unconfirmed-spent outputs) instead of the aggregate-only
zeros, so the Receive/Balance UI shows per-address amounts. The notes parser shape is
confirmed against do_list_notes in the backend source.
testLitePerAddressBalances covers the summing + spent-exclusion. Completes M2.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
LiteWalletGateway::refresh() aborted the entire refresh on the first command whose bridge
call or parse failed — which turned a single real-backend shape mismatch (e.g. syncstatus)
into a total, empty-everything refresh. Since the balance/addresses/list real shapes are
still unverified and we've already hit shape drift twice, make refresh resilient:
- Run every planned command; assembleLiteWalletRefreshBundle already skips failed results.
- result.ok = any usable data came back (bundle.complete still reflects all-succeeded).
- One command's failure now degrades gracefully — the other sections still populate.
testLiteWalletGatewayRefreshSkipsFailedCommand (fake balance returns invalid JSON) asserts
the refresh still succeeds with addresses/transactions/info populated and balance skipped.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The backend `sync` command is a blocking, uninterruptible full chain scan (do_sync(true);
does not honor the shutdown flag), and balance/list block until synced. Previously
startSync() ran on the main thread (would freeze wallet creation) and the worker could
block, making the destructor join() hang at shutdown.
Redesign:
- bridge is now std::shared_ptr<LiteClientBridge>, shared with a detached sync thread so
detaching is safe and litelib_shutdown isn't called while a running sync still holds the
bridge; the controller's own ref prevents premature shutdown during normal operation.
- startSync() launches the blocking `sync` on a detached thread (non-blocking; never joined).
- refreshModel() gates on syncDone_: while syncing it publishes syncstatus progress only;
once synced it does the full balance/addresses/list refresh (now fast).
- destructor joins only the fast poll worker and detaches the sync thread -> no hang.
- syncComplete() accessor added.
Tests (deterministic, via a blocking-sync fake; counters made atomic for the detached
thread): testLiteWalletControllerShutdownDoesNotHangDuringSync (destructor returns <1.5s
with sync blocked); refresh/worker tests wait for syncComplete()/a balance-bearing model.
Stable across repeated runs; lite+backend and full-node apps build clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The real backend returns syncstatus as idle {"syncing":"false"} (string) or in-progress
{"syncing":"true","synced_blocks":N,"total_blocks":M} (commands.rs:83-87), but
parseLiteSyncStatusResponse hard-required the block fields and failed whenever the wallet
wasn't actively syncing — so sync/progress never updated in the real app.
- Read "syncing" as a string; require synced_blocks/total_blocks only when syncing=true;
idle => complete, synced/total 0.
- fake_lite_backend syncstatus now uses the real "syncing":"true" shape.
- testLiteSyncStatusParserRealShapes covers idle, in-progress, and missing-counts-while-syncing.
- Verified against the live backend via lite_smoke --refresh (syncstatus parse_ok=1).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- 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>
Shared-bridge refactor (litelib is a global singleton; every LiteClientBridge calls
litelib_shutdown() on destruction, so services must not each own one):
- LiteWalletLifecycleService, LiteWalletGateway, LiteSyncService now take a non-owning
LiteClientBridge*; LiteWalletController owns the single bridge and passes &bridge_.
Sync + controller refresh:
- LiteSyncService::startSync executes the real "sync" command (was a stub).
- LiteWalletController: startSync() (auto-fires when a wallet becomes ready) and
refreshWalletState(WalletState&) — polls syncstatus, runs gateway.refresh(), maps the
bundle, applies balances/addresses/transactions/sync into WalletState.
Tests:
- fake_lite_backend.h returns command-shaped JSON (per tests/fixtures/lite/result_parsers.json).
- testLiteWalletControllerRefreshPopulatesState drives the full path against the fake.
- Surfaced + worked around a real integration issue: parseLiteInfoResponse requires
latest_block_height and the gateway aborts the whole refresh on the first command's
parse failure (fragile vs partial backend responses; hardening tracked for M2b-3).
Verified: ctest green; lite+backend, full-node, lite-no-backend apps + lite_smoke build clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Preserve the previously-uncommitted lite wallet implementation and related dev WIP
under version control:
- src/wallet/ lite services: client bridge, bridge runtime, connection, lifecycle,
sync, gateway, result parsers, state mapper, artifact contract/resolver, refresh
services, UI adapters, wallet_backend/capabilities. (Includes two small M1 fixes:
lifecycle walletReady now parses the response; default chain name -> "main".)
- src/chat/ chat protocol; tests/fixtures/ (lite + hushchat); tools/hushchat_fixture_check.cpp;
scripts/build-lite-backend-artifact.sh.
- Pre-existing modified app_network/security/wizard, network_refresh_service, sidebar,
mining_tab, bootstrap dialog, and version headers captured as-is.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add an encrypted SQLite transaction history cache with cached tip metadata and
per-address shielded scan progress so startup and full refreshes avoid
re-scanning every z-address while still invalidating on wallet/address/rescan
changes.
Improve wallet history loading by paging transparent transactions, preserving
cached shielded and sent rows, keeping recent/unconfirmed activity visible, and
classifying mining-address receives. Show z_sendmany opid sends immediately in
History and Overview, pin pending rows through refreshes, and apply optimistic
address/balance debits until opids resolve.
Add timestamped RPC console tracing by source/method without logging params or
results, reduce redundant refresh/RPC calls, and cache Explorer recent block
summaries in SQLite.
Expand focused tests for transaction cache encryption, scan-progress
persistence/invalidation, history preservation, operation-status parsing,
pending send visibility, and Explorer/RPC refresh behavior.
Replay cached outgoing viewtransaction entries during transaction refresh so shielded sends created from the wallet remain in the History tab after send tracking is cleared.
Keep incomplete tracked sends retryable, preserve cached send timestamp/confirmation metadata, and emit a send placeholder from gettransaction metadata when viewtransaction enrichment is not yet available.
Add regression coverage for cached sends, retryable empty entries, placeholder sends, and send txid cleanup behavior.