From 7aa4b13be3fdf192d8e04fe55472236e382858a4 Mon Sep 17 00:00:00 2001 From: igneum-labs <337424239+igneum-labs@users.noreply.github.com> Date: Wed, 7 Oct 2026 10:14:43 +0000 Subject: [PATCH] live: the Proven tile reads a 600 s window of its own every 10 s (?summary=1 counts), the observer marks claimed shards proving behind OBSERVER_CLAIMS Main's two follow-ons of 7 October 2026 after the per-block read. (1) /api/live?window=N&summary=1 returns the window's counts alone (blocks, chain, planned, proven, shards by state, the proving counters), and the /live Proven tile reads window 600 that way every 10 s with the line "of the last 10 minutes, every shard verified or paid", while the scene keeps its 60 s window; the stat was the scene window's count, zero by the proof lag. (2) The observer's shardStates reads an open claim as 'proving' from the claim until the proof lands: the records reply's own claims field whenever the node sends one, and the claims RPC behind OBSERVER_CLAIMS ('1' = igneum_getProofClaims, or a method name; unset = off); a node without the method turns the flag off for the run after one log line. Nothing lights until the node lane ships the RPC on the 0.3.20 line. Co-Authored-By: Claude Fable 5.1 --- site/api/live.mjs | 7 +++++++ site/index.html | 2 +- site/journey.json | 36 ++++++++++++++++++------------------ tools/observer/observer.mjs | 9 +++++++++ 4 files changed, 35 insertions(+), 19 deletions(-) diff --git a/site/api/live.mjs b/site/api/live.mjs index 3ba211d2..9450c5b4 100644 --- a/site/api/live.mjs +++ b/site/api/live.mjs @@ -41,6 +41,9 @@ export default async function handler(req, res) { // (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(1800, Number(q.get('window')) || 90)); + // ?summary=1: the counts of the window alone (blocks, chain blocks, proven, shards by state), for a tile that reads a long + // window every 10 s while the scene keeps its short one (7 October 2026); no block rows, no miners, no events + const summary = q.get('summary') === '1'; 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, @@ -173,6 +176,10 @@ export default async function handler(req, res) { // peer events never carry an address on the public API (review round 4, R4.6.2); older stored rows are redacted here too const events = (head[0].events || []).map(e => ({ ts: e.ts, kind: e.kind, text: String(e.text).replace(/\b\d{1,3}(\.\d{1,3}){3}(:\d+)?\b/g, 'a peer') })); + if (summary) { + const by = {}; for (const b of blocks) for (const x of b.shards) by[x.state] = (by[x.state] || 0) + 1; + return res.status(200).json({ ok: true, now: new Date(now).toISOString(), window: windowS, stale, blocks: blocks.length, chain: blocks.filter(b => b.chain).length, planned: blocks.filter(b => b.shards.length).length, proven: blocks.filter(b => b.proven).length, shards: by, proving }); + } return res.status(200).json({ ok: true, now: new Date(now).toISOString(), state, blocks, miners, events, finality, proving }); } catch (e) { res.setHeader('Cache-Control', 'no-store'); diff --git a/site/index.html b/site/index.html index b27cdb83..9efcc57b 100644 --- a/site/index.html +++ b/site/index.html @@ -279,7 +279,7 @@ - + diff --git a/site/journey.json b/site/journey.json index 53f3b74a..f0402beb 100644 --- a/site/journey.json +++ b/site/journey.json @@ -207,48 +207,48 @@ }, { "date": "2026-10-04", - "text": "One-click Windows workers: what the Apple M5 Max could measure", - "short": "One-click Windows workers" + "text": "Sim/economy: mining versus proving under stress, agent-based", + "short": "Economy simulation: mining versus proving under stress" }, { "date": "2026-10-04", - "text": "First finality lock on the live devnet: checkpoint 242 at 77.4% of all weight, two hours after genesis", - "short": "First live finality lock: 77.4% of weight, 17 voters" + "text": "Difficulty rule under attack: pool hopping, pulsed rental, timestamp stretching, short-lane oscillation, epoch games, polluted window, block flood", + "short": "Difficulty rule attacked seven ways" }, { "date": "2026-10-04", - "text": "The gfx1036 worker fault and what the Apple M5 Max could and could not reproduce", - "short": "The gfx1036 worker fault and what the Apple M5 Max could and could…" + "text": "Difficulty rule: timestamp attack fixed , simulator regression, 3-node forger test", + "short": "Timestamp attack on the difficulty rule fixed" }, { "date": "2026-10-04", - "text": "A node 60 s behind the clock is silently dead", - "short": "A node 60 s behind the clock is silently dead" + "text": "Devnet-v4 integration: nine branches merged, 3-node test network on the merged node, Windows cross-build", + "short": "Devnet v4: nine branches merged into one node" }, { "date": "2026-10-04", - "text": "First machine on the Igneum Miner app: the RTX 5090 Windows rig's RTX 5090 at 118 MH/s, via Setup.exe", - "short": "First machine on the one-click app: a 5090 at 118 MH/s" + "text": "Generator version 2 adopted: exact load count, fresh-source loads, program acceptance; every vector re-cut, three workers re-checked, 20,000-program census, devnet-v4 binaries rebuilt", + "short": "Generator v2 adopted: every hash does 128 distinct reads" }, { "date": "2026-10-04", - "text": "Difficulty rule v2: the live oscillation, its cause, the DAG replay, the fix behind a height switch", - "short": "Difficulty rule v2" + "text": "Proving v0 on the RTX 5090: first GPU proof of an Igneum block", + "short": "First GPU proof of an Igneum block: 1.4 s on an RTX 5090" }, { "date": "2026-10-04", - "text": "The observer stored nothing for 78 minutes, then 7,022 blocks in two minutes", - "short": "The observer stored nothing for 78 minutes, then 7,022 blocks in two…" + "text": "Devnet v4 cut-over: generator v2, 2/3 floor, three nodes and a seed on a fresh chain", + "short": "Devnet v4 live: generator v2, two-thirds floor, fresh chain" }, { "date": "2026-10-04", - "text": "The RTX 5090 Windows rig at the 14:20 boundary: a worker stuck on the previous epoch", - "short": "The RTX 5090 Windows rig at the 14" + "text": "First hourly program swap on the live devnet: compile-ahead, no pause, two cards", + "short": "First live hourly swap: no pause on Mac, NVIDIA or AMD" }, { "date": "2026-10-04", - "text": "Shard proving on the RTX 5090: a full shard compressed in 10.9 s, a two-shard block aggregated in 2.2 s, all verified", - "short": "Shard layer on the RTX 5090: a full shard proven in 10.9 s, a block aggregated in 2.2 s" + "text": "Proving: devnet v4 shards on the Apple M5 Max CPU, loaded machine", + "short": "Proving: devnet v4 shards on the Apple M5 Max CPU, loaded machine" } ] } diff --git a/tools/observer/observer.mjs b/tools/observer/observer.mjs index 730affe7..5883bb89 100644 --- a/tools/observer/observer.mjs +++ b/tools/observer/observer.mjs @@ -853,6 +853,9 @@ async function backfillCertificate(rpc, report) { // 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; +// OBSERVER_CLAIMS: '1' reads open claims through igneum_getProofClaims; a method name reads them through that method; unset +// or '0' reads none (the records reply's own claims field is used whenever the node sends one, flag or not) +let CLAIMS_RPC = process.env.OBSERVER_CLAIMS === '1' ? 'igneum_getProofClaims' : /^[a-z_]+$/i.test(process.env.OBSERVER_CLAIMS || '') && process.env.OBSERVER_CLAIMS !== '0' ? process.env.OBSERVER_CLAIMS : null; 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) @@ -942,6 +945,9 @@ async function flushPlans() { // checked by consensus, which is what happens before activation) > proving (a record in the pool) > planned. function shardStates(block, r) { const n = block.shards.length, states = block.shards.map(() => 'planned'), out = states.map(() => ({ prover: null, verified: null, carrier: null, carrierNumber: null, payout: null })); + // an open claim (a prover has taken the shard, no proof yet) reads 'proving' from the claim until the proof lands: the + // records reply's own claims field when the node carries one, or the claims RPC behind OBSERVER_CLAIMS (7 October 2026) + for (const c of r.claims || []) { const i = c.shard; if (i >= n || states[i] !== 'planned') continue; states[i] = 'proving'; out[i] = { ...out[i], prover: short(String(c.keyHash || c.prover || '').replace(/^0x/, '')) }; } for (const e of r.pool || []) { const i = e.shard; if (i >= n) continue; const rank = e.verified === true ? 2 : 1; if (rank > ['planned', 'proving', 'verified', 'paid'].indexOf(states[i])) { states[i] = rank === 2 ? 'verified' : 'proving'; out[i] = { ...out[i], prover: short(String(e.keyHash || '').replace(/^0x/, '')), verified: e.verified, carrier: e.includedIn ? String(e.includedIn).replace(/^0x/, '') : null }; } } for (const c of r.carried || []) { const i = c.shard; if (i >= n || !c.valid) continue; if (states[i] !== 'paid') { states[i] = 'verified'; out[i] = { ...out[i], prover: short(String(c.keyHash || '').replace(/^0x/, '')), carrier: String(c.carrier || '').replace(/^0x/, ''), carrierNumber: hx(c.carrierNumber) }; } } (r.paid || []).forEach((p, i) => { if (!p || i >= n) return; states[i] = 'paid'; out[i] = { ...out[i], prover: short(String(p.keyHash || '').replace(/^0x/, '')), carrierNumber: hx(p.carrierNumber), payout: weiStr(p.wei) }; }); @@ -961,6 +967,9 @@ async function pollRecords() { await Promise.all(batch.slice(i, i + 4).map(async ([h, b]) => { b.polledAt = now; let r; try { r = await evm('igneum_getProofRecords', [{ blockHash: '0x' + h }]); } catch (e) { if (!/not found/.test(e.message)) log('proof records failed', h.slice(0, 8), e.message); return; } + // the claims read, behind the flag until the node ships the RPC (main's ask of 7 October 2026 on the 0.3.20 line); a + // method the node lacks turns the flag off for the run instead of logging on every block + if (CLAIMS_RPC && !b.done && !(r && r.claims)) { try { const c = await evm(CLAIMS_RPC, [{ blockHash: '0x' + h }]); r = { ...(r || {}), claims: Array.isArray(c) ? c : (c && c.claims) || [] }; } catch (e) { if (/method not found|unknown method|not supported/i.test(e.message)) { log('claims RPC', CLAIMS_RPC, 'not on this node; claims off for the run'); CLAIMS_RPC = null; } else if (!/not found/.test(e.message)) log('proof claims failed', h.slice(0, 8), e.message); } } const { states, out } = shardStates(b, r); for (let s = 0; s < states.length; s++) { const key = `${states[s]}|${out[s].prover}|${out[s].carrierNumber}|${out[s].carrier}`;