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:
@@ -386,52 +386,80 @@ static std::string getPortOwnerInfo(int port)
|
||||
#endif
|
||||
}
|
||||
|
||||
// Check if a TCP port is already in use (something is LISTENING)
|
||||
// Check if a TCP port is already in use (something is LISTENING). The daemon binds BOTH 127.0.0.1 (IPv4)
|
||||
// and ::1 (IPv6); during shutdown one can linger after the other releases (and a "Binding RPC on ::1 …
|
||||
// failed" is fatal to a fresh start), so we treat the port as in use if EITHER localhost family has it.
|
||||
static bool isPortInUse(int port)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
WSADATA wsa;
|
||||
if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) return false;
|
||||
SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
||||
if (sock == INVALID_SOCKET) { WSACleanup(); return false; }
|
||||
struct sockaddr_in addr;
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_port = htons(static_cast<u_short>(port));
|
||||
addr.sin_addr.s_addr = inet_addr("127.0.0.1");
|
||||
int result = connect(sock, (struct sockaddr*)&addr, sizeof(addr));
|
||||
closesocket(sock);
|
||||
bool inUse = false;
|
||||
{ // IPv4 127.0.0.1
|
||||
SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
||||
if (sock != INVALID_SOCKET) {
|
||||
struct sockaddr_in addr; memset(&addr, 0, sizeof(addr));
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_port = htons(static_cast<u_short>(port));
|
||||
addr.sin_addr.s_addr = inet_addr("127.0.0.1");
|
||||
if (connect(sock, (struct sockaddr*)&addr, sizeof(addr)) == 0) inUse = true;
|
||||
closesocket(sock);
|
||||
}
|
||||
}
|
||||
if (!inUse) { // IPv6 ::1
|
||||
SOCKET sock = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP);
|
||||
if (sock != INVALID_SOCKET) {
|
||||
struct sockaddr_in6 addr; memset(&addr, 0, sizeof(addr));
|
||||
addr.sin6_family = AF_INET6;
|
||||
addr.sin6_port = htons(static_cast<u_short>(port));
|
||||
addr.sin6_addr = in6addr_loopback; // ::1 — avoids inet_pton's _WIN32_WINNT gating on mingw
|
||||
if (connect(sock, (struct sockaddr*)&addr, sizeof(addr)) == 0) inUse = true;
|
||||
closesocket(sock);
|
||||
}
|
||||
}
|
||||
WSACleanup();
|
||||
return (result == 0);
|
||||
return inUse;
|
||||
#else
|
||||
// On macOS /proc doesn't exist; on Linux prefer /proc/net/tcp to avoid
|
||||
// creating sockets. Fall back to connect() if /proc is unavailable.
|
||||
FILE* fp = fopen("/proc/net/tcp", "r");
|
||||
if (fp) {
|
||||
char line[256];
|
||||
// On macOS /proc doesn't exist; on Linux prefer /proc/net/tcp{,6} to avoid creating sockets. The
|
||||
// parse is family-agnostic: %*X skips the local IP (8 hex for v4, 32 for v6), %X grabs the port.
|
||||
auto scanProc = [port](const char* path) -> bool {
|
||||
FILE* fp = fopen(path, "r");
|
||||
if (!fp) return false;
|
||||
char line[512];
|
||||
unsigned int localPort, state;
|
||||
bool found = false;
|
||||
while (fgets(line, sizeof(line), fp)) {
|
||||
if (sscanf(line, " %*d: %*X:%X %*X:%*X %X", &localPort, &state) == 2) {
|
||||
if (localPort == static_cast<unsigned int>(port) && state == 0x0A) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
if (localPort == static_cast<unsigned int>(port) && state == 0x0A) { found = true; break; }
|
||||
}
|
||||
}
|
||||
fclose(fp);
|
||||
return found;
|
||||
};
|
||||
if (FILE* probe = fopen("/proc/net/tcp", "r")) { // /proc available → authoritative LISTEN check
|
||||
fclose(probe);
|
||||
return scanProc("/proc/net/tcp") || scanProc("/proc/net/tcp6");
|
||||
}
|
||||
// Fallback (macOS): try to connect
|
||||
int sock = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (sock < 0) return false;
|
||||
struct sockaddr_in addr;
|
||||
memset(&addr, 0, sizeof(addr));
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_port = htons(static_cast<uint16_t>(port));
|
||||
addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
||||
int result = connect(sock, (struct sockaddr*)&addr, sizeof(addr));
|
||||
close(sock);
|
||||
return (result == 0);
|
||||
// Fallback (macOS): connect() probe on both loopback families.
|
||||
auto connProbe = [port](int family, const char* addr) -> bool {
|
||||
int sock = socket(family, SOCK_STREAM, 0);
|
||||
if (sock < 0) return false;
|
||||
bool ok = false;
|
||||
if (family == AF_INET) {
|
||||
struct sockaddr_in a; memset(&a, 0, sizeof(a));
|
||||
a.sin_family = AF_INET; a.sin_port = htons(static_cast<uint16_t>(port));
|
||||
a.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
||||
ok = (connect(sock, (struct sockaddr*)&a, sizeof(a)) == 0);
|
||||
} else {
|
||||
struct sockaddr_in6 a; memset(&a, 0, sizeof(a));
|
||||
a.sin6_family = AF_INET6; a.sin6_port = htons(static_cast<uint16_t>(port));
|
||||
inet_pton(AF_INET6, addr, &a.sin6_addr);
|
||||
ok = (connect(sock, (struct sockaddr*)&a, sizeof(a)) == 0);
|
||||
}
|
||||
close(sock);
|
||||
return ok;
|
||||
};
|
||||
return connProbe(AF_INET, "127.0.0.1") || connProbe(AF_INET6, "::1");
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user