diff --git a/src/app.cpp b/src/app.cpp index 50ba895..b222982 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -988,7 +988,10 @@ void App::update() // saw the rescan running. Without the confirmed-active gate, the first // poll (which hits the still-running pre-restart daemon, rescanning=false) // would fire a false "complete" the instant rescan was clicked. - ui::Notifications::instance().success("Blockchain rescan complete"); + if (user_initiated_rescan_) { + ui::Notifications::instance().success("Blockchain rescan complete"); + user_initiated_rescan_ = false; // surfaced once; not for background rebuilds + } resetWitnessRescanProgress(); state_.sync.rescan_progress = 1.0f; } @@ -1091,8 +1094,9 @@ void App::update() // Apply results directly — we are already on the main thread. const std::string& status = scan.lastStatus; if (scan.finished) { - if (state_.sync.rescanning) { + if (state_.sync.rescanning && user_initiated_rescan_) { ui::Notifications::instance().success("Blockchain rescan complete"); + user_initiated_rescan_ = false; // surfaced once; not for background rebuilds } // Witness rebuild finishes with the rescan it's part of. resetWitnessRescanProgress(); @@ -5464,6 +5468,7 @@ void App::rescanBlockchain() // pre-restart daemon and see rescanning=false) can't be misread as instant completion. state_.sync.rescanning = true; rescan_confirmed_active_ = false; + user_initiated_rescan_ = true; // user-triggered rescan → its completion should toast state_.sync.rescan_progress = 0.0f; state_.sync.rescan_status = decision.status; transactions_dirty_ = true; @@ -5515,6 +5520,7 @@ void App::repairWallet() // confirmed-active gating as rescan: the first poll may still reach the pre-restart daemon. state_.sync.rescanning = true; rescan_confirmed_active_ = false; + user_initiated_rescan_ = true; // user-triggered repair (implies rescan) → completion should toast state_.sync.rescan_progress = 0.0f; state_.sync.rescan_status = decision.status; transactions_dirty_ = true; @@ -6930,6 +6936,7 @@ void App::reindexBlockDatabase() } if (!daemon_controller_) return; daemon_controller_->setReindexOnNextStart(true); + user_initiated_rescan_ = true; // reindex implies a rescan → its completion should toast daemon_controller_->resetCrashCount(); // the abort no longer counts against the restart budget show_block_db_reindex_confirm_ = false; block_db_reindex_available_ = false; // un-gate → the connect loop restarts the node with -reindex diff --git a/src/app.h b/src/app.h index 75460ec..9509600 100644 --- a/src/app.h +++ b/src/app.h @@ -1302,6 +1302,11 @@ private: // the per-second mining/rescan-status pollers are suppressed (the daemon holds cs_main for // the whole scan and would block them); completion is signalled by the rescan RPC callback. bool runtime_rescan_active_ = false; + // True only for a rescan the WALLET/USER initiated (the Rescan button, a -rescan/salvage/zap/reindex + // restart, key import, seed migration) — not an autonomous background witness rebuild the daemon does + // on its own. Gates the "Blockchain rescan complete" toast so background rebuilds don't fire it; + // cleared when the toast is shown. + std::atomic user_initiated_rescan_{false}; // atomic: some rescan triggers run on worker threads // Set when a bootstrap completes; consumed once the daemon is connected to auto-run a rescan // that reconciles the preserved wallet.dat against the freshly-imported chain. bool post_bootstrap_rescan_pending_ = false; diff --git a/src/app_network.cpp b/src/app_network.cpp index 06fda63..57a1c23 100644 --- a/src/app_network.cpp +++ b/src/app_network.cpp @@ -1365,6 +1365,7 @@ void App::switchToWallet(const std::string& walletFile, bool stopDaemonConfirmed if (daemon_controller_) daemon_controller_->clearExternalDaemonDetected(); if (salvage && daemon_controller_) daemon_controller_->setSalvageOnNextStart(true); // repair a corrupt wallet else if (needRescan && daemon_controller_) daemon_controller_->setRescanOnNextStart(true); // (salvage implies rescan) + if (salvage || needRescan) user_initiated_rescan_ = true; // wallet-triggered repair/rescan → completion should toast // Start ONCE. Do NOT retry-spawn: a second start while the first is still shutting down leaves // two dragonxd holding wallet.dat against each other (BDB "Failed to rename … Error"). The // stopDaemonForWalletSwitch() wait already ensured the old node's process is gone, so a valid @@ -1680,7 +1681,10 @@ void App::refreshCoreData() transactions_dirty_ = true; last_tx_block_height_ = -1; invalidateShieldedHistoryScanProgress(true); - ui::Notifications::instance().success("Blockchain rescan complete"); + if (user_initiated_rescan_) { + ui::Notifications::instance().success("Blockchain rescan complete"); + user_initiated_rescan_ = false; // surfaced once; not for background rebuilds + } } NetworkRefreshService::applyConnectionInfoResult(state_, result.info); @@ -4831,7 +4835,7 @@ void App::beginAdoptSeedWallet() // 3. Rescan on next start (only if the swap happened) and bring the daemon back up — // unless we're quitting, in which case don't resurrect it. - if (swapDone && daemon_controller_) daemon_controller_->setRescanOnNextStart(true); + if (swapDone && daemon_controller_) { daemon_controller_->setRescanOnNextStart(true); user_initiated_rescan_ = true; } if (!shutting_down_) { // We stopped the daemon ourselves (port_free) — clear the adopted-external latch so the // relaunched process is treated as owned (stop/isRunning/exit behave normally afterward). @@ -5130,7 +5134,7 @@ void App::rebuildWalletDatabase() fs::rename(datadir + "/database", datadir + "/database.prerebuild-" + std::string(ts) + ".bak", e2); for (const auto& e : fs::directory_iterator(datadir, e2)) if (e.path().filename().string().rfind("__db.", 0) == 0) { std::error_code e3; fs::remove(e.path(), e3); } - if (daemon_controller_) daemon_controller_->setRescanOnNextStart(true); + if (daemon_controller_) { daemon_controller_->setRescanOnNextStart(true); user_initiated_rescan_ = true; } } } } @@ -5558,6 +5562,7 @@ void App::runtimeRescan(int startHeight) runtime_rescan_active_ = true; state_.sync.rescanning = true; rescan_confirmed_active_ = true; + user_initiated_rescan_ = true; // user clicked Rescan → completion should toast state_.sync.rescan_progress = 0.0f; state_.sync.rescan_status = "Rescanning from block " + std::to_string(startHeight) + "..."; transactions_dirty_ = true;