fix: FAQ CJK glyphs, outgoing-tx history, lite encryption, migrate & sync safety

Bug-report fixes plus adversarial-audit follow-ups. Bumps full 2.0.1->2.0.2 and
lite 1.1.0->1.1.1.

i18n / fonts:
- Rebuild the NotoSansCJK subset with the glyphs the FAQ back-fill introduced
  (什/么/门/做); Chinese FAQ titles rendered as "??" on zh (and ja/ko).

Transaction history (enumeration gaps):
- Surface outgoing shielded z->z sends via z_listsentbyaddress (listtransactions
  and z_listreceivedbyaddress never report them).
- Surface z->t deshields by parsing z_listsentbyaddress transparentSends (which
  z_viewtransaction does not expose); dedupe t->t against listtransactions.
- Key send-row dedup on address so equal-value multi-output sends aren't dropped.

Lite wallet (key safety):
- The async create/restore/open path the UI uses now applies the passphrase:
  encrypt a new/restored wallet, unlock an existing one. It previously discarded
  the passphrase, storing the seed/keys in PLAINTEXT (create/restore) or leaving
  an encrypted wallet locked (open).

Migrate-to-seed (fund safety):
- Persist a sweep-submitted marker at broadcast so a resumed migration can't treat
  a ~0 balance (an unconfirmed in-flight sweep) as an empty wallet and adopt (swap
  wallet.dat) before the sweep confirms; best-effort locate routes to the confirm gate.

Sync / threading:
- Gate isSynced() on a peer-derived tip (tip_known) so a peerless node isn't reported
  synced (Send against a stale balance); add a "no peers" status banner.
- Guard daemon_status_ with a mutex (monitor-thread write vs UI-thread read).

Tests: extends tests/test_phase4.cpp across all of the above; suite green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BwycPDQSfEKtrTS2uC4JeT
This commit is contained in:
2026-09-04 04:02:52 -05:00
co-authored by Claude Opus 4.8
parent f30fdc5ed1
commit fae8b20fb7
16 changed files with 646 additions and 34 deletions
+14
View File
@@ -388,6 +388,20 @@ private:
std::make_shared<std::optional<LiteWalletLifecycleResult>>();
LiteWalletLifecycleResult lastLifecycleResult_; // main-thread only; last finalized request
// Passphrase to apply on the main thread once an async create/restore/open finalizes. The
// backend wallet is not controller-"open" (walletOpen_) until pumpLifecycleResult() runs
// onLifecycleResult(), and encryptWallet()/unlockWallet() gate on walletOpen_ — so the async
// worker thread cannot do it. The passphrase is stashed here (main-thread only) and applied in
// pumpLifecycleResult(), mirroring the synchronous createWallet()/restoreWallet()/openWallet()
// wrappers. Without this the UI's async path silently discarded the passphrase, storing the
// seed UNENCRYPTED (create/restore) or never unlocking an encrypted wallet (open) — audit C1/M1.
enum class PendingSecurityOp { None, Encrypt, Unlock };
std::string pendingLifecyclePassphrase_; // main-thread only; wiped after use
PendingSecurityOp pendingLifecycleSecurityOp_ = PendingSecurityOp::None;
void stashPendingLifecycleSecurity(const std::string& passphrase, PendingSecurityOp op);
void applyPendingLifecycleSecurity(); // encrypt (create/restore) or unlock (open), then wipe
void clearPendingLifecycleSecurity(); // wipe + reset (rejected request / failed open)
// Joinable background refresh worker (fast iterations: syncstatus, plus data once synced).
std::thread worker_;
std::atomic<bool> running_{false};