1717#include < vector>
1818
1919#ifdef _WIN32
20+ #include < winsock2.h>
21+ #include < ws2tcpip.h>
2022#include < windows.h>
2123#include < conio.h>
24+ #pragma comment(lib, "Ws2_32.lib")
2225#endif
2326
2427// ---- VT100 escape helpers --------------------------------------------------
@@ -361,11 +364,24 @@ void ConsoleDashboard::render() {
361364 bool dgbOnline = (dgb != nullptr );
362365 bool dbOnline = (db != nullptr );
363366 bool ipfsOnline = (app->getIPFSIfSet () != nullptr );
364- RPC::Server* rpcServer = app->getRpcServerIfSet ();
365- bool rpcOnline = (rpcServer != nullptr );
366- unsigned int rpcPort = 0 ;
367- if (rpcOnline) {
368- rpcPort = rpcServer->getPort ();
367+
368+ // RPC: probe the actual listen socket. AppMain stores a raw pointer to
369+ // RPC::Server that becomes dangling if the detached accept-loop thread
370+ // dies, so trusting getRpcServerIfSet() can show a stale port for a
371+ // service that isn't actually listening.
372+ {
373+ auto rnow = std::chrono::steady_clock::now ();
374+ auto rElapsed = std::chrono::duration<double >(rnow - _lastRpcProbe).count ();
375+ if (rElapsed >= 30.0 || !_rpcProbed) {
376+ std::thread ([this ]() { probeRpcServer (); }).detach ();
377+ }
378+ }
379+ bool rpcOnline;
380+ unsigned int rpcPort;
381+ {
382+ std::lock_guard<std::mutex> lock (_rpcProbeMutex);
383+ rpcOnline = _rpcListening;
384+ rpcPort = _rpcProbedPort;
369385 }
370386 WebServer* webServer = app->getWebServerIfSet ();
371387 bool webOnline = (webServer != nullptr && webServer->isRunning ());
@@ -644,17 +660,70 @@ void ConsoleDashboard::render() {
644660 std::cout << out.str () << std::flush;
645661}
646662
663+ // ---- RPC server liveness probe ---------------------------------------------
664+
665+ void ConsoleDashboard::probeRpcServer () {
666+ // Read the configured port (default 14024 matches RPC::Server::Server)
667+ unsigned int port = 14024 ;
668+ try {
669+ Config config (" config.cfg" );
670+ port = (unsigned int )config.getInteger (" rpcassetport" , 14024 );
671+ } catch (...) {}
672+
673+ bool listening = false ;
674+ #ifdef _WIN32
675+ WSADATA wsa;
676+ if (WSAStartup (MAKEWORD (2 , 2 ), &wsa) == 0 ) {
677+ SOCKET s = socket (AF_INET, SOCK_STREAM, 0 );
678+ if (s != INVALID_SOCKET) {
679+ // Non-blocking + select-with-timeout: short, never hang the dashboard
680+ u_long nonblock = 1 ;
681+ ioctlsocket (s, FIONBIO, &nonblock);
682+
683+ sockaddr_in addr{};
684+ addr.sin_family = AF_INET;
685+ addr.sin_port = htons ((u_short)port);
686+ addr.sin_addr .s_addr = htonl (0x7F000001 ); // 127.0.0.1
687+
688+ int rc = connect (s, (sockaddr*)&addr, sizeof (addr));
689+ if (rc == 0 ) {
690+ listening = true ;
691+ } else if (WSAGetLastError () == WSAEWOULDBLOCK) {
692+ fd_set wfds;
693+ FD_ZERO (&wfds);
694+ FD_SET (s, &wfds);
695+ timeval tv{0 , 200000 }; // 200 ms
696+ int sel = select (0 , nullptr , &wfds, nullptr , &tv);
697+ if (sel > 0 ) {
698+ int err = 0 ;
699+ int errlen = sizeof (err);
700+ getsockopt (s, SOL_SOCKET, SO_ERROR, (char *)&err, &errlen);
701+ if (err == 0 ) listening = true ;
702+ }
703+ }
704+ closesocket (s);
705+ }
706+ WSACleanup ();
707+ }
708+ #endif
709+
710+ std::lock_guard<std::mutex> lock (_rpcProbeMutex);
711+ _rpcListening = listening;
712+ _rpcProbedPort = port;
713+ _rpcProbed = true ;
714+ _lastRpcProbe = std::chrono::steady_clock::now ();
715+ }
716+
647717// ---- PSP registration status ------------------------------------------------
648718
649719void ConsoleDashboard::checkPspRegistration () {
650720 auto now = std::chrono::steady_clock::now ();
651721
652- // Check pool reachability via map.json (public, no auth needed).
653- // map.json is the authoritative list of nodes the server has recently
654- // seen alive, so its contents are the only honest signal for "am I
655- // registered". The /keepalive endpoint ALWAYS returns
656- // {"error":"unsubscribe failed will time out anyways"} regardless of
657- // success — don't use it as a registration signal.
722+ // map.json is anonymized geo data — entries are {version,country,region,city,
723+ // longitude,latitude} only, no payout addresses or peer IDs. So all we can
724+ // tell from it is "the pool server is up" and "how many nodes it sees online".
725+ // Anything stronger (am I registered, am I being paid) requires a per-node
726+ // endpoint mctrivia hasn't exposed.
658727 try {
659728 std::string mapResponse = CurlHandler::get (" https://ipfs.digiassetx.com/map.json" , 5000 );
660729 int nodeCount = 0 ;
@@ -664,28 +733,7 @@ void ConsoleDashboard::checkPspRegistration() {
664733 pos++;
665734 }
666735 _pspNodeCount = nodeCount;
667-
668- // Check if our own payout address appears anywhere in map.json —
669- // that's a strong signal the server sees us as an active contributor.
670- // (Peer ID matching would be better but map.json keys by payout.)
671- bool selfListed = false ;
672- try {
673- Config config (" config.cfg" );
674- std::string payout = config.getString (" psp0payout" , " " );
675- if (!payout.empty () && mapResponse.find (payout) != std::string::npos) {
676- selfListed = true ;
677- }
678- } catch (...) {}
679-
680- if (selfListed) {
681- _pspStatus = " Pool reachable - this node is listed" ;
682- } else {
683- // Server-side registration is not instant. Once keepalives start
684- // arriving, mctrivia's server has to crawl us, verify the pinned
685- // content over libp2p, and refresh map.json on its own schedule.
686- // In practice this takes a few hours after a brand-new subscribe.
687- _pspStatus = " Pool reachable - awaiting server registration (can take a few hours)" ;
688- }
736+ _pspStatus = " Pool reachable" ;
689737 _lastPspCheck = now; // cache for 10 min
690738 } catch (...) {
691739 _pspStatus = " Pool unreachable" ;
@@ -699,7 +747,12 @@ void ConsoleDashboard::loadPayoutInfo() {
699747 if (!_payoutLoaded) {
700748 try {
701749 Config config (" config.cfg" );
702- _payoutAddress = config.getString (" psp0payout" , " " );
750+ // mctrivia is pool index 1 (local is 0). Fall back to psp0payout
751+ // for legacy configs that wrote the wrong key.
752+ _payoutAddress = config.getString (" psp1payout" , " " );
753+ if (_payoutAddress.empty ()) {
754+ _payoutAddress = config.getString (" psp0payout" , " " );
755+ }
703756 } catch (...) {}
704757 _payoutLoaded = true ;
705758 _lastBalanceTime = std::chrono::steady_clock::now () - std::chrono::seconds (120 ); // force immediate fetch
0 commit comments