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) <noreply@anthropic.com>
This commit is contained in:
2026-07-17 00:50:58 -05:00
parent 30bd0d99ff
commit 3465acc57b

View File

@@ -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<chat::HushChatTransactionMetadata> 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