Every share and rank is computed over a fraction of the window the page advertises #13
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Found while confirming #10/#11 had taken effect. Unresolved — filing the evidence rather than a guess.
Symptom
observed_blocksis far belowwindow_blockson all three chains at once, minutes after a restart:This is not a display quirk.
observed_blocksis the sum of the returned rows'blocks, and it equals the tally'stotal, which is the denominator of every share:So the standings panel is labelled
LAST 3,600 BLOCKS · ~11 Hwhile the ranks and shares behind it come from ~208. The per-miner hashrates divide summed work byspan_secondsover that same short tail, so they describe a much shorter period than the header claims.Mainnet makes this concrete: it holds 19,928 blocks and the six-hour window is 3,600, so there is no shortage of history to tally — and it still reports 1,040.
What does not explain it
blackbeard_core::window::leaderboardfilters nothing; every miner in the tally becomes a row.leaderboard_max_ageis a cache freshness parameter inChainRuntime::leaderboard, not a row filter.housekeepingrecomputes everyleaderboard_refresh_seconds, which is 5.RollingWindow::new(Window::Week.blocks())is 100,800, well above both the restore size and 3,600.tail.tail(n)isiter().skip(len - n), so it yieldsmin(n, len).total = 208therefore meanswindow.len() = 208.What contradicts it
warm_startlogs what it pushed, and it is two orders of magnitude larger:Those go into
inner.windowviapush(block, false)under a single write lock, on the sameArc<ChainRuntime>thatspawnand the registry then share —registry.rsbuilds oneruntime, warm-starts it, spawns againstArc::clone, and inserts that same clone intoby_slug. Nothing anywhere assigns to.window, clears it, or rebuildsChainInner.So the log says the window holds 39,881 and the arithmetic in the served response says it holds 208. One of those is wrong, and the served one is what readers see.
Where to start
Instrument
window.len()at the end ofwarm_start, immediately before the firstrecompute_leaderboard, and insidetally. That distinguishes "the restore never landed" from "something drains it between warm start and the first tally" without guessing which.Worth checking
recent_blocks' ordering at the same time: warm start pushes rows in whatever order the query returns them, andtailis insertion-ordered, not height-ordered. That does not explain a short window, but it decides which blocks a partial window keeps, and a window restored newest-first would tally the oldest end of the restore.Not new
The screenshot from before #10/#11 landed shows the same thing on mainnet — a top row of 828 blocks at 83%, so a denominator of ~998 rather than 3,600. This predates today's fixes; it was simply invisible next to a frozen chain.
Invalid — I measured the wrong process. There is no bug. Closing.
The deployed API, asked on bob itself:
A full window on every chain. The shares divide by 3,600 exactly as designed.
What actually happened
Every number in the issue came from
curl http://127.0.0.1:25864/...on my workstation, through what I believed was an SSH tunnel to bob. It was not:A local
./target/debug/blackbeard-apifrom earlier in the session already held IPv4 loopback, sossh -Lfell back to binding IPv6 only — withExitOnForwardFailure=yessatisfied, because one of the two binds succeeded.curl 127.0.0.1prefers IPv4, so every request went to the local process. It had been up 99 minutes with a window filling from live blocks, which is precisely the "window far short of 3,600" signature I then attributed to production.The tell was in the issue and I read past it: the log line said
tallied: 3600at the same instant the response said 1,177. Two irreconcilable answers from one process is not a subtle bug, it is a sign you are talking to two processes. I kept looking for a mechanism inside the code instead.What stands
heldbesideblocksonwarm start: window restored— a real invariant, cheap, and it is what proved the restore lands.chainon the recompute line. Its absence made three chains' recomputes indistinguishable and cost real time.a_restored_window_tallies_the_whole_requested_spanin core, andthe_board_divides_by_the_whole_restored_windowdriving push →recompute_leaderboard. They pin behaviour that was previously only assumed, and they passed throughout — which was itself evidence the fault was not where I was looking.The temporary debug drop-in on bob has been removed and the stray local process stopped.
The part worth keeping
The claim in "Not new" — that the pre-fix mainnet screenshot showed 828 blocks at 83%, implying a ~998 denominator — was the same measurement error. That screenshot was taken through the same tunnel, against the same local process. It was never evidence of anything about production.