class v5 harness: the stale miner's fall-off is judged by the block's DAA score (a nonce found on a pre-boundary template and submitted after it is a flip-epoch block, valid; the wall-clock count failed the 20:4x UK run on one such block), nonces matched as the nearest double the JSON RPC carries

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
igneum-labs 2026-10-07 19:51:43 +00:00
parent d1ca2416fc
commit 6cf648e6ae

View file

@ -237,7 +237,7 @@ async function allBlocks(n) {
const r = await n.rpc.call('getBlocks', { lowHash: low, includeBlocks: true, includeTransactions: false });
const blocks = r.blocks || [];
let added = 0;
for (const b of blocks) { const h = b.verboseData?.hash || b.header?.hash; if (seen.has(h)) continue; seen.add(h); out.push({ hash: h, daa: +b.header.daaScore, version: +b.header.version, chain: !!b.verboseData?.isChainBlock }); added++; }
for (const b of blocks) { const h = b.verboseData?.hash || b.header?.hash; if (seen.has(h)) continue; seen.add(h); out.push({ hash: h, daa: +b.header.daaScore, version: +b.header.version, nonce: Number(b.header.nonce ?? 0), chain: !!b.verboseData?.isChainBlock }); added++; }
if (!blocks.length || added === 0) break;
low = (r.blockHashes || []).at(-1) || blocks.at(-1).verboseData?.hash; if (!low) break;
}
@ -332,9 +332,30 @@ const statelessAcceptedAfterFlip = statelessNode ? acceptedAfter(statelessNode.i
const honestAcceptedAfterFlip = SIGNALLING.filter(n => n.i !== STALE).map(n => acceptedAfter(n.i, flipAtMs));
// the refusal reaches the miner as a template error and the node's log as the engine's line; either counts
const statelessRefusals = statelessNode ? statelessNode.grepLog(REFUSAL_LINE).length + minerLog(statelessNode.i).filter(l => REFUSAL_LINE.test(l)).length : 0;
// the stale miner: accepted after the FIRST REFRESH after the flip (one epoch later), and its rejections
const refreshWall = flipAtMs == null ? null : flipAtMs + EPOCH * 1000;
const staleAcceptedAfterRefresh = STALE == null ? null : acceptedAfter(STALE, refreshWall);
// the stale miner: accepted blocks IN the first refresh epoch after the flip or later (its frozen leaves are the flip
// epoch's; from the next epoch its blocks fail PoW), and its rejections. Judged by the block's DAA score, not the wall
// clock: a nonce found on a template taken before the boundary and submitted a few seconds after it is a flip-epoch
// block (the 20:4x UK run of 7 October 2026 read one such ACCEPTED line, "template 2 gens old", 4.7 s after the
// boundary's wall time, and the wall-clock form of this count failed the case on a block that was valid). The miner's
// ACCEPTED line carries the nonce; the chain carries the nonce and the DAA score of every block.
const refreshDaa = firstV4 ? (firstV4.epoch + 1) * EPOCH : null;
// the JSON RPC carries the 64-bit nonce as a JSON number, so the node's value arrives as the nearest double; Number(BigInt(hex))
// of the miner's line is the nearest double to the same integer, so the two keys are equal (660 blocks never share one)
const blockByNonce = new Map(blocks.map(b => [b.nonce, b]));
function acceptedAtOrAboveDaa(i, daa) {
if (daa == null) return 0;
let count = 0, unmatched = 0;
for (const l of minerLog(i)) {
const m = /ACCEPTED block nonce=(0x[0-9a-f]+)/.exec(l);
if (!m) continue;
const b = blockByNonce.get(Number(BigInt(m[1])));
if (!b) { unmatched++; continue; }
if (b.daa >= daa) count++;
}
if (unmatched) log(`miner ${i}: ${unmatched} ACCEPTED nonce(s) not found on the chain read from n0 (pruned or a side block)`);
return count;
}
const staleAcceptedAfterRefresh = STALE == null ? null : acceptedAtOrAboveDaa(STALE, refreshDaa);
const staleRejected = STALE == null ? null : rejectedMiner[STALE].length;
const daysSeen = [...new Set([...dayOfEpoch.values()])];
const sinks = dag.map(d => String(d.sink || '?').slice(0, 16));