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>
42 lines
3.4 KiB
JavaScript
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));
|
|
}
|