From 3d7c30ebb6ac0c586635c693a40bc39180f539b9 Mon Sep 17 00:00:00 2001 From: bitflash-maintainer Date: Tue, 11 Aug 2026 20:52:09 +0000 Subject: pool: refuse jobs when no peer has reported a height for 20 minutes The peer-height gate replaced a tip-age check that was wrong in both directions. It is a strict improvement and it is still blind in the one case that matters most. When the node goes deaf -- peers still in vNodes, nothing arriving -- no peer announces anything, GetPeerMedianHeight() returns -1, and the fallback mines our best chain. That fallback is right for a node that has not been told yet and wrong for a node that has stopped being able to hear, and a height nobody sent cannot tell those apart. A wedged node is exactly a node with no peer height information, so the gate was silent for the whole of heights 3966-4085: 120 blocks, 5890.50 BTF voided. Time can tell them apart. GetPeerHeightAgeSecs() measures how long since any peer reported a height, counting from first use rather than the epoch so a starting node does not refuse jobs before its first handshake. Past 20 minutes the pool stops issuing jobs and says so unconditionally in the log. /poolmineblind overrides it: without an escape hatch a genuinely isolated pool that is 90% of the hashrate could never restart its own chain, which is the deadlock the tip-age check used to cause. Also stop nStartingHeight from going stale. It was a snapshot taken once during the version handshake and never touched again, so on a long-lived connection the median is built from heights the peers had hours ago, and the behind-the-network check drifts toward 'everyone is behind us' and quietly stops firing. A peer that hands us a block is demonstrably at least at that block's height. (cherry picked from commit 0800c18faf703b4acde1b131d0b742d37a86d92b) --- src/rpc.cpp | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) (limited to 'src/rpc.cpp') diff --git a/src/rpc.cpp b/src/rpc.cpp index 8a0d112..da22b1d 100644 --- a/src/rpc.cpp +++ b/src/rpc.cpp @@ -180,6 +180,38 @@ static bool RebuildJob() return false; } + // The comparison above is blind in the one case that matters most. + // + // When the node goes deaf -- peers still in vNodes, nothing arriving -- + // no peer announces anything, GetPeerMedianHeight() returns -1, and the + // fallback ("nobody has told us anything, so mine our best chain") let + // the pool build a private fork for as long as the deafness lasted. + // That fallback is right for a node that has simply not been told yet + // and wrong for a node that has stopped being able to hear, and a + // height nobody sent cannot tell those apart. Time can. + // + // Deliberately not behind a debug category, and deliberately not a + // silent refusal: an operator whose pool has stopped issuing jobs needs + // the reason in the log. /poolmineblind is the escape hatch for a node + // that really is alone -- without one, a genuinely isolated pool that is + // the majority of the hashrate could never restart its own chain, which + // is the deadlock the tip-age check used to cause. + if (nPeerHeight < 0 && !fPoolMineBlind) { + int64 nAge = GetPeerHeightAgeSecs(); + if (nAge >= BTF_NO_PEER_HEIGHT_MAX_SECS) { + static int64 nLastSaid = 0; + if (GetTime() - nLastSaid > 60) { + nLastSaid = GetTime(); + printf("RebuildJob: no peer has reported a height for %d minutes " + "-- not issuing jobs (our height %d, %d peers connected). " + "This node is deaf; mining now would fork it. " + "Pass /poolmineblind to override.\n", + (int)(nAge / 60), nBestHeight, (int)vNodes.size()); + } + return false; + } + } + CBlockIndex* pindexPrev = pindexBest; unsigned int nBits = GetNextWorkRequired(pindexPrev); -- cgit v1.2.3