Explorer: the 10-minute proving counter counts payments landing in the window for blocks of any age (the observer's per-block count read 0 while the lag runs to 500 DAA); the badge reads "certificate re-checked in this browser; the node's verdict on the STARK"

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
igneum-labs 2026-10-08 11:18:09 +00:00
parent af873d4bcb
commit aba773a44e
4 changed files with 17 additions and 10 deletions

View file

@ -125,11 +125,14 @@ export function createHandler({ env = process.env, sql, evm = evmCall } = {}) {
// ---- stats ----
if (q.has('stats')) {
const [chain10, fin] = await Promise.all([
const [chain10, fin, paid10] = await Promise.all([
sql(`SELECT count(*)::int AS blocks_10m, count(*) FILTER (WHERE is_chain_block)::int AS chain_10m, count(DISTINCT vote_key_hash)::int AS miners_10m, max(daa_score) AS daa, max(number) AS height FROM ${T}live_blocks WHERE received_at > now() - interval '10 minutes'`),
Promise.resolve(s && s.finality ? s.finality : null),
// payments landing in the window for blocks of any age (the observer's shards_paid_10m counts only blocks of the last 10 minutes, which reads 0 while the lag runs to 500 DAA)
sql(`SELECT count(*)::int AS n, count(DISTINCT prover)::int AS provers, sum(payout_wei::numeric)::text AS payout_wei FROM ${T}live_proofs WHERE state = 'paid' AND updated_at > now() - interval '10 minutes'`).catch(() => [{ n: null, provers: null, payout_wei: null }]),
]);
const c = chain10[0] || {};
const p10 = paid10[0] || {};
const w = fin && fin.weights ? fin.weights : null;
const activations = params ? Object.entries(params).filter(([k, v]) => /_activation_daa$/.test(k) && Number.isFinite(Number(v))).map(([k, v]) => ({ name: k.replace(/_activation_daa$/, ''), daa: Number(v) })) : [];
return res.status(200).json({
@ -140,6 +143,7 @@ export function createHandler({ env = process.env, sql, evm = evmCall } = {}) {
miners_10m: num(c.miners_10m),
finality: fin ? { active: !!fin.finality_active, voters: w ? num(w.voters) : null, total_weight: w ? num(w.total_weight) : null, active_weight: w ? num(w.active_weight) : null, keys: w && Array.isArray(w.keys) ? w.keys.length : null, latest_locked_index: num(fin.latest_locked_index), latest_locked_blue_score: num(fin.latest_locked_blue_score), params: fin.params || null } : null,
proving: s && s.proving ? s.proving : null,
paid_10m_any_age: { shards: num(p10.n), provers: num(p10.provers), payout_wei: p10.payout_wei || '0', note: 'shards paid in the last 10 minutes for blocks of any age' },
class: classAt(num(c.daa), params), activations, digest: ix && ix.node_info ? ix.node_info.digest : null, genesis: ix && ix.node_info ? ix.node_info.genesis : null,
budgets: null,
});
@ -147,7 +151,7 @@ export function createHandler({ env = process.env, sql, evm = evmCall } = {}) {
// ---- proving view ----
if (q.has('proving')) {
const [blocks, provers24, provers1, lat, totals] = await Promise.all([
const [blocks, provers24, provers1, lat, totals, paid10] = await Promise.all([
sql(`SELECT b.hash, b.number, b.daa_score, b.blue_score, b.timestamp_ms, b.vote_key_hash, b.evm_miner, b.tx_count, b.proof_records,
count(p.shard)::int AS total, count(*) FILTER (WHERE p.state = 'planned')::int AS planned, count(*) FILTER (WHERE p.state = 'proving')::int AS proving,
count(*) FILTER (WHERE p.state = 'verified')::int AS verified, count(*) FILTER (WHERE p.state = 'paid')::int AS paid,
@ -161,6 +165,7 @@ export function createHandler({ env = process.env, sql, evm = evmCall } = {}) {
sql(`SELECT count(*)::int AS paid_1h, percentile_cont(0.5) WITHIN GROUP (ORDER BY lag_daa) AS p50, percentile_cont(0.9) WITHIN GROUP (ORDER BY lag_daa) AS p90, max(lag_daa) AS max_lag, min(lag_daa) AS min_lag
FROM ${T}live_proofs WHERE state = 'paid' AND updated_at > now() - interval '1 hour'`),
sql(`SELECT state, count(*)::int AS n, sum(payout_wei::numeric)::text AS payout_wei FROM ${T}live_proofs WHERE received_at > now() - interval '24 hours' GROUP BY state`),
sql(`SELECT count(*)::int AS n, count(DISTINCT prover)::int AS provers, sum(payout_wei::numeric)::text AS payout_wei FROM ${T}live_proofs WHERE state = 'paid' AND updated_at > now() - interval '10 minutes'`).catch(() => []),
]);
const hour = new Map(provers1.map(p => [p.prover, p]));
return res.status(200).json({
@ -169,6 +174,7 @@ export function createHandler({ env = process.env, sql, evm = evmCall } = {}) {
provers: provers24.map(p => ({ id: p.prover, shards_24h: num(p.shards), payout_wei_24h: p.payout_wei || '0', median_lag_24h: p.median_lag === null ? null : Number(p.median_lag), shards_1h: hour.has(p.prover) ? num(hour.get(p.prover).shards) : 0, payout_wei_1h: hour.has(p.prover) ? hour.get(p.prover).payout_wei || '0' : '0', last_paid: p.last_paid })),
latency_1h: lat[0] ? { paid: num(lat[0].paid_1h), p50_daa: lat[0].p50 === null ? null : Number(lat[0].p50), p90_daa: lat[0].p90 === null ? null : Number(lat[0].p90), min_daa: num(lat[0].min_lag), max_daa: num(lat[0].max_lag), note: 'lag in DAA score from the proven block to the block whose coinbase paid the shard; Devnet 3 targets one DAA step per second' } : null,
totals_24h: Object.fromEntries(totals.map(t => [t.state, { shards: num(t.n), payout_wei: t.payout_wei || '0' }])),
paid_10m_any_age: paid10[0] ? { shards: num(paid10[0].n), provers: num(paid10[0].provers), payout_wei: paid10[0].payout_wei || '0' } : null,
});
}

View file

@ -352,14 +352,14 @@ const id = decodeURIComponent(location.pathname.split('/').pop() || '');
const query = /^\d{1,12}$/.test(id) ? 'height=' + id : 'block=' + encodeURIComponent(id.replace(/^0x/i, ''));
async function recheck(index, blockHash, isCheckpoint) {
// the verified badge: this browser fetches the certificate that covers the block and checks it with the site's light client
const el = $('recheck'); el.innerHTML = `<p class="note" id="rc-line">Re-checking certificate ${int(index)} in this browser.</p>`;
const el = $('recheck'); el.innerHTML = `<p class="note" id="rc-line">Re-checking certificate ${int(index)} in this browser. The STARK proofs of the shards are not re-run here; the node's verdict on them is shown in the shard table as the node's.</p>`;
const badge = document.createElement('span'); badge.className = 'badge wait'; badge.textContent = 'checking the certificate'; $('chips').appendChild(badge);
try {
const { fetchCheckpoint, verify } = await import('/verify/verify.js');
const data = await fetchCheckpoint('/api/checkpoint?index=' + index);
const r = verify(data);
if (r.verified) {
badge.className = 'badge ok'; badge.textContent = isCheckpoint ? 'verified in this browser' : `verified under lock ${int(index)} in this browser`;
badge.className = 'badge ok'; badge.textContent = "certificate re-checked in this browser; the node's verdict on the STARK";
$('rc-line').innerHTML = `Certificate ${int(index)} re-checked in this browser in ${r.ms} ms: ${int(r.headers_checked)} header hashes recomputed, ${int(r.signers)} of ${int(r.voters)} voters' BLS keys aggregated, the signature verifies over ${(r.weight_fraction_total * 100).toFixed(1)}% of all weight. ${isCheckpoint ? 'This block is the certified checkpoint.' : 'This block sits under that lock on the selected chain.'} The light client is <a href="/verify/core.js">/verify/core.js</a>; the data it checked is <a href="/api/checkpoint?index=${index}">/api/checkpoint?index=${index}</a>.`;
} else { badge.className = 'badge bad'; badge.textContent = 'certificate did not verify'; $('rc-line').textContent = `Certificate ${int(index)} did not verify in this browser: ${r.error || 'unknown reason'}.`; }
} catch (e) { badge.className = 'badge wait'; badge.textContent = 're-check unavailable'; $('rc-line').textContent = 'The re-check could not run: ' + (e.message || e); }

View file

@ -268,7 +268,7 @@ main{padding-bottom:100px}.card{background:var(--row);border:1px solid var(--lin
<div class="cell"><div class="k">Chain rate, 10 min</div><div class="v" id="st-rate">0<small>blocks/s</small></div><div class="s" id="st-rate-s">DAG blocks per second; chain blocks per second</div></div>
<div class="cell"><div class="k">Finality</div><div class="v" id="st-fin">n/a</div><div class="s" id="st-fin-s">voters and weight</div></div>
<div class="cell"><div class="k">Program class</div><div class="v" id="st-class">v4</div><div class="s" id="st-class-s">the hash program family at the tip</div></div>
<div class="cell"><div class="k">Proving</div><div class="v" id="st-prov">0<small>paid</small></div><div class="s" id="st-prov-s">shards paid in 10 min</div></div>
<div class="cell"><div class="k">Proving</div><div class="v" id="st-prov">0<small>paid</small></div><div class="s" id="st-prov-s">shards paid in the last 10 min for blocks of any age</div></div>
</div>
<div class="card" aria-label="The DAG">
<div class="viz-head"><h2>The DAG</h2><div class="eyebrow" id="dag-eyebrow">the newest 150 blocks, one lane per miner</div></div>
@ -452,9 +452,9 @@ async function loadStats() {
$('st-class').textContent = s.class ? s.class.class : 'v4';
const next = s.class && s.class.floors ? s.class.floors.find(x => x.daa > (s.daa || 0)) : null;
$('st-class-s').textContent = next ? `${next.class} from DAA ${int(next.daa)}` : (s.class && s.class.floors && s.class.floors.length ? `since DAA ${int(s.class.floors[s.class.floors.length - 1].daa)}` : 'the hash program family at the tip');
const p = s.proving || {};
$('st-prov').innerHTML = esc(int(p.shards_paid_10m ?? 0)) + '<small>paid</small>';
$('st-prov-s').textContent = p.supported === false ? 'the node reports no proving layer' : `shards paid in 10 min, ${int(p.provers_10m ?? 0)} provers, ${int(p.open_blocks ?? 0)} open blocks`;
const p = s.proving || {}, pa = s.paid_10m_any_age || {};
$('st-prov').innerHTML = esc(int(pa.shards ?? 0)) + '<small>paid</small>';
$('st-prov-s').textContent = p.supported === false ? 'the node reports no proving layer' : `shards paid in the last 10 min for blocks of any age, ${int(pa.provers ?? 0)} provers, ${int(p.open_blocks ?? 0)} open blocks`;
}
async function loadProving() {
const j = await fetch('/api/explorer?proving=1').then(r => r.json()); if (!j.ok) throw new Error(j.error);

View file

@ -258,7 +258,7 @@ main{padding-bottom:100px}.card{background:var(--row);border:1px solid var(--lin
</div>
<div class="strip" aria-live="polite">
<div class="cell status"><div class="k">Proving</div><div class="v"><span class="dot"></span><span id="p-status">OFFLINE</span></div><div class="s" id="p-status-s">the node's proving layer</div></div>
<div class="cell"><div class="k">Paid, 10 min</div><div class="v" id="p-paid10">0</div><div class="s" id="p-paid10-s">shards</div></div>
<div class="cell"><div class="k">Paid, 10 min</div><div class="v" id="p-paid10">0</div><div class="s" id="p-paid10-s">shards, for blocks of any age</div></div>
<div class="cell"><div class="k">Paid, 24 h</div><div class="v" id="p-paid24">0</div><div class="s" id="p-paid24-s">shards, IGN to provers</div></div>
<div class="cell"><div class="k">Open blocks</div><div class="v" id="p-open">0</div><div class="s" id="p-open-s">chain blocks with unpaid shards</div></div>
<div class="cell"><div class="k">Provers, 24 h</div><div class="v" id="p-provers">0</div><div class="s" id="p-provers-s">vote keys paid for a shard</div></div>
@ -374,7 +374,8 @@ async function load() {
document.body.classList.toggle('on', on); document.body.classList.toggle('off', !on);
$('p-status').textContent = j.stale ? 'OFFLINE' : (p.supported === false ? 'NO LAYER' : (p.active ? 'ACTIVE' : 'INACTIVE'));
$('p-status-s').textContent = j.stale ? 'observer stale' : `read ${stamp(j.now)}, tip DAA ${int(p.tip_daa)}${p.paused_since_ms ? ', paused' : ''}`;
$('p-paid10').textContent = int(p.shards_paid_10m ?? 0); $('p-paid10-s').textContent = `shards; ${int(p.blocks_fully_proven_10m ?? 0)} of ${int(p.blocks_10m ?? 0)} blocks of the window fully proven`;
const pa = j.paid_10m_any_age || {};
$('p-paid10').textContent = int(pa.shards ?? 0); $('p-paid10-s').textContent = `shards paid in the last 10 min for blocks of any age, ${ignFromWei(pa.payout_wei || '0', 2)} IGN; ${int(p.blocks_fully_proven_10m ?? 0)} of ${int(p.blocks_10m ?? 0)} blocks of the window fully proven`;
$('p-paid24').textContent = int(t.paid ? t.paid.shards : 0); $('p-paid24-s').textContent = `shards, ${ignFromWei(t.paid ? t.paid.payout_wei : '0', 2)} IGN to provers`;
$('p-open').textContent = int(p.open_blocks ?? 0); $('p-open-s').textContent = `chain blocks with unpaid shards, ${int(p.pending_plans ?? 0)} plans not read yet`;
$('p-provers').textContent = int(j.provers.length); $('p-provers-s').textContent = `vote keys paid for a shard; ${int(p.provers_10m ?? 0)} in the last 10 min`;