igneum/tools/harness/scenarios/s4-eclipse.mjs
igneum-labs 6b72c3b3e6 Fast time: the devnet at 60x for test networks (override-60x.json, --fast-time in both harnesses, proof script)
infra/fast-time/override-60x.json is the devnet with every clock-like consensus parameter divided by 60 and every
block count unchanged (finality window, ban and min_daa 120 DAA; merge depth 60; Kaspa finality depth 720; pruning
depth at the anticone bound 13,838; coinbase maturity 2; the hourly program epoch 60 blocks with a 10-block lead;
the dataset day 24 minutes). The epoch length, lead and day are consensus parameters of the node since devnet-v4
a5ef8b07, carried by the override file. README lists each field, why it scales or not, the flags and the numbers.

Measured (simnet.mjs, three devnet-v4 nodes, three vmine voters at 1 block/s, one real-hash CPU miner): next
epoch seed in the template at 56.1 s, program swap at 65.1 s wall (DAA 60), first finality lock at 185.5 s wall
(checkpoint 5, DAA 149). Both harnesses take --fast-time: finality-attacks s3 PASS in 113 s wall with 16 locks
per node (the devnet rule needs 20 min of warm-up at 6 blocks/s before any lock); harness s3 partition and heal
43 s wall for three cuts against 983 s for four on the devnet profile with the same binary. Bench-log entry.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-04 10:34:19 +00:00

42 lines
3.4 KiB
JavaScript

// Scenario 4: eclipse of one node. The victim (10% of hash rate) has one link, to an adversary (20%) that is cut
// off from the honest pair (35% + 35%), so the victim only ever sees the adversary's slower chain. After the eclipse
// (10 and 30 minutes) the victim is linked to the honest nodes and the backlog is replayed.
// Criterion (spec 02 section 2.1, merge depth 3,600 s): the victim rejoins the honest chain on reconnection within
// the merge-depth bound: its sink equals the honest sink, and the reorg depth it suffers is recorded.
// Runs in the in-process simulator.
import { runSims, maxDepth } from '../lib/sim.mjs';
import { clockParams } from '../lib/net.mjs';
import { saveResult } from '../lib/report.mjs';
export async function run({ quick = false } = {}) {
// Eclipses of 1/6 and 1/2 of the merge depth (devnet 600 and 1,800 s; 10 and 30 s under --fast-time).
const { fastTime, mergeDepth } = clockParams();
const cuts = (quick ? [1 / 6] : [1 / 6, 1 / 2]).map(f => Math.round(mergeDepth * f));
const runAfter = fastTime ? '120' : '600', sample = fastTime ? '2' : '10';
const jobs = cuts.map(c => ({ name: `s4-eclipse${c}`, args: ['--scenario', 'eclipse', '--cut-at', '60', '--cut-secs', String(c), '--run-after', runAfter, '--sample-secs', sample, '--seed', '4'] }));
const reports = await runSims(jobs, { parallel: 2 });
const rows = []; const data = [];
reports.forEach((r, i) => {
const c = cuts[i];
if (!r) { rows.push({ scenario: `4 eclipse ${c} s`, criterion: 'victim rejoins the honest chain', result: 'sim failed', pass: false }); return; }
// Convergence of the victim (node 3) with honest node 0 by blue score (the adversary node 2 stays isolated by
// design, so we measure the victim against the honest chain, not all four nodes).
const g = r.genesis_time_ms; let rejoin = null;
for (const s of r.samples) { const t = (s.t - g) / 1000; if (r.heal_at_s != null && t >= r.heal_at_s && Math.abs(s.blue_scores[3] - s.blue_scores[0]) <= 18) { rejoin = +(t - r.heal_at_s).toFixed(1); break; } }
const victimDepth = r.heal_reorg_depth[3];
const lastSample = r.samples[r.samples.length - 1];
const victimGap = Math.abs(lastSample.blue_scores[3] - lastSample.blue_scores[0]);
const onHonestSink = lastSample.sinks[3] === lastSample.sinks[0];
const pass = victimGap <= 18 && rejoin != null;
data.push({ eclipse_s: c, heal_at_s: r.heal_at_s, victim_rejoin_after_s: rejoin, victim_reorg_depth: victimDepth, victim_blue_gap_end: victimGap, victim_on_honest_sink: onHonestSink, reorg_depth_at_heal: r.heal_reorg_depth, victim_max_reorg: maxDepth(r.reorg_hist[3]), counts: r.counts, rejects: r.rejects, final_sinks: r.final_sinks.map(s => s.slice(0, 12)), wall_s: r.wall_s });
rows.push({ scenario: `4 eclipse ${c} s${fastTime ? ' [fast-time 60x]' : ''}`, criterion: 'victim rejoins the honest chain on reconnection within the merge-depth bound; reorg depth recorded', result: `victim rejoined ${rejoin ?? 'never'} s after reconnection (blue-score gap to honest ${victimGap} at the end); victim reorg depth ${victimDepth} chain blocks; adversary built ${r.counts[2].created} blocks that never entered the honest chain`, pass });
});
saveResult('s4-eclipse', { rows, data });
return { rows, data };
}
if (import.meta.url === `file://${process.argv[1]}`) {
const r = await run({ quick: process.argv.includes('--quick') });
console.log(JSON.stringify(r.rows, null, 2));
}