fix(rescan): probe the node's block range off the UI thread
Finding the lowest block a node still has (Settings > Rescan, and the automatic reconcile after a bootstrap) ran a binary search of about 42 synchronous getblockhash/getblock calls on the UI thread, each waiting for the RPC connection behind any wallet scan (10-20 s on a big wallet) and each with a 30 s timeout. The window froze for that long. It now runs as one task on the wallet RPC worker, bounded to 90 s, and the answer comes back on the UI thread; meanwhile the dialog shows "Checking which blocks..." with Rescan disabled. An answer from an earlier connection counts as "could not tell", and one for an earlier opening of the dialog is dropped. The search probes block 1 first, so a full-history node answers in one probe, and it answers "could not tell" at once while a runtime rescan holds the lane. Fixed along the way: any probe error (timeout, busy node) read as "block missing", so a busy node could get a bogus "bootstrapped, rescan from the tip"; and a non-string getblockhash reply never called back, leaving the dialog on "Checking..." for good. Only the daemon's "can't read block" error now counts as missing; anything else means "could not tell". User-visible: - after a bootstrap, a probe that cannot tell is tried up to 6 times, 30 s apart and only while the node is not busy, then a toast points to Settings > Rescan, instead of falling back to the -rescan restart, which fails on a bootstrapped node; - the Settings Rescan dialog warns when the probe could not tell. Two new strings, translated into all eight languages. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -174,6 +174,8 @@ struct SettingsPageState {
|
||||
bool rescan_height_detected = false;
|
||||
bool rescan_full_history = true; // genesis present → traditional -rescan restart
|
||||
int rescan_start_height = 0; // editable pre-fill for the runtime rescan
|
||||
bool rescan_probe_failed = false; // the probe could not tell (node busy): warn before the -rescan restart
|
||||
int rescan_probe_seq = 0; // the probe is async: a late result for an earlier opening is dropped
|
||||
bool confirm_repair_wallet = false;
|
||||
bool confirm_reinstall_daemon = false;
|
||||
// Cached daemon-binary status for the "daemon binary" panel (loaded once / on Refresh,
|
||||
@@ -2404,6 +2406,7 @@ void RenderSettingsPage(App* app) {
|
||||
s_settingsState.confirm_rescan = true;
|
||||
s_settingsState.rescan_height_detecting = false;
|
||||
s_settingsState.rescan_height_detected = false;
|
||||
s_settingsState.rescan_probe_failed = false;
|
||||
}
|
||||
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) material::Tooltip("%s", TR("tt_rescan"));
|
||||
ImGui::EndDisabled();
|
||||
@@ -3062,9 +3065,14 @@ void RenderSettingsPage(App* app) {
|
||||
// Kick off the one-shot block-range probe the first frame the dialog is open.
|
||||
if (!s_settingsState.rescan_height_detecting && !s_settingsState.rescan_height_detected) {
|
||||
s_settingsState.rescan_height_detecting = true;
|
||||
app->detectLowestAvailableBlockHeight([](bool ok, int lowest, bool fullHistory) {
|
||||
// Asynchronous now (worker_): the callback runs on the UI thread some frames later, or at once when
|
||||
// there is nothing to probe. A result for an earlier opening of the dialog is dropped.
|
||||
const int probeSeq = ++s_settingsState.rescan_probe_seq;
|
||||
app->detectLowestAvailableBlockHeight([probeSeq](bool ok, int lowest, bool fullHistory) {
|
||||
if (probeSeq != s_settingsState.rescan_probe_seq) return;
|
||||
s_settingsState.rescan_height_detecting = false;
|
||||
s_settingsState.rescan_height_detected = true;
|
||||
s_settingsState.rescan_probe_failed = !ok && !fullHistory; // (false, 0, true): nothing to probe
|
||||
s_settingsState.rescan_full_history = (!ok) || fullHistory;
|
||||
s_settingsState.rescan_start_height = (ok && !fullHistory) ? lowest : 0;
|
||||
});
|
||||
@@ -3088,6 +3096,13 @@ void RenderSettingsPage(App* app) {
|
||||
ImGui::InputInt("##rescanHeight", &s_settingsState.rescan_start_height);
|
||||
if (s_settingsState.rescan_start_height < 0) s_settingsState.rescan_start_height = 0;
|
||||
} else {
|
||||
if (s_settingsState.rescan_probe_failed) {
|
||||
// A long line: TextColored does not wrap, so colour a wrapped one.
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.8f, 0.2f, 1.0f));
|
||||
ImGui::TextWrapped("%s", TR("rescan_probe_failed"));
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::Spacing();
|
||||
}
|
||||
ImGui::TextWrapped("%s", TR("confirm_rescan_msg"));
|
||||
ImGui::Spacing();
|
||||
ImGui::TextColored(ImVec4(0.3f, 0.8f, 0.3f, 1.0f), "%s", TR("confirm_rescan_safe"));
|
||||
|
||||
Reference in New Issue
Block a user