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:
parent
af873d4bcb
commit
aba773a44e
4 changed files with 17 additions and 10 deletions
|
|
@ -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,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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); }
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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`;
|
||||
|
|
|
|||
Loading…
Reference in a new issue