feat: RPC caching, background decrypt import, fast-lane peers, mining fix

RPC client:
- Add call() overload with per-call timeout parameter
- z_exportwallet uses 300s, z_importwallet uses 1200s timeout

Decrypt wallet (app_security.cpp, app.cpp):
- Show per-step and overall elapsed timers during decrypt flow
- Reduce dialog to 5 steps; close before key import begins
- Run z_importwallet on detached background thread
- Add pulsing "Importing keys..." status bar indicator
- Report success/failure via notifications instead of dialog

RPC caching (app_network.cpp, app.h):
- Cache z_viewtransaction results in viewtx_cache_ across refresh cycles
- Skip RPC calls for already-cached txids (biggest perf win)
- Build confirmed_tx_cache_ for deeply-confirmed transactions
- Clear all caches on disconnect
- Remove unused refreshTransactions() dead code

Peers (app_network.cpp, peers_tab.cpp):
- Route refreshPeerInfo() through fast_worker_ to avoid head-of-line blocking
- Replace footer "Refresh Peers" button with ICON_MD_REFRESH in toggle header
- Refresh button triggers both peer list and full blockchain data refresh

Mining (mining_tab.cpp):
- Allow pool mining toggle when blockchain is not synced
- Pool mining only needs xmrig, not local daemon sync
This commit is contained in:
dan_s
2026-03-04 15:12:24 -06:00
parent 7fb1f1de9d
commit 0ca1caf148
8 changed files with 459 additions and 270 deletions

View File

@@ -1021,6 +1021,8 @@ void App::renderDecryptWalletDialog() {
decrypt_step_ = 0;
decrypt_in_progress_ = true;
decrypt_status_ = "Unlocking wallet...";
decrypt_overall_start_time_ = std::chrono::steady_clock::now();
decrypt_step_start_time_ = decrypt_overall_start_time_;
// Run entire decrypt flow on worker thread
if (worker_) {
@@ -1040,6 +1042,7 @@ void App::renderDecryptWalletDialog() {
// Update step on main thread
return [this]() {
decrypt_step_ = 1;
decrypt_step_start_time_ = std::chrono::steady_clock::now();
decrypt_status_ = "Exporting wallet keys...";
// Continue with step 2
@@ -1050,7 +1053,7 @@ void App::renderDecryptWalletDialog() {
std::string exportPath = dataDir + exportFile;
try {
rpc_->call("z_exportwallet", {exportFile});
rpc_->call("z_exportwallet", {exportFile}, 300L);
} catch (const std::exception& e) {
std::string err = e.what();
return [this, err]() {
@@ -1062,6 +1065,7 @@ void App::renderDecryptWalletDialog() {
return [this, exportPath]() {
decrypt_step_ = 2;
decrypt_step_start_time_ = std::chrono::steady_clock::now();
decrypt_status_ = "Stopping daemon...";
// Continue with step 3
@@ -1077,6 +1081,7 @@ void App::renderDecryptWalletDialog() {
return [this, exportPath]() {
decrypt_step_ = 3;
decrypt_step_start_time_ = std::chrono::steady_clock::now();
decrypt_status_ = "Backing up encrypted wallet...";
// Continue with step 4 (rename)
@@ -1100,6 +1105,7 @@ void App::renderDecryptWalletDialog() {
return [this, exportPath]() {
decrypt_step_ = 4;
decrypt_step_start_time_ = std::chrono::steady_clock::now();
decrypt_status_ = "Restarting daemon...";
auto restartAndImport = [this, exportPath]() {
@@ -1136,28 +1142,50 @@ void App::renderDecryptWalletDialog() {
return;
}
// Update step on main thread
// Update step on main thread — close dialog, import in background
if (worker_) {
worker_->post([this]() -> rpc::RPCWorker::MainCb {
return [this]() {
decrypt_step_ = 5;
decrypt_status_ = "Importing keys (this may take a while)...";
// Close the decrypt dialog — user can use the wallet now
decrypt_in_progress_ = false;
show_decrypt_dialog_ = false;
decrypt_import_active_ = true;
// Mark rescanning so status bar picks it up immediately
state_.sync.rescanning = true;
state_.sync.rescan_progress = 0.0f;
// Clear encryption state early — vault/PIN removed now,
// wallet file is already unencrypted
if (vault_ && vault_->hasVault()) {
vault_->removeVault();
}
if (settings_ && settings_->getPinEnabled()) {
settings_->setPinEnabled(false);
settings_->save();
}
ui::Notifications::instance().info(
"Importing keys & rescanning blockchain — wallet is usable while this runs",
8.0f);
};
});
}
// Step 6: Import wallet (use full path)
// Step 6: Import wallet in background (use full path)
// Use 20-minute timeout — import + rescan can be very slow
try {
rpc_->call("z_importwallet", {exportPath});
rpc_->call("z_importwallet", {exportPath}, 1200L);
} catch (const std::exception& e) {
std::string err = e.what();
if (worker_) {
worker_->post([this, err]() -> rpc::RPCWorker::MainCb {
return [this, err]() {
decrypt_in_progress_ = false;
decrypt_status_ = "Import failed: " + err +
"\nYour encrypted wallet backup is at wallet.dat.encrypted.bak";
decrypt_phase_ = 3;
decrypt_import_active_ = false;
ui::Notifications::instance().error(
"Key import failed: " + err +
"\nEncrypted backup: wallet.dat.encrypted.bak",
12.0f);
};
});
}
@@ -1168,19 +1196,12 @@ void App::renderDecryptWalletDialog() {
if (worker_) {
worker_->post([this]() -> rpc::RPCWorker::MainCb {
return [this]() {
decrypt_in_progress_ = false;
decrypt_status_ = "Wallet decrypted successfully!";
decrypt_phase_ = 2;
if (vault_ && vault_->hasVault()) {
vault_->removeVault();
}
if (settings_ && settings_->getPinEnabled()) {
settings_->setPinEnabled(false);
settings_->save();
}
decrypt_import_active_ = false;
refreshWalletEncryptionState();
ui::Notifications::instance().success(
"Wallet decrypted successfully! All keys imported.",
8.0f);
DEBUG_LOGF("[App] Wallet decrypted successfully\n");
};
});
@@ -1214,11 +1235,17 @@ void App::renderDecryptWalletDialog() {
"Exporting wallet keys",
"Stopping daemon",
"Backing up encrypted wallet",
"Restarting daemon",
"Importing keys (rescan)"
"Restarting daemon"
};
const int numSteps = 6;
const int numSteps = 5;
// Compute elapsed times
auto now = std::chrono::steady_clock::now();
auto stepElapsed = std::chrono::duration_cast<std::chrono::seconds>(
now - decrypt_step_start_time_).count();
auto totalElapsed = std::chrono::duration_cast<std::chrono::seconds>(
now - decrypt_overall_start_time_).count();
ImGui::Spacing();
for (int i = 0; i < numSteps; i++) {
ImGui::PushFont(Type().iconMed());
@@ -1237,7 +1264,16 @@ void App::renderDecryptWalletDialog() {
ImGui::SameLine();
if (i == decrypt_step_) {
ImGui::TextColored(ImVec4(1.0f, 0.9f, 0.6f, 1.0f), "%s...", stepLabels[i]);
// Show step label with elapsed time
int mins = (int)(stepElapsed / 60);
int secs = (int)(stepElapsed % 60);
if (mins > 0) {
ImGui::TextColored(ImVec4(1.0f, 0.9f, 0.6f, 1.0f),
"%s... (%dm %02ds)", stepLabels[i], mins, secs);
} else {
ImGui::TextColored(ImVec4(1.0f, 0.9f, 0.6f, 1.0f),
"%s... (%ds)", stepLabels[i], secs);
}
} else if (i < decrypt_step_) {
ImGui::TextColored(ImVec4(0.6f, 0.8f, 0.6f, 1.0f), "%s", stepLabels[i]);
} else {
@@ -1271,8 +1307,22 @@ void App::renderDecryptWalletDialog() {
}
ImGui::Spacing();
ImGui::TextWrapped("Please wait. The daemon is exporting keys, restarting, "
"and re-importing. This may take several minutes.");
// Step-specific hints
if (decrypt_step_ == 4) {
ImGui::TextWrapped("Waiting for the daemon to finish starting up...");
} else {
ImGui::TextWrapped("Please wait. The daemon is exporting keys, restarting, "
"and re-importing. This may take several minutes.");
}
// Total elapsed
{
int tMins = (int)(totalElapsed / 60);
int tSecs = (int)(totalElapsed % 60);
ImGui::Spacing();
ImGui::TextDisabled("Total elapsed: %dm %02ds", tMins, tSecs);
}
// ---- Phase 2: Success ----
} else if (decrypt_phase_ == 2) {