fix(rpc): run rescans and key imports without the 30 s timeout
A rescan or key import holds the node's cs_main for the whole wallet scan. On the shared clients it hit the 30 s curl timeout, reported a failure that wasn't one, abandoned a daemon RPC slot and flipped the app into "node busy". - One long wallet RPC at a time (rescan, importprivkey, importaddress, importwallet, z_importkey, z_importviewingkey, z_importwallet) runs on its own ungated client with no client timeout (rpc/long_call_lane.h). It can be aborted from any thread, and an abort is never lost, even one that lands before the client opens. - The GUI rescan, key import and sweep use it. The sweep's fund-moving body is unchanged; only the client it calls differs. runtimeRescan() returns bool, and a refused post-bootstrap reconcile stays armed. - The console routes rescan and the import commands through the lane; its input stays usable meanwhile, and a refusal is printed like any other result. RPCClient gains callRaw(.., timeoutSec). - Restart, wallet switch, delete-chain, bootstrap (now also from Settings) and the other node-stopping flows are refused while it runs, with a message saying a key import or rescan is still running; quit asks for confirmation. The lane is aborted at quit. - A second import or rescan is refused before the key is copied anywhere. An import cut off after the request reached the node (a lost reply, or a proxy's 502/504 while the node still imports) says the key may already be in the wallet and to run Settings -> Rescan. Only the node's own HTTP-layer refusals (401, 503, work queue full, unhandled request) say that the key was not imported. A pure classifyLongCallCutOff() makes that choice. - New strings in all 8 languages; console results stay English. Tests: the long-method list and the console's routing; one lane at a time; an abort lands whether it comes before the client opens or during the call; a call with no client timeout outlasts a 3 s scan, where a 1 s gated client times out and marks the node busy; classifyLongCallCutOff() for every failure kind with and without a sent request, and from raw HTTP replies (502, 504, a plain 500 -> cut off; 401, 503, queue full, unhandled -> refused). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -299,6 +299,8 @@ private:
|
||||
// ---- Shared: kick off download ----
|
||||
static void startDownload(const std::string& url) {
|
||||
if (!s_app || !s_app->supportsFullNodeLifecycleActions()) return;
|
||||
// It stops the node first: not while it is busy or running a key import / rescan (as the wizard's bootstrap).
|
||||
if (s_app->refuseIfNodeBusy()) return;
|
||||
s_wasDaemonRunning = s_app->stopDaemonForBootstrap();
|
||||
s_app->setBootstrapDownloading(true);
|
||||
s_bootstrap = std::make_unique<util::Bootstrap>();
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "../../data/wallet_state.h"
|
||||
#include "../../rpc/rpc_client.h"
|
||||
#include "../../rpc/rpc_worker.h"
|
||||
#include "../../rpc/long_call_lane.h" // isLongWalletMethod
|
||||
#include "../../daemon/embedded_daemon.h"
|
||||
#include "../../daemon/xmrig_manager.h"
|
||||
#include "../../wallet/lite_wallet_controller.h"
|
||||
@@ -20,6 +21,7 @@
|
||||
#include "../material/colors.h"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <sodium.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
@@ -34,6 +36,19 @@ using namespace material;
|
||||
// executors drain the same LiteDiagnostics ring, so declare it up front for the full-node drain.
|
||||
namespace { ConsoleChannel liteLogChannel(const std::string& line); }
|
||||
|
||||
namespace {
|
||||
// Zero every string in a console call's params: a key import's carry the key.
|
||||
void scrubParamStrings(nlohmann::json& j)
|
||||
{
|
||||
if (j.is_string()) {
|
||||
std::string& s = j.get_ref<std::string&>();
|
||||
if (!s.empty()) sodium_memzero(&s[0], s.size());
|
||||
} else if (j.is_structured()) {
|
||||
for (auto& el : j) scrubParamStrings(el);
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
// ============================================================================
|
||||
// FullNodeConsoleExecutor
|
||||
// ============================================================================
|
||||
@@ -61,6 +76,44 @@ void FullNodeConsoleExecutor::submit(const std::string& cmd)
|
||||
}
|
||||
return;
|
||||
}
|
||||
// Rescans and key imports scan the chain inside the call (C61): run them on the app's long-call lane (no
|
||||
// client timeout, one at a time) instead of this lane's 30 s client. in_flight_ is left alone, so the input
|
||||
// stays usable while one runs; the result (or the refusal, in English like every console result) is
|
||||
// pushed as usual.
|
||||
if (rpc::isLongWalletMethod(call.method)) {
|
||||
std::string why;
|
||||
bool refused = app_->longWalletOpRefused(&why);
|
||||
if (!refused) {
|
||||
refused = !app_->runLongWalletTask(
|
||||
[this, method = call.method, params = call.params](rpc::RPCClient& c) mutable -> rpc::RPCWorker::MainCb {
|
||||
std::string result_str;
|
||||
bool is_error = false;
|
||||
try {
|
||||
rpc::RPCClient::TraceScope trace("Console tab / Long wallet command");
|
||||
result_str = c.callRaw(method, params, 0L); // 0 = no client timeout
|
||||
} catch (const std::exception& e) {
|
||||
result_str = e.what();
|
||||
is_error = true;
|
||||
} catch (...) {
|
||||
result_str = "RPC failed (unknown error)";
|
||||
is_error = true;
|
||||
}
|
||||
scrubParamStrings(params);
|
||||
return [this, result_str, is_error]() {
|
||||
if (app_) app_->noteWalletMutation();
|
||||
std::lock_guard<std::mutex> lk(results_mutex_);
|
||||
results_.push_back({result_str, is_error});
|
||||
};
|
||||
});
|
||||
if (refused) why = "A key import or rescan is still running. Try again when it finishes.";
|
||||
}
|
||||
scrubParamStrings(call.params);
|
||||
if (refused) {
|
||||
std::lock_guard<std::mutex> lk(results_mutex_);
|
||||
results_.push_back({why, true});
|
||||
}
|
||||
return;
|
||||
}
|
||||
const std::string method = call.method;
|
||||
const nlohmann::json params = call.params;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user