Step 1: app/igneum-app/src/provedefault.rs decides once per install (NVIDIA 12 GB or more, WSL2 answering on Windows, Linux native, Apple silicon off until measured), never switching an explicit on back off; the Settings switch line and the tile line say why (5 unit tests). tools/proving-v1/pc2-prover-cost.ps1 is the PC 2 job (5 min mining alone, 5 min with the prover, GPU memory and host RAM peaks, the sp1-gpu-server's SM targets). Step 2: the host gains --mode chain (consecutive fixtures, each block aggregated with the previous block's proof by recursion), --mode aggregate (the live aggregator over shard proof files, a run of blocks in one process) and --mode verify-segment (the node's verifier against the pinned aggregator key); the app's prover loop gains aggregate_once (spec 7.8). Eight consecutive live fixtures (blocks 81046 to 81053, node 1's export at tip 81076) under proving/fixtures/chain/. tools/proving-v1/pc2-chain.ps1 is the PC 2 job (held). Steps 3 and 4: tools/proving-v1/coverage.mjs (the proven-block share and the on-chain latency from one node's RPC), tools/proving-v1/net.mjs (the fast-time 3-node harness on 29950+ with the known-finished and known-failed cases of the chain rule and the unproven rule), the four proving_v1 fields in infra/fast-time/override-60x.json. Spec 7.8, the 7.4 rows, the 5.3 sentence, docs/plans/proving-v1.md. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
89 lines
5.9 KiB
JavaScript
89 lines
5.9 KiB
JavaScript
#!/usr/bin/env node
|
|
// Proving v1 step 3: the live devnet's proven-block share and the proof latency distribution over a window, read
|
|
// from one node's execution-layer RPC (what the chain carried: every node agrees on it) and, beside it, the live
|
|
// page's 10-minute proving object. Read-only: no job, no switch.
|
|
//
|
|
// node tools/proving-v1/coverage.mjs [--rpc http://127.0.0.1:26790] [--minutes 30] [--live https://igneum.network/api/live]
|
|
// [--out <json>] [--watch]
|
|
//
|
|
// Without --watch: one pass over the last --minutes of chain blocks (by block timestamp) at the tip: per block the
|
|
// shard plan (shards), the paid rows (carrierNumber) and the carrier's timestamp; prints the table for the bench log.
|
|
// With --watch: samples the live page every 60 s for --minutes, then the pass.
|
|
//
|
|
// Latency here is on-chain: the carrying chain block's timestamp minus the proven block's timestamp (the record was
|
|
// verified by its producer before that, so this bounds "block time to verified record" from above).
|
|
|
|
import { writeFileSync } from 'node:fs';
|
|
const args = process.argv.slice(2);
|
|
const opt = (n, d) => { const i = args.indexOf(n); return i >= 0 && args[i + 1] !== undefined ? args[i + 1] : d; };
|
|
const RPC = opt('--rpc', 'http://127.0.0.1:26790');
|
|
const LIVE = opt('--live', 'https://igneum.network/api/live');
|
|
const MINUTES = +opt('--minutes', 30);
|
|
const OUT = opt('--out', null);
|
|
const WATCH = args.includes('--watch');
|
|
const log = (...a) => console.log(new Date().toISOString().slice(11, 19), ...a);
|
|
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
let id = 0;
|
|
async function rpc(method, params = []) {
|
|
const r = await fetch(RPC, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ jsonrpc: '2.0', id: ++id, method, params }), signal: AbortSignal.timeout(30000) });
|
|
const j = await r.json(); if (j.error) throw new Error(j.error.message); return j.result;
|
|
}
|
|
const hexn = (v) => (v == null ? null : Number(BigInt(v)));
|
|
const pct = (a, b) => (b ? (100 * a / b).toFixed(1) + '%' : 'n/a');
|
|
function quantiles(xs) {
|
|
if (!xs.length) return { n: 0 };
|
|
const s = [...xs].sort((a, b) => a - b);
|
|
const q = (p) => s[Math.min(s.length - 1, Math.floor(p * (s.length - 1)))];
|
|
return { n: s.length, min: s[0], p50: q(0.5), p90: q(0.9), p99: q(0.99), max: s[s.length - 1], mean: +(s.reduce((a, b) => a + b, 0) / s.length).toFixed(1) };
|
|
}
|
|
|
|
async function pass() {
|
|
const tip = hexn(await rpc('eth_blockNumber'));
|
|
const tipBlock = await rpc('eth_getBlockByNumber', ['0x' + tip.toString(16), false]);
|
|
const tipTs = hexn(tipBlock.timestamp);
|
|
const from = tipTs - MINUTES * 60;
|
|
// walk back by timestamp
|
|
const ts = new Map();
|
|
const tsOf = async (n) => { if (!ts.has(n)) { const b = await rpc('eth_getBlockByNumber', ['0x' + n.toString(16), false]); ts.set(n, hexn(b.timestamp)); } return ts.get(n); };
|
|
let first = tip;
|
|
while (first > 1 && (await tsOf(first - 1)) >= from) first--;
|
|
log(`window: chain blocks ${first}..${tip} (${tip - first + 1} blocks, ${MINUTES} min by block timestamp), tip DAA via status`);
|
|
const status = await rpc('igneum_getProvingStatus');
|
|
const rows = [];
|
|
for (let n = first; n <= tip; n++) {
|
|
const r = await rpc('igneum_getProofRecords', ['0x' + n.toString(16)]);
|
|
const plan = await rpc('igneum_getShardPlan', ['0x' + n.toString(16)]).catch(() => null);
|
|
const shards = plan ? plan.shards.length : 0;
|
|
const paid = (r.paid || []).filter(Boolean);
|
|
const bt = await tsOf(n);
|
|
const lat = [];
|
|
for (const p of paid) { const c = hexn(p.carrierNumber); if (c != null) lat.push((await tsOf(c)) - bt); }
|
|
rows.push({ n, shards, pgas: plan ? plan.shards.reduce((a, s) => a + hexn(s.pgas), 0) : 0, paid: paid.length, latencies: lat, carried: (r.carried || []).length });
|
|
}
|
|
const blocks = rows.length;
|
|
const any = rows.filter((x) => x.paid > 0).length;
|
|
const full = rows.filter((x) => x.shards > 0 && x.paid === x.shards).length;
|
|
const shardsTotal = rows.reduce((a, x) => a + x.shards, 0), shardsPaid = rows.reduce((a, x) => a + x.paid, 0);
|
|
const lat = quantiles(rows.flatMap((x) => x.latencies));
|
|
const content = rows.filter((x) => x.pgas > 0);
|
|
const contentPaid = content.filter((x) => x.paid === x.shards).length;
|
|
const summary = { rpc: RPC, window: { first, last: tip, blocks, minutes: MINUTES, from_ts: from, to_ts: tipTs }, blocks_with_any_paid_shard: any, blocks_fully_proven: full, share_any: pct(any, blocks), share_full: pct(full, blocks), shards_total: shardsTotal, shards_paid: shardsPaid, share_shards: pct(shardsPaid, shardsTotal), content_blocks: content.length, content_blocks_fully_proven: contentPaid, latency_s: lat, status: { paidShards: status.paidShards, pool: status.pool, verifier: status.verifier, tipDaa: hexn(status.tipDaa), v1: status.v1 || null } };
|
|
log(`RESULT coverage: ${blocks} blocks, ${any} with a paid shard (${summary.share_any}), ${full} fully proven (${summary.share_full}); shards ${shardsPaid}/${shardsTotal} (${summary.share_shards}); content blocks ${content.length}, fully proven ${contentPaid}; on-chain latency s: ${JSON.stringify(lat)}`);
|
|
return { summary, rows };
|
|
}
|
|
|
|
const samples = [];
|
|
if (WATCH) {
|
|
const end = Date.now() + MINUTES * 60 * 1000;
|
|
while (Date.now() < end) {
|
|
const lv = await fetch(LIVE, { signal: AbortSignal.timeout(20000) }).then((r) => r.json()).catch((e) => ({ error: e.message }));
|
|
const p = lv.proving || {};
|
|
const s = { t: new Date().toISOString(), block_count: lv.state?.block_count, blocks_per_second_60s: lv.state?.blocks_per_second_60s, shards_proven_10m: p.shards_proven_10m, blocks_fully_proven_10m: p.blocks_fully_proven_10m, median_proof_lag_s: p.median_proof_lag_s, provers_10m: p.provers_10m, error: lv.error };
|
|
samples.push(s);
|
|
log(`live: ${JSON.stringify(s)}`);
|
|
await sleep(60000);
|
|
}
|
|
}
|
|
const result = await pass();
|
|
result.live_samples = samples;
|
|
if (OUT) { writeFileSync(OUT, JSON.stringify(result, null, 2)); log(`written ${OUT}`); }
|