From 50193aa44e1abb7fb9d6986a24a9d6a4c6cfa78d Mon Sep 17 00:00:00 2001 From: igneum-josh <337424239+igneum-josh@users.noreply.github.com> Date: Wed, 7 Oct 2026 11:06:17 +0100 Subject: [PATCH] observer and live API: a block in view carries its shard states whatever its age; windows up to 1800 s (1,800 blocks); the record window 30 minutes Main's order of 7 October 2026 on the per-block proof read. The live API joined proof rows by when the plan landed and capped the window at 300 s while the fleet's proofs land a median 262 s after the block, so a 60 or 90 s scene window read zero proven blocks by construction. Now: /api/live?window=N accepts 30 to 1800 s and returns at most 1,800 blocks; the shard rows are joined by block hash alone, so the proof's own landing is what moves a block to verified or paid in any window. The observer keeps reading a planned block's records for 30 minutes (was 10) so the lag tail still lands. provers_10m, shards_proven_10m and median_proof_lag_s are unchanged. Read against the live chain through the local API at 11:05 UK: window 90 proven 0 before and after (every block younger than the lag), 300 proven 2, 600 proven 49 of 139 chain blocks, 1800 proven 143 of 329 chain blocks (1,334 s of blocks under the cap). Observation for the fleet lane: in 1,800 blocks no shard reads proving or verified; states go planned straight to paid. Co-Authored-By: Claude Fable 5.1 --- site/api/live.mjs | 21 ++++++++++++--------- tools/observer/observer.mjs | 6 +++++- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/site/api/live.mjs b/site/api/live.mjs index 7b1751f51..3ba211d2a 100644 --- a/site/api/live.mjs +++ b/site/api/live.mjs @@ -1,5 +1,5 @@ // Igneum live devnet feed. GET /api/live returns what tools/observer wrote to Neon: -// {now, state (incl. height = chain block number), blocks (last 90 s, or ?window=N up to 300), miners (last 10 min), events (last 30), finality (checkpoints, newest lock), +// {now, state (incl. height = chain block number), blocks (last 90 s, or ?window=N up to 1800, at most 1,800 blocks), miners (last 10 min), events (last 30), finality (checkpoints, newest lock), // proving (activation and 10-minute counts; every chain block carries shards: [{i, state, prover, lag, payout}])}. // Five queries, all on indexed columns. Cached one second at the edge. // LIVE_TABLE_PREFIX (default empty) reads a test observer's tables (fintest_live_*, provtest_live_*) instead of the devnet's. @@ -37,9 +37,10 @@ export default async function handler(req, res) { } try { const sql = neon(); - // ?window=N seconds of blocks (30 to 300, default 90): the page's diagnostic long view; the row cap keeps a busy DAG bounded + // ?window=N seconds of blocks (30 to 1800, default 90): the page's long views; the row cap of 1,800 blocks keeps a busy DAG bounded + // (7 October 2026: the cap was 300 s while proofs land two to five minutes after the block, so no window could show a proven block) const q = new URL(req.url || '/', 'http://x').searchParams; - const windowS = Math.max(30, Math.min(300, Number(q.get('window')) || 90)); + const windowS = Math.max(30, Math.min(1800, Number(q.get('window')) || 90)); const [head, blockRows, minerRows, checkpointRows, proofRows] = await Promise.all([ sql(`SELECT now() AS now, (SELECT row_to_json(s) FROM ${T}live_state s WHERE s.id = 1) AS state, @@ -49,7 +50,7 @@ export default async function handler(req, res) { FROM ${T}live_blocks WHERE received_at > now() - ($1 || ' seconds')::interval ORDER BY received_at DESC - LIMIT 2000`, [String(windowS)]), + LIMIT 1800`, [String(windowS)]), sql(`SELECT vote_key_hash, count(*)::int AS blocks, max(received_at) AS last_seen, max(engine) AS engine, max(miner_address) AS address FROM ${T}live_blocks WHERE received_at > now() - interval '10 minutes' AND vote_key_hash IS NOT NULL @@ -60,11 +61,13 @@ export default async function handler(req, res) { FROM ${T}live_checkpoints ORDER BY index DESC LIMIT 40`).catch(() => []), - // Proving v0: the planned shards of the chain blocks in the window and what became of them (an older observer has no table) - sql(`SELECT block_hash, shard, shards, state, prover, lag_daa, payout_wei, pgas - FROM ${T}live_proofs - WHERE received_at > now() - ($1 || ' seconds')::interval - ORDER BY block_hash, shard`, [String(windowS + 30)]).catch(() => []), + // Proving v0: the planned shards of the blocks in the window and what became of them (an older observer has no table). + // The rows are joined by block hash, never by when the plan or the proof landed: a block in view carries its shard + // states (planned, proving, verified, paid) whatever its age, and the proof's own landing is what moves them. + sql(`SELECT p.block_hash, p.shard, p.shards, p.state, p.prover, p.lag_daa, p.payout_wei, p.pgas + FROM ${T}live_proofs p + WHERE p.block_hash IN (SELECT hash FROM ${T}live_blocks WHERE received_at > now() - ($1 || ' seconds')::interval ORDER BY received_at DESC LIMIT 1800) + ORDER BY p.block_hash, p.shard`, [String(windowS)]).catch(() => []), ]); const now = new Date(head[0].now).getTime(); diff --git a/tools/observer/observer.mjs b/tools/observer/observer.mjs index 98527020a..730affe73 100644 --- a/tools/observer/observer.mjs +++ b/tools/observer/observer.mjs @@ -848,7 +848,11 @@ async function backfillCertificate(rpc, report) { // {blockHash}. The EVM node may trail the observer's node by a segment or two: a plan that is "not found" is retried // with a growing delay, up to 30 attempts. A node without the RPCs answers -32601 once and the feed is marked // unsupported (rechecked every 5 minutes, so an upgraded node is picked up without a restart). -const PLAN_BATCH = 24, RECORD_BATCH_ACTIVE = 80, RECORD_BATCH_INACTIVE = 10, PROOF_WINDOW_MS = 10 * 60_000; +// PROOF_WINDOW_MS: how long a planned block keeps being read for its records. 30 minutes (7 October 2026, was 10): the fleet's +// proofs land a median 258 s after the block and the tail runs past 10 minutes, and a block dropped from the open set never +// moves past 'proving' on the page. The open set at 0.45 blocks a second is about 800 blocks; the batch of 80 reads each one +// every 10 ticks. +const PLAN_BATCH = 24, RECORD_BATCH_ACTIVE = 80, RECORD_BATCH_INACTIVE = 10, PROOF_WINDOW_MS = 30 * 60_000; const pendingPlans = new Map(); // chain block hash -> { tries, nextAt } const openBlocks = new Map(); // chain block hash -> { number, daa, ts, shards: [state...], provers, polledAt, done } const daaOfNumber = new Map(); // chain block number -> daa score, from the plans fetched (carriers are later chain blocks)