#!/usr/bin/env node // Exec sync harness (6 October 2026): the executor's persisted state and the snapshot file, on a private fast-time // simnet (ports 29970+, suffix 957; never the live devnet). Cases, each asserted: // 1. known-finished: node A mines past N chain blocks; `igneum_exportExecSnapshot` writes a file; its tip, state // root and sha256 are reported; A is stopped (the state is persisted at stop) and started again: it resumes from // the data dir's snapshot (startedFrom "snapshot", snapshotTip = the persisted tip) and keeps executing // 2. a fresh node B with --igneum-exec-snapshot=, and no bodies of its own beyond what it syncs: // startedFrom "snapshot", snapshotTip = the file's tip, eth_blockNumber reaches A's tip, every miner balance // and the state root at the file's tip equal A's // 3. known-failed: a fresh node C with the file and a WRONG pin ignores the file (startedFrom "genesis" here, since // a simnet peer still serves every body) and says why in its log // 4. a fresh node D with the file and NO consensus data of its own but the file's tip unknown: refused with // "unknown to consensus" (the file's tip is from another chain: a second simnet) // 5. known-failed: a node E whose flag names a file that cannot be read blocks loudly (0.3.13.1), never genesis // 6. the account dump (0.3.14): export [tip-1, tip] from B carries preState; the exporter seeds from it and cuts // Usage: node tools/exec-sync/net.mjs [--blocks 60] // IGNEUM_EXEC_BIN= (default vendor/igneum-node/target-exec-sync/release) import { spawn, spawnSync } from 'node:child_process'; import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync, openSync } from 'node:fs'; import { createHash } from 'node:crypto'; const ROOT = new URL('../../', import.meta.url).pathname; const REL = process.env.IGNEUM_EXEC_BIN || `${ROOT}vendor/igneum-node/target-exec-sync/release`; const IGNEUMD = `${REL}/igneumd`; const MINER = `${REL}/igneum-miner`; const EXPORTER = process.env.IGNEUM_EXPORTER || `${ROOT}proving/igneum-prove/target/release/igneum-prove-export`; const TMP = '/tmp/igneum-exec-sync'; const BASE = 29970, SUFFIX = 957; const flag = (name, d) => { const i = process.argv.indexOf(name); return i >= 0 ? Number(process.argv[i + 1]) : d; }; const BLOCKS = flag('--blocks', 60); const FILE = `${ROOT}infra/fast-time/override-60x.json`; for (const b of [IGNEUMD, MINER, EXPORTER]) if (!existsSync(b)) { console.error(`missing ${b}`); process.exit(2); } rmSync(TMP, { recursive: true, force: true }); mkdirSync(TMP, { recursive: true }); const override = `${TMP}/override.json`; // the exec-sync node is cut from 0.3.12 and knows no proof-system-2 field: drop it as text (JSON.parse would turn // the u64::MAX "never" values into floats the node refuses) // the 60x profile ships skip_proof_of_work false; the fast-time miner (vmine) submits unmined nonces, so it is on here let overrideText = readFileSync(FILE, 'utf8').replace(/\s*"proving_v2_activation_daa":\s*\d+,?/, '').replace(/"skip_proof_of_work":\s*false/, '"skip_proof_of_work": true'); if (!/"skip_proof_of_work": true/.test(overrideText)) throw new Error('override edit failed: skip_proof_of_work'); writeFileSync(override, overrideText); const t0 = Date.now(); const log = (m) => console.log(`${new Date().toISOString().slice(11, 23)} t=${((Date.now() - t0) / 1000).toFixed(1)}s ${m}`); const sleep = (ms) => new Promise(r => setTimeout(r, ms)); const hexn = (h) => Number(BigInt(h)); const started = []; class Node { constructor(i, connect = [], extra = [], suffix = SUFFIX) { this.i = i; this.grpcPort = BASE + i * 10; this.p2pPort = BASE + i * 10 + 1; this.jsonPort = BASE + i * 10 + 2; this.evmPort = BASE + i * 10 + 3; this.connect = connect; this.extra = extra; this.suffix = suffix; this.dir = `${TMP}/n${i}`; this.logFile = `${this.dir}/node.log`; } get evm() { return `http://127.0.0.1:${this.evmPort}`; } async start() { mkdirSync(this.dir, { recursive: true }); const a = ['--devnet', `--devnet-suffix=${this.suffix}`, '--nodnsseed', '--disable-upnp', '--nologfiles', '--enable-unsynced-mining', '--utxoindex', '--unsaferpc', `--appdir=${this.dir}`, `--rpclisten=127.0.0.1:${this.grpcPort}`, `--rpclisten-json=127.0.0.1:${this.jsonPort}`, `--evm-rpclisten=127.0.0.1:${this.evmPort}`, `--listen=127.0.0.1:${this.p2pPort}`, `--override-params-file=${override}`, '--loglevel=info', '--yes', ...this.connect.map(c => `--addpeer=${c}`), ...this.extra]; const out = openSync(this.logFile, 'a'); this.proc = spawn(IGNEUMD, a, { stdio: ['ignore', out, out], env: { ...process.env, IGNEUM_PROOF_VERIFY: 'trust' } }); started.push(this.proc); for (let k = 0; k < 120; k++) { try { await this.eth('eth_chainId'); return this; } catch { await sleep(500); } } throw new Error(`node ${this.i} did not answer on ${this.evm}`); } async stop() { try { this.proc.kill('SIGINT'); } catch { } for (let k = 0; k < 60; k++) { if (this.proc.exitCode !== null) return; await sleep(500); } try { this.proc.kill('SIGKILL'); } catch { } } async eth(method, params = []) { const r = await fetch(this.evm, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ jsonrpc: '2.0', id: 1, method, params }) }); const j = await r.json(); if (j.error) throw new Error(`${method}: ${JSON.stringify(j.error)}`); return j.result; } logText() { try { return readFileSync(this.logFile, 'utf8'); } catch { return ''; } } } // the fast-time miner of the proving harness: `vmine` submits templates at the given share of 1 block/s function mine(node, label) { const out = openSync(`${TMP}/miner-${label}.log`, 'a'); const p = spawn(MINER, ['vmine', `grpc://127.0.0.1:${node.grpcPort}`, '3600', '--label', label, '--share', '1', '--bps', '1', '--evm-address', '0x4343434343434343434343434343434343434343'], { stdio: ['ignore', out, out] }); started.push(p); return p; } const checks = []; const check = (name, ok, detail) => { checks.push({ name, ok }); log(`${ok ? 'PASS' : 'FAIL'} ${name}${detail ? ': ' + JSON.stringify(detail).slice(0, 300) : ''}`); if (!ok) throw new Error(`check failed: ${name}`); }; async function waitTip(n, want, secs = 600) { for (let k = 0; k < secs * 2; k++) { const tip = hexn(await n.eth('eth_blockNumber')); if (tip >= want) return tip; await sleep(500); } throw new Error(`node ${n.i} did not reach ${want}`); } function sha256(path) { return '0x' + createHash('sha256').update(readFileSync(path)).digest('hex'); } async function main() { const a = await new Node(0).start(); log(`node A up on ${a.evm}`); const miner = mine(a, 'exec-sync-a'); await waitTip(a, BLOCKS, 900); const tipA = hexn(await a.eth('eth_blockNumber')); const st0 = await a.eth('igneum_getExecStatus'); check('A executes from genesis', st0.startedFrom === 'genesis' && st0.blocked === null && hexn(st0.executedTip) >= BLOCKS, st0); // case 1: the export, the stop, the resume const file = `${TMP}/snapshot.bin`; const ex = await a.eth('igneum_exportExecSnapshot', [file]); const sha = sha256(file); check('the export writes the file with its tip, root and sha256', existsSync(file) && ex.sha256 === sha && hexn(ex.tip) >= BLOCKS && ex.records === hexn(ex.tip) + 1, { tip: ex.tip, bytes: ex.bytes, sha256: ex.sha256, accounts: ex.accounts }); const blockAtTip = await a.eth('eth_getBlockByNumber', [ex.tip, false]); check('the export\'s state root is the tip block\'s', blockAtTip.stateRoot === ex.stateRoot, { root: ex.stateRoot }); try { miner.kill('SIGINT'); } catch { } await a.stop(); const persistedLine = a.logText().split('\n').find(l => l.includes('exec state persisted at stop')); check('A persisted its state at stop', !!persistedLine, persistedLine && persistedLine.slice(-120)); await a.start(); const resumed = a.logText().split('\n').filter(l => l.includes('exec sync: resumed from')).pop(); const st1 = await a.eth('igneum_getExecStatus'); check('A resumed from the data dir\'s snapshot', st1.startedFrom === 'snapshot' && hexn(st1.snapshotTip) >= BLOCKS && !!resumed, { startedFrom: st1.startedFrom, snapshotTip: st1.snapshotTip, line: resumed && resumed.slice(-160) }); const miner2 = mine(a, 'exec-sync-a2'); await waitTip(a, hexn(st1.snapshotTip) + 5, 300); check('A keeps executing after the resume', hexn(await a.eth('eth_blockNumber')) > hexn(st1.snapshotTip)); // case 2: a fresh node from the file with the right pin const b = await new Node(1, [`127.0.0.1:${a.p2pPort}`], [`--igneum-exec-snapshot=${file},${sha}`]).start(); const tipNow = hexn(await a.eth('eth_blockNumber')); await waitTip(b, tipNow, 600); const stB = await b.eth('igneum_getExecStatus'); check('B started from the file', stB.startedFrom === 'snapshot' && hexn(stB.snapshotTip) === hexn(ex.tip) && stB.snapshotSha256 === sha, { startedFrom: stB.startedFrom, snapshotTip: stB.snapshotTip }); const blkB = await b.eth('eth_getBlockByNumber', [ex.tip, false]); check('B\'s state root at the file\'s tip equals A\'s', blkB.stateRoot === ex.stateRoot, { b: blkB.stateRoot }); const payout = '0x4343434343434343434343434343434343434343'; const h = '0x' + tipNow.toString(16); const [balA, balB] = await Promise.all([a.eth('eth_getBalance', [payout, h]), b.eth('eth_getBalance', [payout, h])]); check('the miner\'s balance at the same height equals on A and B', balA === balB && BigInt(balA) > 0n, { balA, balB, height: tipNow }); const [rootA, rootB] = await Promise.all([a.eth('eth_getBlockByNumber', [h, false]), b.eth('eth_getBlockByNumber', [h, false])]); check('the state root at the same height equals on A and B', rootA.stateRoot === rootB.stateRoot, { height: tipNow }); // case 3: known-failed, the wrong pin const wrong = '0x' + 'ab'.repeat(32); const c = await new Node(2, [`127.0.0.1:${a.p2pPort}`], [`--igneum-exec-snapshot=${file},${wrong}`]).start(); await sleep(3000); const stC = await c.eth('igneum_getExecStatus'); const cline = c.logText().split('\n').find(l => l.includes('is not the pinned')); check('known-failed: C refuses the file under a wrong pin and says so', stC.startedFrom !== 'snapshot' && !!cline, { startedFrom: stC.startedFrom, line: cline && cline.slice(-140) }); // case 4: known-failed, a file from another chain const z = await new Node(3, [], [], 958).start(); const zm = mine(z, 'exec-sync-z'); await waitTip(z, 12, 300); const fileZ = `${TMP}/snapshot-z.bin`; const exZ = await z.eth('igneum_exportExecSnapshot', [fileZ]); try { zm.kill('SIGINT'); } catch { } const d = await new Node(4, [`127.0.0.1:${a.p2pPort}`], [`--igneum-exec-snapshot=${fileZ},${exZ.sha256}`]).start(); await sleep(3000); const stD = await d.eth('igneum_getExecStatus'); const dline = d.logText().split('\n').find(l => l.includes('unknown to consensus') || l.includes('not on this node') || l.includes('is not this network')); check('known-failed: D refuses a snapshot from another chain and says why', stD.startedFrom !== 'snapshot' && !!dline, { startedFrom: stD.startedFrom, line: dline && dline.slice(-160) }); try { miner2.kill('SIGINT'); } catch { } // 6. the account dump (0.3.14): an export [tip-1, tip] from B (a snapshot-started node) carries the full state after // tip-1 (preState); the exporter seeds from it and cuts block tip without any older record { const tipB = hexn(await b.eth('eth_blockNumber')); const want = tipB - 2; const exp = await b.eth('igneum_exportSegments', ['0x' + (want - 1).toString(16), '0x' + want.toString(16)]); check('the export from one block below carries the account dump at that block', Array.isArray(exp.preState) && exp.preState.length >= 2 && exp.segments.length === 2 && exp.from === want - 1, { accounts: exp.preState && exp.preState.length, from: exp.from, to: exp.to }); const seq = `${TMP}/seq-${want}.json`, fix = `${TMP}/block-${want}.json`; writeFileSync(seq, JSON.stringify(exp)); const r = spawnSync(EXPORTER, [seq, String(want), fix], { encoding: 'utf8', timeout: 120000 }); const out = (r.stdout || '') + (r.stderr || ''); check('the exporter seeds from the dump and cuts the block (state root equals the node\'s)', r.status === 0 && existsSync(fix) && /account dump: \d+ accounts after chain block/.test(out) && !/differs from the node/.test(out), { status: r.status, line: (out.split('\n').find(l => /account dump/.test(l)) || out.slice(-200)).slice(0, 200) }); } // 5. known-failed: a node E whose flag names a file that does not exist (the seed, 6 October 2026: unreadable under // /root) blocks loudly, never runs as if the flag were unset, never executes genesis const e = await new Node(5, [`127.0.0.1:${a.p2pPort}`], ['--igneum-exec-snapshot=/nonexistent/exec-snapshot.bin,0x00']).start(); await sleep(4000); const stE = await e.eth('igneum_getExecStatus'); const eline = e.logText().split('\n').find(l => /the flag's file .* does not exist or cannot be read/.test(l)); check('known-failed: E with an unreadable flag file blocks loudly and never starts at genesis', !!eline && stE.blocked !== null && /did not load/.test(stE.blocked) && hexn(stE.executedTip) === 0 && stE.startedFrom === 'fresh' && !/starts at genesis/.test(e.logText()), { line: eline && eline.slice(-160), blocked: stE.blocked && stE.blocked.slice(0, 80) }); await e.stop(); log(`RESULT exec sync harness: PASSED (${checks.length} checks) in ${((Date.now() - t0) / 1000).toFixed(1)} s; snapshot ${ex.bytes} bytes at tip ${hexn(ex.tip)}, ${ex.accounts} accounts`); } main().then(() => cleanup(0)).catch(e => { log(`FAILED: ${e.message}`); cleanup(1); }); function cleanup(code) { for (const p of started.reverse()) { try { p.kill('SIGINT'); } catch { } } setTimeout(() => { for (const p of started) { try { p.kill('SIGKILL'); } catch { } } process.exit(code); }, 3000); }