fix(rescan): stop the instant false "rescan complete"; show live status
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<std::string>() would have thrown on a numeric field).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
38
src/app.cpp
38
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<std::string>();
|
||||
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<std::string>()) * 100.0f; } catch (...) {}
|
||||
} else if (info.contains("rescan_progress") && info["rescan_progress"].is_number()) {
|
||||
progress = info["rescan_progress"].get<float>() * 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;
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user