diff --git a/site/api/explorer.mjs b/site/api/explorer.mjs index a570fa41d..6ee22e6b1 100644 --- a/site/api/explorer.mjs +++ b/site/api/explorer.mjs @@ -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, }); } diff --git a/site/block.html b/site/block.html index 47aa4c99d..b4544108b 100644 --- a/site/block.html +++ b/site/block.html @@ -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 = `

Re-checking certificate ${int(index)} in this browser.

`; + const el = $('recheck'); el.innerHTML = `

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.

`; 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 /verify/core.js; the data it checked is /api/checkpoint?index=${index}.`; } 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); } diff --git a/site/explorer.html b/site/explorer.html index a716b4feb..6c0a35476 100644 --- a/site/explorer.html +++ b/site/explorer.html @@ -268,7 +268,7 @@ main{padding-bottom:100px}.card{background:var(--row);border:1px solid var(--lin
Chain rate, 10 min
0blocks/s
DAG blocks per second; chain blocks per second
Finality
n/a
voters and weight
Program class
v4
the hash program family at the tip
-
Proving
0paid
shards paid in 10 min
+
Proving
0paid
shards paid in the last 10 min for blocks of any age

The DAG

the newest 150 blocks, one lane per miner
@@ -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)) + 'paid'; - $('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)) + 'paid'; + $('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); diff --git a/site/proving.html b/site/proving.html index 4dc258edd..f9f6b5e1f 100644 --- a/site/proving.html +++ b/site/proving.html @@ -258,7 +258,7 @@ main{padding-bottom:100px}.card{background:var(--row);border:1px solid var(--lin
Proving
OFFLINE
the node's proving layer
-
Paid, 10 min
0
shards
+
Paid, 10 min
0
shards, for blocks of any age
Paid, 24 h
0
shards, IGN to provers
Open blocks
0
chain blocks with unpaid shards
Provers, 24 h
0
vote keys paid for a shard
@@ -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`;