From 30bc80b2f29fe89ea95418e0b9b98ff74c770836 Mon Sep 17 00:00:00 2001 From: DanS Date: Wed, 15 Jul 2026 19:00:21 -0500 Subject: [PATCH] fix(seed): stop an adopted daemon before the migrate-to-seed swap MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit beginAdoptSeedWallet had the identical adopted-external-daemon bug the wallet switch fix (3808b3e) fixed: it gated the wallet.dat swap on isEmbeddedDaemonRunning(), which is process-handle-only and reads false immediately for an adopted daemon — so the swap could run while a live daemon still held wallet.dat, and the restart fast-failed on the held RPC port ("wallet swapped but daemon didn't restart"). Route the stop through stopDaemonForWalletSwitch() (RPC-stop the adopted daemon, wait for the RPC port to actually free) and gate the swap on that port_free signal instead. Clear the external latch before relaunch only when we actually stopped it (port_free), so a still-running foreign process is never marked owned. Owned daemons are unchanged in effect: stopEmbeddedDaemon() already blocks for full process exit, so wallet.dat is closed before the swap. The funded new wallet is never at risk (read-only copy source; its isolated creator daemon was already stopped). Two rounds of adversarial review (fund/swap safety + control-flow) cleared it. Still pending per policy: a live mainnet run. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/app_network.cpp | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/app_network.cpp b/src/app_network.cpp index f65f094..216178b 100644 --- a/src/app_network.cpp +++ b/src/app_network.cpp @@ -3561,11 +3561,14 @@ void App::beginAdoptSeedWallet() bool swapDone = false; try { std::error_code ec; - // 1. Stop the main daemon and wait for it to fully exit. - stopEmbeddedDaemon(); - for (int i = 0; i < 60 && isEmbeddedDaemonRunning(); ++i) - std::this_thread::sleep_for(std::chrono::milliseconds(500)); - if (isEmbeddedDaemonRunning()) { + // 1. Stop the main daemon and wait for it to FULLY release wallet.dat + the RPC port before we + // swap the file. Use the wallet-switch stop path: for an ADOPTED (external) daemon it sends a + // graceful RPC "stop" and gates on the port actually freeing — isEmbeddedDaemonRunning() is + // process-handle-only and would falsely read "stopped" for an adopted daemon, letting the swap + // run against a live daemon still holding wallet.dat. (For an owned daemon stopEmbeddedDaemon() + // already blocks for full process exit, so the file is closed before the swap.) + const bool port_free = stopDaemonForWalletSwitch(); + if (!port_free) { err = "The daemon did not stop in time; your wallet was not changed."; } else { // 2. Swap wallet.dat. Move the legacy one aside to a timestamped backup (NEVER @@ -3607,6 +3610,11 @@ void App::beginAdoptSeedWallet() // unless we're quitting, in which case don't resurrect it. if (swapDone && daemon_controller_) daemon_controller_->setRescanOnNextStart(true); if (!shutting_down_) { + // We stopped the daemon ourselves (port_free) — clear the adopted-external latch so the + // relaunched process is treated as owned (stop/isRunning/exit behave normally afterward). + // Skip when the stop failed: the old adopted daemon is still up, and we must not mark a + // process we can't control as owned. + if (port_free && daemon_controller_) daemon_controller_->clearExternalDaemonDetected(); if (!startEmbeddedDaemon() && swapDone) warn = "Your wallet was swapped, but the daemon did not restart — start it from Settings."; }