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; void MainFrameRepaint(); int GetDiscoveredPeerCount(); +int GetPeerMedianHeight(); string DateTimeStr(int64 nTime); bool SendMoney(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew); int64 GetBalance(); @@ -238,6 +239,15 @@ static void DrawStatusBar() connected, discovered, nBestHeight); } + // A node that has stopped receiving looks exactly like one with nothing to + // do. Now that peers announce their height, say the difference out loud + // instead of leaving the user to guess from a number that stopped moving. + int peerHeight = GetPeerMedianHeight(); + if (peerHeight >= 0 && nBestHeight < peerHeight - 1) + ImGui::TextColored(ImVec4(0.95f, 0.65f, 0.20f, 1.0f), + "Behind the network by %d block(s) -- peers report height %d", + peerHeight - nBestHeight, peerHeight); + ImGui::End(); } diff --git a/src/main.cpp b/src/main.cpp index f4e6ce2..fff6108 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1884,6 +1884,12 @@ bool ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) if (pfrom->nVersion == 0) return false; + // Optional: peers older than this field simply end the message here, + // and nStartingHeight stays -1 for them. Never make this a requirement + // -- every node released so far sends the short form. + if (!vRecv.empty()) + vRecv >> pfrom->nStartingHeight; + pfrom->vSend.SetVersion(min(pfrom->nVersion, VERSION)); pfrom->vRecv.SetVersion(min(pfrom->nVersion, VERSION)); @@ -1912,7 +1918,7 @@ bool ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) if (!vPexOut.empty()) pfrom->PushMessage("btfpeers", vPexOut); - if (LogAcceptsCategory("net")) printf("version addrMe = %s\n", addrMe.ToString().c_str()); + if (LogAcceptsCategory("net")) printf("version addrMe = %s, peer height = %d\n", addrMe.ToString().c_str(), pfrom->nStartingHeight); } 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) } } +int GetPeerMedianHeight() +{ + vector vHeights; + CRITICAL_BLOCK(cs_vNodes) + foreach(CNode* pnode, vNodes) + if (pnode->nStartingHeight >= 0) + vHeights.push_back(pnode->nStartingHeight); + + if (vHeights.empty()) + return -1; + + sort(vHeights.begin(), vHeights.end()); + return vHeights[vHeights.size() / 2]; +} + + +// Say out loud when we are behind the network. +// +// This is the whole reason the height is in the handshake. Every serious bug +// this node has had looked identical from the outside: running, threads alive, +// nothing in the log, and no blocks arriving. A node that knows where everyone +// else is can say so, and "8 blocks behind for 27 minutes" is a sentence a +// user can act on. Deliberately not behind a debug category -- an operator who +// already suspects trouble is not the one who needs telling. +static void WarnIfBehind() +{ + static int64 nLastWarned; + static int64 nBehindSince; + + int nPeers = GetPeerMedianHeight(); + if (nPeers < 0 || nBestHeight < 0) + return; + + // One block of slack: somebody is always mid-relay. + if (nBestHeight >= nPeers - 1) + { + nBehindSince = 0; + return; + } + + int64 nNow = GetTime(); + if (nBehindSince == 0) + { + nBehindSince = nNow; + return; + } + + // Falling briefly behind is ordinary. Staying behind is not. + if (nNow - nBehindSince < BTF_BEHIND_GRACE_SECS) + return; + if (nNow - nLastWarned < BTF_BEHIND_WARN_INTERVAL_SECS) + return; + + nLastWarned = nNow; + printf("WARNING: %d blocks behind the network (height %d, peers report %d) for %d minutes\n", + nPeers - nBestHeight, nBestHeight, nPeers, (int)((nNow - nBehindSince) / 60)); +} + + void ThreadMessageHandler2(void* parg) { printf("ThreadMessageHandler started\n"); @@ -1250,6 +1309,8 @@ void ThreadMessageHandler2(void* parg) pnode->Release(); } + WarnIfBehind(); + // Wait and allow messages to bunch up vfThreadRunning[2] = false; 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; // stay well under BTF_RECV_TIMEOUT_SECS. static const int BTF_PING_INTERVAL_SECS = 10 * 60; +// How long a node must stay behind the network before it says so, and how +// often it repeats itself. Five minutes is two and a half block intervals -- +// long enough that ordinary relay lag never trips it. +static const int BTF_BEHIND_GRACE_SECS = 5 * 60; +static const int BTF_BEHIND_WARN_INTERVAL_SECS = 5 * 60; + +// Defined in main.cpp. Declared here because CNode announces it in the version +// message, and net.h is included before main.h. +extern int nBestHeight; + +// Median height announced by the peers we are connected to, or -1 while nobody +// has told us anything. Median rather than maximum so one peer claiming an +// absurd height cannot move it. +int GetPeerMedianHeight(); + // Descriptors to hand a peer: ours first, then peers that answered us. void BtfPexCollect(std::vector& vDescOut); // Verify descriptors a peer sent and remember the good ones. Returns how many. @@ -528,6 +543,11 @@ class CNode int64 nLastRecv; int64 nLastSendEmpty; + // Height this peer announced in its version message, or -1 if it sent a + // version message that predates the field. Knowing how far along everyone + // else is, is the only way a node can tell "in sync" from "not hearing". + int nStartingHeight; + CNode(SOCKET hSocketIn, CAddress addrIn, bool fInboundIn=false) { @@ -549,12 +569,16 @@ class CNode nLastSend = 0; nLastRecv = 0; nLastSendEmpty = GetTime(); + nStartingHeight = -1; vfSubscribe.assign(256, false); // Push a version message /// when NTP implemented, change to just nTime = GetAdjustedTime() int64 nTime = (fInbound ? GetAdjustedTime() : GetTime()); - PushMessage("version", VERSION, nLocalServices, nTime, addr); + // nBestHeight goes last so a node that predates it stops reading after + // addr and treats the rest as trailing bytes, which cost it a log line + // and nothing else. + PushMessage("version", VERSION, nLocalServices, nTime, addr, nBestHeight); } ~CNode() @@ -771,6 +795,22 @@ class CNode } } + template + void PushMessage(const char* pszCommand, const T1& a1, const T2& a2, const T3& a3, const T4& a4, const T5& a5) + { + try + { + BeginMessage(pszCommand); + vSend << a1 << a2 << a3 << a4 << a5; + EndMessage(); + } + catch (...) + { + AbortMessage(); + throw; + } + } + void PushRequest(const char* pszCommand, void (*fn)(void*, CDataStream&), void* param1)