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;
|
bool swapDone = false;
|
||||||
try {
|
try {
|
||||||
std::error_code ec;
|
std::error_code ec;
|
||||||
// 1. Stop the main daemon and wait for it to fully exit.
|
// 1. Stop the main daemon and wait for it to FULLY release wallet.dat + the RPC port before we
|
||||||
stopEmbeddedDaemon();
|
// swap the file. Use the wallet-switch stop path: for an ADOPTED (external) daemon it sends a
|
||||||
for (int i = 0; i < 60 && isEmbeddedDaemonRunning(); ++i)
|
// graceful RPC "stop" and gates on the port actually freeing — isEmbeddedDaemonRunning() is
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(500));
|
// process-handle-only and would falsely read "stopped" for an adopted daemon, letting the swap
|
||||||
if (isEmbeddedDaemonRunning()) {
|
// 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.";
|
err = "The daemon did not stop in time; your wallet was not changed.";
|
||||||
} else {
|
} else {
|
||||||
// 2. Swap wallet.dat. Move the legacy one aside to a timestamped backup (NEVER
|
// 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.
|
// unless we're quitting, in which case don't resurrect it.
|
||||||
if (swapDone && daemon_controller_) daemon_controller_->setRescanOnNextStart(true);
|
if (swapDone && daemon_controller_) daemon_controller_->setRescanOnNextStart(true);
|
||||||
if (!shutting_down_) {
|
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)
|
if (!startEmbeddedDaemon() && swapDone)
|
||||||
warn = "Your wallet was swapped, but the daemon did not restart — start it from Settings.";
|
warn = "Your wallet was swapped, but the daemon did not restart — start it from Settings.";
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user