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:
36
src/app.cpp
36
src/app.cpp
@@ -600,27 +600,46 @@ void App::update()
|
|||||||
auto info = rescanRpc->call("getrescaninfo");
|
auto info = rescanRpc->call("getrescaninfo");
|
||||||
bool rescanning = info.value("rescanning", false);
|
bool rescanning = info.value("rescanning", false);
|
||||||
float progress = 0.0f;
|
float progress = 0.0f;
|
||||||
if (info.contains("rescan_progress")) {
|
if (info.contains("rescan_progress") && info["rescan_progress"].is_string()) {
|
||||||
std::string progStr = info["rescan_progress"].get<std::string>();
|
try { progress = std::stof(info["rescan_progress"].get<std::string>()) * 100.0f; } catch (...) {}
|
||||||
try { progress = std::stof(progStr) * 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]() {
|
return [this, rescanning, progress]() {
|
||||||
rescan_status_poll_in_progress_ = false;
|
rescan_status_poll_in_progress_ = false;
|
||||||
if (rescanning) {
|
if (rescanning) {
|
||||||
state_.sync.rescanning = true;
|
state_.sync.rescanning = true;
|
||||||
|
rescan_confirmed_active_ = true;
|
||||||
if (progress > 0.0f) {
|
if (progress > 0.0f) {
|
||||||
state_.sync.rescan_progress = progress / 100.0f;
|
state_.sync.rescan_progress = progress / 100.0f;
|
||||||
}
|
}
|
||||||
} else if (state_.sync.rescanning) {
|
} else if (state_.sync.rescanning && rescan_confirmed_active_) {
|
||||||
// Rescan just finished
|
// 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");
|
ui::Notifications::instance().success("Blockchain rescan complete");
|
||||||
state_.sync.rescanning = false;
|
state_.sync.rescanning = false;
|
||||||
|
rescan_confirmed_active_ = false;
|
||||||
state_.sync.rescan_progress = 1.0f;
|
state_.sync.rescan_progress = 1.0f;
|
||||||
state_.sync.rescan_status.clear();
|
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 (...) {
|
} catch (...) {
|
||||||
// RPC not available yet or failed
|
|
||||||
return [this](){ rescan_status_poll_in_progress_ = false; };
|
return [this](){ rescan_status_poll_in_progress_ = false; };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -2801,8 +2820,11 @@ void App::rescanBlockchain()
|
|||||||
DEBUG_LOGF("[App] Starting blockchain rescan - stopping daemon first\n");
|
DEBUG_LOGF("[App] Starting blockchain rescan - stopping daemon first\n");
|
||||||
ui::Notifications::instance().info("Restarting daemon with -rescan flag...");
|
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;
|
state_.sync.rescanning = true;
|
||||||
|
rescan_confirmed_active_ = false;
|
||||||
state_.sync.rescan_progress = 0.0f;
|
state_.sync.rescan_progress = 0.0f;
|
||||||
state_.sync.rescan_status = decision.status;
|
state_.sync.rescan_status = decision.status;
|
||||||
transactions_dirty_ = true;
|
transactions_dirty_ = true;
|
||||||
|
|||||||
@@ -595,6 +595,10 @@ private:
|
|||||||
bool transactions_dirty_ = false; // true → force tx refresh regardless of block height
|
bool transactions_dirty_ = false; // true → force tx refresh regardless of block height
|
||||||
bool encryption_state_prefetched_ = false; // suppress duplicate getwalletinfo on connect
|
bool encryption_state_prefetched_ = false; // suppress duplicate getwalletinfo on connect
|
||||||
bool rescan_status_poll_in_progress_ = false;
|
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;
|
bool opid_poll_in_progress_ = false;
|
||||||
// Consecutive Core-refresh cycles where BOTH core RPCs failed → likely a dead
|
// Consecutive Core-refresh cycles where BOTH core RPCs failed → likely a dead
|
||||||
// connection. After kCoreFailuresBeforeDisconnect, tear down and reconnect.
|
// connection. After kCoreFailuresBeforeDisconnect, tear down and reconnect.
|
||||||
|
|||||||
Reference in New Issue
Block a user