Merge site-ui-4: the live API's per-block proof read (joined by hash, windows to 1800 s) and the observer's 30-minute record window

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
igneum-labs 2026-10-07 10:06:19 +00:00
commit 66eb4f3ab6
2 changed files with 17 additions and 10 deletions

View file

@ -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();

View file

@ -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)