diff --git a/docs/design/class-v5-harness/attempt-3-seed/seed.json b/docs/design/class-v5-harness/attempt-3-seed/seed.json new file mode 100644 index 000000000..1727db2b7 --- /dev/null +++ b/docs/design/class-v5-harness/attempt-3-seed/seed.json @@ -0,0 +1,11 @@ +{ + "what": "The class v5 pairing rule's fixed attempt-3 seed (8 October 2026, 14:1x UK): the 0.3.25 pin d5b68fae's FAIL seed from the cross harness run cross-0325-d5b68fae, epoch 9 of that run, whose draw lands at attempt 3 under the freeze's rule. Every harness run reads it through the pair's engine path (kaspa-pow program-id) and the run's CLI (igneum-pow show) and compares attempt and id; a difference is FAILED CHECK attempt_3_seed_reads_equal.", + "epoch_hex": "ec0a8cf9e1c6874fc3bc7f9e5a2d8113f2b2353fced8bf293e9d451a48d68212", + "era_hex": "234e082d653dc69db36d875b256d0f5f4a123b353d6aa069c3c7aa9aa6c46062", + "day": 1244071, + "genesis_day": 1243740, + "genesis_log2": 28, + "state_file": "state.igsd1", + "state_root": "3046a7c17feb51c91b30f29d26e1b0de1", + "expected": { "class": "v5", "attempt": 3, "id": "1f1cf82877f46ee6", "read_by": "igneum-pow at exactly 1c420786 and kaspa-pow program-id on class-v5-node f912e614, build-1, 14:07 UK" } +} diff --git a/docs/design/class-v5-harness/attempt-3-seed/state.igsd1 b/docs/design/class-v5-harness/attempt-3-seed/state.igsd1 new file mode 100644 index 000000000..ae8538b8f Binary files /dev/null and b/docs/design/class-v5-harness/attempt-3-seed/state.igsd1 differ diff --git a/docs/design/class-v5-stored-state.md b/docs/design/class-v5-stored-state.md index 141ad1cc6..9656c731c 100644 --- a/docs/design/class-v5-stored-state.md +++ b/docs/design/class-v5-stored-state.md @@ -50,6 +50,7 @@ Clock rule for this page: every time is UK time (UK time tonight, UTC+1). The bu | 13:4x | The shipper's STOP on the 0.3.25 pin d5b68fae (13:42 UK): the fast-time pair's `v5_ids_equal_the_cli_v5_id` FAILED on epoch 9's attempt-3 seed ec0a8cf9 (the freeze CLI 1c420786 draws ebf64b32e2d84c5b, the pin's miner 65b57e3b847d362e, its nodes accepting 4 of 4 on it); every 0.3.25 node binary embeds "igneum-pow-v5/src", not the freeze's crate: rule 7's pairing broken in the node lane's build path, a chain-split class. PAIRING RULE, added: the fast-time set carries an attempt-3 seed on every run (an attempt-0 seed cannot tell two acceptance rules apart). The node lane folds the freeze pairing and the ceiling re-cut into one commit; this lane's read-back on the rebuilt pair: the freeze CLI built at exactly 1c420786 on build-1 (/srv/builds/_scratch/v5-freeze-cli) against the new sha's miner, id for id and attempt for attempt on every v5 epoch of the flip case (the harness's POW_BIN override, 8c810148), and the live network's path: the 0.3.24 miner's epoch-19 read 3d375a55029e7e60 against the freeze CLI | | 13:5x | The pairing read-back on the rebuilt pair 6ccaf9e9 (release-0.3.25-node, igneumd's IGNEUM_POW_FINGERPRINT=cbc5bd0a..., the same PoW code as the print-only c9ad753a): the flip case on build-1 (13:44 to 13:56 UK, `class-v5-harness/readback-0325.{log,json}`) with the CLI side igneum-pow built at exactly 1c420786 (/srv/builds/_scratch/v5-freeze-cli, sha256 7ba781db...): SUMMARY PASS; every v5 epoch id for id, miners 3 of 3 against the freeze CLI: epoch 8 13a54a0dd793ca79 (attempt 2), epoch 9 d35cd0e9cb186d00 (attempt 1), epoch 10 6104176723170d72 (attempt 2). The gap: the run's seeds are its block hashes and drew no attempt-3 seed, so the case that caught the pin d5b68fae is not exercised by this read; the seeded attempt-3 case (a fixed seed through the freeze CLI and the pair's miner pack path, one line per run) is the next harness item | | 14:1x | The pin's FAIL seed re-read (ec0a8cf9...8212, its era 234e082d...6062, day 1244071, the run's own epoch-9 stream from /tmp/igneum-fast-time-v5x-cross-0325-d5b68fae): igneum-pow at exactly 1c420786 draws attempt 3, program id 1f1cf82877f46ee6 (8f481459 the same). Neither id of the FAIL was the freeze's: the cross harness's "cli v5 ebf64b32e2d84c5b" came from the release worktree's pre-freeze igneum-pow copy and the pin miner's 65b57e3b847d362e from "igneum-pow-v5/src"; rule 19's fingerprint pairing catches both. The miner's side of a past seed cannot be re-read (igneum-miner reads ids from a node's template only), so the pin goes on the fingerprint equality and the record says the attempt-3 seed's miner id was not re-read; the seeded attempt-3 case (a miner `program-id` read on a given seed, era, day and stream, asked of the node lane by 15:00 UK, default a kaspa-pow test binary on class-v5-node) is the next harness item | +| 14:1x | The seeded attempt-3 case closed EQUAL: kaspa-pow `program-id` (class-v5-node f912e614, consensus/pow/src/bin: the template prepare's own path on a given seed, era, day, genesis day and stream file; a stream-less v5 read refuses with the engine's line, exit 3) reads the pin's FAIL seed ec0a8cf9...8212 (era 234e082d...6062, day 1244071, genesis day 1243740, the run's epoch-9 stream) as attempt 3, id 1f1cf82877f46ee6, equal to igneum-pow at exactly 1c420786 (build-1, 14:07 UK); the class v4 control reads attempt 0, 17d03dc8ddfe865c. The harness now reads that seed on every run (`class-v5-harness/attempt-3-seed/{seed.json,state.igsd1}`, check `attempt_3_seed_reads_equal`: the engine path beside the run's CLI, attempt and id against the record's expectation; an unreadable side is a FAILED CHECK, never a skip); the node lane lifts the read into `igneum-miner program-id` on its next line | ## 1. The claim, in one paragraph diff --git a/infra/fast-time/class-v5-signal.mjs b/infra/fast-time/class-v5-signal.mjs index f33004245..e6590f559 100644 --- a/infra/fast-time/class-v5-signal.mjs +++ b/infra/fast-time/class-v5-signal.mjs @@ -34,6 +34,7 @@ import { spawn, spawnSync } from 'node:child_process'; import { mkdirSync, rmSync, writeFileSync, readFileSync, openSync, existsSync } from 'node:fs'; +import { dirname } from 'node:path'; import { connectRpc } from '../../tools/finality-attacks/lib/rpc.mjs'; import { devAddress } from '../../tools/harness/lib/address.mjs'; @@ -429,8 +430,32 @@ if (EXPECT === 'flip') { stateless_node_stops_at_the_flip: !statelessNode || (counts[statelessNode.i] <= before.length && counts[statelessNode.i] >= before.length - 5), }; } +// The pairing rule's fixed attempt-3 seed (8 October 2026, 14:1x UK; the 0.3.25 pin d5b68fae's FAIL seed): read on every +// run through the pair's engine path (kaspa-pow `program-id`, beside the node binary or in the fork worktree's target) and +// through the run's CLI (igneum-pow show), attempt and id compared with each other and with the record's expectation. +// A run's own seeds are its block hashes and rarely land at attempt 3, which is where two acceptance rules show; this +// read does not wait for the chain to draw one. An unreadable side is a FAILED CHECK, never a skip. +const A3_DIR = `${ROOT}docs/design/class-v5-harness/attempt-3-seed`; +const a3 = (() => { + try { + const j = JSON.parse(readFileSync(`${A3_DIR}/seed.json`, 'utf8')); + const state = `${A3_DIR}/${j.state_file}`; + const dayHex = Buffer.concat([Buffer.from('igneum-day/'), Buffer.from(BigInt(j.day).toString(16).padStart(16, '0'), 'hex').reverse()]).toString('hex'); + const cli = IGNEUM_POW && existsSync(IGNEUM_POW) ? spawnSync(IGNEUM_POW, ['show', '--program-class', 'v5', '--epoch-hex', j.epoch_hex, '--era-hex', j.era_hex, '--day-hex', dayHex, '--state', state], { encoding: 'utf8' }).stdout || '' : ''; + const cm = /attempt (\d+) program id ([0-9a-f]{16})/.exec(cli); + const pidBin = [`${dirname(IGNEUMD)}/program-id`, `${ROOT}vendor/igneum-node-class-v5/target/release/program-id`].find(p => existsSync(p)); + const eng = pidBin ? spawnSync(pidBin, ['--epoch-hex', j.epoch_hex, '--era-hex', j.era_hex, '--day', String(j.day), '--class', 'v5', '--state', state, '--genesis-day', String(j.genesis_day), '--genesis-log2', String(j.genesis_log2)], { encoding: 'utf8' }) : null; + const em = eng ? /attempt (\d+) id ([0-9a-f]{16})/.exec(eng.stdout || '') : null; + const row = { seed: j.epoch_hex.slice(0, 16), expected: j.expected, cli: cm ? { attempt: +cm[1], id: cm[2] } : null, engine: em ? { attempt: +em[1], id: em[2], binary: pidBin } : null, engine_refusal: eng && !em ? (eng.stderr || '').trim().slice(0, 200) : null }; + row.equal = !!(cm && em && cm[1] === em[1] && cm[2] === em[2] && +cm[1] === j.expected.attempt && cm[2] === j.expected.id); + log(`ATTEMPT-3 SEED ${row.seed}: cli ${cm ? `attempt ${cm[1]} id ${cm[2]}` : 'UNREAD'} | engine ${em ? `attempt ${em[1]} id ${em[2]}` : `UNREAD${row.engine_refusal ? ` (${row.engine_refusal})` : pidBin ? '' : ' (no program-id binary)'}`} | expected attempt ${j.expected.attempt} id ${j.expected.id} | ${row.equal ? 'EQUAL' : 'DIFFER'}`); + return row; + } catch (e) { log(`ATTEMPT-3 SEED: ${e.message}`); return { equal: false, error: e.message }; } +})(); +checks.attempt_3_seed_reads_equal = !!a3.equal; const pass = Object.values(checks).every(Boolean); const summary = { + attempt_3_seed: a3, pass, expect: EXPECT, signals: SIGNAL, checks, window: WINDOW, windows: WINDOWS, floor: FLOOR ?? 'never', v4_floor: V4_FLOOR ?? 'never', v3_activation: V3_ACTIVATION ?? 'never', epoch_blocks: EPOCH, lead: LEAD, first_full_window_epoch: FIRST_FULL_EPOCH, floor_epoch: FLOOR_EPOCH, stale_miner: STALE, stale_accepted_after_first_refresh: staleAcceptedAfterRefresh, honest_accepted_after_flip: honestAcceptedAfterFlip, stale_rejected: staleRejected, stateless_node: statelessNode ? statelessNode.i : null, stateless_accepted_after_flip: statelessAcceptedAfterFlip, stateless_refusal_lines: statelessRefusals, state_roots_at_flip: rootsAtFlip, streams, days_seen: daysSeen, day_boundary_crossed: daysSeen.length > 1,