diff --git a/infra/fast-time/class-v3.mjs b/infra/fast-time/class-v3.mjs index a4cb213f0..69fd8f685 100644 --- a/infra/fast-time/class-v3.mjs +++ b/infra/fast-time/class-v3.mjs @@ -41,12 +41,23 @@ 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); } rmSync(TMP, { recursive: true, force: true }); mkdirSync(TMP, { recursive: true }); -const base = JSON.parse(readFileSync(FILE, 'utf8')); -const EPOCH = base.pow_epoch_blocks; +// The file is merged as TEXT, never through JSON.parse: a `never` height is 18446744073709551615, which JavaScript +// rounds to 1.8446744073709552e+19 and the node refuses ("expected u64"; the first gate run, 5 October 2026 21:30Z). +// The merged fields are appended after the file's last field; a field already in the file is removed first. +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'); +const DAY_MS = field('pow_day_ms'); const FIRST_V3_EPOCH = Math.ceil(ACTIVATION / EPOCH); const BOUNDARY = FIRST_V3_EPOCH * EPOCH; const override = `${TMP}/override.json`; -writeFileSync(override, JSON.stringify({ ...base, genesis_bits: GENESIS_BITS, skip_proof_of_work: false, program_class_v3_activation_daa: ACTIVATION })); +export function mergeOverrideText(text, fields) { + let out = text; + for (const k of Object.keys(fields)) out = out.replace(new RegExp(`\\s*"${k}":\\s*[^,}\\n]+,?`), ''); + const extra = Object.entries(fields).map(([k, v]) => `"${k}": ${typeof v === 'string' ? JSON.stringify(v) : v}`).join(', '); + return out.replace(/,?\s*}\s*$/, `,\n ${extra}\n}\n`); +} +writeFileSync(override, mergeOverrideText(baseText, { genesis_bits: GENESIS_BITS, skip_proof_of_work: false, program_class_v3_activation_daa: ACTIVATION })); log(`activation ${ACTIVATION} at ${EPOCH} DAA per epoch: the first v3 epoch is ${FIRST_V3_EPOCH} (DAA ${BOUNDARY}); run ${SECS} s, CPU genesis bits 0x${GENESIS_BITS.toString(16)}`); class Node { @@ -99,7 +110,7 @@ log(`n0 PoW schedule: ${n0.grepLog(/PoW schedule from the override file/).map(l log(`n0 digest: ${n0.grepLog(/Consensus params digest/).map(l => l.replace(/^.*?digest: /, '').slice(0, 16)).join(' ')}`); // one real CPU miner per node, 1 thread, 2^16 expected hashes per block at genesis -nodes.forEach((n, i) => miner(CPU_MINER, ['mine', n.grpc, String(THREADS), String(SECS), `cpu${i}`, '--engine', 'igneum-pow', '--payout-label', `cpu${i}`, '--status-secs', '30', '--no-vote'], `cpu${i}`, { IGNEUM_POW_DAY_MS: String(base.pow_day_ms) })); +nodes.forEach((n, i) => miner(CPU_MINER, ['mine', n.grpc, String(THREADS), String(SECS), `cpu${i}`, '--engine', 'igneum-pow', '--payout-label', `cpu${i}`, '--status-secs', '30', '--no-vote'], `cpu${i}`, { IGNEUM_POW_DAY_MS: String(DAY_MS) })); const pay = devAddress('fast-time-v3'); const epochs = new Map(); // epoch index -> { class, firstSeenDaa, at } diff --git a/infra/fast-time/simnet.mjs b/infra/fast-time/simnet.mjs index f70fc7d53..0f7156529 100644 --- a/infra/fast-time/simnet.mjs +++ b/infra/fast-time/simnet.mjs @@ -35,7 +35,9 @@ for (const b of [IGNEUMD, VMINE]) if (!existsSync(b)) { console.error(`missing $ rmSync(TMP, { recursive: true, force: true }); mkdirSync(TMP, { recursive: true }); const override = `${TMP}/override.json`; -writeFileSync(override, JSON.stringify({ ...JSON.parse(readFileSync(FILE, 'utf8')), skip_proof_of_work: true })); +// merged as text, not through JSON.parse: a `never` height (18446744073709551615) does not survive a JavaScript number +// (the class-v3.mjs gate run of 5 October 2026 21:30Z: "invalid type: floating point 1.8446744073709552e+19") +writeFileSync(override, readFileSync(FILE, 'utf8').replace(/\s*"skip_proof_of_work":\s*[^,}\n]+,?/, '').replace(/,?\s*}\s*$/, ',\n "skip_proof_of_work": true\n}\n')); class Node { constructor(i, connect = []) {