diff --git a/crates/blackbeard-api/src/ingest.rs b/crates/blackbeard-api/src/ingest.rs index 7a5bc45..0c7680e 100644 --- a/crates/blackbeard-api/src/ingest.rs +++ b/crates/blackbeard-api/src/ingest.rs @@ -189,7 +189,13 @@ pub async fn warm_start(chain: &Arc, 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"), } diff --git a/crates/blackbeard-api/src/state.rs b/crates/blackbeard-api/src/state.rs index 13be1b4..0b42525 100644 --- a/crates/blackbeard-api/src/state.rs +++ b/crates/blackbeard-api/src/state.rs @@ -364,6 +364,13 @@ impl ChainRuntime { pub fn recompute_leaderboard(&self, window: Window) -> (Vec, 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 diff --git a/crates/blackbeard-core/src/window.rs b/crates/blackbeard-core/src/window.rs index ce2e67e..c32e453 100644 --- a/crates/blackbeard-core/src/window.rs +++ b/crates/blackbeard-core/src/window.rs @@ -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);