From 25fef8ff4d8ba5fa01cd046eb1e44ad6aa796d76 Mon Sep 17 00:00:00 2001 From: DanS Date: Fri, 12 Jun 2026 17:34:44 -0500 Subject: [PATCH] fix(rescan): stop the instant false "rescan complete"; show live status MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Clicking Settings → Rescan restarted the daemon with -rescan correctly, but the progress poll fired "Blockchain rescan complete" the instant it was clicked, then showed nothing for the entire (multi-hour) rescan — so it looked broken. Cause: the very first getrescaninfo poll runs before the daemon has restarted and hits the still-running pre-restart daemon, which answers rescanning=false. The completion branch took that as "done", cleared the rescanning flag, and the real rescan then ran invisibly. (Confirmed from a Windows debug-log capture: an instant OK{"rescanning":false}, then ~6400 warmup errors over ~5h, all swallowed.) Fixes: - Gate completion on a new rescan_confirmed_active_ flag that's only set once we actually observe the rescan running, so a pre-restart rescanning=false can't be misread as completion. - While the daemon is in -rescan RPC warmup it rejects every call with the live phase as the message ("Loading block index..." -> "Rescanning..."). Treat that as proof-of-progress: surface it as rescan_status and mark confirmed-active, instead of silently swallowing it. The status bar keeps its animated "Rescanning..." for the whole run, then reports complete when warmup ends. - Read rescan_progress whether the daemon returns it as a string or a number (the get() would have thrown on a numeric field). Co-Authored-By: Claude Opus 4.8 --- src/app.cpp | 38 ++++++++++++++++++++++++++++++-------- src/app.h | 4 ++++ 2 files changed, 34 insertions(+), 8 deletions(-) diff --git a/src/app.cpp b/src/app.cpp index b129489..3dda687 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -600,27 +600,46 @@ void App::update() auto info = rescanRpc->call("getrescaninfo"); bool rescanning = info.value("rescanning", false); float progress = 0.0f; - if (info.contains("rescan_progress")) { - std::string progStr = info["rescan_progress"].get(); - try { progress = std::stof(progStr) * 100.0f; } catch (...) {} + if (info.contains("rescan_progress") && info["rescan_progress"].is_string()) { + try { progress = std::stof(info["rescan_progress"].get()) * 100.0f; } catch (...) {} + } else if (info.contains("rescan_progress") && info["rescan_progress"].is_number()) { + progress = info["rescan_progress"].get() * 100.0f; } return [this, rescanning, progress]() { rescan_status_poll_in_progress_ = false; if (rescanning) { state_.sync.rescanning = true; + rescan_confirmed_active_ = true; if (progress > 0.0f) { state_.sync.rescan_progress = progress / 100.0f; } - } else if (state_.sync.rescanning) { - // Rescan just finished + } else if (state_.sync.rescanning && rescan_confirmed_active_) { + // Genuine completion: getrescaninfo answers cleanly AND we previously + // 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"); state_.sync.rescanning = false; + rescan_confirmed_active_ = false; state_.sync.rescan_progress = 1.0f; state_.sync.rescan_status.clear(); } + // else: rescanning=false but not yet confirmed → pre-restart daemon; keep waiting. + }; + } catch (const std::exception& e) { + // During -rescan the daemon is in RPC warmup and rejects every call with the + // current phase as the message ("Loading block index..." → "Rescanning..."). + // That's not a failure — it's proof the rescan is running. Surface it and mark + // the rescan confirmed-active so completion is recognised when warmup ends. + std::string phase = e.what(); + return [this, phase]() { + rescan_status_poll_in_progress_ = false; + if (state_.sync.rescanning) { + rescan_confirmed_active_ = true; + state_.sync.rescan_status = phase; + } }; } catch (...) { - // RPC not available yet or failed return [this](){ rescan_status_poll_in_progress_ = false; }; } }); @@ -2797,12 +2816,15 @@ void App::rescanBlockchain() ui::Notifications::instance().warning(decision.warning); return; } - + DEBUG_LOGF("[App] Starting blockchain rescan - stopping daemon first\n"); ui::Notifications::instance().info("Restarting daemon with -rescan flag..."); - // Initialize rescan state for status bar display + // Initialize rescan state for status bar display. rescan_confirmed_active_ stays false until we + // actually observe the restarted daemon rescanning — so the first poll (which may still reach the + // pre-restart daemon and see rescanning=false) can't be misread as instant completion. state_.sync.rescanning = true; + rescan_confirmed_active_ = false; state_.sync.rescan_progress = 0.0f; state_.sync.rescan_status = decision.status; transactions_dirty_ = true; diff --git a/src/app.h b/src/app.h index bba85cd..a00e355 100644 --- a/src/app.h +++ b/src/app.h @@ -595,6 +595,10 @@ private: bool transactions_dirty_ = false; // true → force tx refresh regardless of block height bool encryption_state_prefetched_ = false; // suppress duplicate getwalletinfo on connect bool rescan_status_poll_in_progress_ = false; + // True once we've actually observed the rescan running (daemon restarted into -rescan warmup). + // Gates the "rescan complete" detection so a getrescaninfo poll that hits the still-running + // pre-restart daemon (which reports rescanning=false) can't fire a false "complete" instantly. + bool rescan_confirmed_active_ = false; bool opid_poll_in_progress_ = false; // Consecutive Core-refresh cycles where BOTH core RPCs failed → likely a dead // connection. After kCoreFailuresBeforeDisconnect, tear down and reconnect.