From db2692dcd8984fe0e5a2a6a796f7b3cbee216c32 Mon Sep 17 00:00:00 2001 From: bitflash-maintainer Date: Tue, 11 Aug 2026 13:10:57 +0000 Subject: pool: gate jobs on peer height, not tip age The sync gate compared the tip timestamp against 30 minutes. That cannot distinguish "we are not synced" from "the network is slow", and it failed in both directions on the same day. A pool mining its own fork keeps its tip fresh, so the check passed for the whole of heights 3966-4085 -- 120 consecutive blocks, all orphaned, 5890.50 BTF of payouts voided, and nothing in the log to say so. Then, once the pool stopped finding blocks, the same check refused to issue jobs against a tip that was the network's own best. The pool is ~90% of the hashrate, so nobody else could move the chain either: stale tip, no jobs; no jobs, stale tip. Gate on the height peers announce in their version message instead. Median, so one peer cannot stall us with an absurd claim, and a block of slack to match WarnIfBehind. Peers predating the announcement report -1 and are ignored; with no reports at all we mine our best chain, which is what the node does anyway without a pool attached. (cherry picked from commit 4e76e34f37a82c0885f75db7dfb9e49db410171d) --- src/rpc.cpp | 37 ++++++++++++++++++++++++++++++------- 1 file changed, 30 insertions(+), 7 deletions(-) (limited to 'src/rpc.cpp') diff --git a/src/rpc.cpp b/src/rpc.cpp index ae94289..8a0d112 100644 --- a/src/rpc.cpp +++ b/src/rpc.cpp @@ -143,17 +143,40 @@ static bool RebuildJob() return false; } - // Don't issue jobs until the chain is synced: mining against a stale - // tip produces blocks the network will orphan, and payout entries - // that can never be paid (their coinbases never exist on the main - // chain). Require at least one peer and a fresh tip. + // Don't issue jobs while we are behind the network: mining against a + // stale tip produces blocks the network will orphan, and payout + // entries that can never be paid (their coinbases never exist on the + // main chain). Require at least one peer, and require that no peer + // claims a longer chain than ours. if (vNodes.empty()) { LogPrint("pool", "RebuildJob: no peers yet -- not issuing jobs\n"); return false; } - if (GetAdjustedTime() - (int64)pindexBest->nTime > 30*60) { - LogPrint("pool", "RebuildJob: tip is %llds old -- still syncing, not issuing jobs\n", - (long long)(GetAdjustedTime() - (int64)pindexBest->nTime)); + // This used to compare the tip's timestamp against 30 minutes, which + // was wrong in both directions and cost real money. + // + // It cannot tell "we are not synced" from "the network is slow". A + // pool mining its own fork keeps its tip timestamp fresh forever, so + // the check passed for the whole of heights 3966-4085 -- 120 + // consecutive blocks, every one of them orphaned, 5890.50 BTF of + // payouts voided. Then, once the pool stopped finding blocks, the + // same check refused to issue jobs against a tip that was perfectly + // valid and was the network's own best, and the pool -- roughly 90% + // of the hashrate -- could not restart the chain it was the majority + // of. Stale tip, so no jobs; no jobs, so the tip stays stale. + // + // The height peers announce in their version message answers the + // question the age check was only guessing at. Median, so one peer + // claiming an absurd height cannot stall us, and one block of slack + // to match WarnIfBehind, because somebody is always mid-relay. A + // peer that predates the announcement reports -1 and is ignored; if + // nobody has told us anything we mine our best chain, which is what + // the node would do without a pool attached. + int nPeerHeight = GetPeerMedianHeight(); + if (nPeerHeight >= 0 && nBestHeight < nPeerHeight - 1) { + LogPrint("pool", "RebuildJob: %d blocks behind the network " + "(height %d, peers report %d) -- not issuing jobs\n", + nPeerHeight - nBestHeight, nBestHeight, nPeerHeight); return false; } -- cgit v1.2.3