From 3465acc57b496163708d73621b71eca4d66f2b29 Mon Sep 17 00:00:00 2001 From: DanS Date: Fri, 17 Jul 2026 00:50:58 -0500 Subject: [PATCH] fix(chat): session-guard the fast-scan callback (stale-wallet ingest) Review of 30bd0d9 found the 0-conf fast-scan MainCb was the one async chat callback missing the chat_session_generation_ guard the broadcast + identity-fetch callbacks use. worker_ survives a wallet switch/lock, so a fast-scan posted under wallet A could drain after resetChatSession() and ingest A's metadata (or toast) against wallet B's freshly-provisioned store. - Capture scanGen at post time and drop the result if it changed by drain time. - Clear chat_fast_scan_in_flight_ in resetChatSession() so a switch immediately re-enables the fast path; the stale callback returns WITHOUT clearing the flag so it can't clobber the new session's own in-flight scan (generation is bumped only in resetChatSession, which already reset the flag). Co-Authored-By: Claude Opus 4.8 (1M context) --- src/app_network.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/app_network.cpp b/src/app_network.cpp index c2ad22f..4e68bce 100644 --- a/src/app_network.cpp +++ b/src/app_network.cpp @@ -2765,6 +2765,7 @@ void App::resetChatSession() chat_db_.lock(); chat_identity_provisioned_ = false; chat_identity_fetch_in_flight_ = false; + chat_fast_scan_in_flight_ = false; // a stale in-flight fast-scan is dropped by its session guard chat_identity_unavailable_ = false; // Invalidate any in-flight identity fetch that captured the PREVIOUS wallet's secret, so its // completion callback can't provision that secret under the new wallet. @@ -3178,7 +3179,8 @@ void App::fastScanChatMemos() if (addr.empty()) return; chat_fast_scan_in_flight_ = true; - worker_->post([this, addr]() -> rpc::RPCWorker::MainCb { + const int scanGen = chat_session_generation_; // guard: drop the result if the wallet switches/locks + worker_->post([this, addr, scanGen]() -> rpc::RPCWorker::MainCb { std::vector metadata; try { rpc::RPCClient::TraceScope trace("HushChat / 0-conf fast scan"); @@ -3208,7 +3210,11 @@ void App::fastScanChatMemos() } } } catch (const std::exception&) {} - return [this, metadata = std::move(metadata)]() mutable { + return [this, scanGen, metadata = std::move(metadata)]() mutable { + // The wallet was switched/locked between post and now — this metadata belongs to the previous + // session. resetChatSession already reset the in-flight flag, so just drop; clearing it here + // would clobber a new session's own in-flight scan (mirrors the broadcast/identity guards). + if (scanGen != chat_session_generation_) return; chat_fast_scan_in_flight_ = false; if (metadata.empty()) return; // Skip HIDDEN conversations — the mempool fast path deliberately doesn't surface them; they