fix(seed): stop an adopted daemon before the migrate-to-seed swap
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) <noreply@anthropic.com>
This commit is contained in:
@@ -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.";
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user