diff options
| author | bitflash-maintainer <noreply@bitflash.invalid> | 2026-08-11 20:52:09 +0000 |
|---|---|---|
| committer | bitflash-maintainer <noreply@bitflash.invalid> | 2026-08-19 22:48:27 +0000 |
| commit | e236d013e973fd00cbdbed4bea1e5f373c0d1236 (patch) | |
| tree | b184641daad4310cdef51dff8bece2743af29f8a /src/rpc.cpp | |
| parent | db2692dcd8984fe0e5a2a6a796f7b3cbee216c32 (diff) | |
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)
Diffstat (limited to 'src/rpc.cpp')
| -rw-r--r-- | src/rpc.cpp | 32 |
1 files changed, 32 insertions, 0 deletions
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() | |||
| 180 | return false; | 180 | return false; |
| 181 | } | 181 | } |
| 182 | 182 | ||
| 183 | // The comparison above is blind in the one case that matters most. | ||
| 184 | // | ||
| 185 | // When the node goes deaf -- peers still in vNodes, nothing arriving -- | ||
| 186 | // no peer announces anything, GetPeerMedianHeight() returns -1, and the | ||
| 187 | // fallback ("nobody has told us anything, so mine our best chain") let | ||
| 188 | // the pool build a private fork for as long as the deafness lasted. | ||
| 189 | // That fallback is right for a node that has simply not been told yet | ||
| 190 | // and wrong for a node that has stopped being able to hear, and a | ||
| 191 | // height nobody sent cannot tell those apart. Time can. | ||
| 192 | // | ||
| 193 | // Deliberately not behind a debug category, and deliberately not a | ||
| 194 | // silent refusal: an operator whose pool has stopped issuing jobs needs | ||
| 195 | // the reason in the log. /poolmineblind is the escape hatch for a node | ||
| 196 | // that really is alone -- without one, a genuinely isolated pool that is | ||
| 197 | // the majority of the hashrate could never restart its own chain, which | ||
| 198 | // is the deadlock the tip-age check used to cause. | ||
| 199 | if (nPeerHeight < 0 && !fPoolMineBlind) { | ||
| 200 | int64 nAge = GetPeerHeightAgeSecs(); | ||
| 201 | if (nAge >= BTF_NO_PEER_HEIGHT_MAX_SECS) { | ||
| 202 | static int64 nLastSaid = 0; | ||
| 203 | if (GetTime() - nLastSaid > 60) { | ||
| 204 | nLastSaid = GetTime(); | ||
| 205 | printf("RebuildJob: no peer has reported a height for %d minutes " | ||
| 206 | "-- not issuing jobs (our height %d, %d peers connected). " | ||
| 207 | "This node is deaf; mining now would fork it. " | ||
| 208 | "Pass /poolmineblind to override.\n", | ||
| 209 | (int)(nAge / 60), nBestHeight, (int)vNodes.size()); | ||
| 210 | } | ||
| 211 | return false; | ||
| 212 | } | ||
| 213 | } | ||
| 214 | |||
| 183 | CBlockIndex* pindexPrev = pindexBest; | 215 | CBlockIndex* pindexPrev = pindexBest; |
| 184 | unsigned int nBits = GetNextWorkRequired(pindexPrev); | 216 | unsigned int nBits = GetNextWorkRequired(pindexPrev); |
| 185 | 217 | ||