feat(lite): add "Redownload blocks" (rescan from lite server) to Settings
Add a maintenance option for the lite wallet to re-download and re-scan every block from the lite server — useful when balances or history look wrong. - LiteWalletController::startRescan() runs the backend `rescan` command (which clears the wallet's synced block cache and re-syncs from its birthday) on a detached thread, reusing the existing sync progress/refresh machinery: it resets syncDone_ so refreshModel() shows progress again and refreshes data on completion. No-op if no wallet is open or a scan is already running. - scanInProgress() exposes the initial-sync-or-rescan state. - Settings (lite, open wallet) gains a "Redownload blocks" button behind a confirmation modal, disabled while a scan is running. i18n strings added. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -147,6 +147,7 @@ struct SettingsPageState {
|
||||
bool confirm_delete_blockchain = false;
|
||||
bool confirm_rescan = false;
|
||||
bool confirm_restart_daemon = false;
|
||||
bool confirm_lite_redownload = false;
|
||||
effects::ScrollFadeShader fade_shader;
|
||||
};
|
||||
|
||||
@@ -1819,6 +1820,24 @@ void RenderSettingsPage(App* app) {
|
||||
Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(),
|
||||
s_settingsState.lite_encryption_status.c_str());
|
||||
}
|
||||
|
||||
// ---- Maintenance: re-download blocks from the lite server ----
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
|
||||
ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
|
||||
Type().text(TypeStyle::Body2, TR("lite_maintenance"));
|
||||
|
||||
const bool scanning = app->liteWallet()->scanInProgress();
|
||||
ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
|
||||
ImGui::BeginDisabled(scanning);
|
||||
if (TactileButton(TR("lite_redownload_blocks"), ImVec2(0, 0), S.resolveFont("button"))) {
|
||||
s_settingsState.confirm_lite_redownload = true;
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled))
|
||||
ImGui::SetTooltip("%s", TR("tt_lite_redownload"));
|
||||
ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
|
||||
Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(),
|
||||
scanning ? TR("lite_redownload_running") : TR("lite_redownload_desc"));
|
||||
} else if (!s_settingsState.lite_export_secret.empty()) {
|
||||
// Wallet closed while a backup/secret was revealed — don't leave it in memory.
|
||||
wallet::secureWipeLiteSecret(s_settingsState.lite_export_secret);
|
||||
@@ -2640,6 +2659,36 @@ void RenderSettingsPage(App* app) {
|
||||
}
|
||||
}
|
||||
|
||||
// Confirm: lite wallet re-download blocks (rescan from the lite server — long but safe)
|
||||
if (s_settingsState.confirm_lite_redownload) {
|
||||
if (BeginOverlayDialog(TR("confirm_lite_redownload_title"), &s_settingsState.confirm_lite_redownload, 500.0f, 0.94f)) {
|
||||
ImGui::PushFont(Type().iconLarge());
|
||||
ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.2f, 1.0f), ICON_MD_WARNING);
|
||||
ImGui::PopFont();
|
||||
ImGui::SameLine();
|
||||
ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.2f, 1.0f), "%s", TR("warning"));
|
||||
|
||||
ImGui::Spacing();
|
||||
ImGui::TextWrapped("%s", TR("confirm_lite_redownload_msg"));
|
||||
ImGui::Spacing();
|
||||
ImGui::TextColored(ImVec4(0.3f, 0.8f, 0.3f, 1.0f), "%s", TR("confirm_lite_redownload_safe"));
|
||||
ImGui::Spacing();
|
||||
ImGui::Separator();
|
||||
ImGui::Spacing();
|
||||
|
||||
float btnW = (ImGui::GetContentRegionAvail().x - ImGui::GetStyle().ItemSpacing.x) * 0.5f;
|
||||
if (ImGui::Button(TrId("cancel", "lite_redl_cancel").c_str(), ImVec2(btnW, 40))) {
|
||||
s_settingsState.confirm_lite_redownload = false;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button(TrId("lite_redownload_blocks", "lite_redl_confirm").c_str(), ImVec2(btnW, 40))) {
|
||||
if (auto* lite = app->liteWallet()) lite->startRescan();
|
||||
s_settingsState.confirm_lite_redownload = false;
|
||||
}
|
||||
EndOverlayDialog();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
|
||||
@@ -418,6 +418,14 @@ void I18n::loadBuiltinEnglish()
|
||||
strings_["confirm_rescan_safe"] = "Your wallet.dat and blockchain data are not deleted — only re-scanned.";
|
||||
strings_["confirm_restart_daemon_title"] = "Restart Daemon";
|
||||
strings_["confirm_restart_daemon_msg"] = "This stops and restarts the daemon to apply the changed options. The wallet will briefly disconnect and reconnect.";
|
||||
strings_["lite_maintenance"] = "Maintenance";
|
||||
strings_["lite_redownload_blocks"] = "Redownload blocks";
|
||||
strings_["lite_redownload_desc"] = "Re-fetch all blocks from the lite server (use if your balance or history looks wrong).";
|
||||
strings_["lite_redownload_running"] = "Re-downloading blocks…";
|
||||
strings_["tt_lite_redownload"] = "Re-download and re-scan all blocks from the lite server";
|
||||
strings_["confirm_lite_redownload_title"] = "Redownload Blocks";
|
||||
strings_["confirm_lite_redownload_msg"] = "This clears the wallet's downloaded block data and re-fetches and re-scans every block from the lite server. It can take a while; the wallet shows sync progress until it finishes.";
|
||||
strings_["confirm_lite_redownload_safe"] = "Your wallet, keys, and seed are not affected — only the block data is re-downloaded.";
|
||||
strings_["tt_encrypt"] = "Encrypt wallet.dat with a passphrase";
|
||||
strings_["tt_change_pass"] = "Change the wallet encryption passphrase";
|
||||
strings_["tt_lock"] = "Lock the wallet immediately";
|
||||
|
||||
@@ -605,6 +605,38 @@ void LiteWalletController::startSync()
|
||||
});
|
||||
}
|
||||
|
||||
bool LiteWalletController::startRescan()
|
||||
{
|
||||
if (!walletOpen_.load()) return false;
|
||||
// Refuse if a sync/rescan is already running (would race two scans on the backend wallet lock).
|
||||
if (!syncDone_ || !syncDone_->load()) return false;
|
||||
|
||||
// Reset the done flag so refreshModel() re-enters its "scanning, publish progress only" path
|
||||
// and the UI shows progress again; clearing it BEFORE launching the thread avoids a window
|
||||
// where the worker would query balances mid-rescan.
|
||||
syncDone_->store(false);
|
||||
syncStarted_ = true;
|
||||
liteLog("Block re-download (rescan) started");
|
||||
|
||||
// The previous sync/rescan thread has finished (syncDone_ was true above); detach it before
|
||||
// reassigning syncThread_. Like startSync's thread it captures shared refs (bridge_ + syncDone_),
|
||||
// never `this`, so detaching is safe.
|
||||
if (syncThread_.joinable()) syncThread_.detach();
|
||||
|
||||
auto bridge = bridge_;
|
||||
auto done = syncDone_;
|
||||
syncThread_ = std::thread([bridge, done] {
|
||||
if (bridge) {
|
||||
// `rescan` clears the wallet's synced block cache and re-downloads/re-scans from the
|
||||
// birthday height — a blocking, uninterruptible full scan, same as `sync`.
|
||||
bridge->execute("rescan", "");
|
||||
bridge->execute("save", ""); // backend doesn't auto-save after a rescan
|
||||
}
|
||||
done->store(true);
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
std::optional<LiteWalletAppRefreshModel> LiteWalletController::refreshModel()
|
||||
{
|
||||
if (!walletOpen_.load()) return std::nullopt;
|
||||
|
||||
@@ -220,6 +220,15 @@ public:
|
||||
// op produces a ready wallet; safe to call once.
|
||||
void startSync();
|
||||
|
||||
// Re-download and re-scan every block from the lite server: runs the backend `rescan`
|
||||
// command (which clears the wallet's synced block cache and re-syncs from its birthday
|
||||
// height) on a detached thread, reusing the sync progress + refresh machinery. No-op if no
|
||||
// wallet is open or a scan is already running. True if a rescan was actually started.
|
||||
bool startRescan();
|
||||
|
||||
// True while the initial sync OR a rescan is actively scanning (not yet complete).
|
||||
bool scanInProgress() const { return syncStarted_ && !(syncDone_ && syncDone_->load()); }
|
||||
|
||||
// Generate a new address (shielded if true, else transparent) via the backend. Fast (local
|
||||
// key derivation), safe to call on the UI thread; the next refresh lists the new address.
|
||||
LiteNewAddressResult newAddress(bool shielded);
|
||||
|
||||
Reference in New Issue
Block a user