class v5 harness: the object bytes follow the 0.3.20 node line (the amended class v4 signals 5, class v5 signals 6)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
igneum-labs 2026-10-07 11:55:54 +00:00
parent 7f2bc3821c
commit 2618ea3acf
2 changed files with 14 additions and 12 deletions

View file

@ -4,23 +4,24 @@
// the v4 FLOOR at --v4-floor (default 120, epoch 2), the class v5 floor at --floor (default 100000: far away, so v5 is
// ENABLED and the signal decides; `never` turns the object off), the signal window at --window (default 60 DAA, one
// epoch; seven windows = 420 DAA, so the first epoch whose seed block has seven full windows below it is epoch 8 at
// DAA 480), each node's object byte by IGNEUM_CLASS_SIGNAL (--signal a,b,c), each node's exec RPC on its own loopback
// DAA 480), each node's object byte by IGNEUM_CLASS_SIGNAL (--signal a,b,c; since the 0.3.20 node line the amended
// class v4 is object byte 5 and class v5 is 6), each node's exec RPC on its own loopback
// port and each CPU miner's --exec-rpc pointing at its node (the state stream after each epoch's seed block). A fourth
// node runs with --evm-disable (--stateless-node, default on): it mines until the flip and then serves no class v5
// template, which the harness counts. Ports 29760 and up, network igneum-devnet-976, data /tmp/igneum-fast-time-v5s.
//
// The cases and the known-failed cases:
// --signal 5,5,4 --expect no-flip two of three signal v5: 67 percent, the class must stay v4 (run 11 epochs)
// --signal 5,5,5 --expect flip all three: v5 from epoch 8, the first boundary with seven full windows; every
// --signal 6,6,5 --expect no-flip two of three signal v5: 67 percent, the class must stay v4 (run 11 epochs)
// --signal 6,6,6 --expect flip all three: v5 from epoch 8, the first boundary with seven full windows; every
// miner's v5 id equals the CLI's --program-class v5 id and differs from the same
// seed's v4 id; the stateless node's miner accepts 0 blocks after the flip; 0 rejected
// --signal 5,5,5 --floor never --expect no-flip the object off: byte 5 counts as v4 only
// --signal 4,4,4 --floor 600 --expect floor nobody signals v5: it flips at the floor (epoch 10) and not before
// --signal 5,5,5 --expect flip --stale 2 proof of following's known-failed case: miner 2 keeps the first stream it
// --signal 6,6,6 --floor never --expect no-flip the object off: byte 6 counts as v4 only
// --signal 5,5,5 --floor 600 --expect floor nobody signals v5: it flips at the floor (epoch 10) and not before
// --signal 6,6,6 --expect flip --stale 2 proof of following's known-failed case: miner 2 keeps the first stream it
// fetched (--freeze-state); from the epoch after the flip every block it mines is
// rejected (accepted after the first refresh 0, rejected above 0); PASS means the
// chain threw the stale miner off
// --signal 5,5,4 --expect flip the known-failed case of the harness itself: it must report FAIL (no flip)
// --signal 6,6,5 --expect flip the known-failed case of the harness itself: it must report FAIL (no flip)
//
// node infra/fast-time/class-v5-signal.mjs --signal a,b,c --expect flip|no-flip|floor [--floor <daa>|never]
// [--v4-floor 120] [--window 60] [--v3-activation 60] [--secs 780] [--epochs 11] [--stale <miner index>] [--no-stateless-node]
@ -55,7 +56,7 @@ const WINDOW = flag('window', 60);
const STALE = sflag('stale') == null ? null : +sflag('stale');
const STATELESS_NODE = !args.includes('--no-stateless-node');
const WINDOWS = 7;
const SIGNAL = (sflag('signal') || '5,5,5').split(',').map(Number);
const SIGNAL = (sflag('signal') || '6,6,6').split(',').map(Number);
const EXPECT = sflag('expect') || 'flip';
if (!['flip', 'no-flip', 'floor'].includes(EXPECT) || SIGNAL.length !== 3) { console.error('usage: --signal a,b,c --expect flip|no-flip|floor'); process.exit(2); }
const started = [];
@ -219,7 +220,8 @@ const BOUNDARY = firstV4 ? firstV4.epoch * EPOCH : Infinity;
const before = blocks.filter(b => b.daa < BOUNDARY), after = blocks.filter(b => b.daa >= BOUNDARY);
// the signal bytes on the chain: the share of blocks whose version high byte is 4
const versionBytes = blocks.reduce((m, b) => { const v = b.version >> 8; m[v] = (m[v] || 0) + 1; return m; }, {});
const signalShareOnChain = blocks.length ? Math.round(10000 * (blocks.filter(b => (b.version >> 8) >= 5).length) / blocks.length) : 0;
const V5_BYTE = 6;
const signalShareOnChain = blocks.length ? Math.round(10000 * (blocks.filter(b => (b.version >> 8) >= V5_BYTE).length) / blocks.length) : 0;
const programs = new Map();
for (const i of nodes.map(n => n.i)) for (const l of minerLog(i)) {
@ -376,7 +378,7 @@ const summary = {
sinks, block_counts: counts, signal_lines: signalLines, floor_lines: floorLines, samples,
};
writeFileSync(`${TMP}/summary.json`, JSON.stringify(summary, null, 2));
log(`SUMMARY ${pass ? 'PASS' : 'FAIL'} (expect ${EXPECT}, signals ${SIGNAL.join('/')}, stale ${STALE ?? 'none'}): ${firstV4 ? `v5 from epoch ${firstV4.epoch} at DAA ${firstV4.daa}` : 'no v5 epoch'}; epochs seen ${[...epochs.entries()].map(([e, v]) => `e${e}:${v.class}:${v.bps5}bps:d${v.day}`).join(' ')}; chain bytes ${JSON.stringify(versionBytes)} (${signalShareOnChain} bps at byte 5+); blocks ${before.length} / ${after.length}; rejected miners ${rejectedMiner.map(r => r.length).join('/')} nodes ${rejectedNode.map(r => r.length).join('/')}; sinks ${sinks.join(' ')} (${checks.sinks_agree ? 'agree' : 'DIFFER'}); counts ${counts.join('/')}; signal lines ${signalLines.filter(Boolean).length}/3 (epochs ${signalEpochs.join('/')}, shares ${signalShares.join('/')}); floor lines ${floorLines.filter(Boolean).length}/3; state roots at the flip ${rootsAtFlip.map(r => String(r).slice(0, 18)).join(' ')}; stateless node accepted after the flip ${statelessAcceptedAfterFlip} (refusal lines ${statelessRefusals}); stale miner accepted after the first refresh ${staleAcceptedAfterRefresh ?? 'n/a'} rejected ${staleRejected ?? 'n/a'}; days ${daysSeen.join('/')}`);
log(`SUMMARY ${pass ? 'PASS' : 'FAIL'} (expect ${EXPECT}, signals ${SIGNAL.join('/')}, stale ${STALE ?? 'none'}): ${firstV4 ? `v5 from epoch ${firstV4.epoch} at DAA ${firstV4.daa}` : 'no v5 epoch'}; epochs seen ${[...epochs.entries()].map(([e, v]) => `e${e}:${v.class}:${v.bps5}bps:d${v.day}`).join(' ')}; chain bytes ${JSON.stringify(versionBytes)} (${signalShareOnChain} bps at byte ${V5_BYTE}+); blocks ${before.length} / ${after.length}; rejected miners ${rejectedMiner.map(r => r.length).join('/')} nodes ${rejectedNode.map(r => r.length).join('/')}; sinks ${sinks.join(' ')} (${checks.sinks_agree ? 'agree' : 'DIFFER'}); counts ${counts.join('/')}; signal lines ${signalLines.filter(Boolean).length}/3 (epochs ${signalEpochs.join('/')}, shares ${signalShares.join('/')}); floor lines ${floorLines.filter(Boolean).length}/3; state roots at the flip ${rootsAtFlip.map(r => String(r).slice(0, 18)).join(' ')}; stateless node accepted after the flip ${statelessAcceptedAfterFlip} (refusal lines ${statelessRefusals}); stale miner accepted after the first refresh ${staleAcceptedAfterRefresh ?? 'n/a'} rejected ${staleRejected ?? 'n/a'}; days ${daysSeen.join('/')}`);
for (const r of idRows) log(`PROGRAM ID epoch ${r.epoch} seed ${r.seed}: miners ${r.miners_id} (${r.miners} of 3) cli v5 ${r.cli_v5} cli v4 ${r.cli_v4}; state root ${r.state_root} (${r.state_records} records)`);
for (const [k, v] of Object.entries(checks)) if (!v) log(`FAILED CHECK ${k}`);
log(`summary: ${TMP}/summary.json`);

View file

@ -7,8 +7,8 @@
# no build slot and no measure hold (the Mac rule's `run` kind).
#
# 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
# tools/class-v5/harness-remote.sh failed-case -- --signal 6,6,5 --expect flip
# BASE=29830 tools/class-v5/harness-remote.sh flip-stale -- --signal 6,6,6 --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)"