diff --git a/src/app.cpp b/src/app.cpp index 7c661f1..cab4384 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -4171,14 +4171,22 @@ bool App::isEmbeddedDaemonRunning() const bool App::stopDaemonForWalletSwitch() { - const bool external = daemon_controller_ && daemon_controller_->externalDaemonDetected(); - if (external) { - // Adopted daemon: stopEmbeddedDaemon()'s policy would DisconnectOnly and leave it running, but a - // switch MUST free the RPC port to relaunch on -wallet=. Send a graceful RPC "stop" using the - // creds we actually connected with (saved_config_) — only OUR daemon accepts them, so a foreign - // dragonxd (which would have a different rpcpassword) is a safe no-op: its port never frees and the - // caller aborts without starting. No PID/name kill is ever issued at an adopted daemon. switchToWallet - // already disconnected rpc_, so build a fresh temporary connection from the known-good creds. + // "Owned" means we spawned the node this session and hold a live process handle. That is the ONLY + // reliable ownership signal here: externalDaemonDetected() is latched only inside EmbeddedDaemon::start(), + // so when the app just direct-connects to an already-running, config-provided node (the common + // keep-node-running case) start() is never called and that flag stays false even though we clearly did + // not start the node. Gate on the handle instead. + const bool owned = daemon_controller_ && isEmbeddedDaemonRunning(); + if (owned) { + // We spawned it — the normal policy stop (RPC stop + bounded SIGTERM/SIGKILL escalation). + stopEmbeddedDaemon(); + } else { + // Adopted OR direct-connected: we have NO process handle, so RPC "stop" is the only lever, and + // stopEmbeddedDaemon()'s temp connection (autoDetectConfig) can't be trusted here. Send a graceful + // RPC "stop" over the exact creds we're connected with (saved_config_) — guaranteed to reach our + // node, and only OUR node accepts them, so a foreign dragonxd (different rpcpassword) is a safe + // no-op: its port never frees and the caller aborts without starting. No PID/name kill is issued. + // switchToWallet disconnects rpc_ before this worker runs, so build a fresh temporary connection. bool sent = false; auto tmp = std::make_unique(); if (tmp->connect(saved_config_.host, saved_config_.port, saved_config_.rpcuser, @@ -4186,10 +4194,7 @@ bool App::stopDaemonForWalletSwitch() sent = sendStopCommandSafely(*tmp, "wallet-switch stop"); tmp->disconnect(); } - DEBUG_LOGF("[App] wallet-switch stop of adopted daemon: %s\n", sent ? "sent" : "FAILED"); - } else { - // We own the process — the normal policy stop (RPC stop + bounded SIGTERM/SIGKILL escalation). - stopEmbeddedDaemon(); + DEBUG_LOGF("[App] wallet-switch stop of unowned node (saved_config_): %s\n", sent ? "sent" : "FAILED"); } // Gate on the RPC port ACTUALLY releasing (mirrors reinstallBundledDaemon). isEmbeddedDaemonRunning() diff --git a/src/app_network.cpp b/src/app_network.cpp index d1c821a..43ee5c8 100644 --- a/src/app_network.cpp +++ b/src/app_network.cpp @@ -1056,11 +1056,11 @@ void App::switchToWallet(const std::string& walletFile, bool stopDaemonConfirmed ui::Notifications::instance().warning("Finish or cancel the pending send before switching wallets."); return; } - // The node we'd restart may be one this session ADOPTED — a dragonxd already running when the wallet - // launched (left running by "keep node running", or started by the user). Switching must stop+restart - // it on the new wallet, so confirm first rather than killing a node the user may have kept up on - // purpose. (An owned node we started this session restarts silently, as before.) - if (!stopDaemonConfirmed && daemon_controller_ && daemon_controller_->externalDaemonDetected()) { + // If we're connected to a node this session did NOT spawn (no live process handle — it was left + // running by "keep node running", started by the user, or we just direct-connected to a config-provided + // one), confirm before stopping it: switching must stop+restart it on the new wallet, but the user may + // have kept it running on purpose. A node we started ourselves (handle held) restarts silently. + if (!stopDaemonConfirmed && state_.connected && !isEmbeddedDaemonRunning()) { pending_switch_wallet_file_ = walletFile; show_switch_stop_daemon_confirm_ = true; return;