tools/harness runs the standard consensus-attack catalogue against a private test network of our own igneumd nodes (127.0.0.1 ports 27200+, /tmp/igneum-harness, never the live devnet or the PC node), with a pass criterion per scenario from the spec and a measured result each. Built on the node fork's own crates (igneum-harness-sim on kaspa_utils::sim as simpa does; igneum-p2p-probe for the wire). Scenarios: 1 withholding, 2 timestamp edges and drift, 3 partition and heal, 4 eclipse, 5 malformed and boundary inputs on every p2p and RPC surface, 6 resource exhaustion, 7 fast-miner flood. Finality and difficulty-controller scenarios are stubs with their criteria written. bench-log: one dated entry, a row per scenario (criterion, measured, pass or fail). First run: 19 of 20 measured rows pass. Findings recorded in the entry: scenario 5 reproduces ledger M15 on HEAD (bogus past-day or DAA headers build a 256 MiB cache before rejection; the r3-fixes branch removes it); scenario 1 at 45% hash with burst withholding shows a selfish-mining blue-share gain (50.7% of blues), the one failing row. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
47 lines
2.5 KiB
JavaScript
47 lines
2.5 KiB
JavaScript
// Runs the in-process simulator (vendor worktree crate igneum/harness-sim) and reads its JSON report.
|
|
import { spawnSync, spawn } from 'node:child_process';
|
|
import { readFileSync, mkdirSync, existsSync } from 'node:fs';
|
|
import { SIM, TMP, log } from './net.mjs';
|
|
|
|
export function runSim(name, args, { timeoutMs = 60 * 60_000 } = {}) {
|
|
if (!existsSync(SIM)) throw new Error(`missing ${SIM}; build igneum-harness-sim in the harness worktree`);
|
|
mkdirSync(`${TMP}/sim`, { recursive: true });
|
|
const out = `${TMP}/sim/${name}.json`;
|
|
const argv = [...args, '--out', out];
|
|
log(`sim ${name}: igneum-harness-sim ${argv.join(' ')}`);
|
|
const t0 = Date.now();
|
|
const r = spawnSync('nice', ['-n', '19', SIM, ...argv], { encoding: 'utf8', timeout: timeoutMs, maxBuffer: 64 * 1024 * 1024 });
|
|
if (!existsSync(out)) throw new Error(`sim ${name} produced no report: ${(r.stderr || '').slice(-400)}`);
|
|
const report = JSON.parse(readFileSync(out, 'utf8'));
|
|
report.wall_s = (Date.now() - t0) / 1000;
|
|
log(`sim ${name}: ${report.end_time_s} virtual s in ${report.wall_s.toFixed(0)} s wall`);
|
|
return report;
|
|
}
|
|
|
|
/// Several simulations at once (each uses a couple of threads); returns reports in order.
|
|
export async function runSims(jobs, { parallel = 3 } = {}) {
|
|
const results = new Array(jobs.length);
|
|
let next = 0;
|
|
async function worker() {
|
|
while (next < jobs.length) {
|
|
const i = next++;
|
|
const { name, args } = jobs[i];
|
|
mkdirSync(`${TMP}/sim`, { recursive: true });
|
|
const out = `${TMP}/sim/${name}.json`;
|
|
const argv = [...args, '--out', out];
|
|
log(`sim ${name}: igneum-harness-sim ${argv.join(' ')}`);
|
|
const t0 = Date.now();
|
|
await new Promise((resolve) => {
|
|
const p = spawn('nice', ['-n', '19', SIM, ...argv], { stdio: ['ignore', 'ignore', 'pipe'] });
|
|
let err = ''; p.stderr.on('data', d => { err += d; if (err.length > 4000) err = err.slice(-4000); });
|
|
p.on('exit', () => { if (!existsSync(out)) log(`sim ${name} FAILED: ${err.slice(-300)}`); resolve(); });
|
|
});
|
|
if (existsSync(out)) { results[i] = JSON.parse(readFileSync(out, 'utf8')); results[i].wall_s = (Date.now() - t0) / 1000; log(`sim ${name}: ${results[i].end_time_s} virtual s in ${results[i].wall_s.toFixed(0)} s wall`); }
|
|
}
|
|
}
|
|
await Promise.all(Array.from({ length: Math.min(parallel, jobs.length) }, worker));
|
|
return results;
|
|
}
|
|
|
|
export function hist(h) { return Object.entries(h || {}).map(([d, n]) => `${d}:${n}`).join(' ') || 'none'; }
|
|
export function maxDepth(h) { return Math.max(0, ...Object.keys(h || {}).map(Number)); }
|