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:27 -05:00
co-authored by Claude Opus 4.8
parent cc2d383166
commit 424954ff4f
16 changed files with 646 additions and 34 deletions
+9 -2
View File
@@ -267,7 +267,7 @@ public:
bool isConnected() const { return state_.connected; }
int getBlockHeight() const { return state_.sync.blocks; }
const std::string& getConnectionStatus() const { return connection_status_; }
const std::string& getDaemonStatus() const { return daemon_status_; }
std::string getDaemonStatus() const; // thread-safe copy (daemon_status_ is written off-thread)
// Balance info (convenience wrappers)
double getShieldedBalance() const { return state_.shielded_balance; }
@@ -884,6 +884,7 @@ private:
std::function<void(bool, const std::string&)> makeSweepCompletionCallback(bool resumed);
void beginSweepToSeedWallet(); // z_mergetoaddress ["ANY_TADDR","ANY_ZADDR"] -> dest
void pollSweepStatus(); // Confirming step: poll sweep confirmations + legacy balance
void locateSweepTxid(); // recover a lost sweep txid (send to dest) -> Confirming gate
void beginAdoptSeedWallet(); // stop daemon -> swap wallet.dat -> restart with -rescan
void provisionChatIdentityFromSecret(std::string secret);
std::string chatReplyZaddr(); // a stable (persisted) wallet z-addr — chat IDENTITY (reply-to)
@@ -1123,7 +1124,13 @@ private:
// Embedded daemon state
bool use_embedded_daemon_ = wallet::supportsEmbeddedDaemon(wallet::currentWalletCapabilities());
std::string daemon_status_;
// Written by the DaemonController state callback (which fires on EmbeddedDaemon's monitor thread)
// AND read on the UI thread — so all access goes through set/getDaemonStatus() under this mutex.
// getDaemonStatus() returns a COPY: a returned reference could dangle if the monitor thread
// reassigned the string mid-read (data race / use-after-free).
mutable std::mutex daemon_status_mutex_;
std::string daemon_status_; // guarded by daemon_status_mutex_ — use set/getDaemonStatus()
void setDaemonStatus(std::string s);
mutable std::string daemon_mem_diag_; // diagnostic info for daemon memory detection
size_t daemon_output_offset_ = 0; // for incremental output parsing (rescan detection)