diff --git a/docs/bench-log.md b/docs/bench-log.md index 681390749..06e743c57 100644 --- a/docs/bench-log.md +++ b/docs/bench-log.md @@ -2651,3 +2651,24 @@ gate script `tools/fleet/base-unit-gate.sh` at 72b02b48, run by the fleet lane o Consequences: the layout, the switches and the bridge hold at 18 decimals on real GPU blocks; no tier changes (docs/design/base-unit.md section 5 stands). The first run of the form read 165 of 561 rewards one ramp step low because the gate priced every reward at the chain block's DAA while the object credits each merged block its own (subsidy_per_block_activation_daa 0): the script, not the node. + +## 7 October 2026, 11:12 UK: igneum-testnet-1 re-cut, the object at fast time (devnet-suffix, 5 nodes) PASS + +`infra/fast-time/testnet-object.mjs` run 10 on igneum-build-1 (the archive binaries, fork `testnet-genesis-2-node` 5c25c1fb; network +igneum-devnet-973 on the 60x profile with every switch of `infra/seed-nodes/testnet-object.json` from genesis, the fast-time finality +profile, leave delay 60; four voting CPU miners and one `--no-vote` key; 600 s, the leave at 240 s). + +| Check | Result | +|---|---| +| start-up lines, one digest | every node: ladder active at rung 0, fees v1 from DAA 0, verification from DAA 0 under the pinned ids, rule v3 and C1 from DAA 0, the leave rule from DAA 0 | +| class v4 at rung 0 | ten epochs, 27 shadow passes, no ladder bits on the chain, object byte 0 | +| the chain | 551 blocks, 525 chain blocks, 0 rejected, one sink on five nodes | +| finality | first lock at DAA 142 (172 s), lock 18 at the end, active | +| the signing bonus (UTXO side) | the silent key's 87 blocks at 72 percent of each block's own subsidy; the voters' 320 at 80 percent | +| the bridge identity | UTXO payments x 10^10 = execution credits for all five miners over the chain (the tip's own credit set aside) | +| the leave item | accepted at DAA 212, voters 5 to 4 at DAA 351 (within the delay and a window), longest pause after it 1 s | + +Consequences: a miner that stops signing while in the table earns 72 percent on both ledgers, never 80 on one and 72 on the other (the +N7 fix, c7ea1e21); a side block merged one DAA late is credited its own subsidy on both ledgers (the N8 fix, d840537b, from genesis); +a miner that leaves cleanly is out of every denominator an hour after its leave is carried on the testnet (60 DAA here) and holds +nothing; nothing changes for hash rate, power or any tier's hardware. diff --git a/docs/plans/testnet-go.md b/docs/plans/testnet-go.md index c09becfaa..f0c8d4b8e 100644 --- a/docs/plans/testnet-go.md +++ b/docs/plans/testnet-go.md @@ -119,7 +119,7 @@ Mac: 27 passed (the crate is not in the PC build inputs). The packager's self-te | 9c | Mission item 8: the genesis forward-compatibility fields (the sig_scheme byte, the W5 key-succession item, the cache rung on the ladder), lane `genesis-forward`; the hash and digest above move once more when they land | lane `genesis-forward`, then this lane re-cuts | NOT DONE | | 9d | Windows: `igneum-prove-host` beside every Windows node (verification from genesis); PC 1's node refuses to start without it | the release engineer (the 0.4.0 package) | NOT DONE: to check in the 0.4.0 Windows installer | | 9h | The two-node 18-decimal gate on the re-cut binaries (`tools/fleet/base-unit-gate.sh` 72b02b48, the GPU form: RunPod RTX 3090, driver 580.65.06, the hive CUDA worker, rented and run by the fleet lane): PASS at 10:08 UK, 7 October 2026, on igneumd 8a6f1f56... and igneum-miner 726cf290... (fork commit f1717419): 577 blocks in 600 s, 421 chain blocks, both nodes on one sink and one exec tip (0x1a5), the 30 inspected coinbase payloads all the 18-decimal schedule (day-0 block 10 IGN = 10^19 base units), 80/20 exact on 18 of 18 single-payee coinbases, 577 segment rewards checked against the schedule at each blue block's own DAA with 0 wrong, eth_getBalance 4,617.53654629 IGN (4,617,536,546,296,296,296,298 wei) equal on both nodes and to the sum of the rewards; both node heads carry `Base unit: 10^18`, the digest `80af8aa1...`, the ladder active at rung 0, fees v1 from DAA 0 and proof verification from DAA 0 under the pinned ids. The first run of the same form (09:4x UK) read 165 of 561 rewards one ramp step low: the gate's check 5 still priced every reward at the chain block's DAA, fixed in 72b02b48 (each blue block its own DAA, the per-block rule). On the testnet object itself no voter exists inside a run (the 30-day window), so this line shows the layout, the switches and the identity without the bonus; the devnet-suffix line (9g) is the proof of the identity under the bonus | the fleet lane ran it, this lane reads it | PASS; the known-failed form (GATE_EXPECT_DECIMALS=8, the 8-decimal schedule against the same chain, 420 s) FAILED as it must at 10:15 UK: payload subsidies wrong 30 of 30, every segment reward wrong; the pod destroyed on this lane's done line | -| 9g | The fast-time line of the object on a devnet-suffix network (`infra/fast-time/testnet-object.mjs`, 5 nodes, four voting keys and one `--no-vote` key, the fast-time finality profile with leave delay 60): the start-up lines, class v4 at rung 0 through ten epochs, no ladder bits, one sink, the first lock, a signed leave accepted and the voter count dropping once the frozen table refreshes, the bonus split on the UTXO side for the silent key and the plain split for the voters, the bridge identity per miner over the whole chain (the chain block paid by its child's coinbase and credited in its own record). Runs so far on the fixed binaries: the facts of the first ten-minute runs are in the lane's report; the pass line is the run under way | this lane | RUNNING (10:2x UK) | +| 9g | The fast-time line of the object (`infra/fast-time/testnet-object.mjs`, a devnet-suffix network on igneum-build-1 at the 60x profile: 5 nodes, four voting keys and one `--no-vote` key, every switch of `testnet-object.json` from genesis, the fast-time finality profile with leave delay 60, the testnet schedule at the devnet's unit): PASS at 11:12 UK, 7 October 2026, run 10 on the archive binaries (igneumd 57ad7dc2..., fork 5c25c1fb): the start-up lines and one digest on every node; class v4 at rung 0 (27 passes) through ten epochs, no ladder bits, the object byte 0 on 551 blocks; 0 rejected, one sink on five nodes; the first lock at DAA 142 (172 s); the silent key's 87 blocks paid the bonus split on the UTXO side (72 percent of the subsidy at each block's own DAA, 720,118,333 sompi at DAA 142 against 800,131,481 for a voter) and the voters' 320 blocks the plain split; the bridge identity exact for all five miners over the chain (UTXO payments x 10^10 = execution credits less the tip's own: 96,832,949,992 sompi against 968,329,499,920,000,000,000 wei for voter a, and so on); a signed leave accepted at DAA 212, its key out of the voter count (5 to 4) at DAA 351, no pause longer than one second after it, finality active at the end at lock 18. The road to it: run 1 (pre-fix binaries) showed the executor crediting 80 percent to the silent key while the coinbase paid 72 (the N7 finding), run 7 the side blues credited one ramp step high when the per-block switch was left out of the profile (the N8 shape), run 9 every check green but the harness's own pause reading (one-second reason flickers at each new lock, corrected to consecutive seconds). On the testnet object itself the weight window is 30 days, so this line is the proof of the bonus and the identity before the go | this lane | PASS (the known-failed form, the pre-fix binaries at `--expect bonus-fails`, for the record) | | 10 | The first miner: one app on the testnet (PC 1 or PC 2 with the 0.4.0 build, or `igneumd --testnet` plus `igneum-miner --network testnet` by hand) produces block 1; the seeds relay it, `health.sh` shows blocks=1 on all three, `synced=True`; note the young-window join fault: a node that joins a chain younger than its finality window sees `synced=False` until blocks pass genesis, which is the no-blocks state, not a fault | the project lead says go, the miner-community lead starts it | NOT DONE: nothing mines until the word | | 11 | Watch: `NET=testnet ./health.sh --watch`, the RPC's `eth_blockNumber`, the DAA after 600 blocks (the launch difficulty `0x1d100000` is sized for a few hundred MH/s) | the infrastructure engineer | ready | diff --git a/infra/fast-time/testnet-object.mjs b/infra/fast-time/testnet-object.mjs index 376296569..9cf41b4a1 100644 --- a/infra/fast-time/testnet-object.mjs +++ b/infra/fast-time/testnet-object.mjs @@ -92,6 +92,7 @@ const switches = { latency_ladder_window_daa: String(LADDER_WINDOW), fees_v1_activation_daa: num(objectText, 'fees_v1_activation_daa'), proving_consensus_verify_daa: num(objectText, 'proving_consensus_verify_daa'), + subsidy_per_block_activation_daa: num(objectText, 'subsidy_per_block_activation_daa'), proving_shard_program_id: JSON.stringify(str(objectText, 'proving_shard_program_id')), proving_aggregator_id: JSON.stringify(str(objectText, 'proving_aggregator_id')), }; @@ -270,6 +271,7 @@ async function allBlocks(n) { } let blocks = []; try { blocks = await allBlocks(n0); } catch (e) { log(`getBlocks: ${e.message}`); } +const chainBlocks = blocks.filter(b => b.chain && b.daa > 0).sort((a, b) => a.daa - b.daa); const segments = []; const tip = Number(BigInt(await n0.evm('eth_blockNumber', []).catch(() => '0x0'))); for (let i = 1; i <= tip; i++) { const s = await n0.evm('igneum_getSegment', [`0x${i.toString(16)}`]).catch(() => null); if (s) segments.push(s); } @@ -392,6 +394,14 @@ const counts = dag.map(d => d.blockCount ?? '?'); const accepted = LABELS.map((_, i) => minerLog(i).filter(l => /ACCEPTED block/.test(l)).length); const rejectedNode = nodes.map(n => n.grepLog(/PoW rejected|Rejected block|rejected block/i).length); const finalReports = await Promise.all(nodes.map(n => report(n, 3))); +// the report's reason flickers for one read at every new lock (the table frozen at the lock reads 0.00 percent signing +// for a second); a pause is consecutive inactive reads, measured in seconds of the 1-s poll, never one read +function longestInactiveStretchAfter(daa) { + const xs = inactiveAfterLeave.filter(x => x.daa > daa).map(x => x.at).sort((a, b) => a - b); + let best = 0, start = null, prev = null; + for (const t of xs) { if (prev == null || t - prev > 2.5) { start = t; } prev = t; best = Math.max(best, t - start + 1); } + return best; +} const checks = { start_lines_on_every_node: nodes.every(n => Object.entries(START_LINES).every(([k, re]) => k === 'class_v4' || n.grepLog(re).length > 0)), digest_equal_on_every_node: new Set(nodes.map(n => (n.grepLog(START_LINES.digest)[0] || '').replace(/^.*?digest: /, '').slice(0, 64))).size === 1, @@ -409,7 +419,9 @@ const checks = { leave_took_effect_within_delay_and_a_window: leaveSeenAt != null && leaveSent != null && (leaveSeenAt.daa - leaveSent.daa) <= LEAVE_DELAY + 120 + 30 + 5, // the point of the leave item: a clean departure never holds finality for a window; the pause after the leave, if any, // ends inside the delay plus one checkpoint, never a full window (120 DAA at fast time) - no_window_pause_after_the_leave_took_effect: leaveSeenAt != null && !inactiveAfterLeave.some(x => x.daa > leaveSeenAt.daa + 30), + // rule v3's share swings per checkpoint while CPU voters sign late; what the leave item buys is that a clean departure + // never holds finality for a window: no inactive stretch of a window (120 DAA) after the leave took effect + no_window_pause_after_the_leave_took_effect: leaveSeenAt != null && longestInactiveStretchAfter(leaveSeenAt.daa) < 120, // seconds: one block a second at fast time, so a window of 120 DAA is 120 s finality_active_at_the_end: finalReports.every(r => r && r.finalityActive === true), }; let pass; @@ -422,7 +434,7 @@ if (EXPECT === 'bonus-fails') { const summary = { pass, expect: EXPECT, checks, node: IGNEUMD, miner: CPU_MINER, override, run_secs: SECS, final_daa: lastDaa, epochs: Object.fromEntries([...epochs.entries()]), first_lock: firstLock, - leave: { sent: leaveSent, outcome: leaveOutcome, voters_before: votersBeforeLeave, voters_after: votersAfterLeave, seen_at: leaveSeenAt, delay_daa: LEAVE_DELAY, inactive_after: inactiveAfterLeave }, + leave: { sent: leaveSent, outcome: leaveOutcome, voters_before: votersBeforeLeave, voters_after: votersAfterLeave, seen_at: leaveSeenAt, delay_daa: LEAVE_DELAY, longest_inactive_stretch_secs_after_effect: leaveSeenAt ? longestInactiveStretchAfter(leaveSeenAt.daa) : null, inactive_after: inactiveAfterLeave }, blocks: { total: blocks.length, chain: chainBlocks.length, segments: segments.length, object_bytes: objectBytes, ladder_bits: ladderBits }, bridge_rows: bridgeRows, bonus_rows: bonusRows, accepted_per_miner: accepted, rejected_by_nodes: rejectedNode, sinks, block_counts: counts, samples, final_reports: finalReports.map(r => r && { active: r.finalityActive, reason: r.finalityReason, locked: r.latestLockedIndex, voters: r.checkpoints?.at(-1)?.voters ?? null }), diff --git a/infra/fast-time/tn-prover-loop.mjs b/infra/fast-time/tn-prover-loop.mjs index 53f31f331..7c6363f18 100644 --- a/infra/fast-time/tn-prover-loop.mjs +++ b/infra/fast-time/tn-prover-loop.mjs @@ -67,6 +67,7 @@ for (;;) { log(`RESULT export number=${number} secs=${ex.secs}`); const results = `${TMP}/results-${number}-${target.shard}.json`; const pr = run(HOST, [fixture, '--mode', 'compressed', '--shard', String(target.shard), '--prover', PAYOUT, '--out', results], { SP1_PROVER: 'cpu', RUST_LOG: 'off' }); + writeFileSync(`${TMP}/prove-${number}-${target.shard}.log`, pr.out); if (pr.code !== 0) { log(`RESULT prove_failed number=${number} secs=${pr.secs} tail=${pr.out.split('\n').filter(l => /RESULT|rror/.test(l)).slice(-3).join(' | ').slice(0, 400)}`); continue; } const res = JSON.parse(readFileSync(results, 'utf8')); log(`RESULT prove number=${number} shard=${target.shard} secs=${pr.secs} cycles=${res.cycles} proof_bytes=${res.compressed_proof_bytes}`);