class v5 harness: the previous run's survivors (two nodes of the known-failed case were still bound at 08:49Z and the flip run read a stranger's chain): a pid file per data dir killed at start, a node whose log says a port was in use fails the run at once, a port base per case (IGNEUM_V5_BASE, BASE= in harness-remote.sh)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
igneum-labs 2026-10-07 09:13:13 +00:00
parent 76b7cee8d6
commit 3a9bd0873a
2 changed files with 14 additions and 2 deletions

View file

@ -39,7 +39,7 @@ const IGNEUMD = process.env.IGNEUMD || `${BIN}/igneumd`;
const CPU_MINER = process.env.IGNEUM_MINER || `${BIN}/igneum-miner`;
const IGNEUM_POW = process.env.IGNEUM_POW || `${ROOT}igneum-pow/target/release/igneum-pow`;
const TMP = process.env.IGNEUM_V5_TMP || '/tmp/igneum-fast-time-v5s';
const BASE = 29760, SUFFIX = 976;
const BASE = +(process.env.IGNEUM_V5_BASE || 29760), SUFFIX = 976;
const NEVER = '18446744073709551615';
const args = process.argv.slice(2);
const flag = (name, dflt) => { const i = args.indexOf(`--${name}`); return i >= 0 ? +args[i + 1] : dflt; };
@ -63,7 +63,13 @@ const log = (...a) => console.log(new Date().toISOString().slice(11, 23), ...a);
const sleep = (ms) => new Promise(r => setTimeout(r, ms));
for (const b of [IGNEUMD, CPU_MINER]) if (!existsSync(b)) { console.error(`missing ${b}`); process.exit(2); }
// the previous run's survivors (the flip-stale run of 7 October 2026, 08:49Z: two nodes of the earlier case were still bound
// to the ports and the harness read a stranger's chain): the pid file of this data dir is killed first, by pid, and a node
// whose log says a port was in use fails the run at once
const PIDS = `${TMP}/pids`;
try { for (const pid of readFileSync(PIDS, 'utf8').split('\n').map(Number).filter(Boolean)) { try { process.kill(pid, 'SIGKILL'); console.log(`killed survivor pid ${pid} of the previous run`); } catch { } } } catch { }
rmSync(TMP, { recursive: true, force: true }); mkdirSync(TMP, { recursive: true });
const recordPid = (p) => { try { writeFileSync(PIDS, `${readFileSync(PIDS, 'utf8')}${p.pid}\n`); } catch { writeFileSync(PIDS, `${p.pid}\n`); } };
const baseText = readFileSync(FILE, 'utf8');
const field = (name) => { const m = new RegExp(`"${name}":\\s*([0-9]+)`).exec(baseText); return m ? +m[1] : undefined; };
const EPOCH = field('pow_epoch_blocks');
@ -108,7 +114,9 @@ class Node {
const byte = this.i < SIGNAL.length ? SIGNAL[this.i] : Math.max(...SIGNAL);
this.proc = spawn(IGNEUMD, a, { stdio: ['ignore', out, out], env: { ...process.env, IGNEUM_CLASS_SIGNAL: String(byte) } });
started.push(this.proc);
recordPid(this.proc);
await sleep(1200);
if (this.grepLog(/Address already in use|AddrInUse/).length) { log(`FAILED: n${this.i} could not bind its ports (a previous run's node is still alive): ${this.grepLog(/AddrInUse|Address already in use/)[0].slice(0, 160)}`); await stopAll(); process.exit(4); }
this.rpc = await connectRpc(`ws://127.0.0.1:${this.jsonPort}`);
log(`n${this.i} up pid ${this.proc.pid} json ${this.jsonPort} p2p ${this.p2pPort} exec ${this.stateless ? 'disabled' : this.execPort}, signals ${byte}`);
return this;
@ -119,6 +127,7 @@ function miner(bin, argv, name, env = {}) {
const out = openSync(`${TMP}/${name}.log`, 'a');
const p = spawn(bin, argv, { stdio: ['ignore', out, out], env: { ...process.env, ...env } });
started.push(p);
recordPid(p);
return p;
}
async function stopAll() {

View file

@ -8,6 +8,8 @@
#
# tools/class-v5/harness-remote.sh <case-name> -- <class-v5-signal.mjs arguments>
# tools/class-v5/harness-remote.sh failed-case -- --signal 5,5,4 --expect flip
# BASE=29830 tools/class-v5/harness-remote.sh flip-stale -- --signal 5,5,5 --expect flip --stale 2 (another port base per
# case, so two cases never share ports; the harness kills the previous run's pids of its own data dir first)
set -euo pipefail
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT="$(cd "$HERE/../.." && pwd)"
@ -21,9 +23,10 @@ REMOTE_LOG="/srv/builds/_log/v5-class/harness"
OUT="$ROOT/docs/design/class-v5-harness"
mkdir -p "$OUT"
ARGS="$*"
BASE="${BASE:-29760}"
echo "harness-remote: case $CASE on $HOST: node $REMOTE_ROOT/infra/fast-time/class-v5-signal.mjs $ARGS"
# the harness file travels by rsync (the mirror carries HEAD; an uncommitted change rides along, re-stamped: the copied-sources rule)
rsync -a -e "ssh -i $KEY" "$ROOT/infra/fast-time/class-v5-signal.mjs" "$ROOT/infra/fast-time/override-60x.json" "$HOST:$REMOTE_ROOT/infra/fast-time/"
ssh -i "$KEY" "$HOST" "touch $REMOTE_ROOT/infra/fast-time/class-v5-signal.mjs $REMOTE_ROOT/infra/fast-time/override-60x.json; mkdir -p $REMOTE_LOG; cd $REMOTE_ROOT && IGNEUM_V5_TMP=/tmp/igneum-fast-time-v5s-$CASE IGNEUM_V5_BIN=$REMOTE_ROOT/vendor/igneum-node-class-v5/target/release IGNEUM_POW=$REMOTE_ROOT/igneum-pow/target/release/igneum-pow node infra/fast-time/class-v5-signal.mjs $ARGS > $REMOTE_LOG/$CASE.log 2>&1; rc=\$?; cp /tmp/igneum-fast-time-v5s-$CASE/summary.json $REMOTE_LOG/$CASE.json 2>/dev/null || true; echo \"harness-remote: rc \$rc\"; exit \$rc" || true
ssh -i "$KEY" "$HOST" "touch $REMOTE_ROOT/infra/fast-time/class-v5-signal.mjs $REMOTE_ROOT/infra/fast-time/override-60x.json; mkdir -p $REMOTE_LOG; cd $REMOTE_ROOT && IGNEUM_V5_BASE=$BASE IGNEUM_V5_TMP=/tmp/igneum-fast-time-v5s-$CASE IGNEUM_V5_BIN=$REMOTE_ROOT/vendor/igneum-node-class-v5/target/release IGNEUM_POW=$REMOTE_ROOT/igneum-pow/target/release/igneum-pow node infra/fast-time/class-v5-signal.mjs $ARGS > $REMOTE_LOG/$CASE.log 2>&1; rc=\$?; cp /tmp/igneum-fast-time-v5s-$CASE/summary.json $REMOTE_LOG/$CASE.json 2>/dev/null || true; echo \"harness-remote: rc \$rc\"; exit \$rc" || true
rsync -a -e "ssh -i $KEY" "$HOST:$REMOTE_LOG/$CASE.log" "$HOST:$REMOTE_LOG/$CASE.json" "$OUT/" 2>/dev/null || true
grep -E "SUMMARY|FAILED CHECK|PROGRAM ID|CLASS SWITCH" "$OUT/$CASE.log" || tail -20 "$OUT/$CASE.log"