diff --git a/tools/verify-anchor.mjs b/tools/verify-anchor.mjs index fc0b91f..bb83f6e 100644 --- a/tools/verify-anchor.mjs +++ b/tools/verify-anchor.mjs @@ -31,8 +31,9 @@ // round r (2026-09-25, D-406), on the most recent headers (PROPOSER_HEADERS, default 20); // and, as the nodes judge every header from H_hdr, the round is a canonical integer // at most 1024 and the coinbase is the proposer of some round 0..r at that height. -// Where no header carries it (chain 2800 below its stage 5 height) the tool says so and this leg does not -// change the verdict. +// Where no header carries it BELOW the chain's stage 5 height H_hdr the tool says so and this leg does not +// change the verdict; from H_hdr (25,080,000 on 2800, 3,131,000 on 28001; HEADER_SEAL_BLOCK=) +// a header without it is a FAIL, as the nodes refuse it (2026-10-31, D-401). // 7. ROUND (1(b), D-470, learned 2026-10-07; active on testnet 28001 since block 6,157,024 and on chain 2800 since // block 24,148,752, 2026-10-24) from a chain's round height H_r the certificate is // v3, RLP[3, round, [[scheme, idx, sig], ...]] under "AERE-PQ-ANCHOR-3", and every seal in it is over @@ -156,6 +157,16 @@ const ANCHOR_V2_BLOCK = process.env.ANCHOR_V2_BLOCK === undefined ? ANCHOR_V2_DE if (ANCHOR_V2_BLOCK !== null && !(Number.isInteger(ANCHOR_V2_BLOCK) && ANCHOR_V2_BLOCK > 0)) { console.error('FAIL: ANCHOR_V2_BLOCK must be a positive block height or "none"'); stop(1); } // H_r is above the v2 height on every chain that has one; both clients refuse any other configuration at startup (ROUND-01) if (COMMIT_ROUND_BLOCK !== null && (ANCHOR_V2_BLOCK === null || COMMIT_ROUND_BLOCK <= ANCHOR_V2_BLOCK)) { console.error('FAIL: COMMIT_ROUND_BLOCK=' + COMMIT_ROUND_BLOCK + ' needs the v2 height below it (ANCHOR_V2_BLOCK is ' + (ANCHOR_V2_BLOCK === null ? 'unset' : ANCHOR_V2_BLOCK) + '); both clients refuse that configuration at startup'); stop(1); } +// H_hdr per chain (2026-10-31, D-401): AIP-22 stage 5, the height from which every header carries element 6 (the proposer's Falcon-512 +// seal) and both clients refuse a header without it. Testnet 28001 from block 3,131,000 (2026-09-23), chain 2800 from block 25,080,000 +// (2026-10-30). Until this date this tool did not know H_hdr and counted a header without element 6 as "before stage 5" at ANY height; +// element 6 is outside the block hash, so an endpoint that cut it changed nothing the hash checks, and on chain 2800 above H_hdr the +// tool printed "stage 5 is not active at these heights" and exited 0. HEADER_SEAL_BLOCK overrides (a height, or "none"); another chain +// has none. +const HEADER_SEAL_DEFAULT = CHAIN_ID === 2800 ? 25080000 : CHAIN_ID === 28001 ? 3131000 : null; +const HEADER_SEAL_BLOCK = process.env.HEADER_SEAL_BLOCK === undefined ? HEADER_SEAL_DEFAULT + : (/^(none|unset|)$/i.test(process.env.HEADER_SEAL_BLOCK) ? null : Number(process.env.HEADER_SEAL_BLOCK)); +if (HEADER_SEAL_BLOCK !== null && !(Number.isInteger(HEADER_SEAL_BLOCK) && HEADER_SEAL_BLOCK > 0)) { console.error('FAIL: HEADER_SEAL_BLOCK must be a positive block height or "none"'); stop(1); } // the form the certificate of anchor a must have (PROIECT-1B 2.1, certificateFormAt in both clients): 3 from H_r, 2 from V, 1 below V const certificateFormAt = (a) => (COMMIT_ROUND_BLOCK !== null && a >= COMMIT_ROUND_BLOCK ? 3 : ANCHOR_V2_BLOCK !== null && a >= ANCHOR_V2_BLOCK ? 2 : 1); const POP_DOMAIN = 'AERE-PQ-POP-1'; @@ -1100,7 +1111,11 @@ let propOk = 0, propBad = 0, propUnmeasured = 0, propAbsent = 0, propReproposed const b = rec.b; const ex = hex(b.extraData); const items = rlpRead(ex, 0).item; - if (items.length < 7) { propAbsent++; continue; } + if (items.length < 7) { + // D-401 (2026-10-31): from H_hdr a header without element 6 is refused by both clients, so here it is a FAIL, never "before stage 5" + if (HEADER_SEAL_BLOCK !== null && h >= HEADER_SEAL_BLOCK) { propBad++; console.log(h + ' FAIL: AIP-22 stage 5 is in force on chain ' + CHAIN_ID + ' from block ' + HEADER_SEAL_BLOCK + ', and this header carries no proposer seal (element 6 is missing); the nodes refuse such a header: not admitted'); continue; } + propAbsent++; continue; + } const onchain = keccak(headerRlp(b, rlpEncode([nested(items[0]), nested(items[1]), nested(items[2]), Buffer.alloc(0), []]))); if (!onchain.equals(hex(b.hash))) { propUnmeasured++;