chore: say what the window actually holds, for #13
All checks were successful
deploy / build (push) Successful in 8m15s
deploy / deploy-web (push) Successful in 6s
deploy / deploy-api (push) Successful in 17s

Static reading cannot settle #13. Warm start logs restoring 19,910 blocks for
mainnet and the board it serves divides every share by 1,065, and every
explanation that would reconcile the two is ruled out in the code: the deque is
only ever pushed and popped at a capacity of 100,800, `tail(n)` yields
`min(n, len)`, `tally` counts every entry it walks, nothing filters rows, the
board recomputes every five seconds, and there is exactly one non-test
`ChainRuntime` and one `RollingWindow` per chain, shared by `Arc`.

So: log the length. `warm start: window restored` now carries `held` beside
`blocks` — the two must agree, and when they did not, every share on the site was
computed over the difference with nothing saying so. `recompute_leaderboard`
logs what it asked for, what was held and what it tallied at debug.

The new test pins the half of the contradiction that lives in
`blackbeard-core`: a window pushed 19,910 blocks holds 19,910, tallies 3,600 for
a 3,600 window, and tallies 19,910 when asked for more than it holds. That is
the arithmetic the served response contradicts, so an investigation upstream of
it does not have to re-establish that a deque holds what was pushed.

Refs #13

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jp6a8EDar9ueEhAxzep4V5
This commit is contained in:
2026-09-10 12:34:43 +03:00
parent 4774b8cfaa
commit dedee707f5
3 changed files with 48 additions and 1 deletions

View File

@@ -189,7 +189,13 @@ pub async fn warm_start(chain: &Arc<ChainRuntime>, store: &Store, ticker_blocks:
// interval.
inner.window.push(block, false);
}
tracing::info!(chain = %id, blocks = count, "warm start: window restored");
// `held` as well as `blocks`: the two must agree, and when they did
// not, every share on the site was computed over the difference
// without anything saying so. See #13.
tracing::info!(
chain = %id, blocks = count, held = inner.window.len(),
"warm start: window restored"
);
}
Err(e) => tracing::warn!(chain = %id, error = %e, "warm start: no window restored"),
}

View File

@@ -364,6 +364,13 @@ impl ChainRuntime {
pub fn recompute_leaderboard(&self, window: Window) -> (Vec<LeaderboardRow>, bool) {
let mut inner = self.write();
let (tallies, total) = inner.window.tally(window.blocks() as usize);
// The denominator of every share on the board, against what the window
// actually holds. See #13: these disagreed with the warm-start count by
// two orders of magnitude and nothing on the page said so.
tracing::debug!(
window = ?window, asked = window.blocks(), held = inner.window.len(), tallied = total,
"leaderboard recomputed"
);
// The window's own duration, by the chain's clock. Every per-miner
// hashrate is its summed work over this — not its share of the current
// network, which credits a miner that won while the network was small

View File

@@ -338,6 +338,40 @@ mod tests {
}
}
/// The window keeps everything it is given up to capacity, and the tally
/// reads the newest `n` of them.
///
/// Filed as #13: warm start reported restoring 19,910 blocks and the board
/// it served divided by 1,065. One of the two had to be wrong, and this
/// pins the half that lives in this crate — so an investigation upstream of
/// it does not have to re-establish that a deque holds what was pushed.
#[test]
fn a_restored_window_tallies_the_whole_requested_span() {
let mut w = RollingWindow::new(100_800);
for h in 1..=19_910u64 {
w.push(
Observed {
height: h,
miner: MinerId(format!("m{}", h % 7)),
observed_at: Utc::now(),
authored_at: None,
difficulty: None,
},
false,
);
}
assert_eq!(w.len(), 19_910);
let (_, total) = w.tally(3_600);
assert_eq!(total, 3_600, "a full window must tally the whole window");
let (_, total) = w.tally(100_800);
assert_eq!(
total, 19_910,
"asking for more than is held tallies what is held"
);
}
#[test]
fn the_window_drops_the_oldest_block_when_full() {
let mut w = RollingWindow::new(3);