fix(wallet): let a switch restart an adopted (external) daemon
Switching to a named wallet failed every time when the app had connected to a
pre-existing dragonxd at startup: the daemon is flagged externalDaemonDetected,
so stopEmbeddedDaemon()'s policy is DisconnectOnly ("not ours to stop") and the
switch skips the stop entirely. The old daemon keeps the RPC port, and the
relaunch fast-fails on EmbeddedDaemon::start()'s isPortInUse check — misread as a
bad wallet, reverting after a ~40s hang on the process-handle-only run-wait
(which reads false immediately for an adopted daemon).
A switch legitimately needs to restart the node, so:
- App::stopDaemonForWalletSwitch(): for an adopted daemon, send a graceful RPC
"stop" using the creds we actually connected with (saved_config_) — only our
own daemon obeys it, so a foreign dragonxd is a safe no-op — then wait (bounded
~40s) for the RPC port to actually free (isRpcPortInUse, the same gate start()
uses). Owned daemons take the normal stopEmbeddedDaemon() path. No PID/name kill
is ever issued at an adopted daemon.
- switchToWallet: gate start() on the port actually freeing; if it doesn't,
abort with a distinct switch_stop_failed_ reason instead of starting into a busy
port. Clear the external latch before relaunch so the fresh process is owned.
- EmbeddedDaemon::clearExternalDaemonDetected() (+ controller forwarder).
- Accurate revert message: "the running node didn't release its connection in
time" vs. the bad-wallet message.
Root-caused from live Windows logs; design + implementation adversarially
verified. Note: beginAdoptSeedWallet has the identical pattern and is left for a
separate fund-critical review.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
32
src/app.cpp
32
src/app.cpp
@@ -4132,6 +4132,38 @@ bool App::isEmbeddedDaemonRunning() const
|
||||
return daemon_controller_ && daemon_controller_->isRunning();
|
||||
}
|
||||
|
||||
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=<name>. 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.
|
||||
bool sent = false;
|
||||
auto tmp = std::make_unique<rpc::RPCClient>();
|
||||
if (tmp->connect(saved_config_.host, saved_config_.port, saved_config_.rpcuser,
|
||||
saved_config_.rpcpassword, saved_config_.use_tls)) {
|
||||
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();
|
||||
}
|
||||
|
||||
// Gate on the RPC port ACTUALLY releasing (mirrors reinstallBundledDaemon). isEmbeddedDaemonRunning()
|
||||
// is process-handle-only, so it reports "not running" immediately for an adopted daemon — the port is
|
||||
// the correct readiness signal, and it's the same check start() gates on (isRpcPortInUse). Bounded
|
||||
// ~40s: an adopted daemon has no SIGKILL fallback and a large-chain LevelDB flush can take 15-20s.
|
||||
for (int i = 0; i < 400 && daemon::EmbeddedDaemon::isRpcPortInUse() && !shutting_down_; ++i)
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
return shutting_down_ || !daemon::EmbeddedDaemon::isRpcPortInUse();
|
||||
}
|
||||
|
||||
void App::rescanBlockchain()
|
||||
{
|
||||
if (!supportsFullNodeLifecycleActions()) {
|
||||
|
||||
Reference in New Issue
Block a user