fix(wallet): wait for IPv6 port + retry start so a switch survives the DB-env race
Live logs showed the switch's new node starting on the correct wallet, reaching "Verifying wallet…", then aborting with "Binding RPC on ::1 port 21769 failed" + "Failed to rename wallet-savings.dat" — the OLD node's RPC port (on ::1/IPv6) and Berkeley DB environment weren't fully released yet, so the wallet-verify DB recovery couldn't rename the file. The app then reverted, and the connect loop brought a node up on the DEFAULT wallet. Two causes fixed: - isPortInUse() only probed 127.0.0.1 (IPv4). The daemon also binds ::1 (IPv6), which lingers after IPv4 releases — so the readiness wait returned "free" prematurely. Now probe BOTH families (Windows: IPv4 + ::1 via in6addr_loopback; Linux: /proc/net/tcp + tcp6). mingw-verified. - The datadir/DB-env can still be briefly held right after the old node exits, so the first start can abort. Retry the start (up to 6×, 2s backoff) with the CORRECT -wallet — active_wallet_file isn't reverted until we give up — resetting the crash count and re-arming -rescan each attempt, until one survives. Also: the "Wallet switch failed" modal no longer repeats its title in the warning header — it now shows the actual reason there. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1136,14 +1136,21 @@ void App::switchToWallet(const std::string& walletFile, bool stopDaemonConfirmed
|
||||
// as owned (stop/isRunning/exit behave normally for the next switch and app exit).
|
||||
wallet_switch_phase_.store(static_cast<int>(WalletSwitchPhase::Starting));
|
||||
if (daemon_controller_) daemon_controller_->clearExternalDaemonDetected();
|
||||
if (needRescan && daemon_controller_) daemon_controller_->setRescanOnNextStart(true);
|
||||
ok = shutting_down_ ? true : startEmbeddedDaemon();
|
||||
// A missing/corrupt wallet makes dragonxd exit during init — confirm the process survived a
|
||||
// moment; if not, the wallet is bad. (A valid launch keeps the process alive while it syncs.)
|
||||
if (ok && !shutting_down_) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1500));
|
||||
if (!isEmbeddedDaemonRunning()) ok = false;
|
||||
// Retry the start. Right after the old node exits, its datadir lock can linger for a few
|
||||
// seconds (the process is gone but the OS/BDB lock isn't released yet), so the first start
|
||||
// hits "Cannot obtain a lock on data directory" and exits within the grace below. Retry —
|
||||
// with the CORRECT -wallet, since active_wallet_file isn't reverted until we give up — until
|
||||
// one survives (valid launch stays alive while it loads the block index/rescans, ~30-60s).
|
||||
for (int attempt = 0; attempt < 6 && !ok && !shutting_down_; ++attempt) {
|
||||
if (attempt > 0) std::this_thread::sleep_for(std::chrono::milliseconds(2000)); // let the lock release
|
||||
if (daemon_controller_) daemon_controller_->resetCrashCount(); // retry lock-crashes aren't real
|
||||
if (needRescan && daemon_controller_) daemon_controller_->setRescanOnNextStart(true); // one-shot; re-arm each try
|
||||
if (shutting_down_) { ok = true; break; }
|
||||
if (!startEmbeddedDaemon()) continue; // couldn't spawn (port busy) — back off + retry
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1500)); // grace: a datadir-lock crash exits within this
|
||||
ok = shutting_down_ || isEmbeddedDaemonRunning();
|
||||
}
|
||||
if (ok && daemon_controller_) daemon_controller_->resetCrashCount(); // clean slate for the survivor
|
||||
// Process is up and staying up — now waiting for RPC to answer (onConnected closes the modal).
|
||||
if (ok) wallet_switch_phase_.store(static_cast<int>(WalletSwitchPhase::Reconnecting));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user