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

@@ -68,6 +68,7 @@
#include <cstdio>
#include <ctime>
#include <algorithm>
#include <map>
#include <set>
#include <fstream>
@@ -273,6 +274,8 @@ void App::update()
refresh_timer_ += io.DeltaTime;
price_timer_ += io.DeltaTime;
fast_refresh_timer_ += io.DeltaTime;
tx_age_timer_ += io.DeltaTime;
opid_poll_timer_ += io.DeltaTime;
// Fast refresh (mining stats + daemon memory) every second
// Skip when wallet is locked — no need to poll, and queued tasks
@@ -443,6 +446,64 @@ void App::update()
}
}
// Poll pending z_sendmany operations for completion
if (opid_poll_timer_ >= OPID_POLL_INTERVAL && !pending_opids_.empty()
&& state_.connected && fast_worker_) {
opid_poll_timer_ = 0.0f;
auto opids = pending_opids_; // copy for worker thread
fast_worker_->post([this, opids]() -> rpc::RPCWorker::MainCb {
auto* rpc = (fast_rpc_ && fast_rpc_->isConnected()) ? fast_rpc_.get() : rpc_.get();
if (!rpc) return [](){};
json ids = json::array();
for (const auto& id : opids) ids.push_back(id);
json result;
try {
result = rpc->call("z_getoperationstatus", {ids});
} catch (...) {
return [](){};
}
// Collect completed/failed opids
std::vector<std::string> done;
bool anySuccess = false;
for (const auto& op : result) {
std::string status = op.value("status", "");
std::string opid = op.value("id", "");
if (status == "success") {
done.push_back(opid);
anySuccess = true;
} else if (status == "failed") {
done.push_back(opid);
std::string msg = "Transaction failed";
if (op.contains("error") && op["error"].contains("message"))
msg = op["error"]["message"].get<std::string>();
// Capture for main thread
return [this, done, msg]() {
ui::Notifications::instance().error(msg);
for (const auto& id : done) {
pending_opids_.erase(
std::remove(pending_opids_.begin(), pending_opids_.end(), id),
pending_opids_.end());
}
};
}
}
return [this, done, anySuccess]() {
for (const auto& id : done) {
pending_opids_.erase(
std::remove(pending_opids_.begin(), pending_opids_.end(), id),
pending_opids_.end());
}
if (anySuccess) {
// Transaction confirmed by daemon — force immediate data refresh
transactions_dirty_ = true;
addresses_dirty_ = true;
last_tx_block_height_ = -1;
refresh_timer_ = REFRESH_INTERVAL;
}
};
});
}
// Regular refresh every 5 seconds
// Skip when wallet is locked — same reason as above.
if (refresh_timer_ >= REFRESH_INTERVAL) {
@@ -1288,6 +1349,21 @@ void App::renderStatusBar()
state_.mining.localHashrate);
}
// Decrypt-import background task indicator
if (decrypt_import_active_) {
ImGui::SameLine(0, sbSectionGap);
ImGui::TextDisabled("|");
ImGui::SameLine(0, sbSeparatorGap);
ImGui::PushFont(ui::material::Type().iconSmall());
float pulse = 0.6f + 0.4f * sinf((float)ImGui::GetTime() * 3.0f);
ImGui::TextColored(ImVec4(0.6f, 0.8f, 1.0f, pulse), ICON_MD_LOCK_OPEN);
ImGui::PopFont();
ImGui::SameLine(0, sbIconTextGap);
int dots = (int)(ImGui::GetTime() * 2.0f) % 4;
const char* dotStr = (dots == 0) ? "." : (dots == 1) ? ".." : (dots == 2) ? "..." : "";
ImGui::TextColored(ImVec4(0.6f, 0.8f, 1.0f, 1.0f), "Importing keys%s", dotStr);
}
// Right side: connection status message (if any) + version always at far right
float rightStart = ImGui::GetWindowWidth() - sbRightContentOff;
if (!connection_status_.empty() && connection_status_ != "Connected") {
@@ -1632,6 +1708,9 @@ void App::renderAntivirusHelpDialog()
void App::refreshNow()
{
refresh_timer_ = REFRESH_INTERVAL; // Trigger immediate refresh
transactions_dirty_ = true; // Force transaction list update
addresses_dirty_ = true; // Force address/balance update
last_tx_block_height_ = -1; // Reset tx cache
}
void App::handlePaymentURI(const std::string& uri)