diff --git a/src/app.cpp b/src/app.cpp index 32ebca3..1ea22a8 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -952,8 +952,8 @@ void App::update() const double nowSecs = ImGui::GetTime(); const double retryInterval = lite_wallet_->lastOpenWasWarmup() ? 4.0 : 20.0; if (!lite_wallet_->walletOpen() && !lite_wallet_->openInProgress() && - lite_wallet_->walletExists() && - (!lite_autoopen_done_ || nowSecs - lite_open_last_attempt_ > retryInterval)) { + (!lite_autoopen_done_ || nowSecs - lite_open_last_attempt_ > retryInterval) && + lite_wallet_->walletExists()) { lite_autoopen_done_ = true; lite_open_last_attempt_ = nowSecs; lite_wallet_->beginOpenExisting(); @@ -3718,7 +3718,7 @@ void App::renderLiteFirstRunPrompt() wallet::LiteWalletRestoreRequest req; req.seedPhrase = seedTrim; // normalized: NBSP-glued pastes restore correctly req.birthday = static_cast(std::max(0, restoreBirthday)); - req.overwrite = lite_wallet_->walletExists(); // replace any existing wallet file + req.overwrite = lite_wallet_->walletExistsFresh(); // replace any existing wallet file if (lite_wallet_->beginRestoreWalletAsync(std::move(req))) { restoreErr.clear(); restoring = true; diff --git a/src/wallet/lite_wallet_controller.cpp b/src/wallet/lite_wallet_controller.cpp index d1441e3..445fe71 100644 --- a/src/wallet/lite_wallet_controller.cpp +++ b/src/wallet/lite_wallet_controller.cpp @@ -671,6 +671,7 @@ void LiteWalletController::pumpAsyncOpen() outcome = std::move(openResult_->value()); openResult_->reset(); } + invalidateWalletExists(); // a real open result was consumed: the wallet file may have changed if (openThread_.joinable()) openThread_.join(); // producer set its result, then exits if (outcome.ok) { @@ -842,6 +843,7 @@ void LiteWalletController::pumpLifecycleResult() out = std::move(lifecycleResult_->value()); lifecycleResult_->reset(); } + invalidateWalletExists(); // a real lifecycle result was consumed: the wallet file may have changed if (lifecycleThread_.joinable()) lifecycleThread_.join(); // producer set its result, then exits // Finalize on the main thread: flip walletOpen()/status, persist, start sync/worker on success; @@ -1400,7 +1402,21 @@ bool LiteWalletController::walletExists() const { // chainName_ is always a backend-accepted value ("main"/"test"/"regtest"); litelib_wallet_ // exists panics on unknown chains, but settings migration guarantees a valid one. - return bridge_ && bridge_->walletExists(chainName_); + // Cached ~2s: called every frame while no wallet is open (main-thread only). + const auto now = std::chrono::steady_clock::now(); + if (wexCached_ && now - wexAt_ < std::chrono::seconds(2)) return wexValue_; + wexValue_ = bridge_ && bridge_->walletExists(chainName_); + wexCached_ = true; + wexAt_ = now; + return wexValue_; +} + +bool LiteWalletController::walletExistsFresh() const +{ + wexValue_ = bridge_ && bridge_->walletExists(chainName_); + wexCached_ = true; + wexAt_ = std::chrono::steady_clock::now(); + return wexValue_; } namespace { diff --git a/src/wallet/lite_wallet_controller.h b/src/wallet/lite_wallet_controller.h index 508329c..48914d9 100644 --- a/src/wallet/lite_wallet_controller.h +++ b/src/wallet/lite_wallet_controller.h @@ -193,6 +193,7 @@ public: // True if a wallet file already exists on disk for this chain. Use to auto-open on startup // (open via openWallet({}) needs no passphrase — initialize_existing just loads the file). bool walletExists() const; + bool walletExistsFresh() const; // uncached; also refreshes the cache const WalletBackendStatus& status() const { return status_; } LiteWalletLifecycleAvailability availability() const { return lifecycle_.availability(); } @@ -399,6 +400,13 @@ private: // mutex would instead serialize sync against syncstatus polling and defeat the design. std::shared_ptr bridge_; std::string chainName_; // backend chain id (for walletExists); from connection settings + // walletExists() is called every frame while no wallet is open; the backend call touches the + // filesystem (and prints). Cache it (main-thread only - no lock). walletExistsFresh() bypasses + // the cache for the click-time overwrite decision. + mutable bool wexCached_ = false; + mutable bool wexValue_ = false; + mutable std::chrono::steady_clock::time_point wexAt_{}; + void invalidateWalletExists() { wexCached_ = false; } LiteConnectionSettings connectionSettings_; // kept for the failover candidate-server list WalletCapabilities capabilities_; // for thread-local lifecycle services (async path) LiteWalletLifecycleOptions lifecycleOptions_; // ditto (allowBridgeCalls / rollout gate) diff --git a/tests/test_phase4.cpp b/tests/test_phase4.cpp index 12b8d9c..d350e4e 100644 --- a/tests/test_phase4.cpp +++ b/tests/test_phase4.cpp @@ -18146,6 +18146,22 @@ void testTransactionCsv() { } // namespace +void testLiteWalletExistsCache() +{ + using namespace dragonx::wallet; + const auto liteCaps = makeWalletCapabilities(WalletBuildKind::Lite, /*embeddedDaemon*/ false, /*liteBackendLinked*/ true); + const LiteConnectionSettings conn = defaultLiteConnectionSettings(); + dragonx::test::resetLiteFakeCounters(); + dragonx::test::g_liteFakeWalletExists = true; + LiteWalletController controller(liteCaps, conn, LiteClientBridge::fromApi(dragonx::test::makeFakeLiteApi())); + EXPECT_TRUE(controller.walletExists()); // queries backend, caches true + dragonx::test::g_liteFakeWalletExists = false; + EXPECT_TRUE(controller.walletExists()); // still cached (TTL not elapsed) + EXPECT_FALSE(controller.walletExistsFresh()); // uncached read, refreshes the cache + EXPECT_FALSE(controller.walletExists()); // cache now false + dragonx::test::g_liteFakeWalletExists = true; // restore default for later tests +} + int main() { testConnectionConfig(); @@ -18339,6 +18355,7 @@ int main() testLiteClientBridgeUsesRuntimeOwnedStringCleanup(); testLiteBackendInjectableFakeBridge(); testLiteWalletControllerLifecycle(); + testLiteWalletExistsCache(); testLiteWalletControllerOpenFailover(); testLiteWalletControllerAsyncLifecycleFailover(); testLiteWalletControllerConsoleCommand();