Merge build-server 71f801e7 into master: /api/checkpoint serves the live chain only (the public site answered the finality test fixture as igneum-devnet-7); the deploy's checkpoint check keys on the live network; LIVE_NETWORK defaults to igneum-devnet-4; observer wei columns numeric
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
commit
ed55a74fc2
3 changed files with 40 additions and 19 deletions
|
|
@ -4,9 +4,9 @@
|
|||
// selected-chain headers from the previous locked checkpoint to this one. Read from what tools/observer wrote to
|
||||
// Neon (table live_certificates, or fintest_live_certificates for the test network).
|
||||
//
|
||||
// ?source=live Devnet 3 (default: the dn3_ tables, or LIVE_TABLE_PREFIX; falls back to the test network while the chain has no lock yet)
|
||||
// ?source=dn3 the same as live (the Devnet 3 name, for callers that want to say so)
|
||||
// ?source=test the finality test network's tables (fintest_live_certificates)
|
||||
// ?source=live the live chain (default: the dn4_ tables, or LIVE_TABLE_PREFIX); certificate:null with a reason before its first lock, never the fixture
|
||||
// ?source=dn4 the same as live (the Igneum 2.0 devnet's name, for callers that want to say so)
|
||||
// ?source=test the finality test network's fixture (fintest_live_certificates), only with the ops key in x-igneum-ops-key; 404 otherwise
|
||||
// ?index=N one certified checkpoint by index instead of the newest (the explorer's per-block re-check, 8 October 2026)
|
||||
//
|
||||
// Zero dependencies: Neon's HTTP SQL endpoint over Node's built-in fetch, like live.mjs.
|
||||
|
|
@ -31,7 +31,13 @@ function neon() {
|
|||
|
||||
const num = v => (v === null || v === undefined ? null : Number(v));
|
||||
|
||||
const tablePrefix = () => (process.env.LIVE_TABLE_PREFIX === undefined ? 'dn3_' : process.env.LIVE_TABLE_PREFIX).replace(/[^a-z0-9_]/gi, '');
|
||||
const tablePrefix = () => (process.env.LIVE_TABLE_PREFIX === undefined ? 'dn4_' : process.env.LIVE_TABLE_PREFIX).replace(/[^a-z0-9_]/gi, '');
|
||||
// the live chain's name from its table prefix (dn4_ is igneum-devnet-4); LIVE_NETWORK overrides
|
||||
const liveNetwork = () => process.env.LIVE_NETWORK || (/^dn(\d+)_$/.test(tablePrefix()) ? `igneum-devnet-${tablePrefix().slice(2, -1)}` : 'igneum-devnet-4');
|
||||
// the finality test network's fixture (fintest_live_certificates) is served only to a request carrying the ops key (main, 8 Oct 2026 18:4x:
|
||||
// a fixture chain answered on the public 2.0 site as source test, network igneum-devnet-7); with no IGNEUM_OPS_KEY set it is never served
|
||||
const opsOk = req => { const k = process.env.IGNEUM_OPS_KEY || ''; const h = String((req.headers && req.headers['x-igneum-ops-key']) || ''); return k.length > 0 && h === k; };
|
||||
const noCertificate = source => ({ source, network: liveNetwork(), certificate: null, reason: 'no finality certificate yet; the first lock is expected about 23:00 BST' });
|
||||
async function latest(sql, table, index = null) {
|
||||
// A table the observer has not created yet (the live chain before the cut-over) reads as "no certificate"
|
||||
const rows = await (index === null ? sql(`SELECT * FROM ${table} ORDER BY index DESC LIMIT 1`) : sql(`SELECT * FROM ${table} WHERE index = $1 LIMIT 1`, [index])).catch(() => []);
|
||||
|
|
@ -95,11 +101,12 @@ export async function earliestCheckpointAbove(sql, number, chainNumberOf, table
|
|||
/** The latest certified checkpoint of `want` ('live', 'test' or 'dn3') through `sql`, shaped for the verifier, or null
|
||||
* (the reference apps' read service calls this off Vercel: 'dn3' names the dn3_ tables whatever LIVE_TABLE_PREFIX says). */
|
||||
export async function readCheckpoint(sql, want = 'live') {
|
||||
let row = null, source = null;
|
||||
if (want === 'dn3' || want === 'dn4') { row = await latest(sql, `${want}_live_certificates`); if (row) source = want; }
|
||||
if (!row && want !== 'test' && want !== 'dn3' && want !== 'dn4') { row = await latest(sql, `${tablePrefix()}live_certificates`); if (row) source = 'live'; }
|
||||
if (!row && want !== 'dn3' && want !== 'dn4') { row = await latest(sql, 'fintest_live_certificates'); if (row) source = 'test'; }
|
||||
return row ? shape(row, source) : null;
|
||||
// 'live', 'dn4' (and the retired 'dn3') read that chain's tables and never fall back to the test fixture; 'test' is the fixture, for the ops
|
||||
// caller only (the handler gates it; this function is called off Vercel by the reference apps' read service, which names its chain)
|
||||
if (want === 'test') { const row = await latest(sql, 'fintest_live_certificates'); return row ? shape(row, 'test') : null; }
|
||||
const table = (want === 'dn3' || want === 'dn4') ? `${want}_live_certificates` : `${tablePrefix()}live_certificates`;
|
||||
const row = await latest(sql, table);
|
||||
return row ? shape(row, want === 'dn3' || want === 'dn4' ? want : 'live') : null;
|
||||
}
|
||||
export { neon };
|
||||
|
||||
|
|
@ -114,12 +121,20 @@ export default async function handler(req, res) {
|
|||
const sql = neon();
|
||||
const want = String((req.query && req.query.source) || 'live');
|
||||
const idx = req.query && req.query.index !== undefined && /^\d{1,12}$/.test(String(req.query.index)) ? Number(req.query.index) : null;
|
||||
let row = null, source = null;
|
||||
if (want !== 'test') { row = await latest(sql, `${tablePrefix()}live_certificates`, idx); if (row) source = 'live'; }
|
||||
if (!row && idx === null) { row = await latest(sql, 'fintest_live_certificates'); if (row) source = 'test'; }
|
||||
if (!row) {
|
||||
if (want === 'test') {
|
||||
res.setHeader('Cache-Control', 'no-store');
|
||||
return res.status(404).json({ ok: false, error: 'no certified checkpoint stored yet' });
|
||||
if (!opsOk(req)) return res.status(404).json({ ok: false, error: 'no such source' });
|
||||
const row = await latest(sql, 'fintest_live_certificates', idx);
|
||||
if (!row) return res.status(404).json({ ok: false, error: 'no certified checkpoint stored yet' });
|
||||
return res.status(200).json({ ok: true, now: new Date().toISOString(), ...shape(row, 'test') });
|
||||
}
|
||||
// the public answer: the live chain's tables (source live; dn4 names them too), never the test fixture
|
||||
const source = want === 'dn4' ? 'dn4' : 'live';
|
||||
const row = await latest(sql, `${tablePrefix()}live_certificates`, idx);
|
||||
if (!row) {
|
||||
res.setHeader('Cache-Control', 'public, max-age=5');
|
||||
if (idx !== null) return res.status(404).json({ ok: false, network: liveNetwork(), error: `no certified checkpoint at index ${idx} yet` });
|
||||
return res.status(200).json({ ok: true, now: new Date().toISOString(), ...noCertificate(source) });
|
||||
}
|
||||
// the hand merge of 12:22 BST (a0b62cf7) returned `...cp` with no cp bound: every caller read "cp is not defined" (8 Oct 2026, build-server lane)
|
||||
return res.status(200).json({ ok: true, now: new Date().toISOString(), ...shape(row, source) });
|
||||
|
|
|
|||
|
|
@ -109,6 +109,9 @@ async function setupSchema() {
|
|||
`ALTER TABLE ${TB} ADD COLUMN IF NOT EXISTS proof_records int`,
|
||||
`ALTER TABLE ${TB} ADD COLUMN IF NOT EXISTS subsidy_sompi bigint`,
|
||||
`ALTER TABLE ${TB} ADD COLUMN IF NOT EXISTS paid_sompi bigint`,
|
||||
// wei-scale amounts on the Igneum 2.0 chain (8 Oct 2026): widen both in place, a no-op once numeric
|
||||
`ALTER TABLE ${TB} ALTER COLUMN subsidy_sompi TYPE numeric(40,0)`,
|
||||
`ALTER TABLE ${TB} ALTER COLUMN paid_sompi TYPE numeric(40,0)`,
|
||||
`ALTER TABLE ${TB} ADD COLUMN IF NOT EXISTS selected_parent text`,
|
||||
`ALTER TABLE ${TB} ADD COLUMN IF NOT EXISTS number bigint`,
|
||||
`ALTER TABLE ${TB} ADD COLUMN IF NOT EXISTS detail jsonb`,
|
||||
|
|
@ -503,7 +506,8 @@ async function flushBlocks() {
|
|||
const rows = pendingBlocks.splice(0, 200);
|
||||
const cols = ['hash', 'blue_score', 'daa_score', 'timestamp_ms', 'parents', 'parent_hashes', 'is_chain_block', 'vote_key_hash', 'miner_address', 'engine',
|
||||
'tx_count', 'evm_miner', 'proof_records', 'subsidy_sompi', 'paid_sompi', 'selected_parent', 'detail'];
|
||||
const cast = { parent_hashes: '::text[]', detail: '::jsonb', subsidy_sompi: '::bigint', paid_sompi: '::bigint' };
|
||||
// numeric, not bigint: the Igneum 2.0 chain reports the subsidy and the paid amount in wei (9.52 IGN = 9522644482386090276, past int64; 8 Oct 2026)
|
||||
const cast = { parent_hashes: '::text[]', detail: '::jsonb', subsidy_sompi: '::numeric', paid_sompi: '::numeric' };
|
||||
const params = []; const values = [];
|
||||
for (const r of rows) {
|
||||
const ph = [];
|
||||
|
|
|
|||
|
|
@ -10,11 +10,11 @@
|
|||
# tools/site-deploy-from-mirror.sh --self-test-checks the post-deploy checks against the live site as it stands (known-failed first)
|
||||
#
|
||||
# POST-DEPLOY CHECKS (main, 7 October 2026 22:1x UK), before the edge time is printed: /api/live's network must equal LIVE_NETWORK
|
||||
# (igneum-devnet-3), the index must carry INDEX_STRINGS (the launch-first chip line, the git.igneum.network link), LEGAL_PAGE must
|
||||
# (igneum-devnet-4 since the 8 Oct 2026 re-point), the index must carry INDEX_STRINGS (the launch-first chip line, the git.igneum.network link), LEGAL_PAGE must
|
||||
# carry "Not legal advice" (the litepaper: no commit on master puts it on the index) and /miners must carry at least MINERS_MIN_ROWS table rows; any mismatch prints "DEPLOY RED <field>: ..." and exits 1. The
|
||||
# edge serves the previous deployment for some seconds after the CLI returns, so the checks retry for up to 90 s before the verdict.
|
||||
set -euo pipefail
|
||||
LIVE_NETWORK="${LIVE_NETWORK:-igneum-devnet-3}"
|
||||
LIVE_NETWORK="${LIVE_NETWORK:-igneum-devnet-4}"
|
||||
INDEX_STRINGS=("At launch the strongest chip in our public model" "git.igneum.network/igneum-network/")
|
||||
LEGAL_PAGE="${LEGAL_PAGE:-/litepaper}"; LEGAL_STRING="Not legal advice" # the legal line lives on the litepaper in master's tree (22:16 UK: no commit put it on the index)
|
||||
MINERS_MIN_ROWS="${MINERS_MIN_ROWS:-20}" # the current-class table alone (the datacentre rows sit in their own table since 8 Oct 2026)
|
||||
|
|
@ -53,8 +53,10 @@ post_checks() { # one pass: prints the first mismatch as "field: detail" and ret
|
|||
[ -z "$want" ] || printf '%s' "$body" | /usr/bin/grep -cF -- "$want" >/dev/null || { echo "page /$path: string missing: \"$want\""; return 1; }
|
||||
done
|
||||
# the certified-checkpoint API (the receipt, the light wallet and the explorer's per-block re-check read it): ok:true on Devnet 3
|
||||
local ckp; ckp=$(curl -fsS "${nc[@]}" --max-time 25 "$SITE/api/checkpoint?source=dn3&x=$RANDOM" 2>/dev/null) || { echo "api/checkpoint: not reachable"; return 1; }
|
||||
printf '%s' "$ckp" | python3 -c 'import sys,json; d=json.load(sys.stdin); sys.exit(0 if d.get("ok") is True and d.get("network")=="igneum-devnet-3" else 1)' 2>/dev/null || { echo "api/checkpoint?source=dn3: $(printf '%s' "$ckp" | head -c 120)"; return 1; }
|
||||
local ckp; ckp=$(curl -fsS "${nc[@]}" --max-time 25 "$SITE/api/checkpoint?x=$RANDOM" 2>/dev/null) || { echo "api/checkpoint: not reachable"; return 1; }
|
||||
# rule 25's class (main, 8 Oct 2026 18:4x): the public checkpoint answers the live network only (source dn4 or live, never test), ok:true,
|
||||
# and either a certificate or certificate:null with a reason before the chain's first lock; its network must equal /api/live's
|
||||
printf '%s' "$ckp" | python3 -c 'import sys,json; d=json.load(sys.stdin); ok=d.get("ok") is True and d.get("network")==sys.argv[1] and d.get("source") in ("live","dn4") and ("certificate" in d); sys.exit(0 if ok else 1)' "$LIVE_NETWORK" 2>/dev/null || { echo "api/checkpoint: $(printf '%s' "$ckp" | head -c 160) (want network $LIVE_NETWORK, source live or dn4)"; return 1; }
|
||||
# the explorer's stats API (8 Oct 2026): Devnet 3 named, the indexer not stale, the any-age 10-minute shard count a number
|
||||
local st; st=$(curl -fsS "${nc[@]}" --max-time 25 "$SITE/api/explorer?stats=1&x=$RANDOM" 2>/dev/null) || { echo "api/explorer?stats=1: not reachable"; return 1; }
|
||||
printf '%s' "$st" | python3 -c 'import sys,json; d=json.load(sys.stdin); ok=d.get("network_label")=="Devnet 3" and (d.get("indexer") or {}).get("stale") is False and isinstance(((d.get("paid_10m_any_age") or {}).get("shards")), (int,float)); sys.exit(0 if ok else 1)' 2>/dev/null || { echo "api/explorer?stats=1: $(printf '%s' "$st" | head -c 160)"; return 1; }
|
||||
|
|
|
|||
Loading…
Reference in a new issue