fix(net): stop re-running the connect sequence while the wallet is locked

Connected-but-locked fell through to tryConnect() on every Core tick
while the lock screen sat idle: new sockets, getinfo and getwalletinfo,
a forced address scan, a CoinGecko fetch and a wallet-index rewrite.
Its wallet scans also held cs_main ahead of the user's
walletpassphrase.

- While locked, poll chain state only (refreshCoreData without the
  balance). That keeps the sync height current and the 3-strike
  lost-connection check armed, so a node that exits while locked is
  still restarted. Pool mining keeps the lock screen down, so while it
  runs the throttled balance, the peers and the lock state (for an
  unlock done in the Console tab) keep refreshing too.
- Warmup polls count refused/reset connections toward the
  lost-connection check, so a node that died during warmup is
  restarted instead of leaving the loading or lock screen over it.
- The wallet salvage/degraded detectors run at connect and again when
  warmup ends: for encrypted wallets they relied on the repeated
  tryConnect.
- restartDaemonAfterEncryption records the wallet as encrypted and
  locked itself (the wizard's deferred path never did), and marks the
  main client disconnected so the reconnect path picks up the restarted
  node, aborting any in-flight call first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-03 19:52:09 -05:00
co-authored by Claude Opus 5.5
parent 575abd4bcc
commit 1cb03332e7
6 changed files with 69 additions and 3 deletions
+19
View File
@@ -274,6 +274,14 @@ void App::sweepStaleDecryptExports()
}
void App::restartDaemonAfterEncryption(const char* taskName, bool announceRestartStatus) {
// Both callers have just had encryptwallet succeed. Record it here: the first-run wizard's
// deferred path never did, and the reconnect after the restart usually lands in RPC warmup, where
// the connect-time getwalletinfo fails and can't correct it.
state_.encrypted = true;
state_.locked = true;
state_.unlocked_until = 0;
state_.encryption_state_known = true;
if (isUsingEmbeddedDaemon()) {
if (announceRestartStatus) {
// Update connection_status_ so the loading overlay explains why
@@ -283,6 +291,17 @@ void App::restartDaemonAfterEncryption(const char* taskName, bool announceRestar
// Gate the main-loop reconnect while the daemon is down (so tryConnect can't hit the stopped
// node or start a duplicate) and block a concurrent wallet switch/rescan, which check this flag.
daemon_restarting_ = true;
// Mark the main client disconnected so the update loop's reconnect path (tryConnect, held back
// by daemon_restarting_) picks the restarted node up. Connected-but-locked no longer re-runs
// the connect sequence on its own. Not onDisconnected(): its state_.clear() would drop the
// encrypted/locked flags just set for the lock screen. Abort any in-flight call first:
// disconnect() takes the curl lock, which a worker call (say a balance scan queued behind
// encryptwallet) holds for its whole transfer, up to the 30 s timeout. open() clears the abort
// when tryConnect reconnects.
if (rpc_) {
rpc_->requestAbort();
rpc_->disconnect();
}
// Give daemon a moment to shut down, then restart
// (do this off the main thread to avoid stalling the UI)
async_tasks_.submit(taskName, [this](const util::AsyncTaskManager::Token& token) {