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:
@@ -27,6 +27,7 @@
|
||||
#include "daemon/stop_delivery.h"
|
||||
#include "daemon/daemon_busy_detect.h"
|
||||
#include "rpc/rpc_busy_gate.h"
|
||||
#include "rpc/long_call_lane.h"
|
||||
#include "services/history_scan_policy.h"
|
||||
#include "services/connect_scan_gate.h"
|
||||
#include "services/scan_policy.h"
|
||||
@@ -249,6 +250,15 @@ public:
|
||||
// For an operation that would stop the node (restart, rescan, switch, reinstall, bootstrap, ...): when
|
||||
// nodeBusyForStop(), tell the user to try again later and return true; the caller then does nothing.
|
||||
bool refuseIfNodeBusy();
|
||||
// Long wallet RPCs (rescan, key imports; C61) run one at a time on long_lane_. Main thread. Decide a refusal
|
||||
// BEFORE copying a key into a closure: true when one can't start now, with the reason in English in *why
|
||||
// (the console's text) and as an i18n key in *trKey (the GUI's toast / status line).
|
||||
bool longWalletOpRefused(std::string* why, const char** trKey = nullptr) const;
|
||||
// Main thread. Runs body on the lane's own ungated client (no client timeout, aborted at quit) on an async
|
||||
// task; body must catch everything and returns the continuation, which runs on the main thread once the lane
|
||||
// is free again. false (body never runs) when the lane is already taken.
|
||||
using LongWalletBody = std::function<std::function<void()>(rpc::RPCClient&)>;
|
||||
bool runLongWalletTask(LongWalletBody body);
|
||||
// Record how an RPC to the node just went, for rpcBusyWhileAlive(): busy kinds (ReplyTimeout /
|
||||
// QueueFull) start the clock, an answer stops it, anything else leaves it alone.
|
||||
void noteDaemonRpcResult(bool answered, rpc::RpcFailureKind kind);
|
||||
@@ -651,7 +661,8 @@ public:
|
||||
// Runtime rescanblockchain RPC starting at a snapshot-available height. Unlike the
|
||||
// -rescan restart, this works on bootstrapped/pruned nodes (which lack pre-snapshot
|
||||
// block data), reconciling the wallet's stale spent-state without a daemon restart.
|
||||
void runtimeRescan(int startHeight);
|
||||
// Runs on the long-call lane; false when it did not start (refused, not connected, one already running).
|
||||
bool runtimeRescan(int startHeight);
|
||||
// Probe for the lowest block height the node still has on disk, on worker_ (never on the UI thread).
|
||||
// cb(ok, lowestHeight, fullHistory) runs on the UI thread: from worker_->drainResults(), or synchronously
|
||||
// when there is nothing to probe. fullHistory==true when block 1 is readable (a normal, non-bootstrapped
|
||||
@@ -1212,6 +1223,10 @@ private:
|
||||
void startStartupFilesProvision();
|
||||
void pollStartupFiles(); // UI thread, every update()
|
||||
void renderStartupFilesChip(); // UI thread, top of render()
|
||||
// The long wallet RPC lane (C61). Its task runs on async_tasks_, so it is declared BEFORE async_tasks_ and
|
||||
// outlives the task threads. Aborted at quit (beginShutdown / shutdown); never to make way for another flow:
|
||||
// those are refused while it is active (nodeBusyForStop), because an aborted client does not stop the scan.
|
||||
rpc::LongCallLane long_lane_;
|
||||
util::AsyncTaskManager async_tasks_;
|
||||
bool pending_antivirus_dialog_ = false; // Show Windows Defender help dialog
|
||||
|
||||
|
||||
Reference in New Issue
Block a user