upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorBitflash <btf01@proton.me>2026-08-01 12:23:31 -0300
committerGitHub <noreply@github.com>2026-08-01 12:23:31 -0300
commit24ed292f0524f1bc77c317b60d7ecf265596b3dd (patch)
treeacdbccfca6bc0725751af8e653c16b7797db8a3f /src
parent64b9507846846e468ebba7e00fb59971edceca3b (diff)
diag: let the node say what is wrong with it (#81)
* Bump to 1.2.11 One change, and it is the reason a node on Windows kept collecting peers it could not hear. FD_SETSIZE was never defined in this tree, so it defaulted to 64 and select() could watch the listen socket plus 63 peers -- everything past that stayed connected and was never read again. Windows FD_SET stops adding once its array is full and says nothing, so there was no error to notice. Because new connections go on the end of vNodes, it was always the newest connection that starved, including the ones dialed to fix connectivity. Measured on two nodes: 277 and 90 CNode objects against 93 and 90 live sockets, 316 and 355 sockets in a bound-but-never-connected state, and a status bar reporting hundreds of peers for a node really talking to thirty. 1.2.11 raises FD_SETSIZE to 1024 on Windows, caps connections at 125, drops a node stuck undeliverable after 60 seconds instead of holding it forever, stops answering a failed select() with POSIX code that has no meaning on Windows, and warns when there are more peers than select can watch. * diag: let the node say what is wrong with it Every hard problem in this project so far was found from outside the node -- sockets read over WMI, committed memory compared against a README, a grep over somebody debug.log. In each case the node knew and had no way to say so. One report, rendered identically for the GUI panel and the log: peers held vs peers actually watched by select(), blocks received and how many arrived without a parent, proof-of-work mode with the live miner thread count, and per peer the age of the last message in each direction plus anything stuck unsent.
Diffstat (limited to 'src')
-rw-r--r--src/gui.cpp38
-rw-r--r--src/main.cpp7
-rw-r--r--src/net.cpp117
-rw-r--r--src/net.h19
4 files changed, 181 insertions, 0 deletions
diff --git a/src/gui.cpp b/src/gui.cpp
index 53cb6db..db6b69e 100644
--- a/src/gui.cpp
+++ b/src/gui.cpp
@@ -50,6 +50,7 @@ static int64 g_lastWalletRefresh = 0;
50static bool g_showSend = false; 50static bool g_showSend = false;
51static bool g_showOptions = false; 51static bool g_showOptions = false;
52static bool g_showAbout = false; 52static bool g_showAbout = false;
53static bool g_showDiagnostics = false;
53static bool g_needRefresh = true; 54static bool g_needRefresh = true;
54 55
55static char g_sendAddr[128] = {}; 56static char g_sendAddr[128] = {};
@@ -310,6 +311,7 @@ static void DrawMainWindow()
310 ImGui::EndMenu(); 311 ImGui::EndMenu();
311 } 312 }
312 if (ImGui::MenuItem("Options")) g_showOptions = true; 313 if (ImGui::MenuItem("Options")) g_showOptions = true;
314 if (ImGui::MenuItem("Diagnostics")) g_showDiagnostics = true;
313 if (ImGui::MenuItem("About")) g_showAbout = true; 315 if (ImGui::MenuItem("About")) g_showAbout = true;
314 ImGui::EndMenuBar(); 316 ImGui::EndMenuBar();
315 } 317 }
@@ -815,6 +817,41 @@ static void DrawOptionsDialog()
815// --------------------------------------------------------------------------- 817// ---------------------------------------------------------------------------
816// About dialog 818// About dialog
817// --------------------------------------------------------------------------- 819// ---------------------------------------------------------------------------
820// What the node knows about itself, in the words it writes to the log. Same
821// text in both places on purpose: what a user pastes into an issue is then
822// exactly what a developer has already read a hundred times.
823static void DrawDiagnosticsDialog()
824{
825 if (!g_showDiagnostics) return;
826 ImGui::SetNextWindowSize(ImVec2(720.0f, 460.0f), ImGuiCond_FirstUseEver);
827 ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(),
828 ImGuiCond_FirstUseEver, ImVec2(0.5f, 0.5f));
829 if (ImGui::Begin("Diagnostics", &g_showDiagnostics, ImGuiWindowFlags_NoCollapse))
830 {
831 // Rebuilt at most once a second: it walks every peer and takes their
832 // send locks, which is not something to do at frame rate.
833 static std::string strReport;
834 static int64 nLastBuilt = 0;
835 if (GetTime() != nLastBuilt)
836 {
837 nLastBuilt = GetTime();
838 strReport = GetDiagnosticsText();
839 }
840
841 if (ImGui::Button("Copy to clipboard", ImVec2(160.0f, 0.0f)))
842 ImGui::SetClipboardText(strReport.c_str());
843 ImGui::SameLine();
844 ImGui::TextDisabled("The same report goes to debug.log every ten minutes.");
845 ImGui::Separator();
846
847 ImGui::BeginChild("##diagtext", ImVec2(0.0f, 0.0f), false,
848 ImGuiWindowFlags_HorizontalScrollbar);
849 ImGui::TextUnformatted(strReport.c_str());
850 ImGui::EndChild();
851 }
852 ImGui::End();
853}
854
818static void DrawAboutDialog() 855static void DrawAboutDialog()
819{ 856{
820 if (!g_showAbout) return; 857 if (!g_showAbout) return;
@@ -921,6 +958,7 @@ int RunGUI(int argc, char* argv[])
921 DrawMainWindow(); 958 DrawMainWindow();
922 DrawSendDialog(); 959 DrawSendDialog();
923 DrawOptionsDialog(); 960 DrawOptionsDialog();
961 DrawDiagnosticsDialog();
924 DrawAboutDialog(); 962 DrawAboutDialog();
925 DrawStatusBar(); 963 DrawStatusBar();
926 964
diff --git a/src/main.cpp b/src/main.cpp
index 5610de9..1a4c9ea 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -1494,6 +1494,11 @@ bool CBlock::AcceptBlock()
1494 1494
1495bool ProcessBlock(CNode* pfrom, CBlock* pblock) 1495bool ProcessBlock(CNode* pfrom, CBlock* pblock)
1496{ 1496{
1497 // Counted before any check, so the ratio below is against everything that
1498 // arrived rather than everything that was any good.
1499 if (pfrom)
1500 nBlocksReceived++;
1501
1497 // Check for duplicate 1502 // Check for duplicate
1498 uint256 hash = pblock->GetHash(); 1503 uint256 hash = pblock->GetHash();
1499 if (mapBlockIndex.count(hash)) 1504 if (mapBlockIndex.count(hash))
@@ -1512,6 +1517,8 @@ bool ProcessBlock(CNode* pfrom, CBlock* pblock)
1512 if (!mapBlockIndex.count(pblock->hashPrevBlock)) 1517 if (!mapBlockIndex.count(pblock->hashPrevBlock))
1513 { 1518 {
1514 printf("ProcessBlock: ORPHAN BLOCK, prev=%s\n", pblock->hashPrevBlock.ToString().substr(0,14).c_str()); 1519 printf("ProcessBlock: ORPHAN BLOCK, prev=%s\n", pblock->hashPrevBlock.ToString().substr(0,14).c_str());
1520 if (pfrom)
1521 nBlocksWithoutParent++;
1515 mapOrphanBlocks.insert(make_pair(hash, pblock)); 1522 mapOrphanBlocks.insert(make_pair(hash, pblock));
1516 mapOrphanBlocksByPrev.insert(make_pair(pblock->hashPrevBlock, pblock)); 1523 mapOrphanBlocksByPrev.insert(make_pair(pblock->hashPrevBlock, pblock));
1517 1524
diff --git a/src/net.cpp b/src/net.cpp
index 5ef8316..19893ee 100644
--- a/src/net.cpp
+++ b/src/net.cpp
@@ -46,6 +46,96 @@ CCriticalSection cs_mapRelay;
46map<CInv, int64> mapAlreadyAskedFor; 46map<CInv, int64> mapAlreadyAskedFor;
47string strBtfConnect; // .btf peer to keep connected to (from /connectbtf) 47string strBtfConnect; // .btf peer to keep connected to (from /connectbtf)
48 48
49int nPeersWatched = 0;
50int64 nBlocksReceived = 0;
51int64 nBlocksWithoutParent = 0;
52static int64 nNodeStartTime = 0;
53
54// Defined further down, next to the counter it reads. The miner keeps its own
55// live count -- threads bump it on the way in and on the way out -- which is
56// the honest number here: how many are hashing now, not how many were asked to.
57static int MinersRunningCount();
58
59// Seconds rendered the way a person reads them off a screen.
60static string FormatAge(int64 nSeconds)
61{
62 if (nSeconds < 0) return "never";
63 if (nSeconds < 60) return strprintf("%llds", (long long)nSeconds);
64 if (nSeconds < 3600)
65 return strprintf("%lldm%02llds", (long long)(nSeconds / 60), (long long)(nSeconds % 60));
66 return strprintf("%lldh%02lldm", (long long)(nSeconds / 3600), (long long)((nSeconds % 3600) / 60));
67}
68
69string GetDiagnosticsText()
70{
71 int64 nNow = GetTime();
72 string str;
73
74 int nInbound = 0, nHeld = 0;
75 vector<CNode*> vCopy;
76 CRITICAL_BLOCK(cs_vNodes)
77 {
78 vCopy = vNodes;
79 nHeld = (int)vNodes.size();
80 foreach(CNode* pnode, vNodes)
81 if (pnode->fInbound)
82 nInbound++;
83 }
84
85 int nMedian = GetPeerMedianHeight();
86
87 str += "Bitflash node diagnostics\n";
88 str += strprintf(" uptime %s\n",
89 FormatAge(nNodeStartTime ? nNow - nNodeStartTime : -1).c_str());
90 if (nMedian < 0)
91 str += strprintf(" height %d (no peer has said where it is)\n", nBestHeight);
92 else
93 str += strprintf(" height %d (peers report %d, %s)\n",
94 nBestHeight, nMedian,
95 nBestHeight >= nMedian ? "level or ahead"
96 : strprintf("behind by %d", nMedian - nBestHeight).c_str());
97 str += strprintf(" peers held %d (%d inbound, %d outbound)\n",
98 nHeld, nInbound, nHeld - nInbound);
99
100 // The number that would have made the deafness obvious. Anything held but
101 // not watched is a socket this node will never read again.
102 str += strprintf(" peers watched %d of %d that select() can hold%s\n",
103 nPeersWatched, (int)FD_SETSIZE - 1,
104 nHeld > nPeersWatched ? " <-- NOT ALL PEERS ARE BEING READ" : "");
105
106 if (nBlocksReceived > 0)
107 str += strprintf(" blocks received %lld (%lld arrived without a parent, %.1f%%)\n",
108 (long long)nBlocksReceived, (long long)nBlocksWithoutParent,
109 100.0 * nBlocksWithoutParent / nBlocksReceived);
110 else
111 str += " blocks received 0\n";
112
113 int nMining = MinersRunningCount();
114 str += strprintf(" proof of work %s mode%s\n",
115 RandomXFastReady() ? "fast (2 GB dataset)" : "light (256 MB cache)",
116 nMining > 0
117 ? strprintf(", mining on %d thread(s), about %d MB",
118 nMining,
119 (RandomXFastReady() ? 2080 : 256) + 2 * nMining).c_str()
120 : ", not mining");
121
122 str += "\n peer dir height last recv last send unsent\n";
123 foreach(CNode* pnode, vCopy)
124 {
125 int nSendSize = 0;
126 TRY_CRITICAL_BLOCK(pnode->cs_vSend)
127 nSendSize = (int)pnode->vSend.size();
128 str += strprintf(" %-28s %-4s %6d %10s %10s %7d\n",
129 pnode->addr.ToString().substr(0, 28).c_str(),
130 pnode->fInbound ? "in" : "out",
131 pnode->nStartingHeight,
132 FormatAge(pnode->nLastRecv ? nNow - pnode->nLastRecv : -1).c_str(),
133 FormatAge(pnode->nLastSend ? nNow - pnode->nLastSend : -1).c_str(),
134 nSendSize);
135 }
136 return str;
137}
138
49 139
50bool GetMyExternalIP(unsigned int& ipRet) 140bool GetMyExternalIP(unsigned int& ipRet)
51{ 141{
@@ -1058,6 +1148,24 @@ void ThreadSocketHandler2(void* parg)
1058 (int)vNodes.size() - (int)FD_SETSIZE + 1); 1148 (int)vNodes.size() - (int)FD_SETSIZE + 1);
1059 } 1149 }
1060 } 1150 }
1151 nPeersWatched = (int)nWatched - 1; // less the listen socket
1152 }
1153
1154 // Say the state of the node out loud now and then, so a log pulled off
1155 // a machine three weeks later still answers "was it hearing anybody".
1156 {
1157 static int64 nLastReport = 0;
1158 // Wound forward on the first pass so the first report lands about a
1159 // minute in, once peers have had time to connect. A report written
1160 // at second zero says nothing, and waiting ten minutes for the
1161 // first one is ten minutes of a log that cannot answer anything.
1162 if (nLastReport == 0)
1163 nLastReport = GetTime() - 9 * 60;
1164 if (GetTime() - nLastReport > 10 * 60)
1165 {
1166 nLastReport = GetTime();
1167 printf("%s\n", GetDiagnosticsText().c_str());
1168 }
1061 } 1169 }
1062 1170
1063 vfThreadRunning[0] = false; 1171 vfThreadRunning[0] = false;
@@ -1432,6 +1540,14 @@ void ThreadMessageHandler2(void* parg)
1432static CCriticalSection cs_nMinersRunning; 1540static CCriticalSection cs_nMinersRunning;
1433static int nMinersRunning = 0; 1541static int nMinersRunning = 0;
1434 1542
1543static int MinersRunningCount()
1544{
1545 int n = 0;
1546 CRITICAL_BLOCK(cs_nMinersRunning)
1547 n = nMinersRunning;
1548 return n;
1549}
1550
1435void ThreadBitcoinMiner(void* parg) 1551void ThreadBitcoinMiner(void* parg)
1436{ 1552{
1437 int nThreadId = (int)(intptr_t)parg; 1553 int nThreadId = (int)(intptr_t)parg;
@@ -1490,6 +1606,7 @@ int StartMinerThreads()
1490bool StartNode(string& strError) 1606bool StartNode(string& strError)
1491{ 1607{
1492 strError = ""; 1608 strError = "";
1609 nNodeStartTime = GetTime();
1493 1610
1494 // Sockets startup 1611 // Sockets startup
1495 WSADATA wsadata; 1612 WSADATA wsadata;
diff --git a/src/net.h b/src/net.h
index 01f314d..bd4eb77 100644
--- a/src/net.h
+++ b/src/net.h
@@ -102,6 +102,25 @@ static const int BTF_PING_INTERVAL_SECS = 10 * 60;
102static const int BTF_BEHIND_GRACE_SECS = 5 * 60; 102static const int BTF_BEHIND_GRACE_SECS = 5 * 60;
103static const int BTF_BEHIND_WARN_INTERVAL_SECS = 5 * 60; 103static const int BTF_BEHIND_WARN_INTERVAL_SECS = 5 * 60;
104 104
105// --- Self-diagnosis -------------------------------------------------------
106//
107// A node that has stopped working looks exactly like a node with nothing to
108// do. That has cost this project real time more than once: a node deaf to
109// most of its peers, a miner allocating one RandomX dataset per thread, a
110// third of arriving blocks missing their parent -- each was found from
111// outside, by reading the process's sockets or its memory from another
112// machine, because the node itself had no way to say so.
113//
114// These counters exist so it can. Cheap to keep, and they name the failures
115// that actually happened rather than the ones that sound impressive.
116extern int nPeersWatched; // peers in the last select() set
117extern int64 nBlocksReceived; // blocks handed to ProcessBlock
118extern int64 nBlocksWithoutParent; // ...of those, how many arrived orphaned
119
120// One report, rendered the same way for the GUI panel and the log, so what a
121// user pastes into an issue is what a developer already knows how to read.
122string GetDiagnosticsText();
123
105// Defined in main.cpp. Declared here because CNode announces it in the version 124// Defined in main.cpp. Declared here because CNode announces it in the version
106// message, and net.h is included before main.h. 125// message, and net.h is included before main.h.
107extern int nBestHeight; 126extern int nBestHeight;