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-07-30 18:22:51 -0300
committerGitHub <noreply@github.com>2026-07-30 18:22:51 -0300
commit86f080cc6deb0464edf3913daa2e1131b5eed7c7 (patch)
tree058f6aa96501191a76cd35ee1a5e7831ca3227ba /src
parent773440fcc7752b85d99ee5b8500b64dbb5869603 (diff)
net: announce our height in the handshake, and say when we are behind (#56)
A node running this code cannot tell "in sync with everyone" from "not hearing anyone". The version message carries no height -- 0.1.0 never had the field and this fork inherited the gap -- so the only evidence of a problem is a number on screen that stopped moving, which looks the same as a network with nothing to report. It is the structural reason a broken node here has always been indistinguishable from an idle one. Append nBestHeight to the version message and keep what a peer announced in CNode::nStartingHeight. The field goes last, so a node that predates it stops reading after addr and treats the rest as trailing bytes, which cost it one log line. Reading it is guarded by vRecv.empty() and never required: peers that do not send it keep nStartingHeight at -1 and are left out of the median. Every release so far sends the short form. GetPeerMedianHeight() takes the median rather than the maximum so a single peer claiming an absurd height cannot move it. Then use it. After five minutes more than one block behind the peer median, the node says so in the log -- not behind a debug category, because an operator who already enabled -debug is not the one who needs telling -- and the GUI carries the same sentence in its status area. "8 blocks behind for 27 minutes" is something a user can act on; a frozen counter is not. Verified against two nodes on the live network: the one starting from an empty datadir read its peer at 3797, reported 2636 then 1274 then 433 blocks behind as it caught up, and went quiet on its own once level. The other logged "peer height = -1" for the older nodes it met on the real network, which is the compatibility path doing its job. Known limit: nStartingHeight is a snapshot from handshake time and goes stale on a long-lived connection, so this catches a node that is receiving something but not blocks, not one that has gone completely deaf. The inactivity timeout from #55 covers that case and refreshes these heights when it forces the reconnect.
Diffstat (limited to 'src')
-rw-r--r--src/gui.cpp10
-rw-r--r--src/main.cpp8
-rw-r--r--src/net.cpp61
-rw-r--r--src/net.h42
4 files changed, 119 insertions, 2 deletions
diff --git a/src/gui.cpp b/src/gui.cpp
index 825202b..28f5ffa 100644
--- a/src/gui.cpp
+++ b/src/gui.cpp
@@ -26,6 +26,7 @@ extern CAddress addrLocalHost;
26 26
27void MainFrameRepaint(); 27void MainFrameRepaint();
28int GetDiscoveredPeerCount(); 28int GetDiscoveredPeerCount();
29int GetPeerMedianHeight();
29string DateTimeStr(int64 nTime); 30string DateTimeStr(int64 nTime);
30bool SendMoney(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew); 31bool SendMoney(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew);
31int64 GetBalance(); 32int64 GetBalance();
@@ -238,6 +239,15 @@ static void DrawStatusBar()
238 connected, discovered, nBestHeight); 239 connected, discovered, nBestHeight);
239 } 240 }
240 241
242 // A node that has stopped receiving looks exactly like one with nothing to
243 // do. Now that peers announce their height, say the difference out loud
244 // instead of leaving the user to guess from a number that stopped moving.
245 int peerHeight = GetPeerMedianHeight();
246 if (peerHeight >= 0 && nBestHeight < peerHeight - 1)
247 ImGui::TextColored(ImVec4(0.95f, 0.65f, 0.20f, 1.0f),
248 "Behind the network by %d block(s) -- peers report height %d",
249 peerHeight - nBestHeight, peerHeight);
250
241 ImGui::End(); 251 ImGui::End();
242} 252}
243 253
diff --git a/src/main.cpp b/src/main.cpp
index c77448d..15805ca 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -1884,6 +1884,12 @@ bool ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)
1884 if (pfrom->nVersion == 0) 1884 if (pfrom->nVersion == 0)
1885 return false; 1885 return false;
1886 1886
1887 // Optional: peers older than this field simply end the message here,
1888 // and nStartingHeight stays -1 for them. Never make this a requirement
1889 // -- every node released so far sends the short form.
1890 if (!vRecv.empty())
1891 vRecv >> pfrom->nStartingHeight;
1892
1887 pfrom->vSend.SetVersion(min(pfrom->nVersion, VERSION)); 1893 pfrom->vSend.SetVersion(min(pfrom->nVersion, VERSION));
1888 pfrom->vRecv.SetVersion(min(pfrom->nVersion, VERSION)); 1894 pfrom->vRecv.SetVersion(min(pfrom->nVersion, VERSION));
1889 1895
@@ -1912,7 +1918,7 @@ bool ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)
1912 if (!vPexOut.empty()) 1918 if (!vPexOut.empty())
1913 pfrom->PushMessage("btfpeers", vPexOut); 1919 pfrom->PushMessage("btfpeers", vPexOut);
1914 1920
1915 if (LogAcceptsCategory("net")) printf("version addrMe = %s\n", addrMe.ToString().c_str()); 1921 if (LogAcceptsCategory("net")) printf("version addrMe = %s, peer height = %d\n", addrMe.ToString().c_str(), pfrom->nStartingHeight);
1916 } 1922 }
1917 1923
1918 1924
diff --git a/src/net.cpp b/src/net.cpp
index 81932d7..ac8df82 100644
--- a/src/net.cpp
+++ b/src/net.cpp
@@ -1225,6 +1225,65 @@ void ThreadMessageHandler(void* parg)
1225 } 1225 }
1226} 1226}
1227 1227
1228int GetPeerMedianHeight()
1229{
1230 vector<int> vHeights;
1231 CRITICAL_BLOCK(cs_vNodes)
1232 foreach(CNode* pnode, vNodes)
1233 if (pnode->nStartingHeight >= 0)
1234 vHeights.push_back(pnode->nStartingHeight);
1235
1236 if (vHeights.empty())
1237 return -1;
1238
1239 sort(vHeights.begin(), vHeights.end());
1240 return vHeights[vHeights.size() / 2];
1241}
1242
1243
1244// Say out loud when we are behind the network.
1245//
1246// This is the whole reason the height is in the handshake. Every serious bug
1247// this node has had looked identical from the outside: running, threads alive,
1248// nothing in the log, and no blocks arriving. A node that knows where everyone
1249// else is can say so, and "8 blocks behind for 27 minutes" is a sentence a
1250// user can act on. Deliberately not behind a debug category -- an operator who
1251// already suspects trouble is not the one who needs telling.
1252static void WarnIfBehind()
1253{
1254 static int64 nLastWarned;
1255 static int64 nBehindSince;
1256
1257 int nPeers = GetPeerMedianHeight();
1258 if (nPeers < 0 || nBestHeight < 0)
1259 return;
1260
1261 // One block of slack: somebody is always mid-relay.
1262 if (nBestHeight >= nPeers - 1)
1263 {
1264 nBehindSince = 0;
1265 return;
1266 }
1267
1268 int64 nNow = GetTime();
1269 if (nBehindSince == 0)
1270 {
1271 nBehindSince = nNow;
1272 return;
1273 }
1274
1275 // Falling briefly behind is ordinary. Staying behind is not.
1276 if (nNow - nBehindSince < BTF_BEHIND_GRACE_SECS)
1277 return;
1278 if (nNow - nLastWarned < BTF_BEHIND_WARN_INTERVAL_SECS)
1279 return;
1280
1281 nLastWarned = nNow;
1282 printf("WARNING: %d blocks behind the network (height %d, peers report %d) for %d minutes\n",
1283 nPeers - nBestHeight, nBestHeight, nPeers, (int)((nNow - nBehindSince) / 60));
1284}
1285
1286
1228void ThreadMessageHandler2(void* parg) 1287void ThreadMessageHandler2(void* parg)
1229{ 1288{
1230 printf("ThreadMessageHandler started\n"); 1289 printf("ThreadMessageHandler started\n");
@@ -1250,6 +1309,8 @@ void ThreadMessageHandler2(void* parg)
1250 pnode->Release(); 1309 pnode->Release();
1251 } 1310 }
1252 1311
1312 WarnIfBehind();
1313
1253 // Wait and allow messages to bunch up 1314 // Wait and allow messages to bunch up
1254 vfThreadRunning[2] = false; 1315 vfThreadRunning[2] = false;
1255 Sleep(100); 1316 Sleep(100);
diff --git a/src/net.h b/src/net.h
index 449cff5..831a880 100644
--- a/src/net.h
+++ b/src/net.h
@@ -69,6 +69,21 @@ static const int BTF_HANDSHAKE_GRACE_SECS = 60;
69// stay well under BTF_RECV_TIMEOUT_SECS. 69// stay well under BTF_RECV_TIMEOUT_SECS.
70static const int BTF_PING_INTERVAL_SECS = 10 * 60; 70static const int BTF_PING_INTERVAL_SECS = 10 * 60;
71 71
72// How long a node must stay behind the network before it says so, and how
73// often it repeats itself. Five minutes is two and a half block intervals --
74// long enough that ordinary relay lag never trips it.
75static const int BTF_BEHIND_GRACE_SECS = 5 * 60;
76static const int BTF_BEHIND_WARN_INTERVAL_SECS = 5 * 60;
77
78// Defined in main.cpp. Declared here because CNode announces it in the version
79// message, and net.h is included before main.h.
80extern int nBestHeight;
81
82// Median height announced by the peers we are connected to, or -1 while nobody
83// has told us anything. Median rather than maximum so one peer claiming an
84// absurd height cannot move it.
85int GetPeerMedianHeight();
86
72// Descriptors to hand a peer: ours first, then peers that answered us. 87// Descriptors to hand a peer: ours first, then peers that answered us.
73void BtfPexCollect(std::vector<std::string>& vDescOut); 88void BtfPexCollect(std::vector<std::string>& vDescOut);
74// Verify descriptors a peer sent and remember the good ones. Returns how many. 89// Verify descriptors a peer sent and remember the good ones. Returns how many.
@@ -528,6 +543,11 @@ public:
528 int64 nLastRecv; 543 int64 nLastRecv;
529 int64 nLastSendEmpty; 544 int64 nLastSendEmpty;
530 545
546 // Height this peer announced in its version message, or -1 if it sent a
547 // version message that predates the field. Knowing how far along everyone
548 // else is, is the only way a node can tell "in sync" from "not hearing".
549 int nStartingHeight;
550
531 551
532 CNode(SOCKET hSocketIn, CAddress addrIn, bool fInboundIn=false) 552 CNode(SOCKET hSocketIn, CAddress addrIn, bool fInboundIn=false)
533 { 553 {
@@ -549,12 +569,16 @@ public:
549 nLastSend = 0; 569 nLastSend = 0;
550 nLastRecv = 0; 570 nLastRecv = 0;
551 nLastSendEmpty = GetTime(); 571 nLastSendEmpty = GetTime();
572 nStartingHeight = -1;
552 vfSubscribe.assign(256, false); 573 vfSubscribe.assign(256, false);
553 574
554 // Push a version message 575 // Push a version message
555 /// when NTP implemented, change to just nTime = GetAdjustedTime() 576 /// when NTP implemented, change to just nTime = GetAdjustedTime()
556 int64 nTime = (fInbound ? GetAdjustedTime() : GetTime()); 577 int64 nTime = (fInbound ? GetAdjustedTime() : GetTime());
557 PushMessage("version", VERSION, nLocalServices, nTime, addr); 578 // nBestHeight goes last so a node that predates it stops reading after
579 // addr and treats the rest as trailing bytes, which cost it a log line
580 // and nothing else.
581 PushMessage("version", VERSION, nLocalServices, nTime, addr, nBestHeight);
558 } 582 }
559 583
560 ~CNode() 584 ~CNode()
@@ -771,6 +795,22 @@ public:
771 } 795 }
772 } 796 }
773 797
798 template<typename T1, typename T2, typename T3, typename T4, typename T5>
799 void PushMessage(const char* pszCommand, const T1& a1, const T2& a2, const T3& a3, const T4& a4, const T5& a5)
800 {
801 try
802 {
803 BeginMessage(pszCommand);
804 vSend << a1 << a2 << a3 << a4 << a5;
805 EndMessage();
806 }
807 catch (...)
808 {
809 AbortMessage();
810 throw;
811 }
812 }
813
774 814
775 void PushRequest(const char* pszCommand, 815 void PushRequest(const char* pszCommand,
776 void (*fn)(void*, CDataStream&), void* param1) 816 void (*fn)(void*, CDataStream&), void* param1)