igneum/site/bench.html
igneum-labs d783b13733 Fast time: the devnet at 60x for test networks (override-60x.json, --fast-time in both harnesses, proof script)
infra/fast-time/override-60x.json is the devnet with every clock-like consensus parameter divided by 60 and every
block count unchanged (finality window, ban and min_daa 120 DAA; merge depth 60; Kaspa finality depth 720; pruning
depth at the anticone bound 13,838; coinbase maturity 2; the hourly program epoch 60 blocks with a 10-block lead;
the dataset day 24 minutes). The epoch length, lead and day are consensus parameters of the node since devnet-v4
a5ef8b07, carried by the override file. README lists each field, why it scales or not, the flags and the numbers.

Measured (simnet.mjs, three devnet-v4 nodes, three vmine voters at 1 block/s, one real-hash CPU miner): next
epoch seed in the template at 56.1 s, program swap at 65.1 s wall (DAA 60), first finality lock at 185.5 s wall
(checkpoint 5, DAA 149). Both harnesses take --fast-time: finality-attacks s3 PASS in 113 s wall with 16 locks
per node (the devnet rule needs 20 min of warm-up at 6 blocks/s before any lock); harness s3 partition and heal
43 s wall for three cuts against 983 s for four on the devnet profile with the same binary. Bench-log entry.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-04 10:34:19 +00:00

243 lines
No EOL
204 KiB
HTML

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<title>Igneum engineering log</title>
<meta name="description" content="Every Igneum benchmark and test, with the hardware and the commands that produced it. Prototype numbers are labelled as such.">
<link rel="canonical" href="https://igneum.network/bench">
<meta name="theme-color" content="#0C0C0E">
<meta property="og:type" content="website">
<meta property="og:site_name" content="Igneum">
<meta property="og:title" content="Igneum engineering log">
<meta property="og:description" content="Every Igneum benchmark and test, with the hardware and the commands that produced it. Prototype numbers are labelled as such.">
<meta property="og:url" content="https://igneum.network/bench">
<meta property="og:image" content="https://igneum.network/og.png">
<meta property="og:image:width" content="256">
<meta property="og:image:height" content="256">
<meta property="og:image:alt" content="Igneum. Mined by GPUs. Proven by fire.">
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="Igneum engineering log">
<meta name="twitter:description" content="Every Igneum benchmark and test, with the hardware and the commands that produced it. Prototype numbers are labelled as such.">
<meta name="twitter:image" content="https://igneum.network/og.png">
<meta name="twitter:image:alt" content="Igneum. Mined by GPUs. Proven by fire.">
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Ccircle cx='50' cy='50' r='48' fill='%230C0C0E'/%3E%3Ccircle cx='50' cy='50' r='45' fill='none' stroke='%23F2541B' stroke-width='6'/%3E%3Cg transform='translate(24 24) scale(0.52)'%3E%3Cpolygon points='50,4 74,34 67,58 80,54 61,96 39,96 20,54 33,58 26,34' fill='%23F2541B'/%3E%3Cpolygon points='50,42 59,58 50,82 41,58' fill='%230C0C0E'/%3E%3C/g%3E%3C/svg%3E" type="image/svg+xml">
<link rel="icon" href="/favicon.ico" sizes="48x48">
<link rel="icon" href="/icon-192.png" type="image/png" sizes="192x192">
<link rel="icon" href="/icon-512.png" type="image/png" sizes="512x512">
<link rel="apple-touch-icon" href="/apple-touch-icon.png" sizes="180x180">
<link rel="manifest" href="/site.webmanifest">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Unbounded:wght@500;700;900&family=IBM+Plex+Sans:wght@400;500;600&family=IBM+Plex+Mono:wght@400;500&display=swap">
<style>
:root{--obsidian:#0C0C0E;--graphite:#16161A;--line:#2A2A30;--ember:#F2541B;--molten:#FFB35C;--bone:#F4F1EC;--ash:#9A9A9E;--ink-2:#C9C7C2;
/* one system for every page: container, gutter, section rhythm, card and tile boxes, heading scale (same tokens as the homepage) */
--max:1200px;--gutter:clamp(16px,4vw,32px);--sec:clamp(56px,8vw,96px);--head:clamp(40px,6vw,64px);
--card-pad:clamp(18px,3vw,28px);--card-r:18px;--tile-pad:18px 20px;--tile-r:14px;--gap:24px;--gap-tile:12px;
--fs-h1:clamp(32px,5.5vw,56px);--fs-h2:clamp(28px,4.2vw,44px);--fs-h3:clamp(18px,2vw,22px);--fs-tile:clamp(20px,2.2vw,26px)}
*{box-sizing:border-box}body{margin:0;background:var(--obsidian);color:var(--bone);font-family:'IBM Plex Sans',system-ui,sans-serif;font-size:16px;line-height:1.6;padding:0}
a{color:var(--ember);text-decoration:none}a:hover{text-decoration:underline}
.wrap{max-width:var(--max);margin:0 auto;padding-inline:var(--gutter)}
header{display:flex;flex-wrap:wrap;justify-content:space-between;align-items:center;gap:16px;padding-block:18px;border-bottom:1px solid var(--line)}
.brand{display:flex;align-items:center;gap:10px;color:var(--bone)}.brand b{font-family:'Unbounded',sans-serif;font-weight:900;letter-spacing:.06em;font-size:18px}
header nav{display:flex;flex-wrap:wrap;gap:18px;font-size:14px}header nav a{color:var(--ink-2)}
h1{font-family:'Unbounded',sans-serif;font-weight:900;font-size:var(--fs-h1);line-height:1.05;margin:var(--head) 0 12px}
.note{color:var(--ash);font-size:15px;max-width:72ch;margin-bottom:32px}
.layout{display:grid;grid-template-columns:minmax(0,1fr);gap:32px;padding-bottom:var(--sec)}
@media(min-width:960px){.layout{grid-template-columns:240px minmax(0,1fr);gap:56px}}
.toc{align-self:start;font-size:14px}@media(min-width:960px){.toc{position:sticky;top:24px}}
.toc ol{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:2px}.toc a{display:block;padding:7px 10px;border-radius:8px;color:var(--ink-2)}.toc a:hover{background:var(--graphite);text-decoration:none;color:var(--bone)}
article{max-width:78ch;min-width:0}
article h1{display:none}
article h2{font-family:'Unbounded',sans-serif;font-weight:700;font-size:var(--fs-h3);line-height:1.3;margin:44px 0 12px;padding-top:24px;border-top:1px solid var(--line)}
article h2:first-of-type{margin-top:0;padding-top:0;border-top:0}
article h3{font-family:'Unbounded',sans-serif;font-weight:500;font-size:16px;margin:26px 0 8px}
article h4{font-size:15px;margin:20px 0 6px;color:var(--molten)}
article p{margin:0 0 14px}article ul,article ol{margin:0 0 14px;padding-left:22px}article li{margin-bottom:6px}
article p,article li,article h2,article h3{overflow-wrap:anywhere}
code{font-family:'IBM Plex Mono',monospace;font-size:.92em;background:var(--graphite);padding:1px 5px;border-radius:4px;overflow-wrap:anywhere}
pre{margin:0 0 16px;padding:16px 18px;background:var(--graphite);border-radius:var(--card-r);font-family:'IBM Plex Mono',monospace;font-size:13px;line-height:1.55;overflow-x:auto}
.tbl{overflow-x:auto;margin:18px 0 24px;border:1px solid var(--line);border-radius:var(--card-r);background:var(--graphite)}
table{border-collapse:collapse;width:100%;min-width:560px;font-size:15px}th,td{padding:12px 14px;text-align:left;vertical-align:top;border-bottom:1px solid var(--line)}
th{font-family:'IBM Plex Mono',monospace;font-size:12px;letter-spacing:.12em;text-transform:uppercase;color:var(--ash);font-weight:500;background:var(--obsidian)}tr:last-child td{border-bottom:0}
footer{border-top:1px solid var(--line);padding-block:32px 48px;font-size:13px;color:var(--ash)}
</style>
</head>
<body>
<div class="wrap">
<header><a class="brand" href="/"><svg viewBox="0 0 100 100" width="26" height="26" aria-hidden="true"><polygon points="50,4 74,34 67,58 80,54 61,96 39,96 20,54 33,58 26,34" fill="#F2541B"></polygon><polygon points="50,42 59,58 50,82 41,58" fill="#0C0C0E"></polygon></svg><b>IGNEUM</b></a><nav><a href="/">Home</a><a href="/litepaper">Litepaper</a><a href="/live">Live devnet</a><a href="/bench">Engineering log</a><a href="/evidence">Evidence</a><a href="https://github.com/igneum-network/spec" rel="noopener">GitHub, spec and vectors</a></nav></header>
<h1>Engineering log</h1>
<p class="note">Every measurement the project has made, newest at the bottom, written by the people and agents who ran it, with the commands and hardware. Prototype numbers are not mining numbers and say so.</p>
<div class="layout">
<nav class="toc" aria-label="Contents"><ol><li><a href="#2026-10-03-proto-metal-igneum-bench-first-run">2026-10-03 proto-metal / igneum-bench, first run</a></li><li><a href="#2026-10-03-proto-cuda-program-pack-export-mac-side-only-rtx-5090-run-pending">2026-10-03 proto-cuda / program pack export (Mac side only; RTX 5090 run pending)</a></li><li><a href="#2026-10-03-sim-finality-sim-py-sustained-mining-finality-vote-weight-model-not-hardware">2026-10-03 sim/finality_sim.py, sustained-mining finality vote weight (model, not hardware)</a></li><li><a href="#2026-10-03-proto-metal-hardening-tests-correctness-and-soundness-of-the-lottery-hash-metal-only">2026-10-03 proto-metal hardening tests (correctness and soundness of the lottery hash, Metal only)</a></li><li><a href="#3-october-2026-rtx-5090-first-run-windows-pc-cuda-12-8-runtime-driver-13-4-visual-studio-2026-with-the-14-30-toolset-selected-via-vcvarsall-vcvars-ver-14-30">3 October 2026, RTX 5090 first run (Windows PC, CUDA 12.8 runtime, driver 13.4, Visual Studio 2026 with the 14.30 toolset selected via vcvarsall -vcvars_ver=14.30)</a></li><li><a href="#2026-10-03-sim-finality-v2-py-finality-rule-v2-with-latency-partitions-and-eclipses-model-not-hardware">2026-10-03 sim/finality_v2.py, finality rule V2 with latency, partitions and eclipses (model, not hardware)</a></li><li><a href="#2026-10-03-proto-metal-memory-hard-dataset-cache-8-dependent-reads-metal-only-cuda-pack-emulated">2026-10-03 proto-metal memory-hard dataset (cache + 8 dependent reads), Metal only; CUDA pack emulated</a></li><li><a href="#2026-10-03-rusty-kaspa-base-build-and-3-node-devnet-on-the-mac-consensus-engineer-pre-fork-proof">2026-10-03 rusty-kaspa base build and 3-node devnet on the Mac (consensus-engineer, pre-fork proof)</a></li><li><a href="#3-october-2026-rtx-5090-memory-hard-dataset-pack-igneum-genesis-mh">3 October 2026, RTX 5090, memory-hard dataset (pack igneum-genesis-mh)</a></li><li><a href="#2026-10-03-proto-vdf-wesolowski-vdf-between-the-certified-checkpoint-and-the-program-seed-epoch-10-min-era-1-h">2026-10-03 proto-vdf, Wesolowski VDF between the certified checkpoint and the program seed (epoch 10 min, era 1 h)</a></li><li><a href="#2026-10-03-igneum-pow-rust-crate-bit-exact-with-proto-metal-consensus-engineer">2026-10-03 igneum-pow: Rust crate bit-exact with proto-metal (consensus-engineer)</a></li><li><a href="#3-october-2026-proto-opencl-opencl-path-built-and-proven-without-amd-silicon-apple-opencl-1-2-pocl-cpu-emulator">3 October 2026, proto-opencl: OpenCL path built and proven without AMD silicon (Apple OpenCL 1.2, pocl, CPU emulator)</a></li><li><a href="#3-october-2026-amd-gfx1036-ryzen-7-9800x3d-integrated-rdna-2-graphics-1-compute-unit-amd-opencl-2-1-driver-3652-0">3 October 2026, AMD gfx1036 (Ryzen 7 9800X3D integrated RDNA 2 graphics, 1 compute unit), AMD OpenCL 2.1 driver 3652.0</a></li><li><a href="#3-october-2026-rtx-5090-through-nvidia-opencl-fourth-compiler-path-on-the-same-card">3 October 2026, RTX 5090 through NVIDIA OpenCL (fourth compiler path on the same card)</a></li><li><a href="#3-october-2026-igneum-node-devnet-v0-3-node-igneum-devnet-at-1-bps-with-the-80-20-coinbase-and-vote-key-hash-consensus-engineer">3 October 2026, igneum-node devnet v0: 3-node igneum-devnet at 1 BPS with the 80/20 coinbase and vote_key_hash (consensus-engineer)</a></li><li><a href="#3-october-2026-first-devnet-blocks-on-the-real-lottery-hash-cpu-then-metal-gpu-three-worker-implementations-consensus-engineer">3 October 2026, first devnet blocks on the real lottery hash: CPU, then Metal GPU, three worker implementations (consensus-engineer)</a></li><li><a href="#3-october-2026-windows-node-package-igneumd-cross-compiled-for-x86-64-pc-windows-gnu-two-peer-sync-test-consensus-engineer">3 October 2026, Windows node package: igneumd cross-compiled for x86_64-pc-windows-gnu, two-peer sync test (consensus-engineer)</a></li><li><a href="#3-october-2026-r3-26-m15-pow-checked-after-the-cheap-checks-cache-build-cap-attack-before-and-after-consensus-engineer">3 October 2026, R3.26 / M15: PoW checked after the cheap checks, cache-build cap, attack before and after (consensus-engineer)</a></li><li><a href="#3-october-2026-difficulty-controller-devnet-record-simulator-igneum-dual-lane-rule-3-node-cpu-test-network-consensus-engineer">3 October 2026, difficulty controller: devnet record, simulator, Igneum dual-lane rule, 3-node CPU test network (consensus-engineer)</a></li><li><a href="#3-october-2026-igneum-node-devnet-v2-sustained-mining-finality-rule-v2-on-a-four-miner-test-network-and-as-a-follower-of-the-live-devnet-consensus-engineer-and-cryptographer">3 October 2026, igneum-node devnet v2: sustained-mining finality rule v2 on a four-miner test network, and as a follower of the live devnet (consensus-engineer and cryptographer)</a></li><li><a href="#3-october-2026-execution-layer-devnet-v3-revm-over-the-selected-chain-3-node-simnet-viem-smoke-test-execution-engineer">3 October 2026, execution layer devnet v3: revm over the selected chain, 3-node simnet, viem smoke test (execution-engineer)</a></li><li><a href="#2026-10-03-consensus-attack-harness-consensus-engineer-catalogue-run-on-the-ordering-layer-node">2026-10-03, consensus attack harness (consensus-engineer), catalogue run on the ordering-layer node</a></li><li><a href="#3-october-2026-weak-program-census-400-000-program-runs-through-the-cpu-reference-the-redundant-load-finding-and-the-rules-for-m5-and-m6-cryptographer">3 October 2026, weak-program census: 400,000 program runs through the CPU reference, the redundant-load finding, and the rules for M5 and M6 (cryptographer)</a></li><li><a href="#3-october-2026-proving-v0-first-sp1-proof-of-an-igneum-block-apple-m5-max-cpu-loaded-machine-execution-engineer-proving">3 October 2026, proving v0: first SP1 proof of an Igneum block, Apple M5 Max CPU, loaded machine (execution-engineer, proving)</a></li><li><a href="#2026-10-03-execution-layer-attack-suite-malformed-txs-nonce-games-rpc-fuzz-pgas-exhaustion-reorgs-registry-abuse-execution-test-engineer">2026-10-03 execution layer attack suite: malformed txs, nonce games, RPC fuzz, pgas exhaustion, reorgs, registry abuse (execution test engineer)</a></li><li><a href="#4-october-2026-sim-economy-mining-versus-proving-under-stress-agent-based-economist-model-not-hardware">4 October 2026, sim/economy: mining versus proving under stress, agent-based (economist; model, not hardware)</a></li><li><a href="#2026-10-04-execution-layer-attack-fixes-f-exec-a-mempool-gas-limit-bound-and-f-exec-b-pgas-abort-rule-spec-7-5-execution-engineer">2026-10-04 execution layer attack fixes: F-exec-A (mempool gas-limit bound) and F-exec-B (pgas abort rule, spec 7.5) (execution-engineer)</a></li><li><a href="#3-october-2026-per-identity-hash-rate-decay-on-the-rtx-5090-diagnosis-and-metal-reproduction-miner-community-lead">3 October 2026, per-identity hash rate "decay" on the RTX 5090: diagnosis and Metal reproduction (miner-community-lead)</a></li><li><a href="#2026-10-04-finality-v2-attack-harness-seven-hostile-scenarios-on-a-six-voter-private-test-network-consensus-test-engineer-cryptographer">2026-10-04 finality v2 attack harness: seven hostile scenarios on a six-voter private test network (consensus test engineer, cryptographer)</a></li><li><a href="#4-october-2026-difficulty-rule-under-attack-pool-hopping-pulsed-rental-timestamp-stretching-short-lane-oscillation-epoch-games-polluted-window-block-flood-consensus-test-engineer">4 October 2026, difficulty rule under attack: pool hopping, pulsed rental, timestamp stretching, short-lane oscillation, epoch games, polluted window, block flood (consensus test engineer)</a></li><li><a href="#2026-10-04-finality-fixes-f17-and-f1-aggregators-drawn-by-weight-first-month-gate-min-daa-window-attack-scenarios-2-and-5-before-and-after-consensus-engineer">2026-10-04 finality fixes F17 and F1: aggregators drawn by weight, first-month gate min_daa = window; attack scenarios 2 and 5 before and after (consensus-engineer)</a></li><li><a href="#4-october-2026-difficulty-rule-timestamp-attack-fixed-tight-bounds-sanitised-clock-target-floor-simulator-regression-3-node-forger-test-consensus-engineer">4 October 2026, difficulty rule: timestamp attack fixed (tight bounds, sanitised clock, target floor), simulator regression, 3-node forger test (consensus-engineer)</a></li><li><a href="#4-october-2026-devnet-v4-integration-nine-branches-merged-3-node-test-network-on-the-merged-node-windows-cross-build-release-engineer">4 October 2026, devnet-v4 integration: nine branches merged, 3-node test network on the merged node, Windows cross-build (release engineer)</a></li><li><a href="#4-october-2026-generator-version-2-adopted-exact-load-count-fresh-source-loads-program-acceptance-every-vector-re-cut-three-workers-re-checked-20-000-program-census-devnet-v4-binaries-rebuilt-cryptographer">4 October 2026, generator version 2 adopted: exact load count, fresh-source loads, program acceptance; every vector re-cut, three workers re-checked, 20,000-program census, devnet-v4 binaries rebuilt (cryptographer)</a></li><li><a href="#2026-10-04-finality-floor-2-3-the-total-weight-floor-raised-from-17-30-to-two-thirds-simulator-a-to-l-re-run-attack-scenarios-6a-and-6b-on-a-three-node-six-voter-network-cryptographer">2026-10-04 finality floor 2/3: the total-weight floor raised from 17/30 to two thirds, simulator A to L re-run, attack scenarios 6A and 6B on a three-node, six-voter network (cryptographer)</a></li><li><a href="#4-october-2026-proving-v0-on-the-rtx-5090-first-gpu-proof-of-an-igneum-block-wsl2-sp1-6-8-1-cuda">4 October 2026, proving v0 on the RTX 5090: first GPU proof of an Igneum block (WSL2, SP1 6.8.1 cuda)</a></li><li><a href="#4-october-2026-devnet-v4-cut-over-generator-v2-2-3-floor-three-nodes-and-a-seed-on-a-fresh-chain">4 October 2026, devnet v4 cut-over: generator v2, 2/3 floor, three nodes and a seed on a fresh chain</a></li><li><a href="#4-october-2026-one-click-windows-workers-what-the-mac-could-measure-no-nvidia-gpu-here">4 October 2026, one-click Windows workers: what the Mac could measure (no NVIDIA GPU here)</a></li><li><a href="#4-october-2026-first-hourly-program-swap-on-the-live-devnet-compile-ahead-no-pause-two-cards">4 October 2026, first hourly program swap on the live devnet: compile-ahead, no pause, two cards</a></li><li><a href="#4-october-2026-proving-devnet-v4-shards-on-the-apple-m5-max-cpu-loaded-machine-execution-engineer-proving">4 October 2026, proving: devnet v4 shards on the Apple M5 Max CPU, loaded machine (execution-engineer, proving)</a></li><li><a href="#2026-10-04-fast-time-60x-test-profile-linux-cross-compile-from-the-mac-ci-on-every-push-consensus-engineer">2026-10-04, fast time (60x test profile), Linux cross-compile from the Mac, CI on every push (consensus-engineer)</a></li></ol></nav>
<article><h1 id="igneum-bench-log">Igneum bench log</h1>
<p>Append-only. Every number here was measured on the machine named, on the date given.</p>
<h2 id="2026-10-03-proto-metal-igneum-bench-first-run">2026-10-03 proto-metal / igneum-bench, first run</h2>
<p>Machine: Apple M5 Max, 40 GPU cores, 64 GB unified memory, macOS Darwin 25.6.0, Swift 5.8.1, Metal 4. Build: <code>swiftc -O -o igneum-bench main.swift -framework Metal</code>. Source: <code>proto-metal/main.swift</code>. Setup: 1 GiB dataset (2^28 uint32), 64 instructions x 8 iterations, threadgroup 32 (threadExecutionWidth 32), 4 timed batches x 2^22 nonces after one warm-up batch. Verification: 3 warps per program, CPU interpreter vs GPU.</p>
<div class="tbl"><table><thead><tr><th>Seed</th><th>Loads/hash</th><th>Compile ms</th><th>Mhash/s</th><th>GB/s useful</th><th>CPU verify ms/warp</th><th>Verify</th></tr></thead><tbody><tr><td>igneum-genesis</td><td>104</td><td>49.6 (cold)</td><td>45.2</td><td>18.8</td><td>0.015</td><td>PASS</td></tr><tr><td>igneum-genesis/epoch1</td><td>104</td><td>20.3</td><td>48.4</td><td>20.1</td><td>0.019</td><td>PASS</td></tr><tr><td>igneum-second-seed</td><td>104</td><td>46.6 (cold)</td><td>35.5</td><td>14.8</td><td>0.016</td><td>PASS</td></tr><tr><td>igneum-second-seed/epoch1</td><td>144</td><td>18.7</td><td>35.4</td><td>20.4</td><td>0.017</td><td>PASS</td></tr><tr><td>igneum-hourly</td><td>128</td><td>52.0 (cold)</td><td>36.6</td><td>18.7</td><td>0.021</td><td>PASS</td></tr><tr><td>igneum-hourly/epoch1</td><td>128</td><td>21.6</td><td>37.5</td><td>19.2</td><td>0.017</td><td>PASS</td></tr><tr><td>igneum-hourly/epoch2</td><td>120</td><td>23.7</td><td>36.6</td><td>17.6</td><td>0.016</td><td>PASS</td></tr></tbody></table></div>
<p>Dataset size sweep (seed igneum-genesis): 4 MiB 569 Mhash/s, 64 MiB 183, 256 MiB 94, 512 MiB 69, 1 GiB 44. Dataset fill 1 GiB: 2.34 ms GPU time (427 GB/s) warm, 5.57 ms on first run of a process. Result: 21 warps, 672 hashes, zero mismatches. OVERALL PASS. Reading: memory bound at 1 GiB (12.8x drop from cache-resident), limited by random access rather than bandwidth, CPU verify roughly 250x under the 10 ms gate with a cheap dataset element. Apple silicon only. Details in <code>proto-metal/README.md</code>.</p>
<h2 id="2026-10-03-proto-cuda-program-pack-export-mac-side-only-rtx-5090-run-pending">2026-10-03 proto-cuda / program pack export (Mac side only; RTX 5090 run pending)</h2>
<p>Machine: the same Apple M5 Max. No CUDA toolchain exists on it, so nothing below is an NVIDIA measurement. Added <code>--export-pack &lt;dir&gt;</code> to <code>proto-metal/main.swift</code>. It writes, per seed, the CUDA kernel (<code>kernel.cu</code>), C headers (<code>program.h</code>, <code>vectors.h</code>), JSON twins, and the Metal source, into <code>proto-cuda/packs/&lt;seed&gt;/</code>. Vectors: 3 warps (base nonces 0, 4096, 1000000), 96 x 64-bit outputs from the CPU interpreter, plus dataset words 0..15 and word [MASK]. The exporter runs the Metal kernel for the same warps and refuses to write unless all 96 match.</p>
<div class="tbl"><table><thead><tr><th>Pack</th><th>Loads/hash</th><th>Op mix</th><th>CPU interpreter vs Metal GPU (3 warps)</th><th>CUDA text in CPU emulation (clang, 32 threads/warp)</th></tr></thead><tbody><tr><td>igneum-genesis</td><td>104</td><td>load=13 xor=13 sub=7 shfl=6 add=5 mulhi=5 mad=4 rotr=4 mul=3 rotl=3 or=1</td><td>PASS 3/3</td><td>PASS: dataset self-test, 3/3 warps standalone, warps 0 and 4096 in batch at 1 and 2 warps/block</td></tr><tr><td>igneum-hourly</td><td>128</td><td>load=16 add=8 xor=7 mad=5 mul=5 mulhi=5 shfl=5 rotl=4 or=3 rotr=3 sub=3</td><td>PASS 3/3</td><td>PASS: dataset self-test, 3/3 warps standalone, warps 0 and 4096 in batch at 1 warp/block</td></tr></tbody></table></div>
<p>Sweep sizes 4, 64, 256, 512 MiB in the emulation: dataset self-test PASS at each (vectors only apply at 1 GiB). The regular Metal bench was re-run after the change: igneum-genesis 44.8 Mhash/s and igneum-hourly 36.3 Mhash/s at 1 GiB with 2 x 2^20 batches, PASS 3/3 warps each (consistent with the first-run table above, not a new figure). Harness: <code>proto-cuda/host.cu</code>, <code>build.sh</code>, <code>build.bat</code>, <code>README.md</code>, <code>CHECKLIST.md</code>, <code>emu/</code>. Pending: build and run on the project's RTX 5090 (CUDA 12.8 or newer, <code>-arch=sm_120</code>). No NVIDIA hash rate exists yet. Emulation rates are not recorded because they measure the Mac's CPU, not a GPU.</p>
<h2 id="2026-10-03-sim-finality-sim-py-sustained-mining-finality-vote-weight-model-not-hardware">2026-10-03 sim/finality_sim.py, sustained-mining finality vote weight (model, not hardware)</h2>
<p>Model: 1 day steps, 86,400 Poisson blocks/day, 1,000 Pareto honest keys (top key 17%), perfect retarget, every block blue, no latency, no VRF noise. Seed 7, seed 11 agrees. Rule: weight = 30-day sum of counted blocks, counted = min(actual, 2 x yesterday + f). Lock at 2/3 of total. Floors f in {1, 10, 100, 1000}. A: weight tracks hashrate at steady state (corr 1.00000); full weight from zero history on day 41 (f=1) to 32 (f=1000). B: 60% renter on one key takes 59.9% of rewards on day 1, crosses 1/3 of weight on day 20 to 26, 50% on day 27 to 34, never 2/3. 75% renter reaches 2/3 on day 29 to 34, day 27 with the cap removed. C: splitting defeats the cap. 10,000 fresh keys at f=1 move the 50% crossing from day 34 to 27 (no-cap figure 26); at f=10 they match no-cap exactly. The 30-day trickle buys 2 more days for 11.6% of the network. D: honest doubling is under-weighted 28 to 31 days; old miners lock alone for 20 to 23 days. E: 30% churn leaves 71% live, lock never lost. Threshold is 1/3: 35% stalls 1 day, 50% stalls 10 to 11 days. Active-24h total removes every stall. F: 51% patient owner holds 51.0% weight from day 45, vetoes from day 7 to 20, never locks alone. 67% owner locks alone from day 34 to 44. Recommend: f=1, cap 2x, window 30 (the window is the defence, the cap is worth 1 to 8 days), total = all keys with weight in the window. Details in sim/results.md.</p>
<h2 id="2026-10-03-proto-metal-hardening-tests-correctness-and-soundness-of-the-lottery-hash-metal-only">2026-10-03 proto-metal hardening tests (correctness and soundness of the lottery hash, Metal only)</h2>
<p>Machine: the same Apple M5 Max. Added <code>--fuzz</code>, <code>--edge</code>, <code>--stats</code>, <code>--determinism</code>, <code>--memcheck</code>, <code>--inline-dataset</code> to <code>proto-metal/main.swift</code>. Full tables and commands in <code>proto-metal/TESTS.md</code>. Fuzz: 10,200 random programs (200 + 10,000, cold compiles 13.6 to 48.5 ms), 4 random full-range warps each, dataset drawn from 64 MiB / 256 MiB / 1 GiB: 40,800 warps, 1,305,600 hashes, 0 mismatches, 0 compile failures, 0 static mask failures, generator contract (rotl 1..31, mask in {1,2,4,8,16}, src != dst) held on every instruction. 196 s for the 10,000 run. Edge: 14 hand-built cases (rotr by register 0 / 32 / -32 / 31 / 63, rotl 1 and 31, mulhi max operands, shfl masks 1..16, loads at index 0 and MASK via in-range and out-of-range registers, add/sub/mul/mad wraparound, zero loads, 64 loads) with operand values proven by a traced interpreter: 14/14 PASS, 128/128 lanes each. rotl by 0 (never generated) agreed too, recorded as informational only. Stats (3 seeds, 2^20 nonces each): bit frequency max deviation 2.90 sigma over 192 bit positions; avalanche 16,000 flips mean 31.99 to 32.04 (expect 32), std 3.98 to 4.01 (expect 4), every output bit flips with probability 0.490 to 0.508; chi-square on four 16-bit windows all within 2.3 sigma; 0 duplicates. Looks uniform. Not a security proof. Determinism: 5 runs and 3 compiles (one forced cold, 30 ms) of 2^20 hashes gave fingerprint 933787e8cfefccb7 every time; dataset fill deterministic (0b1a77899ee60493 twice) and 4,096 sampled words incl. 0 and MASK match the CPU closed form. Memcheck: every <code>dataset[</code> in the MSL is <code>dataset[rN &amp; MASK]</code> (13/13 at 3 sizes), CUDA twin 13/13 plus one guarded fill write; 4 MiB run with nonces up to 0xffffffff completed and 4 wrapping warps matched the CPU; 416/416 load indices exceeded MASK before masking. Bench re-run after the changes: igneum-genesis 44.56 Mhash/s, epoch1 47.74 Mhash/s at 1 GiB, PASS 3/3 warps each (within 2 percent of the first-run table). <code>--export-pack igneum-genesis</code> re-run is byte-identical to the existing pack. SHORTCUT MEASURED: <code>--inline-dataset</code> replaces every load with the six-op closed form ds_elem and never reads memory: 4,888 Mhash/s wall (6,274 GPU time) vs 44.6 honest at 1 GiB, about 110x, and about 9x the cache-resident honest rate. With a closed-form dataset the hash is not memory-hard; an expensive dataset derivation is required, not optional. Not demonstrated: cryptographic strength, weak-program frequency and rejection, NVIDIA/AMD bit-exactness (CUDA run still pending), CPU verify gate with an expensive dataset element. Next three tests for the cryptographer are listed in TESTS.md section 8.</p>
<h2 id="3-october-2026-rtx-5090-first-run-windows-pc-cuda-12-8-runtime-driver-13-4-visual-studio-2026-with-the-14-30-toolset-selected-via-vcvarsall-vcvars-ver-14-30">3 October 2026, RTX 5090 first run (Windows PC, CUDA 12.8 runtime, driver 13.4, Visual Studio 2026 with the 14.30 toolset selected via vcvarsall -vcvars_ver=14.30)</h2>
<p>Pack igneum-genesis, dataset 1024 MiB, 5 batches x 2^24 hashes, 1 warp per block.</p>
<div class="tbl"><table><thead><tr><th>Card</th><th>Mhash/s at 1 GiB</th><th>GB/s useful</th><th>random loads/s</th><th>dataset fill</th><th>vectors</th></tr></thead><tbody><tr><td>NVIDIA RTX 5090 (170 SMs, 32 GB)</td><td>228.1</td><td>94.9</td><td>23.7 G</td><td>0.66 ms, 1638 GB/s</td><td>96/96 PASS, standalone and in batch</td></tr><tr><td>Apple M5 Max (40 GPU cores, same program, same day)</td><td>45.2</td><td>18.8</td><td>4.6 G</td><td>2.34 ms, 427 GB/s</td><td>96/96 PASS</td></tr></tbody></table></div>
<p>Result: the same hourly program, generated on the Mac, compiled by Apple's Metal and NVIDIA's CUDA, produced identical hashes on both vendors. Vendor independence of the lottery program is demonstrated for one program; igneum-hourly and the dataset sweep are the next runs. The ratio 5090 to M5 Max is about 5x on hashes and on random loads per second, approximate, consistent with a memory-bound program (random-access bound, not bandwidth bound: the 5090 writes the dataset at 1638 GB/s but hashes at 95 GB/s of useful 4-byte loads). Caveat unchanged: the prototype dataset is closed-form and not yet memory-hard (see TESTS.md), so these are prototype numbers, not mining numbers.</p>
<p>Build note for Windows: CUDA 12.8 crashes (cudafe++ access violation) under Visual Studio 2026's 14.51 toolset even with -allow-unsupported-compiler. Fix: install the MSVC v143 (14.30) component and open the environment with <code>"C:\Program Files\Microsoft Visual Studio\18\Community\VC\Auxiliary\Build\vcvarsall.bat" x64 -vcvars_ver=14.30</code>, then build normally.</p>
<h3 id="rtx-5090-dataset-sweep-and-second-program-same-session">RTX 5090, dataset sweep and second program (same session)</h3>
<div class="tbl"><table><thead><tr><th>dataset MiB</th><th>Mhash/s</th><th>GB/s useful</th><th>random loads/s (G)</th></tr></thead><tbody><tr><td>4</td><td>1339.8</td><td>557</td><td>139.3</td></tr><tr><td>64</td><td>1352.7</td><td>563</td><td>140.7</td></tr><tr><td>256</td><td>269.8</td><td>112</td><td>28.1</td></tr><tr><td>512</td><td>241.8</td><td>101</td><td>25.2</td></tr><tr><td>1024</td><td>228.7</td><td>95</td><td>23.8</td></tr></tbody></table></div>
<p>Second program igneum-hourly (128 loads per hash): 96/96 vectors PASS, 185.3 Mhash/s at 1 GiB, 23.7 G random loads/s.</p>
<p>Reading: the 5090 carries 96 MiB of L2. At 4 and 64 MiB the dataset sits inside it and the program runs about 5.8x faster than at 1 GiB. Past the L2 the rate settles at about 23.7 G random loads/s for both programs regardless of loads per hash (104 vs 128 loads gives 228 vs 185 Mhash/s, proportional), so the program is random-access bound once the dataset exceeds on-chip cache. Each 4-byte random load moves a 32-byte sector, so DRAM traffic is roughly 760 GB/s, approximate, against a quoted peak near 1.8 TB/s for this card. Design consequence: the dataset must stay well above any plausible on-chip cache, which the 2 GB genesis size and the growth schedule provide; a chip would need gigabytes of on-chip memory to escape the random-access limit. Still prototype numbers: dataset derivation remains closed-form until the 256 MB cache construction lands.</p>
<h2 id="2026-10-03-sim-finality-v2-py-finality-rule-v2-with-latency-partitions-and-eclipses-model-not-hardware">2026-10-03 sim/finality_v2.py, finality rule V2 with latency, partitions and eclipses (model, not hardware)</h2>
<p>Model: 30-s slots, Poisson(30) blocks/slot, 1,000 Pareto honest keys in 3 regions (45/35/20), 2-s inter-region delay (0.5 and 5 swept), uptime 97% (99.5% for pools over 1%), warm 30-day start for B to G. Seed 7, seed 11 agrees on B and E. Run time 5 min. Details in sim/results_v2.md. Rule: weight = flat 30-day blue blocks, dust 100, checkpoint per 30 blocks, lock at 2/3 of ACTIVE (participation over 240 checkpoints) vs TOTAL weight. A: weight = hashrate (corr 1.00000), full weight day 30, all keys over dust by day 20, lock median 2.5 s / p99 4.6 s at 2 s delay, 14 s max at 5 s, 0 stalls in 60 days except 17 at genesis. B: share(t) = (t/30) x a/(1+a) holds to 0.04 points; 1/3 crossed at day 20.0 / 15.0 / 12.5 / 11.1 and 2/3 at never / 30.0 / 25.0 / 22.2 for a = 1 / 2 / 4 / 9; dust hands a 9x renter 1.3 extra points. C: silent set that keeps mining: active recovers in 0 / 13 / 20 / 29 / 38 min at 34 / 40 / 45 / 50 / 55%; total never (silent weight never ages out). D: churn: active 2 min (35%) and 31 min (50%); total 41 h and 10.1 days. E: active FAILS the partition test: 50/50 honest split, no attacker, both sides lock after 60 min (30 with DAA retarget), 60/40 after 121 min; first-lock time = presence x (1 - 1.5 s)/s slots, confirmed. Total: 0 conflicts in every honest partition. 34% attacker breaks every variant at 50/50 (67% per side). F: delayed eclipse of a 20% pool is harmless (participation 0 after 2 h, back in 2 h, 0 conflicts); a 34% attacker poisoning that pool finalises a private fork in 49 min under active, never under total. Floor hybrid: active denominator never below 0.85 x total (lock needs 56.7% of total) gives 0 conflicts in every partition and eclipse, recovers in 0 / 13 min at 34 / 40% silent and 2 min at 35% churn; costs 4.1 days at 50% churn and liveness ends near 42% silent. Floor 0.80 does not stop the eclipse (54% &gt; 53.3%). Recommend: active/cert + floor 0.85, presence 240, dust 100, quorum 2/3, grace at least 3x worst delay. Not modelled: real GHOSTDAG merge and post-heal fork choice, DAA lag, VRF aggregators, certificate revocation.</p>
<h2 id="2026-10-03-proto-metal-memory-hard-dataset-cache-8-dependent-reads-metal-only-cuda-pack-emulated">2026-10-03 proto-metal memory-hard dataset (cache + 8 dependent reads), Metal only; CUDA pack emulated</h2>
<p>Machine: the same Apple M5 Max (one performance core for the CPU figures). Construction, every table and the commands are in <code>proto-metal/MEMHARD.md</code>. Default dataset is now memory-hard; <code>--closed-form</code> keeps the original for comparison. Construction: 256 MiB cache = 2^22 lines of 64 B in 2^16 chains of 64 ChaCha12 blocks with feed-forward (in_j = prev ^ (sigma || K[8] || seg || j || tag)); item t = 16 words, 8 rounds of (seed-parameterised ARX-multiply mixer, read cache line s[0] &amp; (2^22-1), xor) plus a final mixer; dataset[w] = item(w &gt;&gt; 4)[w &amp; 15]. Hash kernel unchanged. Cache fill: 2.0 ms GPU (0.6 to 2.1 across runs), 185 ms one CPU core (Swift), 162 ms C++ host reference. Dataset build 1 GiB: 20.6 ms GPU (29.4 first in process), 814 M items/s, 6.5 G cache-line reads/s. GPU cache == CPU cache on all 2^26 words every run (FNV-1a 64 48c4f5bf24166b2e for day 2026-10-03). Shortcut ratio, seed igneum-genesis, 1 GiB: honest 45.2 Mhash/s in both constructions. Inline kernel (never reads the dataset): closed form 5,014 Mhash/s (111x FASTER than honest); memory-hard 9.49 Mhash/s (0.21 of honest, 4.8x SLOWER). At a 256 MiB dataset: honest 94.8, inline 9.48 (0.10). CPU verify per 32-lane warp (holds only the cache, derives every word on demand, 32 lanes interleaved): 0.649 / 0.631 / 0.701 ms for igneum-genesis, /epoch1, /epoch2 (104, 104, 112 loads; 3,328 to 3,584 items); 0.801 ms igneum-second-seed (104 loads); 1.205 ms igneum-second-seed/epoch1 (144 loads, 4,608 items). Cold single warps 1.16 to 2.11 ms. Closed form was 0.017 ms. 10 ms GATE MET, margin about 8x steady. Levers (implemented, measured, OFF by default; default generator unchanged): (a) --load-weight 17: 72 to 80 loads/hash, CPU 0.457 to 0.512 ms/warp, GPU 55.0 to 73.4 Mhash/s. (b) --wide-frac 50 (warp-coalesced 128 B loads): CPU 0.233 to 0.489 ms/warp, GPU 56.1 to 135.2 Mhash/s and useful bandwidth up to 56 GB/s, so (b) erodes the random-access bound. (a)+(b): CPU 0.223 to 0.276, GPU 106 to 139. Recommendation: no lever; (a) is the fallback if a slower verifier ever threatens the gate; (b) not recommended. Tests re-run on the new dataset: fuzz 200/200 (800 warps, 25,600 hashes, 0 mismatches, CPU interpreter 1.23 s), edge 14/14, determinism PASS (fingerprint 62a4f0eb018df273), memcheck PASS, stats PASS (3 seeds, no obvious bias). 3 warps x 3 seeds bit-exact in the bench run. CUDA: new pack proto-cuda/packs/igneum-genesis-mh (kernel.cu with cache-fill and build kernels, memhard.h shared by device and host, vectors incl. cache head/last/FNV and 64 sampled words). host.cu handles both modes; old packs unchanged (closed-form export re-run is byte-identical in kernel.cu and program.metal). clang emulation (emu/emu.sh igneum-genesis-mh): cache check PASS (all words, FNV == Mac), dataset self-test PASS at 1 GiB, 3/3 vectors standalone and 2/2 in batch at 2 warps/block. RTX 5090 and AMD runs of this pack PENDING; no NVIDIA figure for the memory-hard dataset exists. Not demonstrated: cross-vendor results for the new dataset; the shortcut ratio on a discrete GPU; time-memory trade-offs between the two measured points; cryptographic strength of the mixer and the chained cache; distinct-lines-per-hash census.</p>
<h2 id="2026-10-03-rusty-kaspa-base-build-and-3-node-devnet-on-the-mac-consensus-engineer-pre-fork-proof">2026-10-03 rusty-kaspa base build and 3-node devnet on the Mac (consensus-engineer, pre-fork proof)</h2>
<p>Machine: Apple M5 Max (18 CPU cores), 64 GB, macOS 26.6.2. Toolchain: Homebrew rust 1.69.0 was too old (repo needs 1.91.0), so rustup 1.29.1 was installed non-interactively and gives rustc 1.99.0 and cargo 1.99.0; protobuf 36.2 added via <code>brew install protobuf</code> (protoc was missing); Apple clang 14.0.3 already present. Nothing else was needed. Source: <code>vendor/rusty-kaspa</code> at commit <code>01b532e8b553523216471682649693af92f0fd16</code> (v2.1.0, 2026-09-22). <code>cargo build --release --bin kaspad</code>: 2 min 36 s cold, binary 35,405,104 bytes (34 MB), 131 compiler warnings, zero errors. Devnet: three <code>kaspad --devnet --nodnsseed --disable-upnp --enable-unsynced-mining --yes --loglevel=info</code> nodes, separate <code>--appdir</code>, P2P 16611/16621/16631, gRPC 16610/16620/16630, nodes 2 and 3 <code>--connect</code> to node 1 (node 3 to node 2 never came up because both started at once, so the topology was a star through node 1). Network params: 10 BPS (100 ms blocks), GHOSTDAG k 124, merge depth 36,000 blocks, finality depth 432,000, pruning depth 1,080,000, DAA window 661 samples x 40 blocks, genesis bits 0x1e21bc1c (about 248,663 hashes per block). Miner: kaspad ships none, so a 150-line CPU miner on <code>kaspa-pow::State</code> (real kHeavyHash, 16 threads, 300 ms template refresh) submitted to node 1 only: 27.2 MH/s sustained, 5,718 blocks in 180 s, 0 rejected. Blocks per second over the 180 s run: 31.76 on all three nodes (1,691 to 7,409 blocks each). Two phases: 56 to 62 blocks/s while difficulty sat at genesis (first 6,000 blocks, min window 150 samples), then the DAA raised difficulty to 1.12 M at block 6,018 and the rate fell to 14 to 15 blocks/s, still converging toward the 10 BPS target when the run ended. Propagation: block counts, DAA scores and sink hash were identical on all three nodes at 18 of 19 ten-second samples; the one miss was node 2 trailing by a single block for one sample. Tips stayed at 1 because a single serial miner never produced parallel blocks, so GHOSTDAG k was not exercised; a second miner is the next step for that. Earlier 30 s warm-up run: 1,690 blocks, 56.3 blocks/s on all three nodes, 28.1 MH/s. Fork points mapped with line numbers in <code>docs/fork-map.md</code> (hash, coinbase, DAA, header, depth constants, BPS and k). All nodes stopped at the end. Miner source kept outside the repo (scratchpad); re-create from <code>testing/integration/src/common/utils.rs:271</code> if needed.</p>
<h2 id="3-october-2026-rtx-5090-memory-hard-dataset-pack-igneum-genesis-mh">3 October 2026, RTX 5090, memory-hard dataset (pack igneum-genesis-mh)</h2>
<div class="tbl"><table><thead><tr><th>Check</th><th>Result</th></tr></thead><tbody><tr><td>256 MiB cache, GPU vs host, all 67,108,864 words</td><td>PASS, FNV-1a 48c4f5bf24166b2e matches the Mac</td></tr><tr><td>Cache fill</td><td>0.67 ms GPU, 223 ms one host thread</td></tr><tr><td>Dataset build from the cache, 1 GiB</td><td>13.4 ms, 1,253 M items/s</td></tr><tr><td>Vectors, 3 warps, standalone and in batch</td><td>96/96 PASS</td></tr><tr><td>Hash rate at 1 GiB</td><td>228.95 Mhash/s, 95.2 GB/s useful, 23.8 G random loads/s</td></tr></tbody></table></div>
<p>Reading: the memory-hard construction is now bit-exact across Apple Metal, NVIDIA CUDA and the CPU reference, cache and dataset included. Hash rate is unchanged from the closed-form dataset on both vendors, as expected, since the hash kernel only loads; what changed is that computing items on the fly is now slower than loading them (4.8x slower measured on Apple, not yet measured on NVIDIA). Still unmeasured: the inline shortcut ratio on NVIDIA, and AMD on any dataset.</p>
<h2 id="2026-10-03-proto-vdf-wesolowski-vdf-between-the-certified-checkpoint-and-the-program-seed-epoch-10-min-era-1-h">2026-10-03 proto-vdf, Wesolowski VDF between the certified checkpoint and the program seed (epoch 10 min, era 1 h)</h2>
<p>Machine: Apple M5 Max (18 logical cores), rustc 1.69.0, GMP 6.3.0 via rug 1.19. Source <code>proto-vdf/</code>, details in <code>proto-vdf/README.md</code>. Single core sequential squaring unless stated. Rates: class group 1024-bit prime discriminant (production choice, chiavdf construction, NUDUPL/NUCOMP ported from vendor/chiavdf) 163,000 sq/s; class group 2048-bit 83,500 sq/s; RSA-2048 trusted-setup stand-in (public trapdoor, timing only) 1,257,000 sq/s. T for 10 min / 60 min on this core: class 1024: 98.0 M / 588 M; class 2048: 50.1 M / 301 M; RSA-2048: 754 M / 4.53 G. Full 10-min runs: RSA T=756,516,411 eval 607.1 s (1,246,000 sq/s), prove 9.0 s on 12 threads (71.2 s on 1), verify 0.88 ms, proof 512 bytes. Class 1024 T=97,126,043 eval 585.4 s (165,900 sq/s, the RSA run sharing the chip ended midway), prove 9.1 s on 12 threads (56.8 s on 1), verify 4.47 ms, proof 516 bytes. Prover costs 12 to 13 percent of eval single-threaded (12-bit digits, at most 65,536 checkpoints, 17 MB) and parallelises over residue classes; verify is two 256-bit exponentiations, 4.5 ms class 1024 (12.6 ms including deriving D from the checkpoint hash), 1.4 ms RSA. Seed pipeline: epoch_seed(checkpoint) -&gt; (seed, proof) and verify_epoch_seed; the same checkpoint hash gave the same seed and identical proof bytes in two separate processes at T=1,000,000; wrong checkpoint, flipped seed bit and T+1 all rejected. Attacker speed: delay must only exceed the 2 s publish-or-lose window; margin is 300x at the epoch and 1,800x at the era, so a 2x (or 10x, or 100x) faster evaluator leaves grinding impossible. Requirement: the checkpoint hash must commit to full block hashes incl. nonce. Grinding model (3,600 blocks/epoch, advantage uniform 0 to 15%, keep top quartile, one block burned per withheld candidate), gain per epoch in blocks, no delay vs with delay: s=0.1 +0.40 vs 0; s=0.2 +1.66 vs 0; s=0.3 +3.62 (+0.32%, 13.5:1 on burned blocks) vs 0; s=0.4 +6.06 vs 0. Monte Carlo over 2,000,000 epochs agrees to 0.03 blocks. Correctness: NUDUPL, NUCOMP and the Lehmer partial xgcd agree with Cohen 5.4.7 / plain duplication / plain-division xgcd on 15,000 random cases; block prover equals the naive O(T) prover at T = 37, 5,000 and 100,000 in both groups; 216 associativity triples; 3 tamper cases rejected per size. Recommend: class group 1024-bit D from the checkpoint hash, epoch T = 600 x r_ref and era T = 3,600 x r_ref with r_ref the fastest honest single-core rate measured on the devnet (98 M and 588 M on this Mac), fixed at genesis, 20 min lead time for the epoch seed and 2 h for the era draw, 256-bit Fiat-Shamir prime. Open: external review of classgroup.rs against chiavdf, reference core choice, fallback rule for a node without the seed at epoch start, carry D in the proof.</p>
<h2 id="2026-10-03-igneum-pow-rust-crate-bit-exact-with-proto-metal-consensus-engineer">2026-10-03 igneum-pow: Rust crate bit-exact with proto-metal (consensus-engineer)</h2>
<p>Machine: Apple M5 Max, one performance core, rustc 1.99.0 (rustup), release build with LTO. Crate at <code>igneum-pow/</code> (seed, generator, memhard, verify, emit; CLI <code>bench</code>, <code>export</code>, <code>hash</code>), standard library only, serde_json as a dev-dependency for the pack tests. Agreement with the Swift through <code>proto-cuda/packs/</code>: program.json instruction by instruction for igneum-genesis, igneum-genesis-mh and igneum-hourly (3 x 64 match); mixer rot/mul/rc match; cache head, last line and FNV-1a 64 <code>48c4f5bf24166b2e</code> match; dataset head, <code>[MASK]</code> and 64 sampled words match in all three packs; hash vectors 96/96 for igneum-genesis-mh (memory-hard) and 96/96 each for the two closed-form packs. 23 tests, all pass. Emitted sources: kernel.cu, program.metal, kernel.cl and program.h byte-identical for all three packs, memhard.h and memhard.metal byte-identical for igneum-genesis-mh; <code>igneum-pow export</code> then <code>diff -r</code> against the packs differs only in the provenance string of vectors.json/vectors.h. Found: <code>proto-cuda/packs/igneum-genesis-mh/program.json</code> is not valid JSON (main.swift line 1291 writes <code>jhex(cacheLineMask)</code> inside the <code>"item"</code> string). The Rust emitter writes the mask bare and the test normalises that line; fix pending in the Swift. Cache fill, 256 MiB on one core: 175 to 181 ms in Rust (5 runs) against 184.5 to 190.6 ms Swift and 161.5 ms C++ host reference. CPU verify per 32-lane warp, avg of 20, 1 GiB dataset: igneum-genesis 0.441 ms (Swift 0.649), /epoch1 0.411 (0.631), /epoch2 0.488 (0.701), igneum-second-seed 0.482 (0.801), igneum-second-seed/epoch1 at 144 loads and 4,608 items 0.579 (1.205). Cold single warps 0.41 to 0.87 ms (Swift 1.16 to 2.11). Closed form 0.002 ms (Swift 0.017). Reading: Rust is 1.4x to 2.1x faster than the Swift verifier per warp with the same algorithm (register-major lanes, 32-lane interleaved item derivation); 10 ms gate margin about 17x steady, 11x on the worst cold warp. Cache fill is within 5 percent of the Swift and 10 percent slower than clang C++. API for the fork: <code>Epoch::memory_hard(seed, day)</code> once per epoch (fills the cache), then <code>epoch.hash(nonce)</code>, <code>epoch.hash_warp(base)</code>, <code>epoch.verify_block(nonce, target)</code>; <code>emit::export_pack(&amp;epoch, day, source)</code> for miner programs. <code>seed::seed_words_from_bytes</code> is the boundary for the VDF output. Not done: no GPU run from Rust; the 256-bit target mapping stays in the fork; the seed is still a string.</p>
<h2 id="3-october-2026-proto-opencl-opencl-path-built-and-proven-without-amd-silicon-apple-opencl-1-2-pocl-cpu-emulator">3 October 2026, proto-opencl: OpenCL path built and proven without AMD silicon (Apple OpenCL 1.2, pocl, CPU emulator)</h2>
<p>Machine: the same Apple M5 Max. New: <code>proto-opencl/host.c</code> (C99, OpenCL 1.2 API), <code>kernel.cl</code> in every pack from <code>--export-pack</code> (same emitter, OpenCL C dialect; memory-hard core emitted in three dialects), <code>WAVEFRONT.md</code>, CPU emulator with a 32- or 64-wide sub-group. The AMD rig has not arrived; no AMD compiler or device has touched this code. Exchange rule: <code>sub_group_shuffle_xor</code> only when the device lists <code>cl_khr_subgroup_shuffle</code>, the work-group is exactly 32 and the queried sub-group size for a 32-item work-group is exactly 32; otherwise a <code>__local</code> memory exchange with one barrier per exchange (two alternating buffers). Wave64 hardware (GCN, CDNA, RDNA in wave64) therefore takes the local-memory path and the hash never depends on the wave width. Apple OpenCL 1.2 runtime, Apple M5 Max (40 CUs, OpenCL C 1.2, no sub-group extension, local-memory path): cache check PASS (all 2^26 words, FNV-1a 64 48c4f5bf24166b2e = Mac), dataset self-test PASS, 96/96 vectors standalone and in batch for igneum-genesis-mh; also 96/96 at <code>--exchange local --group-warps 2</code> and <code>--group-warps 4</code>; closed-form packs igneum-genesis 96/96 and igneum-hourly 96/96. Apple OpenCL hash rate (wall time; Apple's event timestamps are unusable), pack igneum-genesis-mh, 1 GiB, 5 x 2^24: 45.03 Mhash/s, 18.73 GB/s useful (repeat run 44.58). igneum-genesis 45.17, igneum-hourly 36.33 (128 loads). Metal on the same chip: 45.2. This is Apple's deprecated OpenCL on the M5 Max, NOT an AMD number. Apple OpenCL sweep (3 batches): 4 MiB 573.7 Mhash/s, 64 MiB 178.9, 256 MiB 94.3, 512 MiB 68.8, 1024 MiB 45.0 (Metal sweep shape reproduced). pocl 7.2 CPU device (OpenCL 3.0, LLVM 23, Khronos ICD loader, <code>brew install pocl</code>, needs SDKROOT): <code>--exchange auto</code> and <code>--exchange subgroup</code> built with <code>-cl-std=CL3.0 -D IGNEUM_EXCHANGE=1</code> and ran the real <code>sub_group_shuffle_xor</code> text: cache FNV = Mac, 96/96 PASS. pocl's <code>clGetKernelSubGroupInfoKHR</code> returns CL_INVALID_OPERATION, so the new probe kernel (<code>igneum_probe_subgroup</code>, reports get_sub_group_size() 32) decided; <code>--exchange local</code> also 96/96. CPU emulator (<code>proto-opencl/emu</code>, kernel.cl compiled as C++, 1 GiB dataset built on 256 host threads): 7 configurations all PASS with the identical batch fingerprint f99fb375b3abeaf5 over 2^13 outputs: exchange 0 with work-group 32/sub-group 32, 64/64, 32/64; exchange 1 (sub-group shuffles) with 32/32, 32/64, 64/64 (wave64 carrying two 32-lane units in one shuffle domain), 64/32. Cross-implementation fingerprint at <code>--batch-log2 13</code>, base nonce 0, igneum-genesis-mh: Apple OpenCL f99fb375b3abeaf5, pocl sub-group f99fb375b3abeaf5, pocl local f99fb375b3abeaf5, emulator f99fb375b3abeaf5 (all 7). At 2^24 Apple OpenCL prints 98af644e993239e2 (reference for the AMD run). proto-cuda emulator re-run after the header changes (program.h, vectors.h, memhard.h now C99-safe): PASS. Not demonstrated: any AMD compile or run, any AMD hash rate, the cost of the local-memory exchange on AMD, whether RDNA compiles igneum_hash as wave32 or wave64. Next: run the seven commands in <code>proto-opencl/README.md</code> on the AMD rig and paste the logs.</p>
<h2 id="3-october-2026-amd-gfx1036-ryzen-7-9800x3d-integrated-rdna-2-graphics-1-compute-unit-amd-opencl-2-1-driver-3652-0">3 October 2026, AMD gfx1036 (Ryzen 7 9800X3D integrated RDNA 2 graphics, 1 compute unit), AMD OpenCL 2.1 driver 3652.0</h2>
<p>Pack igneum-genesis-mh, memory-hard dataset, 1024 MiB, exchange via local memory (the driver lists no sub-group shuffle extension), wavefront 32.</p>
<div class="tbl"><table><thead><tr><th>Check</th><th>Result</th></tr></thead><tbody><tr><td>256 MiB cache, device vs host vs Mac</td><td>PASS, 17.5 ms device fill</td></tr><tr><td>Dataset build from the cache, 1 GiB</td><td>392 ms, 42.8 M items/s</td></tr><tr><td>Dataset self-test, 4 checks</td><td>PASS</td></tr><tr><td>Vectors, 3 warps, standalone and in batch</td><td>96/96 PASS</td></tr><tr><td>Hash rate</td><td>4.38 Mhash/s on one compute unit, 1.82 GB/s useful</td></tr></tbody></table></div>
<p>Reading: the third GPU vendor. The same memory-hard program now produces identical hashes on Apple Metal, NVIDIA CUDA, Apple OpenCL and AMD OpenCL, cache and dataset included. The AMD number is from a two-CU integrated chip sharing system memory and is a correctness result only; the discrete AMD card is still to come. The local-memory exchange path, which wave64 cards will also use, is now proven on AMD silicon.</p>
<h2 id="3-october-2026-rtx-5090-through-nvidia-opencl-fourth-compiler-path-on-the-same-card">3 October 2026, RTX 5090 through NVIDIA OpenCL (fourth compiler path on the same card)</h2>
<p>Pack igneum-genesis-mh, 1024 MiB, local-memory exchange (NVIDIA's OpenCL lists no sub-group shuffle extension).</p>
<div class="tbl"><table><thead><tr><th>Check</th><th>Result</th></tr></thead><tbody><tr><td>Cache check and dataset self-test</td><td>PASS</td></tr><tr><td>Vectors</td><td>96/96 PASS, batch fingerprint 98af644e993239e2, identical to the AMD gfx1036 run</td></tr><tr><td>Hash rate</td><td>219.6 Mhash/s via OpenCL against 229.0 via CUDA, about 4% apart, approximate</td></tr></tbody></table></div>
<p>Reading: NVIDIA's OpenCL compiler and NVIDIA's CUDA compiler agree with each other, with AMD's OpenCL, with Apple's Metal and OpenCL, and with the CPU reference. The batch fingerprint over 16.7 million consecutive nonces is identical on the AMD integrated chip and the 5090, which is a far stronger statement than the 96 vectors alone. The local-memory exchange costs about 4% against CUDA's warp shuffle on this card, approximate.</p>
<h2 id="3-october-2026-igneum-node-devnet-v0-3-node-igneum-devnet-at-1-bps-with-the-80-20-coinbase-and-vote-key-hash-consensus-engineer">3 October 2026, igneum-node devnet v0: 3-node igneum-devnet at 1 BPS with the 80/20 coinbase and vote_key_hash (consensus-engineer)</h2>
<p>Machine: Apple M5 Max (18 logical cores), rustc 1.99.0, fork <code>vendor/igneum-node</code> at commit "Build: forward the igneum-pow feature" on top of rusty-kaspa v2.1.0 <code>01b532e8</code>. Release build of <code>kaspad</code> and <code>igneum-miner</code>; the kHeavyHash stub engine (default feature set); <code>cargo check -p kaspad --features igneum-pow</code> also builds. Network: three <code>kaspad --devnet --nodnsseed --disable-upnp --enable-unsynced-mining</code> nodes, P2P 26611/26621/26631, gRPC 26610/26620/26630, nodes 2 and 3 <code>--connect</code> to node 1 and node 3 also to node 2; network name <code>igneum-devnet</code>, k 18, merge depth 3,600 blocks, DAA 661 samples x 4 blocks, genesis bits 0x1e020000 (2^23 expected hashes per block). Miners: three <code>igneum-miner mine</code> processes, 6 threads each, one per node, 960 s, each with its own vote-key label: 2.21 MH/s each (6.63 MH/s total), found 253 + 284 + 270 = 807 blocks, 0 rejected. Blocks per second: 0.84 on all three nodes over the 901.9 s watch window (755 blocks each); expected 0.79 from hash rate over genesis difficulty, then the DAA lowered difficulty from 4,194,304 to 3,444,348 after its 600-block minimum window (block 600 to 711), still converging to 1.00 when the run ended. Propagation: block count, DAA score and sink hash identical on all three nodes at 90 of 90 ten-second samples; 1.11 parents and 1.11 mergeset per block on average, tips stayed at 1 (three serial CPU miners rarely collide). vote_key_hash: <code>igneum-miner inspect 40</code> read the same 40 selected-chain blocks from all three nodes over gRPC and found the header's vote_key_hash identical on every node for 40 of 40 blocks, with the three miners' distinct hashes (17 + 17 + 6 blocks) all present, so the field round-trips through the template RPC, submit, p2p relay and the header hash. Emission: 80/20 exact on 39 of 39 single-payee coinbases (the 40th merged two blues, three outputs, pool share still 0.2000); at DAA 806 the payload subsidy was 317,767,704 units (launch ramp day 0, 10.03%), split 254,213,284 to the miner and 63,553,320 to the OP_RETURN <code>igneum-proving-pool-v0</code> output. Unit tests, release profile: kaspa-consensus-core igneum 8 pass (subsidy table, ramp, split, cap), params window test 1 pass, kaspa-pow 5 pass (stub) and 6 pass with <code>--features igneum-pow</code> (engine smoke, one 256 MiB cache, 0.39 s), kaspa-consensus coinbase 8 pass. Per-second subsidy, 8 decimals: 3,168,808,781 units (31.68808781 coins) for years 0 to 2, 1,584,404,390 for years 2 to 4, 792,202,195 for years 4 to 6, 1 unit in period 31, 0 from period 32; ramp day 0 is 316,880,878; the sum is under the 4,000,000,000-coin cap by less than 100 coins. Not done: the devnet ran on the kHeavyHash stub, not the lottery engine (the miner has no igneum-pow path yet and the lane hash does not absorb the header); no VDF seed, no finality, no prover payout; 8 versus 18 decimals open (docs/fork-divergence.md).</p>
<h2 id="3-october-2026-first-devnet-blocks-on-the-real-lottery-hash-cpu-then-metal-gpu-three-worker-implementations-consensus-engineer">3 October 2026, first devnet blocks on the real lottery hash: CPU, then Metal GPU, three worker implementations (consensus-engineer)</h2>
<p>Machine: Apple M5 Max (18 logical cores, 40 GPU cores), rustc 1.99.0, Swift 5.8.1, fork <code>vendor/igneum-node</code> at "PoW: header-bound lottery engine, real-hash miner modes, genesis bits 0x1e400000" plus the overnight genesis commit; <code>igneum-pow</code> at "igneum-pow: header binding ...". Release builds with <code>--features igneum-pow</code>. Binding (spec 01 section 1.6, O-1.9, <code>igneum-pow/src/bind.rs</code>): I = seed_words_from_bytes("igneum-block/" || H || nonce_hi_le32), lane nonce = low 32 bits of the 64-bit header nonce, H = header hash with the nonce zeroed and the timestamp kept, pow256 = lane hash in the top 64 bits with zero low bits (pow &lt;= target is exactly lane &lt;= target &gt;&gt; 192). Interim day seed "igneum-day/" || day_le64 (day = timestamp_ms / 86,400,000); epoch seed = the 32 bytes of the epoch block hash (genesis for epoch 0). 8 bound vectors in <code>igneum-pow/README.md</code>; 29 crate tests pass; the 288 pack vectors and the pack files are unchanged, <code>program_bound.metal</code>, <code>kernel_bound.cu</code> and <code>kernel_bound.cl</code> are new pack files. CPU rate on the real hash: 0.44 ms per 32-lane warp on one core (crate bench); 0.142 MH/s with one 6-thread miner (1.35 ms per warp per thread); 0.294 MH/s with three 6-thread miners at once (2.0 ms per warp per thread, memory-latency bound), 0.37 MH/s later in the run. Genesis bits for the CPU devnet: 0x1e400000 = 2^18 expected hashes per block. CPU devnet, 3 nodes (node 1 RPC and p2p on 0.0.0.0, ports 26610/26611, 26620/26621, 26630/26631), three 6-thread <code>igneum-miner mine --engine igneum-pow</code>, 660 s: found 252 + 309 + 272 = 833 blocks, 0 rejected; watch window 641 s: 825 blocks on all three nodes = 1.29 blocks/s; sink identical on all nodes at 61 of 64 samples (tips 1 or 2, twice 3). DAA: 1.43 blocks/s over the first 600 blocks at difficulty 131,072, then difficulty 184,000 to 187,000 (1.41x) and 1.03 blocks/s over blocks 610 to 816. Each node logged "PoW accepted &lt;hash&gt; by igneum-lottery-v1-bound" 833 times, 0 rejections during the run. <code>igneum-miner bad-nonce</code>: Reject(BlockInvalid) and a "PoW rejected ... by igneum-lottery-v1-bound" line. <code>inspect 30</code>: vote_key_hash identical on all 3 nodes for 30 of 30, 80/20 exact on 19 of 19 single-payee coinbases. Epoch 0 seed = devnet genesis hash 03115da0...d86b, day 20729, program 104 loads/hash. Metal GPU worker (<code>proto-metal/igneum-bench --serve</code>, runtime-compiled <code>igneum_hash_bound</code>, init words in buffer 3, 2^22 nonces per dispatch, CPU-side scan of the 64-bit outputs) driven by <code>igneum-miner --worker</code> on node 1 of the same devnet, 300 s: 506 jobs of 2^24 nonces, 5,636 blocks found and accepted, 0 rejected, 0 CPU/GPU mismatches (every found nonce is re-hashed on the CPU before submit); nodes 833 -&gt; 5,073 blocks on all three (14.57 blocks/s over the 291 s watch window), sink identical at 28 of 29 samples; difficulty 180,562 -&gt; 7,134,312 (39x) and still rising at the end. Epoch change crossed live at DAA 3,600: new seed 76c39fcf..., 128-load program compiled by the worker in 129 ms (first program 6.5 ms), no rejected blocks across the change. Rate through the worker: 32.4 MH/s inside jobs, 28.2 MH/s wall, against 45.2 MH/s raw bench (<code>igneum-bench</code> 2^22 x 4 batches): the gap is the read-back and CPU scan per dispatch, one command buffer per dispatch, and the template round trip per job. Early in the run one job found about 45 sibling blocks of one template; the miner now submits one block per job. Overnight devnet (started 20:07 BST): genesis bits 0x1d100000 = 2^28 expected hashes per block, sized for RTX 5090 229 + M5 Max 45 + gfx1036 4 = 278 MH/s (1.04 blocks/s); genesis hash edc4fa84...fb07; epoch 0 program 136 loads/hash compiled in 69.5 ms on Metal; node 1 alone (0.0.0.0:26610/26611, <code>caffeinate -dims</code>, logs /tmp/igneum-devnet/node1.log) with the Metal worker (<code>/tmp/igneum-devnet/metal-worker.log</code>): 31 blocks in 273 s = 0.11 blocks/s at 31.1 MH/s, as expected for 2^28 until the PC joins. Worker implementations, all bit-exact with <code>igneum-pow hash-bound</code> for a 96-nonce job across the 2^32 lane boundary (nonces 4294967264 to 4294967359, prehash ab x 32, devnet seeds): Metal (M5 Max GPU), OpenCL (<code>proto-opencl/host.c --serve</code>, Apple OpenCL 1.2 on the M5 Max, local-memory exchange, <code>--vendor</code> device filter added), CUDA (<code>proto-cuda/host.cu --serve</code> with the pack's <code>kernel_bound.cu</code>, through the clang emulation shim: bench PASS on the Rust-exported devnet pack, serve job bit-exact, seed-mismatch error exercised). CUDA and OpenCL are built ahead of time per pack; the Windows launcher re-exports and rebuilds at each epoch or day change (miner exit 42). <code>igneum-miner.exe</code> cross-compiled for x86_64-pc-windows-gnu with Homebrew mingw-w64 (9.4 MB; no rocksdb in the miner's closure). Not done: no NVIDIA or AMD run of the bound kernels yet (the Windows package <code>~/Desktop/igneum-mine-test.zip</code> is the next step); NVRTC and runtime OpenCL rebuilds inside the workers; the block level for pruning proofs on a lane-only pow value; 8 vs 18 decimals; the VDF seeds and finality.</p>
<h2 id="3-october-2026-windows-node-package-igneumd-cross-compiled-for-x86-64-pc-windows-gnu-two-peer-sync-test-consensus-engineer">3 October 2026, Windows node package: igneumd cross-compiled for x86_64-pc-windows-gnu, two-peer sync test (consensus-engineer)</h2>
<p>Machine: Apple M5 Max, load average 60 to 110 (three other agents building), rustc 1.99.0, Homebrew mingw-w64 (gcc 16.2.0), Homebrew llvm (libclang). Worktree <code>vendor/igneum-node-win</code>, branch <code>windows-node</code> at <code>352495f6</code> (the rename commit <code>d62708a8</code> plus one miner change: <code>watch</code> prints <code>blue=</code> and <code>synced=</code> after <code>sink=</code>). Cross-compile: <code>cargo build --release -j 6 -p kaspad -p igneum-miner --features igneum-pow --target x86_64-pc-windows-gnu</code> at <code>nice -n 19</code>, 8 min 25 s from a clean target directory (recipe in <code>proto-cuda/windows-node/cross-build.sh</code>). <code>librocksdb-sys</code> (bindgen plus the bundled rocksdb and snappy C++) built with <code>LIBCLANG_PATH=/opt/homebrew/opt/llvm/lib</code>, <code>BINDGEN_EXTRA_CLANG_ARGS_x86_64_pc_windows_gnu="--target=x86_64-w64-mingw32 --sysroot=&lt;mingw sysroot&gt; -I&lt;mingw include&gt;"</code> and <code>CXX_x86_64_pc_windows_gnu=x86_64-w64-mingw32-g++</code>; zstd, lz4, bzip2, zlib, secp256k1 and mimalloc built with the mingw gcc. <code>igneumd.exe</code> 43,172,352 bytes, <code>igneum-miner.exe</code> 9,391,104 bytes. The exe imports <code>libstdc++-6.dll</code>: <code>-static-libstdc++</code> is ignored by the gcc driver rustc links with, and <code>-C link-arg=-static</code> (second full build, 8 min) does not cover a library rustc passes as <code>-Bdynamic</code>; the package ships <code>libstdc++-6.dll</code>, <code>libgcc_s_seh-1.dll</code> and <code>libwinpthread-1.dll</code> next to the exe (fix for next time: empty <code>CXXSTDLIB_x86_64_pc_windows_gnu</code> plus <code>-Wl,-Bstatic -lstdc++</code>). Not run on Windows yet. Sync test (native build of the same worktree, 21:44 to 21:46 BST, live node on 26610/26611 untouched): node A on 27000/27001 with START-NODE.bat's flags (<code>--addpeer=192.168.68.64:26611 --listen=0.0.0.0:27001 --nodnsseed --disable-upnp --nologfiles</code>) connected and started IBD within 20 ms, had 5,144 headers and 1,386 blocks at 7 s, and from 17 s on matched the live node's block count, DAA score, blue score and sink at every 10-s sample over 60 s (5,157 to 5,175 blocks, sink identical 5 of 6 samples, <code>synced=true</code>); every header checked by <code>igneum-lottery-v1-bound</code>. Live node <code>peers</code> 1 -&gt; 2 -&gt; 1. Node B on 27010/27011 dialled A's listener, synced to the same sink within 25 s and learnt the live node from A (outbound 2): a node with <code>--addpeer</code> plus <code>--listen</code> accepts inbound connections (<code>--connect</code> would set the inbound limit to 0, <code>kaspad/src/daemon.rs</code>). SIGINT stopped both cleanly. Package: <code>~/Desktop/igneum-node-windows.zip</code> from <code>proto-cuda/windows-node/make-package.sh</code> (exe, miner exe, src.zip snapshot of the worktree HEAD plus <code>igneum-pow/</code>, scripts, README). Build on the PC (BUILD-NODE.bat) estimated at 15 to 25 minutes on the 9800X3D, approximate, untested.</p>
<h2 id="3-october-2026-r3-26-m15-pow-checked-after-the-cheap-checks-cache-build-cap-attack-before-and-after-consensus-engineer">3 October 2026, R3.26 / M15: PoW checked after the cheap checks, cache-build cap, attack before and after (consensus-engineer)</h2>
<p>Machine: Apple M5 Max (18 logical cores), load average 60 to 110 (three other agents building at the same time), rustc 1.99.0. Worktree <code>vendor/igneum-node-r3</code>, branch <code>r3-fixes</code> at <code>5166ee26</code> on top of the rename commit <code>d62708a8</code>. Release builds; the real engine needs <code>--features igneum-pow</code>. Fix: <code>validate_header</code> now runs version, timestamp-not-in-future, parent, vote-key, parents-exist, GHOSTDAG, pruning, DAA-score, difficulty, blue-score, blue-work and past-median checks before the PoW engine; the engine (the one 256 MiB cache per day seed) is the last check that chooses seeds. The engine is process-wide, holds <code>KEEP = 4</code> <code>(epoch seed, day)</code> caches, keeps the chain's current and next day resident, runs at most one build per seed pair and at most 2 at once with a queue of 4 (then <code>PowCacheQueueFull</code>, retryable, not a peer fault). A per-peer p2p guard counts an off-day cold build or a rejected-before-PoW header as a strike; more than <code>IGNEUM_POW_STRIKES</code> (default 2) in an hour disconnects the peer and bans its IP for an hour. Cache build time: one 256 MiB ChaCha12 program-plus-cache build in 222 ms on one core under this load (the engine smoke test on an idle machine earlier the same day measured about 0.2 s; <code>proto-metal</code> reported 273.6 ms for the CPU reference fill under the same load). The honest 20-to-60 s proving lag and the 10 ms CPU verify gate are unaffected. Attack, before and after (ignored test <code>measure_m15_attack_before_and_after</code>, release, <code>--features igneum-pow</code>, <code>validate_and_insert_block</code>, the path <code>submit_block</code> and block relay call into): an honest 10-block chain builds 1 cache (genesis epoch, genesis day). Then 50 headers with bogus timestamps (50 distinct past days) and bogus DAA scores.</p>
<ul><li>Before (the pre-fix order, replayed by calling the engine with the header's own unvalidated day seed): 50 cold 256 MiB builds, 10,595 ms, and the honest day's entry is evicted (<code>KEEP</code> was 3).</li><li>After (the new order): 0 builds, all 50 rejected (<code>TimeTooOld</code> or <code>UnexpectedHeaderDaaScore</code>) in 14 ms total. The live day stays resident.</li></ul>
<p>Tests: kaspa-pow <code>--features igneum-pow</code> 8 pass (engine smoke, <code>one_build_per_seed_pair_under_contention</code>, <code>live_days_survive_off_day_builds</code>, <code>build_queue_is_bounded</code>, index and live-day helpers, shared-engine, stub); kaspa-consensus header_processor <code>cheap_checks_run_before_the_pow_engine</code> pass; kaspa-p2p-flows <code>pow_guard</code> 2 pass; the full kaspa-consensus release suite otherwise unchanged. M16 Metal note (R3.5, cheap reconfirmation only): the Mac <code>--inline-dataset</code> shortcut at the 256 MiB cache, 256 MiB dataset, under the same heavy load, ran honest 91.7 Mhash/s against inline 5.29 Mhash/s (inline about 17x slower); this is noisier and slower than the idle-machine figures already in <code>proto-metal/MEMHARD.md</code> (10x slower at a 256 MiB dataset, 4.8x at 1 GiB), because the inline kernel is compute-bound and the machine was loaded. The 64 MiB on-die-SRAM emulation M16 wants (inline kernel with a 64 MiB cache, <code>cacheLog2Words = 24</code> in <code>proto-metal/main.swift</code>) is the RTX 5090 run reserved for the project lead's PC, as R3.5 states; it is not done here and the Mac number above does not price a die. Not done: the real-engine daemon RPC run (honest blocks need GPU-mined pow, so the measurement used the equivalent validate path with <code>skip_proof_of_work</code>); the 64 MiB-cache inline kernel on the 5090; the chain-derived day seed by DAA score (spec 01 section 1.12, still the timestamp-day devnet rule); the VDF epoch seed and finality.</p>
<h2 id="3-october-2026-difficulty-controller-devnet-record-simulator-igneum-dual-lane-rule-3-node-cpu-test-network-consensus-engineer">3 October 2026, difficulty controller: devnet record, simulator, Igneum dual-lane rule, 3-node CPU test network (consensus-engineer)</h2>
<p>Machine: the same Apple M5 Max, shared with two other build agents (load average 60 to 98 during the Rust builds). Fork worktree <code>vendor/igneum-node-diff</code>, branch <code>difficulty</code> from <code>d62708a8</code>. Everything in <code>docs/analysis/difficulty-2026-10-03.md</code>; raw outputs in <code>sim/difficulty/results.md</code>. Record: 3,682 headers of the overnight devnet pulled read-only through the observer node's wRPC JSON (<code>ws://127.0.0.1:28640</code>, <code>getBlocks</code> from genesis) into <code>sim/difficulty/devnet-2026-10-03.csv</code>. Kaspa's sampled DAA held genesis difficulty 134,217,727 through block 600 at 0.6 blocks/s (PC, 116 MH/s estimated from the blocks), then eased 4.8x at the first retarget (DAA 600, 19:56:12 UTC) and on to 15.7x (8,552,118) because the 600-block window spanned the 21-minute Metal-only period and a 13-minute idle gap; 5.44 blocks/s over the next five minutes, 355 blocks in the peak minute, 1,633 blocks above 2x, then 1.5x too hard; the DAG widened to 3,681 blocks for 1,656 chain blocks. Exact replay of the record's bits through rusty-kaspa's integer arithmetic matches 244 of 244 retargets while the devnet was a chain and diverges from DAA 845 (merged blocks a chain-only replay cannot see). Simulator <code>sim/difficulty/sim.py</code> (Python, one chain, exponential solve times, 1 BPS): Kaspa sampled DAA, Monero 720, LWMA 60 and 120, the Igneum rule and the brief's literal trigger, on nine synthetic profiles plus the record. Two design findings: Zawy's average-target LWMA estimator is biased while targets ramp (the fast lane stalled at 15x of a 50x step), so every Igneum lane uses work over time (Kaspa's <code>estimateNetworkHashesPerSecond</code> estimator); the brief's trigger (short-window rate off target) chatters once the short window is back on target while the long window is still polluted (polluted case 1,876 s against 70 s), so the trigger compares the two lanes. Tuned on the synthetic set only: short window 120, hold 8, prior 16, long lane from 600 blocks of the epoch, trigger 25%, harden 3% per block, ease 10% per block, solvetime cap 20 T. Settled seconds (121-block mean within 10% for 100 blocks), Kaspa / Monero / LWMA60 / LWMA120 / Igneum: x50 step 1,542 / 94 / 105 / 231 / 62; /50 step 12,296 / 6,433 / 578 / 1,074 / 657 (worst gap 179 / 187 / 119 / 65 / 35 s); epoch +-30% steps 1,583 / 456 / 157 / 153 / 144; 10x hopping never / 284 (6 of 12 never) / 264 / 326 / 212; polluted window 2,748 (peak 7.9x) / 124 (peak 15x) / 66 / 110 / 70; genesis 10x too hard never / 287 / 155 / 188 / 322; steady std of rate 0.012 / 0.037 / 0.131 / 0.092 / 0.038; the record's 75x step never (peak 7.6x, 2,340 blocks above 2x) / 136 / 75 / 157 / 79. With +-500 ms timestamp jitter the ordering holds (Igneum x50 169 s, /50 1,047 s, epoch 55 s, polluted 71 s). Implementation: <code>DifficultyRule { KaspaSampled, IgneumDual }</code> as a network parameter (IgneumDual on all four networks, <code>"difficulty_rule": "kaspa-sampled"</code> in the override file selects Kaspa's), <code>OverrideParams.genesis_bits</code> (genesis hash recomputed) for test networks, <code>SampledDifficultyManager::igneum_difficulty_bits</code> over a selected-chain walk plus the in-epoch samples of the existing window, pure integer core <code>igneum_target</code> (Uint320). <code>cargo test -p kaspa-consensus --lib difficulty</code>: 9 pass (hold, steady state, 3% harden, 10% ease, trigger, epoch shrinkage, max target, Kaspa's two level-work tests); <code>cargo test -p kaspa-consensus-core --lib params</code>: 4 pass. The miner now takes the genesis from the node (pruning point before the first pruning) so it mines an override-genesis network; before the fix it hashed the compiled devnet genesis as the epoch seed and every block was rejected. Test network (ports 26800 to 26821, appdir /tmp/igneum-diff-test, override file with <code>difficulty_rule</code> and <code>genesis_bits</code> 0x1e200000): 3 igneumd nodes, 3 CPU miners of 4 threads (A for 1,200 s; B and C from 359 s to 779 s), 1,133 blocks, 0 rejected, one sink. Delivered hash rate 0.0653 / 0.0988 / 0.0686 MH/s (the join is x1.51, not 3x: shared cores at load 50 to 90). Genesis 4x too hard. Measured first within 10% of 1 block/s (61-block mean): warm-up 132 s, join 214 s (23 s to first touch), leave 85 s; simulator on the same profile, 5 seeds: medians 263 s, 61 s, 231 s; worst gap 7.3 s. Kaspa's rule on the same genesis: no retarget inside 20 minutes in any seed (600-block dead zone). Kaspa's rule live on the same genesis, 10 minutes: 70 blocks, bits unchanged on all 70, 0.08 blocks/s, worst gap 63 s, never within 10% of target. Not done: no DAG in the simulator (red blocks' work is ignored by every lane, as by Kaspa's estimator); Monero and LWMA reproduced from memory (approximate); real-time targeting left out; the chain walk (600 reads per header early in an epoch) must be re-measured before the 4 BPS step.</p>
<h2 id="3-october-2026-igneum-node-devnet-v2-sustained-mining-finality-rule-v2-on-a-four-miner-test-network-and-as-a-follower-of-the-live-devnet-consensus-engineer-and-cryptographer">3 October 2026, igneum-node devnet v2: sustained-mining finality rule v2 on a four-miner test network, and as a follower of the live devnet (consensus-engineer and cryptographer)</h2>
<p>Machine: Apple M5 Max, rustc 1.99.0, fork <code>vendor/igneum-node</code> at commits "Finality: BLS12-381 vote keys ..." through "Miner: BLS identity ..." (four commits on top of the rename). Builds in <code>target-finality</code> (<code>CARGO_TARGET_DIR=target-finality cargo build --release --features igneum-pow</code>): <code>igneumd</code> 36 MB (66 MB with line tables for the stall hunt), <code>igneum-miner</code> 7.6 MB; Windows cross-builds with Homebrew mingw-w64 in <code>target-finality/x86_64-pc-windows-gnu/release/</code>: <code>igneum-miner.exe</code> 9.7 MB and <code>igneumd.exe</code> 44 MB (rocksdb compiled under mingw without trouble, 8 min 13 s). Unit tests: kaspa-consensus-core 71 pass (4 new: key derivation and proof of possession, vote sign/verify/aggregate, section codec with reveal, sortition threshold), kaspa-notify 131, kaspa-rpc-core 20, 0 failures. Rule as implemented: <code>docs/spec/03-finality.md</code> section 3.10 and <code>docs/fork-divergence.md</code> "Finality v2". Crypto: blst min-pubkey BLS12-381, votes over <code>"igneum-vote-v1/" || chain_id || 0 || index || hash</code>, sortition VRF = SHA-256 of the sortition signature, aggregate certificates with a bitmap over the canonical voter list. Devnet parameters: checkpoint every 30 blue score, determined at +20, weight window 7,200 DAA s, dust 5 blocks, presence 20 indices, 8 aggregators, ban 7,200 DAA s, quorum 2/3 of active and 17/30 of total. Test network (<code>--devnet --devnet-suffix=7</code>, network id igneum-devnet-7, genesis bits 0x1e400000 through <code>--override-params-file</code>, finality params as devnet): three igneumd nodes on gRPC 26650/26660/26670, p2p 26651/26661/26671, wRPC JSON 28650/28660/28670 (nodes 2 and 3 <code>--addpeer</code> node 1, node 3 also node 2), peers at protocol version 12; four 4-thread CPU <code>igneum-miner mine --engine igneum-pow</code> identities m1, m2 (node 1), m3 (node 2), m4 (node 3); 21:59:48 to 22:53 BST. Block rate 0.25 blocks/s per miner (m1: 736 blocks in 3,000 s, 0.072 MH/s, 0 rejected), 2,193 blocks at 22:35; difficulty 149,037 at DAA 2,193. Key reveal: all four keys revealed from the first block of each identity (<code>Finality: vote key revealed</code>), hash matches the header on all three nodes. Weights at checkpoint 4 (DAA 119): 34 + 31 + 30 + 24 blocks, total 119, 4 voters, participation 1.0 (all keys younger than the presence window). Checkpoints and locks, steady state (indices 1 to 72, all four voting until the equivocation at 43, three after): 72 of 72 determined checkpoints locked on all three nodes; lock latency from determination to lock on node 1: median 0.80 s, p90 1.08 s, max 1.55 s (the vote round trip is bounded by the miners' 1-s poll); first lock 61 s after the first block (checkpoint 1 at blue score 31). Certificates built by the first node to see quorum: node 1 built 91, node 2 92, node 3 90 of 93, 0 conflicting certificates on any node; identical checkpoint hashes, states, signed and total weights on all three nodes at every RPC sample (getFinalityCheckpoints on 28650, 28660, 28670). Checkpoint 2 and 3 locked with 3 of 4 votes (74.6% and 73.0% of total) because the per-template vote carriage and the 1-s poll leave one vote outside the aggregator's first certificate; the lock still met both tests. Sortition: with 4 voters every voter is eligible (threshold = 1 when voters &lt;= 8); <code>aggregators</code> lists the keys whose proof verified, 3 to 4 per checkpoint, and the certificate names the local eligible voter of the building node. Equivocation (m4 restarted with <code>--equivocate</code> at 22:19:41): at index 43 m4 submitted a vote for the checkpoint and one for the hash with its last bit flipped; node 3 answered the second with <code>accepted=false equivocation=true</code>, every node logged <code>EQUIVOCATION by key 56da130c... at index 43 ... weight stripped until daa 8512</code>, and from checkpoint 44 the key is <code>voter false, stripped_until 8931</code> (the ban is re-stamped at each detection, m4 kept equivocating at every index), the voter list is 3, total weight excludes its 526 blocks, and locks continued at 3 of 3 votes (checkpoint 64: 938 of 953 active, 1,400 total). Evidence items were carried in blocks (<code>evidence</code> carriers) and re-detected by the follower path; 21 detections on node 3 in 10 minutes. Partition test (m4 stripped throughout, so the honest set is m1, m2, m3 with 33% of weight each): phase A, m3 stopped 22:31:07 to 22:35:10 (240 s): locks continued (72 at the end, signed 1,088 of 1,105 active and 1,563 total). Phase B, m2 also stopped 22:35:19 to 22:47:48 (749 s), m1 alone voting: 0 locks in 12 checkpoints (73 to 84); at the end m1's weight was 696 of 1,759 total (39.6%) and 696 of 962 active (72.3%), so the ACTIVE test passed as the two silent keys decayed out of the presence window and the 56.7% FLOOR alone held the lock back, which is the sim's 3.3.1 scenario on a real DAG. <code>finality_active</code> stayed true until the lock at 72 fell out of the 20-index window. Phase C, m2 and m3 restarted at 22:47:56: the returning miners signed every open index of the presence window, checkpoints 73 to 85 locked within 30 s (determined-to-locked 749 s for 73 down to 121 s for 84, 0 s for 85), 86 to 92 locked at the steady cadence (signed 1,784 of 1,784 at 86, 1,287 of 1,921 at 92, 3 voters). No conflicting certificate and no stall on any node through the heal. Observer and site: <code>tools/observer/observer.mjs</code> with <code>LIVE_TABLE_PREFIX=fintest_</code> against 28650 wrote <code>fintest_live_checkpoints</code> (49 rows, 42 locked at the first sample) and <code>checkpoint_locked</code> events ("checkpoint 42 locked (76.2% of weight, 96.5% of active, 3 votes of 4 voters) at block 0d1405d0"), from both the <code>FinalityLock</code> subscription and the 2-s poll; <code>live_state.finality</code> carried the weights snapshot. <code>site/api/live.mjs</code> adds the <code>live_checkpoints</code> query and <code>locked</code>/<code>final</code> flags per block; <code>site/live.html</code> draws the locked-checkpoint ring, the dashed "final" line at the newest lock and the locked counter. Not deployed to Vercel tonight. Live devnet follower (<code>igneumd</code> v2 on gRPC 26690, p2p 26691, JSON 28690, <code>--connect=127.0.0.1:26611</code>, never mining): IBD of 5,254 blocks from node 1 in under a second, kept in sync (5,418 blocks at 21:53, 0.70 blocks/s on the live chain), determined checkpoints 1 to 302 within 1 s of IBD; weights at checkpoint 296 (DAA 8,935): 16 keys, 14 above dust, total 7,146 blocks (eight RTX 5090 identities at 862 to 936 blocks, six at 4 to 10), 0 revealed keys, 0 votes, participation 0, 0 locks, <code>finality_active</code> false, exactly as expected while the Windows miners run the pre-v2 binary. RSS 1.1 GB. It only ever receives from node 1 (its protocol version 12 against node 1's 11 means no finality messages in either direction). Open: one stall of all three test nodes at 21:48:43 BST in the first run (stripped binary, right after the follower, the equivocating miner and the test observer started): all three logs stop in the same second, every RPC times out, CPU 0%, node 1 of the live devnet unaffected; not reproduced in 28 minutes of the same scenario on the symbolized build (lock 40 to 93 without a pause, including the equivocation and the partition). The first run's self-deadlock (<code>compute_weights</code> taking the state lock its callers hold) was found and fixed before that stall, and <code>Router::enqueue</code> is a non-blocking try_send, so the gossip pump cannot deadlock across nodes; cause unknown. Also open: C3 validity rule and F3 pruning bound not enforced; d = 20 chosen without the reorg-depth distribution; the weight walk is O(window) per checkpoint; one certificate per index per node means a certificate often names fewer signers than the votes that exist. Not demonstrated: locks on the live devnet (its miners do not vote yet), the Windows binaries on Windows, a certificate carried into a block and verified by a cold node that missed the gossip (the follower had no votes to receive), the 2-hour presence window at full length (the run was 53 minutes).</p>
<h2 id="3-october-2026-execution-layer-devnet-v3-revm-over-the-selected-chain-3-node-simnet-viem-smoke-test-execution-engineer">3 October 2026, execution layer devnet v3: revm over the selected chain, 3-node simnet, viem smoke test (execution-engineer)</h2>
<p>Machine: the same Apple M5 Max (18 cores), shared with two other agents' builds (load 15 to 55). Branch <code>execution-layer</code> in <code>vendor/igneum-node-exec</code>, worktree of <code>vendor/igneum-node</code> from <code>d62708a8</code>; revm 43.0.3, alloy-primitives 1.7.3, alloy-consensus 2.5.0, alloy-trie 0.9.8, axum 0.8.9; viem 2.57.2, solc 0.8.37 (tools/evm-smoke). Release build <code>CARGO_TARGET_DIR=target cargo build --release -p kaspad -p igneum-miner --features igneum-pow</code>: first full build with the new crates about 20 min under <code>nice -n 10 -j 10</code> on the loaded machine; incremental igneumd rebuilds 35 s to 4 min. Test network: 3 <code>igneumd --simnet</code> nodes (devnet block rate and depths, proof of work skipped, chain id 4463) on gRPC 26700/26710/26720, p2p 26701/26711/26721, eth RPC 26790/26791/26792, appdirs /tmp/igneum-exec-test; 3 single-thread <code>igneum-miner --engine stub --hold-ms 2500</code> (exponential hold, mean 2.5 s per miner). Rate: 130 blocks in 120 s = 1.08 blocks/s; 68 chain blocks; selected-chain reorgs 22 in 120 s, depth 1 (17) and 2 (5), identical on the 3 nodes. Earlier run with a fixed 900 ms hold: 3 blocks/s in lockstep rounds and 80 to 92 reorgs in 60 s with flips 45 to 69 deep (equal-work chains kept alive by the hash tie-break); every flip was unwound correctly (state roots identical on 3 nodes at block 32 after 65-deep flips), the fix is Poisson pacing in the miner. Smoke test (<code>node tools/evm-smoke/smoke.mjs</code>, stock viem paths): 87 checks passed, 0 failed, 36 s wall, tip at chain block 78. eth_chainId 0x116f, net_version 4463. Miner 1 (EVM address = low 20 bytes of its vote key hash) held 91.28 IGN at chain block 53 from 80% subsidy shares of 36.59 IGN per blue block (3,168,808,781 sompi x 1e10 x 0.8, launch-ramp day 0 is not applied on simnet's genesis timestamp); proving pool escrow 92.55 IGN at the end. Funding: 3 transfers of 5 IGN, eth_estimateGas 25,380 (21,000 plus the pgas fold and the 15% margin), all status 1, balances exact. 50 transfers between 3 accounts (each sender's transactions to one node, no p2p relay of EVM transactions): all 50 executed in 16 s wall across chain blocks 58 (17), 61 (20), 62 (13); 57 executed transactions in 7 chain blocks over the run, max 20 per chain block; 19 skipped copies (the same miner re-including transactions handed out before its earlier template landed, every one skipped by the nonce rule with no fee and no receipt). Balances of the 3 accounts matched the receipt accounting to the wei (value plus gas_used x effectiveGasPrice plus burnedProvingFee). Transfer receipt: gasUsed 21,000, pgasUsed 200, effectiveGasPrice 2 gwei (base 1 gwei, tip 1 gwei), burnedProvingFee 200 gwei (200 pgas x 1 gwei), minerTip 16,800 gwei (80% of 21,000 gwei), developerShares [burned 4,200 gwei] (unregistered, 20%). Contract call <code>increment(5)</code>: gasUsed 45,354, pgasUsed 1,288, minerTip 36,283.2 gwei (80%), developer share 9,070.8 gwei (20%) credited to the payee the constructor registered (balance delta equal). Deployment via viem <code>deployContract</code>: gasUsed 185,948, pgasUsed 1,438, registry <code>creatorOf</code> = deployer and <code>payeeOf</code> = constructor argument (executor CREATE rule plus <code>register</code>). <code>eth_estimateGas</code> reports the revert of <code>increment(0)</code>; <code>eth_call hashLoop(50)</code> returns; <code>eth_estimateGas hashLoop(200)</code> = 98,900 with the fold; <code>eth_getLogs</code> finds the event. Measured pgas/gas: 0.0095 transfer, 0.028 storage write with event, 0.0077 deployment, 0.0099 averaged (prototype table, below the design's 0.1 to 10 band as expected before calibration). Duplicates in parallel blocks: one identical copy sent to nodes 1 and 2 was included twice (chain blocks 63 and 64, blocks c09c9329... and 3f401bd2...), executed once, the second skipped <code>NonceTooLow { expected: 18, got: 17 }</code>; a conflicting same-nonce pair (different values, one copy per node) executed exactly once (B1), the loser never reached a block because its node's pool dropped it once the nonce had passed (<code>igneum_getTransactionStatus</code>: includedIn [], executed false). Execution time per chain block (node 1, <code>igneum.executionMicros</code>, includes the full-recompute state root): 50, 71, 93, 57, 41, 46, 50 us for the 7 chain blocks with 3, 17, 20, 13, 2, 1, 1 transactions; 71 empty chain blocks averaged 11 us; the same chain block on the 3 nodes: 50 / 192 / 85 us (block 56) and 50 / 88 / 46 us (block 78). State roots as outputs: genesis (registry only) 7e37a9fb19b154d32daf5bf30a50d339a75029fbc9eec9ea20e95439dba5a311; chain block 56 (3 funding transfers) 68cacfd393b00ead784a69b10d57a3e2dd57858029df107b529487a49f393b50; chain block 78 5b18b3a58f6c1d21b22caad4a1dbd9ee8a6394a02db2220255e556a9d4f90878; identical hash and root on all 3 nodes at heights 0, 56, 74 and 78; the root advanced at every block with transactions. Base fees stayed at the 1 gwei floor (segments far below the 15 M gas target). Differential (<code>igneum-exec-diff seq.json</code>, plain revm without inspector, pgas or split, balances adjusted by the exported Igneum-only flows): segments 0 to 78, 57 executed transactions compared (status, gas used, logs), 19 skipped copies confirmed rejected by plain revm at their positions, 10 accounts compared (balance, nonce, code hash), 0 mismatches. <code>cargo test -p igneum-evm-types</code>: 3 passed. Not done: on-disk state and incremental trie (state rebuilt from genesis at start), header fields <code>utxo_commitment</code> and <code>accepted_id_merkle_root</code> kept (proofs_root is an RPC placeholder), body <code>miner</code> field and <code>proofs</code> section, pgas calibration, proving layer, eager virtual execution, eth_getProof/subscribe/debug, EVM transaction relay between nodes, the finality merge (plan in the design document, section 10.4). Test network stopped at the end of the run.</p>
<h2 id="2026-10-03-consensus-attack-harness-consensus-engineer-catalogue-run-on-the-ordering-layer-node">2026-10-03, consensus attack harness (consensus-engineer), catalogue run on the ordering-layer node</h2>
<p>Machine: Apple M5 Max, 64 GB, load 61.19 55.26 50.53. Private test network of igneumd (release, skip_proof_of_work devnet) on 127.0.0.1 ports 27200+, data /tmp/igneum-harness; the live devnet and the PC node were not touched. Harness: tools/harness/, node fork worktree vendor/igneum-node-harness.</p>
<div class="tbl"><table><thead><tr><th>Scenario</th><th>Criterion (spec)</th><th>Measured</th><th>Pass</th></tr></thead><tbody><tr><td>5 malformed and boundary inputs on every p2p message and RPC method the fork touches</td><td>rejected without a crash or a cache build (spec 02 2.4; fork-divergence header and RPC rows; ledger M15)</td><td>63 cases (46 RPC, 17 p2p): node stayed up on every case; all malformed inputs rejected or disconnected. 5 cases (rpc:timestamp-zero, rpc:timestamp-past-3-days, rpc:daa-score-bogus, p2p:ts-past-day, p2p:daa-bogus) built a 256 MiB cache = ledger M15 reproduced on HEAD d62708a8, which the r3-fixes branch drives to 0 (bench-log M15 entry). Other unexpected cache builds: 0. Over-length vote_key_hash (vkh-33-bytes) and an unknown JSON field were normalized and accepted rather than rejected (minor, no safety impact).</td><td>pass</td></tr><tr><td>2 timestamp boundaries (live)</td><td>rejected at ts &lt;= past median, accepted at pmt+1; accepted below now+132 s, rejected above (spec 02 section 2.3)</td><td>past: pmt-1=rejected, pmt=rejected, pmt+1=accepted, pmt+2=accepted; future flip between +132.00 s and +132.01 s</td><td>pass</td></tr><tr><td>2 timestamp stretch drift (sim)</td><td>controller response to a 33% miner stretching timestamps inside the rules is measured (blocks per second drift against an honest run)</td><td>honest 0.9952 b/s (difficulty x1.016); ahead 131 s: 1.0222 b/s (+2.7%, x0.96); oscillate: 1.0422 b/s (+4.7%, x0.923); over 6000 virtual s</td><td>pass</td></tr><tr><td>1 withhold a=0.1 release every 5</td><td>attacker blue share &lt;= 0.1 + 2 sigma (0.014) over 1927 blues</td><td>blue share 5.4% (104 blue, 81 red of 186 made); honest reorgs depth:count 1:2 2:5 3:2 4:2 5:2 8:2, max 8</td><td>pass</td></tr><tr><td>1 withhold a=0.1 release every 20</td><td>attacker blue share &lt;= 0.1 + 2 sigma (0.014) over 1858 blues</td><td>blue share 1.2% (23 blue, 157 red of 186 made); honest reorgs depth:count 1:1 2:1 5:1, max 5</td><td>pass</td></tr><tr><td>1 withhold a=0.25 release every 5</td><td>attacker blue share &lt;= 0.25 + 2 sigma (0.020) over 1942 blues</td><td>blue share 22.0% (428 blue, 42 red of 474 made); honest reorgs depth:count 1:11 2:11 3:6 4:17 5:6 6:9 7:5 8:6 9:2 10:1, max 10</td><td>pass</td></tr><tr><td>1 withhold a=0.25 release every 20</td><td>attacker blue share &lt;= 0.25 + 2 sigma (0.021) over 1693 blues</td><td>blue share 12.6% (214 blue, 266 red of 489 made); honest reorgs depth:count 1:3 3:3 4:1 5:1 6:1 7:1 8:2 11:2 13:1 14:2 16:1 25:1 30:1, max 30</td><td>pass</td></tr><tr><td>1 withhold a=0.33 release every 5</td><td>attacker blue share &lt;= 0.33 + 2 sigma (0.021) over 2039 blues</td><td>blue share 31.5% (643 blue, 17 red of 663 made); honest reorgs depth:count 1:15 2:18 3:21 4:21 5:15 6:14 7:10 8:5 12:1 13:1, max 13</td><td>pass</td></tr><tr><td>1 withhold a=0.33 release every 20</td><td>attacker blue share &lt;= 0.33 + 2 sigma (0.024) over 1561 blues</td><td>blue share 27.4% (428 blue, 212 red of 640 made); honest reorgs depth:count 2:2 3:1 4:1 5:1 6:1 7:1 8:2 12:1 13:1 15:1 19:1 22:1 23:3 25:2 26:1 28:2 29:1 32:2 33:1, max 33</td><td>pass</td></tr><tr><td>1 withhold a=0.45 release every 5</td><td>attacker blue share &lt;= 0.45 + 2 sigma (0.022) over 2002 blues</td><td>blue share 44.2% (885 blue, 0 red of 886 made); honest reorgs depth:count 1:24 2:27 3:23 4:29 5:22 6:16 7:7 8:8 9:2 13:2 14:1 15:1, max 15</td><td>pass</td></tr><tr><td>1 withhold a=0.45 release every 20</td><td>attacker blue share &lt;= 0.45 + 2 sigma (0.024) over 1657 blues</td><td>blue share 50.7% (840 blue, 40 red of 886 made); honest reorgs depth:count 3:1 8:2 9:1 11:3 12:1 13:1 15:1 16:5 17:2 18:2 19:3 20:3 21:1 22:4 23:3 25:3 26:2 28:1 29:1 31:1 32:2 36:1, max 36</td><td>FAIL</td></tr><tr><td>3 partition 120 s</td><td>one chain after the merge-depth rule; reorg depth and time to heal recorded</td><td>one chain: true (blue scores within 3 at the end); healed in 10 s; losing-side reorg at heal 41 chain blocks (per node 41/41/0/2); rejects none</td><td>pass</td></tr><tr><td>3 partition 600 s</td><td>one chain after the merge-depth rule; reorg depth and time to heal recorded</td><td>one chain: true (blue scores within 4 at the end); healed in 10 s; losing-side reorg at heal 234 chain blocks (per node 0/0/233/234); rejects none</td><td>pass</td></tr><tr><td>3 partition 1800 s</td><td>one chain after the merge-depth rule; reorg depth and time to heal recorded</td><td>one chain: true (blue scores within 4 at the end); healed in 10 s; losing-side reorg at heal 920 chain blocks (per node 0/0/920/920); rejects none</td><td>pass</td></tr><tr><td>3 partition 3700 s (beyond merge depth)</td><td>one chain after the merge-depth rule; reorg depth and time to heal recorded</td><td>one chain: true (blue scores within 4 at the end); healed in 10 s; losing-side reorg at heal 2160 chain blocks (per node 0/5/2160/2159); rejects MissingParents:1; MissingParents:1; MissingParents:5; MissingParents:5</td><td>pass</td></tr><tr><td>6 resource exhaustion (50x template, submit and mempool floods from one peer)</td><td>honest template p95 &lt; 200 ms and both nodes under baseline RSS + 512 MB, alive, one sink</td><td>honest template p95 worst 3.7 ms across loads (baseline 33.2 ms); template 500ps 500/s, submit 50ps 50/s, mempool 500ps 500/s; RSS growth template +4MB, submit +11MB, mempool +14MB; alive true; same sink true</td><td>pass</td></tr><tr><td>4 eclipse 600 s</td><td>victim rejoins the honest chain on reconnection within the merge-depth bound; reorg depth recorded</td><td>victim rejoined 10 s after reconnection (blue-score gap to honest 3 at the end); victim reorg depth 149 chain blocks; adversary built 242 blocks that never entered the honest chain</td><td>pass</td></tr><tr><td>4 eclipse 1800 s</td><td>victim rejoins the honest chain on reconnection within the merge-depth bound; reorg depth recorded</td><td>victim rejoined 10 s after reconnection (blue-score gap to honest 0 at the end); victim reorg depth 447 chain blocks; adversary built 497 blocks that never entered the honest chain</td><td>pass</td></tr><tr><td>7 fast-miner flood, controller trajectory (sim, Kaspa sampled DAA on HEAD)</td><td>trajectory recorded for the difficulty branch (bits, blocks per second, settle times)</td><td>50x joins at 600 s: peak 25.27 blocks/s, difficulty x28.4, within 25% of 1 BPS after never s; leaves at 1200 s: trough 0 blocks/s, back within 25% after never s</td><td>pass</td></tr><tr><td>7 fast-miner flood, live (50 blocks/s from one peer)</td><td>node stays responsive: honest template p95 &lt; 200 ms, both nodes alive, same sink</td><td>flood accepted 721 blocks in 60 s (12.0/s); honest template p50/p95/max 0.4/0.8/1.3 ms under flood (baseline 0.4/0.8/1.2); rss a 303-&gt;333 MB, b 305-&gt;332 MB; alive true; same sink true</td><td>pass</td></tr><tr><td>1b withhold vs finality weight (finality branch)</td><td>spec 03: a withholder gains no vote weight beyond its hash share; under the 56.7% total floor, 0 conflicting locks (CLAUDE.md, ledger F18)</td><td>stub: run s1 withhold against a node built with the finality-v2 branch, with miner --vote keys, and read getFinalityWeights and getFinalityCheckpoints; assert blue-weight share within noise and no conflicting lock. Needs the finality branch merged into the harness worktree.</td><td>stub</td></tr><tr><td>3b partition vs finality lock (finality branch)</td><td>spec 03.5 and ledger F16: after a partition heals, no certified lock is revoked (an exchange relies on "locked" being final); the F16 decision (Kaspa halt vs re-evaluate) is exercised</td><td>stub: run s3 partition with voting miners on both sides; record every FinalityLock notification and assert no locked checkpoint changes hash after the heal. Needs the finality branch.</td><td>stub</td></tr><tr><td>4b eclipse vs finality presence window (finality branch)</td><td>spec 03.3 F2 and ledger F2: a 2-hour presence window does not let an eclipsed victim be fed a locked side chain; the victim rejoins without accepting a revoked lock</td><td>stub: run s4 eclipse with voting miners; assert the victim never reports a lock on the adversary chain that the honest chain does not also certify. Needs the finality branch.</td><td>stub</td></tr><tr><td>2b difficulty controller under timestamp stretch (difficulty branch)</td><td>docs/analysis/difficulty-2026-10-03.md: the igneum-dual rule holds the block rate under a timestamp-stretching miner better than Kaspa sampled DAA; forged timestamps move a lane by at most a few percent (spec 02 section 2.3)</td><td>stub: run s2 Part B with {"difficulty_rule":"igneum-dual"} in the override file against the difficulty branch, compare the drift to the kaspa-sampled baseline this branch measured. Needs the difficulty branch (vendor/igneum-node-diff) merged into the harness worktree.</td><td>stub</td></tr><tr><td>7b fast-miner flood on the dual-lane controller (difficulty branch)</td><td>docs/analysis/difficulty-2026-10-03.md: on the igneum-dual rule the 50x step settles within about 62 s and the step-down within about 11 minutes, against Kaspa sampled DAA never settling (the record of the devnet event)</td><td>stub: run s7 Part A with the difficulty branch and {"difficulty_rule":"igneum-dual"}, compare the trajectory to the kaspa-sampled baseline this harness records. Needs the difficulty branch.</td><td>stub</td></tr></tbody></table></div>
<p>Full JSON per scenario under /tmp/igneum-harness/results and /tmp/igneum-harness/sim. The simulator (igneum/harness-sim in the fork worktree) runs real consensus code in virtual time with PoW skipped, as rusty-kaspa simpa does; the live scenarios (5, 6, 7 Part B) drive real igneumd processes over wRPC and the fork's own p2p (igneum/p2p-probe).</p>
<p>Finality and difficulty-controller scenarios are stubs here: their criteria are written and they run against those branches once merged into the harness worktree (see tools/harness/scenarios/stubs.mjs).</p>
<h2 id="3-october-2026-weak-program-census-400-000-program-runs-through-the-cpu-reference-the-redundant-load-finding-and-the-rules-for-m5-and-m6-cryptographer">3 October 2026, weak-program census: 400,000 program runs through the CPU reference, the redundant-load finding, and the rules for M5 and M6 (cryptographer)</h2>
<p>Machine: Apple M5 Max, 8 threads at <code>nice -n 15</code> while a devnet build and its simulations shared the box (load average 25 to 107), rustc 1.99.0, release build with LTO. New crate <code>igneum-census/</code> (path dependency on <code>igneum-pow</code>, nothing in <code>igneum-pow</code> changed); the instrumented interpreter is checked against <code>igneum_pow::hash_warp</code> on the first warp of every program and against the <code>igneum-genesis</code> spec vectors at start. Commands: <code>igneum-census run --root igneum-census-2026-10-03 --count 100000 --warps 128 --threads 8 --gen default</code> (memory-hard, 1 GiB, day 2026-10-03; 2,797 s), the same with <code>--gen fixed16-fresh --warps 64</code> (1,049 s), <code>--gen fixed16-fresh2 --warps 64</code> (6,650 s, starved to under a core for most of it), and <code>--gen default --closed-form --warps 128</code> (125.6 s once the machine was quiet); <code>summarise</code>, <code>probe</code>, <code>show</code>. Full tables and the rules in <code>docs/analysis/weak-program-census-2026-10-03.md</code>. Current generator, 100,000 programs x 4,096 nonces: loads per hash 24 to 256 (mean 127.9); distinct addresses per hash 24 to 200 (mean 102.4): 19.9 percent of all loads re-read an address the same hash already read, 94.8 percent of programs have at least one such load, 22.5 percent have a pair that cancels to the identity. The Mac rates of the eight bench seeds vary 1.38x by static loads/s and 1.10x by distinct loads/s (3.51 to 3.85 G/s), so the GPU is bound by the distinct count; <code>igneum-second-seed/epoch1</code> (144 static, 104 distinct) hashes at the rate of <code>igneum-second-seed</code> (104 and 104). Weak programs, current generator: 2.43 percent have a register with no injecting write (saturates to all ones); 0.73 percent have a register with a nonce-independent bit, 0.03 percent a whole nonce-independent register, 0.03 percent a load site read at one address by all 32 lanes, 0.68 percent more than 1 percent of final registers at 0 or all ones, 6 programs an output bit past 6 sigma (0.03 expected by chance). Avalanche clean on every program (mean 31.85 to 32.15, every output bit flips 0.473 to 0.523). Mechanisms: no injecting write; zero-absorbing register sets closed under mulhi/mul; or or mul as the last write. Rules: G1 exactly 16 load slots drawn first from slots 1..63; G2 a load reads only a register written earlier in the program and not read by a load since; R-a no cyclically redundant load; R-b every register has an injecting write; R-c 64 fixed warps on the seed-keyed closed-form dataset with no constant register bit, no lane-constant site, saturation under 1 percent, no output bit past 6 sigma, more than 120 distinct addresses per hash on average. Rejection: 95.0 percent under the current generator (the redundancy alone), 38.3 percent under the first form of G2 (the iteration wrap), 5.14 percent under the proposed form (R-a or R-b 3.93 percent, R-c 2.05 percent), so 1.054 candidates per epoch on average; the accepted population does 120.05 to 128 distinct loads per hash, median 128.00. Closed-form check: with the same seeds and nonces on the closed-form dataset instead of the memory-hard one, R-c agrees on 99,961 of the 100,000 proposed-generator programs (2,054 rejected memory-hard, 2,055 closed-form; the 39 that differ sit at a threshold edge, one nearly constant bit or a bias near 6 sigma) and the per-program metrics agree to three decimals, so the acceptance test can be a pure function of the program. Hash-rate spread: today 2.7x between the 1st and 99th percentile program by distinct loads (56 to 152 per hash; 321 to 118 Mhash/s projected on the RTX 5090 at 18.0 G distinct loads/s), 8.3x min to max; under G1 + G2 every program does 128 distinct loads, projected 141 Mhash/s on the 5090 and 28 on the M5 Max, with the 1.10x program-shape residual the only spread left, approximate. One 5090 run of <code>igneum-second-seed</code> (predicted 173 Mhash/s if distinct-bound, 228 if static-bound) settles the reading on NVIDIA. Not done: no GPU run of the new generator; the spec text is proposed in the analysis doc, section 9, not written into <code>docs/spec/01-lottery-hash.md</code>; test vectors are re-cut when the generator rule is adopted.</p>
<h2 id="3-october-2026-proving-v0-first-sp1-proof-of-an-igneum-block-apple-m5-max-cpu-loaded-machine-execution-engineer-proving">3 October 2026, proving v0: first SP1 proof of an Igneum block, Apple M5 Max CPU, loaded machine (execution-engineer, proving)</h2>
<p>Machine: Apple M5 Max (18 cores, 64 GB), macOS Darwin 25.6.0, load average 14 to 45 during the runs (the live devnet, the observer and other agents' builds were running), everything under <code>nice -n 19</code>. Toolchain: SP1 v6.8.1 (<code>sp1up</code>, cargo-prove c84ada1 of 24 Sep 2026, succinct rustc 1.96.0-dev, circuit version v6.1.0), sp1-sdk 6.8.1 CPU prover, revm 43.0.3, alloy-primitives 1.7.3 with SP1's sha3 patch and k256 patch. Code: <code>proving/igneum-prove</code> (guest ELF 2.69 MB, host 54 MB), fixtures cut from <code>tools/evm-smoke/seq.json</code> (the 3-node simnet export, execution-layer commit fb33069) by <code>igneum-prove-export</code>, which replayed all 79 segments from genesis through the ported executor and matched every one of the node's state roots (final root <code>0x5b18b3a5...</code>). Statement: re-execute one chain block (rewards by rule, nonce-rule skip, two-dimensional gas with the prototype pgas table, fee flows with the developer split, state root over the whole in-memory state) and commit the pre-root, post-root, receipts root, gas, pgas and the executed and skipped counts; the host checks the guest's public values against its own native run before and after each proof.</p>
<div class="tbl"><table><thead><tr><th>Fixture</th><th>Txs (executed / skipped)</th><th>EVM gas</th><th>pgas</th><th>Pre-state accounts</th><th>SP1 cycles</th><th>Prover gas</th><th>Cycles per EVM gas</th><th>Execute s</th></tr></thead><tbody><tr><td>block-78-increment (Counter <code>increment(5)</code> plus a duplicate copy skipped by the nonce rule)</td><td>2 (1 / 1)</td><td>45,354</td><td>1,488</td><td>10</td><td>626,246</td><td>843,343</td><td>14</td><td>0.03</td></tr><tr><td>block-56-transfers (three funding transfers)</td><td>3 (3 / 0)</td><td>63,000</td><td>600</td><td>5</td><td>549,469</td><td>733,297</td><td>9</td><td>0.03</td></tr></tbody></table></div>
<div class="tbl"><table><thead><tr><th>block-78-increment, CPU prover</th><th>Prove s</th><th>Proof bytes</th><th>Verify s</th><th>Verified</th></tr></thead><tbody><tr><td>Setup (pk, vk; vk hash <code>0x00c3a917...</code>)</td><td>6.9</td><td></td><td></td><td></td></tr><tr><td>Core (STARK shards)</td><td>22.0</td><td>7,317,217</td><td>0.164</td><td>yes</td></tr><tr><td>Compressed (recursion, one shard)</td><td>55.7</td><td>1,272,769</td><td>0.033</td><td>yes</td></tr></tbody></table></div>
<p>Reading: at 626 k cycles the block is far below one SP1 shard, so these times are fixed overhead (proof system setup and the recursion stack), not throughput; cycles per EVM gas (9 to 14) is the first data point for the pgas table calibration (R1) and is dominated by the state-root computation over every account plus one secp256k1 recovery per transaction (through the patched k256). Nothing here is a 12 GB-card shard time (ledger P1); that is the RTX 5090 run of <code>proving/windows-wsl2/</code> and then the 3060-class gate. The devnet was not touched. Not done: the Groth16 or Plonk wrapper (ledger P3), MPT witnesses, more than one shard per block, chain recursion, the prover key in the statement (P12).</p>
<h2 id="2026-10-03-execution-layer-attack-suite-malformed-txs-nonce-games-rpc-fuzz-pgas-exhaustion-reorgs-registry-abuse-execution-test-engineer">2026-10-03 execution layer attack suite: malformed txs, nonce games, RPC fuzz, pgas exhaustion, reorgs, registry abuse (execution test engineer)</h2>
<p>Machine: Apple M5 Max (18 cores), shared with other agents' builds (load 9 to 15). Worktree <code>vendor/igneum-node-exec-attacks</code> on branch <code>exec-attacks</code> (from <code>execution-layer</code> fb330692); <code>igneumd</code>, <code>igneum-miner</code> and a new hostile-miner bin <code>igneum-inject</code> built release with <code>CARGO_TARGET_DIR=target nice -n 19 cargo build -j 4 -p kaspad -p igneum-miner --features igneum-pow</code> (stable-aarch64 toolchain; the default cargo on PATH is too old for edition 2024). Tools and the per-scenario commands: <code>tools/exec-attacks/</code> (README, <code>net.sh</code>, <code>scenario{1,2,3,4,5,6}*.mjs</code>, <code>igneum-inject</code>); raw results under <code>tools/exec-attacks/results/*.json</code>. Network: 3 <code>igneumd --simnet --enable-unsynced-mining --unsaferpc --disable-upnp</code> nodes, PoW skipped, chain id 4463, eth RPC 27690/27691/27692, gRPC 27610/27620/27630, p2p 27611/27621/27631, appdir <code>/tmp/igneum-exec-attacks</code>; one honest stub miner for scenarios 1 to 5 and 4, three miners split into partitions for scenario 6. <code>igneum-inject</code> fetches a block template, replaces the EVM body with arbitrary raw EIP-2718 bytes, recomputes <code>hash_merkle_root</code> and resubmits, so the hostile-miner path reaches body validation and the executor directly. The live devnet (26610, 26611, 26640, 26641, 28640) and other agents' ports (up to 27599) were not touched; every process was stopped at the end.</p>
<p>Run in priority order 1, 2, 5, 3, 6, 4. One row per scenario: criterion (from the design), measured result, verdict.</p>
<div class="tbl"><table><thead><tr><th>#</th><th>Scenario</th><th>Criterion</th><th>Result</th><th>Verdict</th></tr></thead><tbody><tr><td>1</td><td>Malformed and boundary txs (mempool and hostile block)</td><td>State-free faults invalidate the block; state-dependent faults skip the tx with no receipt; no panic; memory bounded</td><td>7 state-free faults (bad RLP, type-3 blob, wrong chain id, intrinsic gas above limit, initcode above 49,152, duplicate hash in block, non-contiguous nonces, invalid signature s=0) each made the hostile block invalid and were rejected by the mempool where decodable; 5 state-dependent faults (nonce far ahead, nonce reuse, zero fee below base, insufficient funds, max fee at 2^120) each landed in an accepted block and were skipped with no receipt; gas limit exactly at B_e executed; node kept producing blocks; node RSS 345 MiB to 348 MiB (x1.01); 0 node panics in any log</td><td>PASS (30/30 checks)</td></tr><tr><td>2</td><td>Nonce games across parallel blocks</td><td>Exactly one execution per nonce; deterministic; state roots identical on all nodes</td><td>nonces n..n+3 spread across 3 parallel blocks with heavy duplication executed once each, account nonce advanced to n+4; a conflicting same-nonce pair in two parallel blocks executed exactly once; state roots identical on all 3 nodes at the tip in both rounds</td><td>PASS (9/9)</td></tr><tr><td>5</td><td>RPC fuzz</td><td>Errors not crashes; honest latency under 200 ms</td><td>31 <code>eth_*</code>/<code>igneum_*</code> methods x 9 junk param shapes plus deep nesting (5,000 levels) and broken bodies all returned a JSON-RPC envelope or a handled HTTP error, none dropped the connection or crashed; under a one-client <code>eth_call</code> flood of 4,184 req/s (about 200x honest) honest p95 latency 29.1 ms, max 33.5 ms, 0 flood errors; node kept advancing</td><td>PASS (5/5)</td></tr><tr><td>3</td><td>Proving-gas (pgas) exhaustion</td><td>The per-block pgas budget B_p caps inclusion and the template respects it; measure execution time per block</td><td>B_p = 30,000,000. modexp loops: 1,000 iters executed 3.45 M pgas in 1.34 ms; 3,000 -&gt; 10.33 M pgas, 4.56 ms; 6,000 -&gt; 20.64 M pgas, 7.54 ms; 9,000 -&gt; would-be 30.96 M pgas, skipped with <code>BlockProvingBudget</code> after 10.85 ms of native execution; no executed block carried more than B_p (max 20.64 M)</td><td>PASS (4/4)</td></tr><tr><td>6</td><td>Reorgs under execution</td><td>State root recomputed deterministically; displaced-tx receipts handled per design; no stuck mempool</td><td>Partition P1={node1}/P2={node2,node3} healed via <code>igneum-inject addpeer</code> after 1/3/5/8 s forced selected-chain reorgs of depth 3, 6, 13, 11 on the losing node; all 3 nodes converged to one sink and agreed on the state root at the common height each time; the tx executed on the pre-heal chain re-resolved to one canonical, cross-node-consistent outcome (DAG merges the losing blocks, design 1.2/1.3; it does not orphan them); a fresh tx was mined after every reorg (mempool not stuck)</td><td>PASS (31/31 checks over 4 cycles)</td></tr><tr><td>4</td><td>Developer registry abuse</td><td>Design 4.5: base fees burned, no positive-expectation loop; record the max share a self-dealer recovers</td><td>register(someone-else's-contract) and register(unrelated EOA) both revert; a factory's CREATE and CREATE2 children inherit the factory payee; a same-tx creator override sets a different payee; an EOA cannot override a factory child; an unregistered factory's child has no payee (share burns); self-dealer (sender = payee = block miner) recovered 100.0% of the tip but only 56.45% of total fees paid, because both base fees are burned; recovered &lt; paid always</td><td>PASS (19/19). Max share a self-dealer recovers: 56.45% of fees paid (tip only; base fees always lost)</td></tr></tbody></table></div>
<p>Totals: 98 checks, 0 failures, 0 node panics, memory bounded. Execution time per block under the pgas attack stayed single-digit to low-tens of milliseconds (1.3 to 10.9 ms) at these loop sizes; the whole-account state-root recompute (design 10.3 item 1) dominates and will fall once the incremental trie lands.</p>
<p>Findings (not consensus failures; filed for the ledger):</p>
<ul><li>F-exec-A (low): the EVM mempool admits a transaction whose <code>gas_limit</code> exceeds the block execution limit <code>B_e</code>. <code>igneum/exec/src/pool.rs</code> <code>EvmPool::add</code> checks funds, nonce and fee cap but never bounds <code>gas_limit</code> by <code>BLOCK_EXECUTION_GAS_LIMIT</code>. Reproduction: fund an account, send a type-2 tx with <code>gas=31_000_000</code> (B_e is 30,000,000) to any node's eth RPC; <code>eth_sendRawTransaction</code> returns a hash (admitted). The transaction can never be selected (<code>EvmPool::select</code> breaks when <code>gas + gas_limit &gt; B_e</code>) nor form a valid block (<code>check_evm_body</code> -&gt; <code>SumGasLimitAboveBlockLimit</code>), so it occupies a queue slot until evicted. Self-limited because admission still reserves <code>gas_limit x max_fee_per_gas</code> in the funds check. Fix: reject <code>gas_limit &gt; B_e</code> in <code>EvmPool::add</code>, as geth rejects <code>gas &gt; block gas limit</code>.</li><li>F-exec-B (medium, griefing): an over-pgas-budget transaction is executed natively in full before it is skipped, and because it is skipped it pays no fee. A transaction whose own pgas exceeds B_p (for example one large modexp, or the 9,000-iter loop above at 30.96 M pgas) is included, executed (10.85 ms of real work here, more for a bigger input), then dropped with <code>BlockProvingBudget</code> and charged nothing (<code>igneum/exec/src/executor.rs</code>: the skip happens after <code>inspect_one_tx</code> runs and before any fee is taken). Every node re-executes it on every inclusion for free, and because the nonce never advances it also head-of-line-blocks that sender's higher nonces (seen here: the 14,000 and 20,000 loops were never includable behind the stuck 9,000). The funds check at admission does not bound pgas (pgas is not known without execution), so a modestly funded account can force repeated free computation network-wide. Fix options: charge the intrinsic plus consumed pgas on a budget skip, cap single-transaction pgas at admission via <code>eth_estimateGas</code>-style simulation, or drop a sender's queue on a <code>BlockProvingBudget</code> skip rather than retrying.</li></ul>
<p>Not covered here (out of scope for this pass, and because the proving layer is not implemented on this branch): proof records, the native-execution veto, sortition, and the finality lock (<code>proven</code>/<code>locked</code> are always false on devnet v3, so only <code>executed</code> was exercised). These need the proving layer and the finality merge (design 10.4) before they can be attacked.</p>
<h2 id="4-october-2026-sim-economy-mining-versus-proving-under-stress-agent-based-economist-model-not-hardware">4 October 2026, sim/economy: mining versus proving under stress, agent-based (economist; model, not hardware)</h2>
<p>Machine: Apple M5 Max, shared (load 9 to 25), single process at nice 19, about 28 minutes of compute in total. <code>sim/economy/sim.py</code>, Python 3.10.10, numpy 2.2.6; 1,000 operators, 30 days, 180-s ticks, 13 to 25 s per run. Inputs: RTX 5090 229 MH/s (measured, this log); every other number approximate (<code>docs/analysis/economy-2026-10-04.md</code>, assumptions table). Six scenarios x 5 seeds (<code>sim/economy/results.md</code>): no backlog, no window miss, no hash under 50% of pre-event in any run. Hash troughs: a 0.95, b (price down 70%, external x10) 0.82, c 0.97, d (20% operator leaves) 0.75, e (30% withholder) 0.98, f (2x pool arrives) 0.95 of pre-event; day 30: 1.00 / 0.87 / 1.00 / 0.80 / 1.00 / 1.92. Blocks proven within 60 s: 1.00 in every hour; within 20 s: 0.14 to 0.41. Cards in hybrid mode (mine, answer own assignments) at day 30: 42 to 54%; cards off: 1% (a, c, e) to 10% (b). Profit $ per card-day, baseline: 5090 6.37, 3090 1.66, 3060 0.78, small 0.39; shard share 5090 0.59, 3090 0.26, 3060 0.16. Proving-share 10-90 range over the last 10 days 3 to 9 points (one seed of f at 10.1). Sensitivities on b (2 seeds, <code>sim/economy/levers.md</code>): traffic 3 / 30 / 100 / 300 shards per block gives hash trough 0.81 / 0.82 / 0.63 / 0.06, oldest unproven age 0 / 0 / 85 / permanent, worst day within 60 s 1.000 / 1.000 / 0.994 / 0.825, hours under 50% hash 0 / 0 / 0 / 22. Observation window 20 min to 24 h: score 0.939 to 0.952, churn only. Lever study on b at 100 shards per block (2 seeds): window 5 / 10 / 20 / 30 s gives age max 565 / 325 / 0 / 0 s, hash trough 0.53 / 0.62 / 0.77 / 0.77, score 0.592 / 0.765 / 0.948 / 0.948; pool 0.1 / 0.2 / 0.3 / 0.4 gives age 168 / 325 / 16 / 0 and cards off 0.05 / 0.07 / 0.07 / 0.10; burn 0 to 0.5 and claim timeout 60 to 600 s leave the age at 325 s in every row. Proposal (not applied): window = p90 shard time plus one swap, 25 s at today's targets (O-5.1); <code>B_p</code> tied to the live proving fleet rather than a launch calibration. Not done: DAG and network latency, pool protocol, bonds on jobs beyond a class filter, price feedback from burns, the launch ramp; the age column of the 300-shard sensitivity row predates the age-formula fix.</p>
<h2 id="2026-10-04-execution-layer-attack-fixes-f-exec-a-mempool-gas-limit-bound-and-f-exec-b-pgas-abort-rule-spec-7-5-execution-engineer">2026-10-04 execution layer attack fixes: F-exec-A (mempool gas-limit bound) and F-exec-B (pgas abort rule, spec 7.5) (execution-engineer)</h2>
<p>Machine: Apple M5 Max (18 cores), shared with other agents' builds (load 13 to 18). Worktree <code>vendor/igneum-node-exec</code>, branch <code>execution-layer</code> (fix commit on top of fb330692); built release with <code>CARGO_TARGET_DIR=target nice -n 19 cargo build --release -j 4 -p kaspad -p igneum-miner --features igneum-pow</code> (stable-aarch64 toolchain), unit tests with <code>cargo test --release -j 4 -p igneum-exec -p igneum-evm-types</code>. Network: 3 <code>igneumd --simnet --enable-unsynced-mining --unsaferpc --disable-upnp</code> nodes from this worktree, one honest stub miner, eth RPC 27990/27991/27992, gRPC 27910/27920/27930, p2p 27911/27921/27931, appdir <code>/tmp/igneum-exec-fix</code>; hostile blocks through the attack suite's <code>igneum-inject</code> (<code>vendor/igneum-node-exec-attacks/target/release</code>, same wire protocol). The attack scripts of <code>tools/exec-attacks</code> ran unchanged against this network with <code>IGNEUM_RPCS</code> and <code>IGNEUM_GRPC1</code> pointed at it, from copies outside the repository so <code>results/*.json</code> of the 3 October run stay as recorded; the after-fix reproduction is a separate script (session scratchpad, <code>scenario3_after.mjs</code>, 25 checks) written against the new rule. The live devnet and the attack suite's 276xx ports were not touched; every process was stopped at the end; 0 panics in the three node logs.</p>
<p>What changed (spec 7.5, design 10 note): the inspector meters pgas against the including block's remaining <code>B_p</code> and halts the transaction before the opcode or precompile that would cross it (a precompile over the cap is answered with a revert that spends none of the forwarded gas, and the parent halts at its next instruction); the executor charges an aborted transaction as out of gas for the gas and pgas consumed to the abort, status 0, nonce advanced, receipt <code>pgasAborted</code>; the proving charge never takes a sender past the signed budget. The mempool refuses <code>gas_limit &gt; B_e</code> (F-exec-A) and an estimated pgas above <code>B_p</code> (estimate = simulation at the tip under the cap), the template packs by the estimate, <code>eth_estimateGas</code> and <code>eth_call</code> fail naming the pgas when the cap is hit, <code>igneum_estimateGas</code> returns both dimensions. Simulations now read the state through <code>DatabaseRef</code> instead of cloning it per call. <code>igneum-exec-diff</code> treats a <code>pgasAborted</code> transaction as an Igneum-only flow (plain revm would run it to its own end).</p>
<p>Unit tests (new, all pass): <code>pool::gas_limit_is_bounded_by_the_block_execution_limit</code>, <code>pool::estimated_proving_gas_is_bounded_by_the_block_proving_limit</code>, <code>pool::template_never_exceeds_the_remaining_proving_budget</code>, <code>executor::over_budget_pgas_is_aborted_charged_and_the_nonce_advances</code> (an SLOAD-loop bomb with a 30 M gas limit, about 54 M pgas if run out, is cut under <code>B_p</code>, charged exactly <code>gas_used x price + pgas_used x f_p</code>, nonce advanced; a second inclusion skips with <code>NonceTooLow</code> in under 100 ms; the next nonce executes), <code>executor::the_cap_is_the_remaining_block_budget</code> (two bombs in one block fill it to within 1,000 pgas of <code>B_p</code>; a third copy skips at its intrinsic pgas), <code>executor::estimate_reports_the_cap</code>. 6 of 6 in <code>igneum-exec</code>, 3 of 3 in <code>igneum-evm-types</code>.</p>
<p>Scenario 3 (pgas exhaustion), before (3 October run, <code>tools/exec-attacks/results/scenario3.json</code>) and after, <code>B_p</code> = 30,000,000, modexp loops from one sender:</p>
<div class="tbl"><table><thead><tr><th>Loop</th><th>Before: outcome</th><th>Before: pgas, time</th><th>After: mempool</th><th>After: hostile inclusion (igneum-inject)</th></tr></thead><tbody><tr><td>1,000</td><td>executed</td><td>3,449,475 pgas, 1.34 ms</td><td>executed, 3,449,475 pgas, 1.46 ms</td><td>not needed</td></tr><tr><td>3,000</td><td>executed</td><td>10,327,475 pgas, 4.56 ms</td><td>executed, 10,327,475 pgas, 3.94 ms</td><td>not needed</td></tr><tr><td>6,000</td><td>executed</td><td>20,644,475 pgas, 7.54 ms</td><td>executed, 20,644,475 pgas, 8.57 ms; two of them in one go land in separate chain blocks (70, 72), neither aborted</td><td>not needed</td></tr><tr><td>9,000</td><td>skipped <code>BlockProvingBudget { would_be: 30,961,475 }</code> after 10.85 ms of execution, nothing charged, nonce stuck</td><td>200 pgas charged to the block</td><td>refused: "proving gas above the block proving limit: at least 29,998,593 pgas metered before the abort, limit 30,000,000"</td><td>executed with status 0, <code>pgasAborted</code>, 29,998,593 pgas, 4,680,437 gas (limit 29,000,000), 11.37 ms, block pgas 29,998,593; sender charged 39,359,467,000,000,000 wei = 0.0394 IGN (gas x 2 gwei + pgas x 1 gwei), nonce 1 to 2; a second hostile inclusion skipped <code>NonceTooLow { expected: 2, got: 1 }</code> in 35 us, no second charge</td></tr><tr><td>14,000</td><td>never included (behind the stuck 9,000)</td><td>none</td><td>refused, same message</td><td>not run</td></tr><tr><td>20,000</td><td>never included</td><td>none</td><td>refused, same message</td><td>not run</td></tr></tbody></table></div>
<p>After-fix checks: F-exec-A (<code>gas_limit</code> 30,000,001 refused: "gas limit 30000001 above the block execution gas limit 30000000"); <code>eth_estimateGas</code> for the 9,000 loop fails naming 29,998,593 pgas, <code>igneum_estimateGas</code> returns <code>exceedsProvingLimit: true</code>, and for the 6,000 loop <code>pgas</code> 20,644,475 with folded <code>gas</code> 27,465,126; the sender's next nonce (a transfer) executed four blocks after the abort; node RSS 322 MiB to 328 MiB (x1.02); blocks kept coming. 25 of 25. The unchanged <code>scenario3_pgas.mjs</code> now reports 3 of 4: its check "a heavy transaction is skipped with <code>BlockProvingBudget</code>" asserts the old rule and fails by design (the 9,000, 14,000 and 20,000 loops are refused at the mempool), the other three pass (max executed block pgas 20,644,475).</p>
<p>Scenario 1 (malformed and boundary, unchanged script): 30 of 30; the <code>single-gas-limit-over-block</code> case now records <code>mempoolAdmitted: false</code> (the observation that filed F-exec-A is gone); the script's RSS probe looks for the attack worktree's binary path and found no process here, so that check was trivial in this run (the after-fix script measured RSS itself, above).</p>
<p>Differential: <code>igneum-exec-diff</code> over the test network's export, segments 0 to 176, 17 executed transactions compared (one <code>pgasAborted</code>), 6 skipped copies confirmed, 11 accounts compared, 0 mismatches.</p>
<p>Not changed: the pgas table magnitudes (prototype), <code>B_p</code> = 30 M (prototype). Open: the admission estimate runs under the RPC's state read lock, so a flood of heavy <code>eth_sendRawTransaction</code> calls delays the follower by up to <code>B_p</code> of simulation each (same shape as the <code>eth_call</code> flood of scenario 5, which stayed under 34 ms p95); a per-sender or per-second cap on estimates is the next step if the devnet shows it.</p>
<h2 id="3-october-2026-per-identity-hash-rate-decay-on-the-rtx-5090-diagnosis-and-metal-reproduction-miner-community-lead">3 October 2026, per-identity hash rate "decay" on the RTX 5090: diagnosis and Metal reproduction (miner-community-lead)</h2>
<p>Machine for the reproduction: Apple M5 Max, 64 GiB, Darwin 25.6.0, load average 2 to 147 (other agents' builds and, during R1, another agent's Metal worker on the same GPU); everything at <code>nice -n 19</code>. Binaries: HEAD <code>proto-metal/main.swift</code> built with <code>swiftc -O</code> into the scratchpad (465,529 bytes, the same size as <code>proto-metal/igneum-bench</code>), <code>vendor/igneum-node-diff/target/release/igneumd</code> and <code>igneum-miner</code> (22:38 and 22:17 BST, the <code>difficulty</code> worktree pair; the miner's <code>Seeder</code> and worker protocol are the same code as HEAD and as the Windows build 745d41ef). Private networks on 127.0.0.1 ports 27500 to 27562, appdirs under <code>/tmp/igneum-decay-test</code>, all stopped afterwards. Full write-up: <code>docs/analysis/hashrate-decay-2026-10-03.md</code>; proposed fix: <code>docs/analysis/hashrate-decay-2026-10-03.patch</code> (not applied; <code>git apply --check</code> passes against <code>vendor/igneum-node</code>). PC data (<code>node tools/logs.mjs &lt;run_id&gt; --all</code>, STATUS lines deduplicated by timestamp, per-interval rates from consecutive cumulative figures): segment 22:57 to 23:04 UTC, nvidia-1: 40 jobs in the first 30 s then exactly 32 per 30 s for 12 intervals at 17.5 to 18.4 MH/s wall while the printed cumulative figure fell 22.18 to 18.13; nvidia-8 (started 4.7 s later) printed a rising 16.80 to 17.71. Segment 22:23 to 22:57 UTC (epoch 2, DAA 8,474 to 10,513): per-identity gap between jobs 0.098 s to 0.330 s per 0.68 to 0.81 s job, inside-jobs rate rising 28.7 to 34.7 MH/s, wall falling 24.6 to 20.6 MH/s, card total 197 to about 165 MH/s; at the 22:57 epoch boundary the gap returned to 2% and the difficulty held (84.5M to 83.0M). Code audit: nothing allocated per job survives the job in <code>proto-cuda/host.cu</code>, <code>proto-opencl/host.c</code> or <code>proto-metal/main.swift</code> serve loops (tables in the analysis); the miner's only per-job growth is time in <code>Seeder::seeds_for</code> (memo keyed by <code>(epoch, sink)</code>, one <code>getBlock</code> RPC per block from the sink to the epoch start on every miss, 1,274 to 3,313 calls on the PC). <code>cudaDeviceSynchronize</code> at the default schedule spins one thread per worker (the project lead's 6.2% per process); the hot-swap working tree sets <code>cudaDeviceScheduleBlockingSync</code> and swaps <code>clFinish</code> for <code>clWaitForEvents</code>. Metal runs (STATUS every 30 s; "gap" = 1 minus wall over inside, per interval): R1 control, epoch 0, genesis bits 0x1d100000, 308 s (cut by the 22:21:37 UTC SIGTERM of every process of this session): first interval 25.18 MH/s alone on the GPU, then 14.0 to 14.4 MH/s in every interval after another agent's worker joined at 25 s, gap 0 to 2%, worker RSS 56.8 MiB flat. R2 walk reproduction, 900 s: <code>skip_proof_of_work</code> node pumped to DAA 4,000 (one-second timestamps, difficulty held at 76.8M), one identity, pumped blocks at 1/s for 300 s, none for 300 s, 1/s for 300 s: inside 27.0 to 27.7 MH/s in all 29 intervals; wall 22.3 to 24.3 (gap 12 to 18%, walk 400 to 700), 25.9 to 27.3 (gap 0 to 4%), 18.0 to 21.0 (gap 25 to 35%, walk 700 to 1,000); miner CPU 0 to 1% in the quiet phase, 11 to 21% in the last. R3 one worker at difficulty 2^25 (Kaspa sampled rule, genesis bits held), 600 s: 30.51 wall / 30.72 inside, 1,091 jobs, 224 blocks, 53 to 56 jobs per 30 s throughout. R4 eight workers at 2^25: 29.38 / 29.45 summed (3.32 to 4.38 each), 1,053 jobs, 242 blocks, 7 jobs per 100 s per identity in every interval, worker CPU 0.0 to 0.6%, RSS 46 to 57 MiB. R5 one worker at 2^31: 37.01 / 37.75, 1,324 jobs, 4 blocks, flat. R6 eight workers at 2^31: 36.67 / 36.75 summed (4.29 to 5.55 each), 1,314 jobs, 5 blocks, flat. (R5 and R6 ran a different epoch-0 program from R3 and R4, 112 loads per hash, hence 37 against 30.5 MH/s.) Side findings: the <code>difficulty</code> worktree's node panics at <code>consensus/src/processes/difficulty.rs:431</code> ("Work should not exceed 2**192") when fed 85 blocks/s with wall-clock timestamps under the Igneum dual rule (a pump artefact, logged for the consensus-engineer); <code>skip_proof_of_work</code> nodes still log "PoW rejected ... by igneum-lottery-v1-bound" for every block they accept. Not done: the fix applied and measured on the PC (the acceptance figure is a flat gap at DAA 10,800 with eight identities); the OpenCL event wait checked on the AMD driver; a unit test of <code>seeds_for</code> (the client is concrete).</p>
<h2 id="2026-10-04-finality-v2-attack-harness-seven-hostile-scenarios-on-a-six-voter-private-test-network-consensus-test-engineer-cryptographer">2026-10-04 finality v2 attack harness: seven hostile scenarios on a six-voter private test network (consensus test engineer, cryptographer)</h2>
<p>Machine: Apple M5 Max, rustc stable, macOS Darwin 25.6.0. Fork: worktree <code>vendor/igneum-node-fin-attacks</code>, branch <code>fin-attacks</code> on master <code>c6d47547</code> to <code>2a00ff55</code> (BLS votes, certificates in coinbase extra data, p2p message 70, the finality RPCs). Build: <code>CARGO_TARGET_DIR=target nice -n 19 cargo build --release -j 4 -p kaspad -p igneum-miner --features kaspad/igneum-pow</code>. Tool: <code>tools/finality-attacks</code> (<code>run.mjs</code>, <code>lib/</code>, README with the proposed fixes). Network: <code>igneum-devnet-800</code>, ports 27800 and up, data <code>/tmp/igneum-fin-attacks</code>, <code>skip_proof_of_work</code> (the hostile miners never hash; each gets its block share from a Poisson clock; every other consensus rule unchanged). Devnet finality parameters: interval 30, depth 20, weight window 7,200 DAA, dust 5, presence 20 indices, 8 aggregators, ban 7,200 DAA, quorum 2/3 of active and 17/30 of total. Durations at SCALE 0.6. The live devnet (26610, 26611, 26640, 26641, 28640) was never touched. Run time 32 min of process time (the machine slept twice during the run, which pauses the monotonic clocks the harness and the miners use, so wall-clock timestamps in the log jump; no result depends on wall time).</p>
<p>Hostile pieces are test-only flags of <code>igneum-miner</code>, never honest node or consensus code: <code>vmine</code> (Poisson submit at a chosen hash share, decoupled 0.5-s voter), <code>--equivocate</code>, <code>--sybil b:bb:a:ab</code> (one miner mints many vote keys), <code>--drop-votes</code> (strips the node's finality section from its coinbase so its blocks carry no votes or certificates while it still votes over RPC), <code>--pulse burst:on:period</code>, and <code>fin-rpc-attack</code> (malformed, mis-signed, replayed, oversized and non-hex votes over <code>submitFinalityVote</code>). Six voters throughout; three nodes for the cross-node scenarios, two nodes over a TCP proxy for the partitions.</p>
<div class="tbl"><table><thead><tr><th>#</th><th>Scenario (priority order)</th><th>Criterion (spec 03)</th><th>Measured</th><th>Verdict</th></tr></thead><tbody><tr><td>3</td><td>Dishonest aggregators (6 voters, 3 nodes, every node aggregates)</td><td>other aggregators' certificates still lock; a sub-quorum certificate cannot lock (Q3); block-carried votes give participation (F3); lock latency under 2 s median</td><td>35 / 35 / 35 locked per node, identical lock hashes on all three, 0 conflicting certificates, median lock latency 1,018 ms (bounded by the miners' 1-s poll). Sub-quorum rejection is by code review (<code>lock_test</code> needs both integer tests; a certificate below either is <code>Certified</code>, never <code>Locked</code>); injecting one on the wire needs a finality-aware p2p probe (not built)</td><td>PASS (wire injection not run)</td></tr><tr><td>2</td><td>Sybil dust (one miner mints 200 keys at 4 blocks and 200 at 6, dust 5; 3 honest voters)</td><td>dust keys zero weight and no voters; above-dust weight = blocks; total weight = voters' blue blocks; sortition by weight not key count (F17)</td><td>200 dust keys seen, all <code>voter false</code>; 203 voters above dust; total weight 1,240 = sum of voter blocks 1,240; aggregator sortition is PER KEY (<code>is_aggregator(output, voters, 8)</code> counts keys), with 203 voters a real signer's chance to be an aggregator fell to about 8/203 and the last checkpoint named 0 aggregators (zero-aggregator certificates, "anyone MAY aggregate")</td><td>weights PASS; sortition FAIL (F17)</td></tr><tr><td>1</td><td>Equivocation at scale (2 of 6 keys sign two checkpoints at every index, 3 nodes)</td><td>both keys stripped within one checkpoint on every node; no conflicting certificate; honest locks continue</td><td>stripped keys 2 / 2 / 2 on the three nodes, 78 / 8 / 8 detections (node-local on the equivocators' node, block-carried evidence on the others), 0 conflicting certificates, 35 / 35 / 35 locks by the 4 honest keys</td><td>PASS</td></tr><tr><td>6A</td><td>Partition 3/3 for 90 s after a 252-s shared warmup (window 1,439 DAA at the cut), then heal</td><td>zero locks on either side during the split; locks resume after the heal; no conflicting certificates</td><td>side 0: no new lock in 90 s; side 1: first new lock at 84 s, 8 locks before the heal; 0 conflicting certificates (side 0 never locked those indices); locks resumed on both sides after the heal. Side 1 crossed the floor because its own fresh blocks raised its share of its window: at the cut each side held 50% of 1,439 DAA of weight; the 3-miner side added about 2.6 blocks/s and by 84 s held (720 + 220) / (1,439 + 220) = 56.7%. The model is share(T) = (F/2 + R T) / (F + R T) with F the window weight at the cut and R the side's block rate, so the floor holds for T* = 2F / (13R): 74 s predicted at F = 1,439 and R = 3, 84 s measured (sibling losses lower R). The simulation used fixed weights and could not see this (spec 3.7 item 8)</td><td>FAIL (floor is time-bounded)</td></tr><tr><td>6B</td><td>Partition 4/2 for 90 s after a 252-s warmup, then heal</td><td>the 4 side (66.7% of total) keeps locking; the 2 side (33%) does not; no conflicting certificates</td><td>4 side locked 47 to 59 (first new lock 15 s after the cut, the active test passes at exactly 2/3); 2 side stayed at 47; 0 conflicting certificates; both resumed after the heal</td><td>PASS</td></tr><tr><td>4</td><td>Vote-dropping block producer (40% of blocks carry no finality section, 2 nodes)</td><td>participation and locks unaffected because other blocks carry the votes; delay measured</td><td>the node that saw the dropper's blocks only through gossip and the other producers' blocks locked 35 checkpoints; median lock latency 1,019 ms with the dropper vs 1,019 ms control, 0 ms added</td><td>PASS</td></tr><tr><td>8</td><td>Malformed votes over the RPC (9 cases, fresh key per case)</td><td>rejected without a crash; node stays up</td><td>control vote accepted; replay answered "already known" (deduplicated, not double-counted); bad signature and wrong chain id rejected "invalid vote signature"; a vote for a hash the node does not hold at a known index is recorded and flagged, not certified; 8-byte, 2 MB and non-hex payloads rejected "vote must be 280 bytes" / "vote is not hex" before any processing; node answered <code>getInfo</code> after all 9. The message-70 half (sub-quorum and replayed certificates, oversized bitmaps) needs the p2p probe; by code review <code>Certificate::read</code> bounds the bitmap at 1 MB, the relay bounds a message at 1 MB and a malformed one is a <code>ProtocolError</code> that disconnects the peer</td><td>PASS (RPC half)</td></tr><tr><td>5</td><td>Pulsed miner (base share 1/6, 10x for 20 s of every 120 s, 5 steady voters, 216 s, Kaspa's DAA rule as master runs it)</td><td>weight proportional to block share over the window (no retarget amplification, W2 and F14); cannot lock alone</td><td>weight share 35.3% vs block share 35.3%, ratio 0.999: W2 counts blocks and the retarget lag bought nothing extra. But checkpoints 1 to 10 were locked by the burster ALONE: its first 20-s burst gave one key 66.7% to 71.7% of a window that held under 300 blocks (cp 5: 98 of 147 signed by 1 of 6 voters; cp 10: 201 of 297), above both Q3 tests. From cp 11 every lock needed 3 or 4 signers as its share decayed to 35%. This is ledger F1 measured live: with no first-month gate (<code>min_daa</code> 0 on devnet, 3,600 DAA on mainnet, spec 3.8 not implemented) a short burst owns a young window</td><td>amplification PASS; lock-alone FAIL (F1)</td></tr><tr><td>7</td><td>Eclipse of one voter with an adversarial side chain</td><td>not run: needs the finality-aware p2p probe to feed a private fork</td><td>not measured</td><td>not run</td></tr></tbody></table></div>
<p>Failures and the proposed fixes (diffs in <code>tools/finality-attacks/README.md</code>, for gate 3 to ratify; no rule was changed here):</p>
<ol><li>F17 (S2): <code>is_aggregator</code> draws the 8 aggregators per key. Liveness only, because any node may aggregate and a certificate must still meet Q3 by weight, which the Sybil split does not change. Proposed: draw by weight, <code>output x total &lt; 8 x weight x 2^64</code>, mirroring the spec 7.2 step-2 fix.</li><li>Floor time bound (S6A): the 56.7% floor protects a partition for about T* = 2F / (13R) of DAA time, with F the window weight at the cut and R the majority side's block rate. Approximate, formula only, window slide ignored: on mainnet with a full 30-day window and a 50/50 split at 1 block/s, 9.2 days; a 55/45 split, 2.1 days; 60/40 locks at once (3.3.1 already says so). Minimum fix: state the bound in spec 3.3.1 and 3.9. Rule option for gate 3: evaluate the floor against the weight table of the last locked checkpoint while no newer lock exists, so a stalled side cannot lift its own share by mining; cost: after a permanent loss of weight the floor needs a manual override instead of the 4.1 days of 3.3.1 D.</li><li>F1 (S5): no certificate should form before the window holds a full window of history (spec 3.8, O-3.1). Proposed: <code>min_daa = weight_window</code> (2,592,000 on mainnet, 7,200 on devnet) in <code>FinalityParams</code>, one line each.</li></ol>
<p>Not demonstrated: certificate injection on the wire (S3, S8 half), the eclipse (S7), the 2-hour presence window at mainnet length, the heal rule of 3.5 (both partitions healed without a conflicting certificate, so it was not exercised).</p>
<h2 id="4-october-2026-difficulty-rule-under-attack-pool-hopping-pulsed-rental-timestamp-stretching-short-lane-oscillation-epoch-games-polluted-window-block-flood-consensus-test-engineer">4 October 2026, difficulty rule under attack: pool hopping, pulsed rental, timestamp stretching, short-lane oscillation, epoch games, polluted window, block flood (consensus test engineer)</h2>
<p>Machine: the same Apple M5 Max, shared with other agents' builds and test networks (load 15 to 30). Simulator <code>sim/difficulty/attacks/attacks.py</code> over <code>sim/difficulty/sim.py</code> (controllers unchanged): several miners with on/off strategies, block attribution by hash share at the solve, hashes per miner, timestamp forging inside the fork's rules (132 s ahead, above the 27-sample past median). Node runs on branch <code>diff-attacks</code> of <code>vendor/igneum-node</code> (worktree <code>vendor/igneum-node-diff-attacks</code>, from <code>difficulty</code> at <code>3ea7a3e3</code>; adds only the attack variable <code>IGNEUM_ATTACK_TS_OFFSET_MS</code> in the template builder and a reproduction test), ports 27700 to 27721, appdir <code>/tmp/igneum-diff-attacks</code>, genesis bits <code>0x1f010000</code>. Everything in <code>sim/difficulty/attacks/README.md</code>, raw tables in <code>results.md</code>, headers of the three test-network runs in <code>testnet/</code>. Simulator, seeds 7 to 9, Igneum / Kaspa's rule, criterion, verdict: (1) pool hopper 10 to 100% of the base, on while D is below its 6-hour mean, 24 h: hopper's blocks per hash +1.5% at most / +0.8% at most, under 5% both, Igneum 0.7 points above Kaspa's in every greedy cell (unchanged with the ease clamp at 6% or 3%: the price of a controller that moves inside the hour; a 60 s dwell turns the 50% and 100% hoppers into losers, -1.7% and -4.0%): PASS under 5%, FAIL on "no larger than Kaspa's" by the letter, no change proposed. (2) 50x burst for 10 min every hour: pulser's weight per hash 0.26 / 0.98 of the base's, blocks per hash 3.7% / 85% of the base's; once: 0.13 / 0.87: PASS (no weight amplifier under either rule; Kaspa's makes the burst cheap, Igneum's makes it 27x dearer; the hour after costs the base 37% and a 153 s worst gap under Igneum). (3) forger at 30 or 50% stamping at the latest allowed, the earliest allowed, or alternating: Igneum block rate 0.66 / 0.42 / 0.51 at 30% and 0.56 / 0.12 / 0.23 at 50%, difficulty 1.5x to 9.9x on an unchanged hash rate, worst gap 234 s; Kaspa's rule +5% to +11% easing: FAIL both, Igneum far worse. Cause: the symmetric per-step clamp turns every forged block and the honest block after it into zero measured time (spec 2.3's "the next honest block cancels it" is the bug, not the defence), so the lanes measure 1 - 2a(1 - a) of real time at share a; past-stamping also drags the past median down without bound. (4) 25% miner on and off every 120 blocks: std of the rate ratio 0.160 / 0.147 against 0.045 / 0.010 steady and a 0.143 floor from the attacker's own square wave: FAIL by the letter for both, neither oscillates (12% and 3% above the floor), no change proposed. (5) hold dodger 30%, hold flooders 10x and 30%: 0.0 / +0.7 / +0.3% against 0.0 / 0.0 / -0.1%: PASS. (6) 10x miner leaving at block 600 of an epoch, where the long lane takes over: settled 303 s (292 s with the short lane engaged at the switch), worst gap 17 s, against 334 s for a leave at block 1,200; Kaspa's 2,910 to 3,540 s: PASS. (7) side finding, blocks every 12 ms fed to the rule (another agent's pump): the target passes below 2^64 after 4,142 blocks and <code>calc_work</code> panics at <code>difficulty.rs:431</code> (<code>should_panic</code> test <code>igneum_flood_at_85_blocks_per_second_drives_the_target_below_block_work_range</code> on <code>diff-attacks</code>): FAIL, floor proposed. Test network, 3 <code>igneumd</code> nodes, honest 4-thread miner A on node 1 for 15 min, forger F (4 threads, about 50%) honest on node 2 for 5 min then on node 3 with the offset: Igneum, earliest allowed stamp: 0.97 blocks/s at difficulty 91,000 before, 0.24 blocks/s at 275,000 during forging and 0.20 at 341,000 in the last 5 min, forger offsets -121 to -566 s, hash unchanged (A 0.078, F 0.069 MH/s). Igneum, latest allowed stamp (+134 s): 0.98 blocks/s at 89,000 before, 0.59 at 170,000 during, 0.50 at 191,000 in the last 5 min (A 0.112, F 0.110 MH/s); the simulator's 50% cases give 0.12 at 9.9x and 0.56 at 1.8x. Kaspa's rule, earliest allowed stamp: 1.73 blocks/s on a 2.5x too easy genesis to block 600 at 210 s, then 1.02 blocks/s at 83,400 through ten minutes of -180 s stamps. 517, 767 and 1,454 blocks, 0 rejected. Proposed (README.md, diffs, not applied): Part A, timestamp rules: 10 s future tolerance (<code>FUTURE_TOLERANCE_MS</code>, a new constant so the past-median window keeps its 27 samples) and a floor at the selected parent's timestamp minus 10 s (<code>BACK_TOLERANCE_MS</code>) beside the past median; Part B, the chain steps of the short and epoch lanes measured on a sanitised running clock c(b) = max(c(p) + clamp(t(b) - c(p), -20 T, +20 T), t(b) - 60 T), step = min(c(b) - c(p), 20 T), stored per header, so forgeries telescope instead of cancelling; the long lane unchanged. Measured, both parts, 3 seeds: the 50% forger drifts the rate +0.7% (past), +0.9% (future), +1.1% (alternating), worst seed +2.7%; base profiles unchanged except down50 762 s against 782 s, warm-up 327 s against 381 s, polluted peak 11.4x against 8.0x; the other attacks identical. Either part alone fails (unchanged rule under the tight rules: -36% and -83% to past-stamping; the clock under the 132 s rules: collapse at 50%, a martingale once the forgery range exceeds half the cap). Part C for the flood: clamp the output at <code>MIN_DIFFICULTY_TARGET</code> = 2^128 beside the existing maximum, in both rules. Not done: the candidate in the node (simulator only; the per-header clock is a store change); DAG effects of forged stamps on red and merged blocks (one chain in the simulator); a rule change for the hopper's 0.7-point excess (none found that keeps the controller fast; README.md, scenario 1); Kaspa's rule under the flood (same hole, 17x slower, not run).</p>
<h2 id="2026-10-04-finality-fixes-f17-and-f1-aggregators-drawn-by-weight-first-month-gate-min-daa-window-attack-scenarios-2-and-5-before-and-after-consensus-engineer">2026-10-04 finality fixes F17 and F1: aggregators drawn by weight, first-month gate min_daa = window; attack scenarios 2 and 5 before and after (consensus-engineer)</h2>
<p>Machine: Apple M5 Max, rustc stable, macOS Darwin 25.6.0. Branch <code>fin-fixes</code> (worktree <code>vendor/igneum-node-fin-fixes</code>, from master <code>2a00ff55</code>), commit <code>da1eb889</code>. Build: <code>CARGO_TARGET_DIR=target nice -n 19 cargo build --release -j 4 -p kaspad -p igneum-miner --features kaspad/igneum-pow</code>. Tests: <code>cargo test --release -p kaspa-consensus-core -p kaspa-consensus -- finality</code>: 6 of 6 in consensus-core (<code>sortition_threshold</code>, <code>sortition_is_by_weight_not_key_count</code> with 200 dust keys and 6 real ones, <code>first_month_rule_is_the_full_window</code>, the three pre-existing), 1 of 1 in consensus (<code>processes::finality::tests::no_certificate_while_the_window_is_filling</code>, a 150-block TestConsensus chain at a 60-DAA window where one key holds all the weight: nothing certifies under DAA 60, a hand-built early certificate is refused, every checkpoint from DAA 60 locks). The live devnet (26610, 26611, 26640, 26641, 28640) and the other agents' nodes (26680, 27700 to 27720, 28680) were never touched.</p>
<p>The two diffs. (1) F17: <code>is_aggregator(output, weight, total_weight, aggregators)</code> is eligible when <code>output x total &lt; aggregators x weight x 2^64</code> (was <code>output x voters &lt; 8 x 2^64</code>, drawn per key); <code>ingest_vote</code> passes the key's weight and the table total. A key split into n parts holds n thresholds that sum to the one it had; a key without weight never draws; a key at 1/8 of total or more always draws. A node that serves a drawn aggregator aggregates at once; any other node after <code>checkpoint_depth + aggregator_fallback</code> (15) DAA seconds, so anyone MAY aggregate stays the liveness fallback. (2) F1: <code>min_daa = weight_window</code> (mainnet 2,592,000, devnet 7,200); <code>evaluate</code> never locks and <code>ingest_certificate</code> refuses any certificate while the checkpoint's DAA score is below it; the node logs and reports "finality not active, window filling, N of M" (<code>finality_reason</code>, <code>window_filled_daa</code>, <code>window_full_daa</code> on <code>getFinalityCheckpoints</code>).</p>
<p>Re-run of scenarios 2 and 5 (<code>tools/finality-attacks</code>, hostile <code>igneum-miner</code> from the fin-attacks worktree, unchanged; it drove the fixed node without modification because the new RPC fields are additive). Six voters on one node, <code>skip_proof_of_work</code>, 600 s per run. The harness copy used for the runs is <code>/tmp/igneum-fin-fixes/harness</code> (<code>lib/net.mjs</code> with ports, data directory, network suffix and the finality override taken from the environment; <code>rerun.mjs</code> with the s2 and s5 measurements below); the repo harness was not edited, and its s2 pass test still reads "voters &gt; 8 means per-key sortition", which is now wrong and needs the by-weight test below. Finality override for every run: interval 30, depth 20, window 1,800 DAA, dust 5, presence 20, 8 aggregators, ban 1,800; <code>min_daa</code> 0 for the before runs (the master default) and 1,800 for the after runs (the fixed rule, min_daa = window), fallback 15. The window was shortened from 7,200 to 1,800 so it fills inside a 10-minute run; the rule under test is the equality, not the number. Before = fin-attacks <code>igneumd</code> (master code, built 3 Oct 23:25) on ports 28100 and 28300, network ids igneum-devnet-801 and 803; after = fin-fixes <code>igneumd</code> on 28500 and 28700, ids 805 and 807. Results in <code>/tmp/igneum-fin-fixes/{before,after}-{s2,s5}/</code>.</p>
<p>Scenario 2, Sybil dust (one miner mints 200 keys at 4 blocks and 200 at 6, dust 5; 3 honest voters at 1/3 each; only the honest keys vote, so the measurable is how many honest keys the node records as drawn aggregators per checkpoint). "Crowded" = checkpoints with more than 8 voters above dust (98 of about 127 in each run, mean 125 voters). Expected honest seats per crowded checkpoint: per key, <code>3 x min(1, 8 / voters)</code>; by weight, <code>3 x min(1, 8 x w / T)</code> with w the honest key's weight.</p>
<div class="tbl"><table><thead><tr><th></th><th>Before (master)</th><th>After (fin-fixes)</th></tr></thead><tbody><tr><td>Dust keys with weight or a vote</td><td>0 of 200</td><td>0 of 200</td></tr><tr><td>Total weight = sum of voter blocks</td><td>1,798 = 1,798</td><td>1,798 = 1,798</td></tr><tr><td>Honest weight share, crowded checkpoints (mean)</td><td>32.3%</td><td>28.8%</td></tr><tr><td>Expected honest seats per crowded checkpoint, per-key draw</td><td>0.33</td><td>0.35</td></tr><tr><td>Expected honest seats per crowded checkpoint, by-weight draw</td><td>1.67</td><td>1.55</td></tr><tr><td>Measured honest seats per crowded checkpoint</td><td>0.32</td><td>1.61</td></tr><tr><td>Locks</td><td>33, first at DAA 629 (a young window held the honest keys alone)</td><td>25, first at DAA 3,059 (window full at 1,800; the silent 1,200 blocks of sybil weight held the honest keys under the 56.7% floor until they aged out, <code>finality_reason</code> "paused" from DAA 1,800 to 3,059, then "active")</td></tr><tr><td>Verdict</td><td>sortition FAIL (per key: 0.32 against 0.33)</td><td>sortition PASS (by weight: 1.61 against 1.55)</td></tr></tbody></table></div>
<p>Scenario 5, pulsed miner (five steady voters at 1/6, one burster at 1/6 pulsing 10x for 20 s of every 120 s).</p>
<div class="tbl"><table><thead><tr><th></th><th>Before (master)</th><th>After (fin-fixes)</th></tr></thead><tbody><tr><td>Burster weight share vs block share over the run</td><td>30.9% vs 32.0%, ratio 0.966</td><td>30.8% vs 32.1%, ratio 0.959</td></tr><tr><td>Checkpoints determined / locked</td><td>148 / 148</td><td>148 / 88</td></tr><tr><td>First lock</td><td>checkpoint 1 at DAA 29, built "by 1 of 1 voters, weight 22 (total 22)", the aggregator and sole voter above dust the burster's key (its first 20-s burst); checkpoints 2 and 3 locked at 3 of 3 and 4 of 4 voters as the others cleared dust</td><td>checkpoint 61 at DAA 1,829 (the first checkpoint at or above min_daa 1,800), 5 of 6 voters, 84.7% of active and of total</td></tr><tr><td>Locks with the checkpoint under min_daa 1,800</td><td>not gated (checkpoints 1 to 60 all locked)</td><td>0</td></tr><tr><td>Locks carried by one voter above dust</td><td>1 (checkpoint 1)</td><td>0</td></tr><tr><td><code>finality_reason</code> over the run</td><td>field absent</td><td>"window filling, 92 of 1800" ... "1448 of 1800" at 180 s, "paused" at 240 s (window full, first lock pending), "active" from 300 s; 59 "window filling" determination lines in the node log</td></tr><tr><td>Conflicting certificates</td><td>0</td><td>0</td></tr><tr><td>Verdict</td><td>amplification PASS, lock-alone FAIL (F1)</td><td>amplification PASS, lock-alone PASS</td></tr></tbody></table></div>
<p>Notes. The fallback path ("fallback: any node may aggregate") fired 0 times in both after runs: every voter on the single node is local and, with six keys of similar weight, each is drawn at every checkpoint, so every certificate named a drawn aggregator. The "paused" reading between the window filling and the first lock is the report's label for "window full, no lock yet"; it lasted one sample in s5 and five minutes in s2 (the floor against silent sybil weight, 3.3.1). The repo harness <code>tools/finality-attacks/run.mjs</code> keeps its pre-fix s2 and s5 criteria and should adopt the two measurements above; the fin-attacks miner prints no <code>FINALITY</code> line (that is the fin-fixes miner).</p>
<h2 id="4-october-2026-difficulty-rule-timestamp-attack-fixed-tight-bounds-sanitised-clock-target-floor-simulator-regression-3-node-forger-test-consensus-engineer">4 October 2026, difficulty rule: timestamp attack fixed (tight bounds, sanitised clock, target floor), simulator regression, 3-node forger test (consensus-engineer)</h2>
<p>Machine: the same Apple M5 Max, shared with other agents' simulations (load 6 to 10). Worktree <code>vendor/igneum-node-diff</code>, branch <code>difficulty</code>, commit "Difficulty: timestamp rules 10 s both ways, sanitised clock per header, target floor 2^128" on <code>3ea7a3e3</code>; built with <code>CARGO_TARGET_DIR=target nice -n 19 cargo ... -j 4</code> on the rustup toolchain (cargo 1.99; the Homebrew cargo 1.69 in PATH cannot read the 2024 edition). Everything in <code>docs/analysis/difficulty-2026-10-03.md</code> section 11 and spec 2.3; ledger M23. The change, the three parts of <code>sim/difficulty/attacks/README.md</code> as proposed: (A) <code>FUTURE_TOLERANCE_MS</code> 10 s in isolation and <code>BACK_TOLERANCE_MS</code> 10 s behind the selected parent in context (new <code>RuleError::TimeTooFarBehindParent</code>), past-median rule and window unchanged, template floor matched; (B) a sanitised clock per header, c(b) = max(c(p) + clamp(t(b) - c(p), -20 T, +20 T), t(b) - 60 T), in a new store (<code>stores::clock</code>, prefix <code>IgneumClock</code> 62, written in <code>commit_header</code>, deleted at pruning, falling back to the parent's raw stamp when the parent has none), the short and epoch lanes walking clock steps min(c(b) - c(p), 20 T); (C) <code>bound_target</code> floors both rules at 2^128. <code>sim/difficulty/sim.py</code> class <code>Igneum</code> carries the same clock and floor. Unit tests: <code>cargo test --release -p kaspa-consensus --lib difficulty</code> 12 pass (the <code>diff-attacks</code> flood test with <code>should_panic</code> removed: every output at or above 2^128, floor reached between blocks 2,000 and 3,000, work under 2^129; <code>both_rules_share_the_target_floor</code>; <code>igneum_clock_steps_pay_a_forgery_back</code>: three blocks 10 s behind their parents then honest blocks, clock steps sum to the 8 s real span, raw clamped solvetimes to -6 s); <code>cargo test --release -p kaspa-consensus-core --lib igneum</code> 9 pass (<code>sanitised_clock_telescopes_a_forged_stamp</code>). Simulator, seeds 7 to 9 (<code>attacks.py --scenario ts --ts-rules tight</code>), forger at 30 / 50% stamping latest / earliest / alternating, block rate after one hour of forging: 1.008 / 1.008 / 1.004 and 1.009 / 1.007 / 1.011 of target (drift +0.4% to +1.1%, worst seed +2.7%), mean difficulty ratio 1.00, worst gap 10.1 s; the 3 October rule under Kaspa's bounds: 0.66 / 0.42 / 0.51 and 0.56 / 0.12 / 0.23, under the 10 s bounds alone 0.636 and 0.170 on the earliest cells. Flood at 85 blocks/s in the simulator: floor reached at block 2,635, no overflow. Pool hopping (<code>--scenario hop</code>, 24 h): unchanged to three decimals, +1.5% at most greedy, -1.7% and -4.0% with a 60 s dwell at 50 and 100%. Base-profile regression, 3-seed means, 3 October against 4 October: record 102.8 against 91.0 s settled (first within 10% 70.3 against 68.5 s); up50 154.4 against 154.4; down50 782.3 against 762.2 (worst gap 78.3 against 73.9 s); epoch30 87.6 against 87.6; hop10 239.4 against 245.1; polluted 70.1 against 74.9 (seed 9: 78.9 against 92.0), peak 8.0x against 11.4x; steady std unchanged. All means within 10%; the two profiles with an idle gap move, because a gap over 60 T is paid back as three 20 T steps instead of one clamped step. Test network (3 <code>igneumd</code> nodes from the fix plus the <code>diff-attacks</code> template hook on a scratch branch, ports 28500 to 28521, appdir <code>/tmp/igneum-diff-fix</code>, genesis bits <code>0x1f010000</code>, honest 4-thread miner A on node 1 for 15 min, forger F 4 threads honest on node 2 for 5 min then on node 3 with the offset, two runs): earliest allowed stamp (offset -1e9 ms, floored by the rules; 986 blocks, 823 chain, 0 rejected): chain rate 0.82 blocks/s honest (60 to 300 s) at difficulty 102,226, 0.88 during forging (300 to 900 s) at 100,134, 0.83 in the last 300 s at 106,141, all-blocks rate 1.00 / 1.02 / 0.95, forger offsets -10 to -90 s (mean -23), hash A 0.110 MH/s, F 0.109. Latest allowed stamp (+9,000 ms; 1,028 blocks, 829 chain, 0 rejected): 0.78 honest at 102,382, 0.89 forging at 96,717, 0.89 last 300 s at 102,754, all-blocks 0.95 / 1.09 / 1.11, offsets +9 to +26 s, hash A 0.112, F 0.111. Flat within the CPU miners' noise; the 3 October rule on the same schedule fell to 0.24 blocks/s at 275,135 and 0.59 at 170,222 (bench-log entry above). Records in <code>/tmp/igneum-diff-fix/ts-past-igneum/record.csv</code> and <code>ts-future-igneum/record.csv</code> (not archived into the repo). Not done: the DAG effect of forged stamps on red and merged blocks (one chain in the simulator); the clock of a header whose parent arrived through a pruning proof starts from the raw stamp (one window of exposure after a sync, unmeasured); upstream's timestamp integration tests assume the 132 s bounds and were not re-run; the hopper's 0.7-point excess over Kaspa's rule stays open.</p>
<h2 id="4-october-2026-devnet-v4-integration-nine-branches-merged-3-node-test-network-on-the-merged-node-windows-cross-build-release-engineer">4 October 2026, devnet-v4 integration: nine branches merged, 3-node test network on the merged node, Windows cross-build (release engineer)</h2>
<p>Machine: Apple M5 Max, shared (load 8 to 16, another agent's build and the live devnet running throughout), rustc stable, every build and test at nice 19 with 6 jobs into <code>vendor/igneum-node/target-integration</code>. Branch <code>devnet-v4</code> of <code>vendor/igneum-node</code>, head <code>dc749905</code>; merge order, conflicts and the cut-over commands in <code>docs/fork-divergence.md</code>, "Integration 4 Oct 2026". The hot swap was not in master (no <code>pow_epoch</code> in <code>2a00ff55</code>); it was captured from the uncommitted hotswap worktree as <code>a4224689</code> and merged first. Build times: first release build 3 min 37 s, the execution layer's crates 6 min more, the Windows cross-build 4 min 49 s from a warm dependency cache (<code>proto-cuda/windows-node/cross-build.sh vendor/igneum-node-v4 6</code>). Tests: 628 passed, 0 failed, 24 ignored across the 21 touched crates (<code>--no-fail-fast</code>), then kaspa-p2p-flows 30 of 30 after the <code>estimated_header_size</code> fix (the header's <code>voteKeyHash</code> field was not counted since <code>815cd00f</code>). Three Kaspa UTXO-body tests are ignored with the reason (the execution layer retires UTXO transactions from bodies); two p2p-lib test modules were brought to the pair-shaped <code>BlockBody</code>. Test network, 02:02:27 to 02:18:53 BST: 3 <code>igneumd</code> on <code>igneum-devnet-880</code> (gRPC 28800/28810/28820, p2p 28801/28811/28821, wRPC JSON 28802/28812/28822, eth RPC 28803/28813/28823; node 2 and 3 <code>--addpeer</code> node 1, node 3 also node 2), override file <code>genesis_bits</code> 0x1f010000 (2^16) and finality interval 30, depth 20, window 300 DAA, dust 5, presence 20, aggregators 8, ban 300, <code>min_daa</code> 300, fallback 15; <code>IGNEUM_POW_EPOCH_BLOCKS=300</code>, <code>IGNEUM_POW_EPOCH_LEAD=60</code> (a short epoch so the hourly swap crosses boundaries inside the run; the devnet values are 3,600 and 600). Miners m1, m2, m3 (<code>igneum-miner mine ... 3 960 --engine igneum-pow --payout-label mN --evm-address 0x7099...79C8</code>), 0.078 MH/s each (3 threads on the loaded machine), 375 / 342 / 338 blocks found, 0 rejected.</p>
<div class="tbl"><table><thead><tr><th>Measure</th><th>Value</th></tr></thead><tbody><tr><td>Blocks accepted per node (PoW accepted lines)</td><td>1,055 / 1,055 / 1,055, 0 rejected, 0 invalid</td></tr><tr><td>Block rate</td><td>95 to 1,026 blocks between the 0-s and 900-s samples: 1.03 blocks/s; 1,055 in 960 s</td></tr><tr><td>Sink identical on all 3 nodes</td><td>31 of 31 samples; peers 2 on every node at every sample; max tips 2</td></tr><tr><td>Difficulty (dual-lane rule)</td><td>56,268 at 20 s, 155,835 peak at 90 s, 110,533 at 900 s</td></tr><tr><td>Epoch boundaries (DAA 300, 600, 900)</td><td>first block of the new epoch accepted 2.23 / 0.50 / 1.47 s after the last of the old; inter-accept gap over the run median 0.59 s, p90 2.21 s, max 7.27 s</td></tr><tr><td>Caches built per node</td><td>4 (genesis day plus the three epoch seeds); miners' CPU program and cache for a new seed ready in 191 to 212 ms</td></tr><tr><td>Finality</td><td>window filling to DAA 300, paused one sample, active from 270 s (DAA 363); first lock checkpoint 11 (blue 331) by 3 of 3 voters at 100%; 24 locks to checkpoint 34, same index on all 3 nodes at every sample; checkpoint 12 locked at 2 of 3 votes (68.1% of active and of total)</td></tr><tr><td>Lock latency (miner, proposed to locked over RPC)</td><td>median 1,011 / 1,012 / 1,010 ms, max 1,988 / 2,716 / 1,965 ms; 34 of 34 votes accepted per miner</td></tr><tr><td>EVM smoke (<code>tools/evm-smoke</code>, copy outside the repo, <code>IGNEUM_RPCS</code> on 28803/28813/28823)</td><td>84 of 85 checks in 118 s: chain id 4463, miner balance 428.5 IGN at chain block 113 (the <code>IGNA</code> payout address), three accounts funded, 59 transfers executed in 10 chain blocks (max 17 per block, 33 to 91 us execution per block), deploy, call, receipts, logs, revert, developer share, state roots identical across nodes; the failing check is "duplicates landed in parallel blocks within 6 attempts" (identical copies to two nodes landed once in 2 of 6 attempts, the conflicting pair never both), which needs parallel blocks the PoW network did not produce in that 36 s (tips 1 at most samples)</td></tr><tr><td><code>igneum-exec-diff</code> on the smoke export</td><td>segments 0 to 198, 59 transactions compared, 8 accounts, 0 mismatches</td></tr><tr><td>Harness scenario 5 (ports 28900+, copy of <code>tools/harness</code>)</td><td>63 cases (46 RPC, 17 p2p): node up on every case, 0 cache builds (RSS flat, node log 1 build = the honest day), the M15 p2p cases disconnected by the strike guard: PASS</td></tr><tr><td>Harness scenario 2 (copy adapted to the merged rules; the repo copy probes 132 s and <code>pmt+1</code>)</td><td>live: floor-2 and floor-1 rejected, floor and floor+1 accepted where floor = max(pmt + 1, parent - 10 s); future flip between +10.00 and +10.02 s; sim: honest 0.908 b/s, ahead +0.2%, oscillate -0.7% over 1,200 virtual s: PASS</td></tr></tbody></table></div>
<p>Binaries: <code>target-integration/release/{igneumd 40,463,680 B, igneum-miner 7,916,096 B, igneum-exec-diff, igneum-inject, igneum-p2p-probe, igneum-harness-sim}</code>; <code>target-integration/x86_64-pc-windows-gnu/release/{igneumd.exe 50,169,344 B, igneum-miner.exe 10,045,440 B}</code>; package <code>/tmp/igneum-integration/igneum-node-windows-v4.zip</code> (32,946,104 B). Not done: the GPU <code>prepare</code> hot-swap path on the merged miner (CPU miners only here), the cut-over itself, v4 builds for the Mac seed relay and igneum-seed-1, the repo harness's scenario 2 rules and its scenario 5 summary text (stale "built a cache on HEAD" wording while the per-case data says 0 builds).</p>
<h2 id="4-october-2026-generator-version-2-adopted-exact-load-count-fresh-source-loads-program-acceptance-every-vector-re-cut-three-workers-re-checked-20-000-program-census-devnet-v4-binaries-rebuilt-cryptographer">4 October 2026, generator version 2 adopted: exact load count, fresh-source loads, program acceptance; every vector re-cut, three workers re-checked, 20,000-program census, devnet-v4 binaries rebuilt (cryptographer)</h2>
<p>Machine: Apple M5 Max, idle at the start (load 3), rustc 1.99.0 (rustup), Swift 5.8.1, builds at nice 10. Decision (the project lead, this morning): adopt the census's generator rule before any public vector ships. Rule as implemented (<code>igneum-pow/src/generator.rs</code>, <code>src/accept.rs</code>, spec 01 sections 1.4.2, 1.4.3 and 1.4.6; mirrored in <code>proto-metal/main.swift</code> as <code>generateProgramV2</code> and <code>acceptProgram</code> because the Metal worker derives its program from the seed itself): G1 exactly 16 load slots, a uniform subset of instructions 1..63 drawn first by partial Fisher-Yates, the other 48 ops from the ten non-load weights (sum 75); G2 a load's source is drawn from the registers other than <code>dst</code> written by an earlier instruction and not read by a load since; R (a) no cyclically stale load source, (b) every register has an injecting write, (c) 64 units at base nonces from <code>SplitMix64(FNV-1a-64("igneum-accept/" || seed words LE))</code> on the seed-keyed closed-form dataset at 2^28 words with init words = seed words: no constant register bit, no lane-constant load site in any unit, fewer than 164 saturated final values, every output bit within 136 of 1,024, distinct addresses above 245,760 over the 2,048 hashes; a rejected candidate is replaced by <code>seed_words_from_bytes(seed || k_le32)</code>, <code>k = 1, 2, ...</code>, 32 consecutive rejections a consensus fault. Every pack carries <code>generator 2</code>, the attempt and the program id <code>FNV-1a-64("igneum-program/" || 2_le32 || seed words LE || attempt_le32)</code>; <code>igneum-pow</code> is 0.2.0 and the node's engine reports <code>igneum-lottery-v2-bound</code>. The crate is now the pack source (<code>igneum-pow export</code>); the Swift exporter is the Metal cross-check. Version 1 stays as <code>generate_v1</code> for the census and MEMHARD.md levers; its vectors are retired.</p>
<p>Packs regenerated (<code>proto-cuda/packs/</code>): <code>igneum-genesis</code> and <code>igneum-hourly</code> (closed form), <code>igneum-genesis-mh</code> (memory-hard, day 2026-10-03, cache FNV unchanged <code>48c4f5bf24166b2e</code>), and new <code>igneum-devnet-v4-epoch0</code> (epoch seed = devnet genesis hash <code>edc4fa84...fb07</code>, day bytes <code>igneum-day/20730</code>, cache FNV <code>448274a57f508cbc</code>). <code>igneum-genesis</code> attempt 0, program id <code>bcc1248b10cc90f2</code>, op mix <code>load=16 add=8 shfl=8 xor=6 mad=5 mul=5 mulhi=5 sub=4 rotl=3 rotr=3 or=1</code>, lane 0 at base 0 <code>42246ba99fc58e4f</code>, lane 31 <code>b08446b1f2de7793</code>; devnet pack id <code>4be132dd1f2ff270</code>, lane 0 <code>285a83011e7ac3fc</code>. Bound vectors re-cut (<code>igneum-pow/README.md</code>: H zero, nonce 0 gives <code>746c567b090acf6a</code>). <code>cargo test --release</code>: 39 of 39 (28 unit, 11 pack).</p>
<div class="tbl"><table><thead><tr><th>Check</th><th>Result</th></tr></thead><tbody><tr><td>Rust CPU reference (<code>igneum-pow</code>)</td><td>the packs by construction; verify 0.631 ms per unit (avg of 20), cold 0.67 to 0.81 ms, 4,096 items per unit, cache fill 179 ms; acceptance 1.3 to 3.4 ms per candidate</td></tr><tr><td>Apple Metal, natively (<code>proto-metal/igneum-bench</code>, Swift v2 generator)</td><td><code>--export-pack igneum-genesis</code> memory-hard: GPU cache == CPU cache, Metal cross-check PASS 3 of 3 warps, instruction list and 96 vectors identical to the Rust pack; closed-form exports of <code>igneum-genesis</code>, <code>igneum-hourly</code>, <code>igneum-census-2026-10-03/22</code>, <code>/37</code>, <code>/51</code> (the last three have attempt 0 rejected: (b) r7, (c) 119.74 distinct, (b) r4; attempt 1 accepted, ids <code>22ed0609d079f4cf</code>, <code>947705cc4eb1df0a</code>, <code>9869afcc028bf9f1</code>): instructions, seed words and 96 vectors identical to Rust on all five; fuzz <code>--fuzz 2000 --fuzz-seed igneum-fuzz-gen2-2026-10-04</code>: 2,000 of 2,000 PASS, 8,000 warps, loads per hash 128 to 128, compile avg 21.8 ms, wall 91.4 s (<code>proto-metal/TESTS.md</code> section 9)</td></tr><tr><td>CUDA through the clang emulation shim (<code>proto-cuda/emu/emu.sh</code>, <code>--batch-log2 13 --block-warps 2</code>)</td><td>all four packs OVERALL PASS: dataset self-test, 3 warps standalone, 2 warps per block in batch; memory-hard packs cache check 67,108,864 of 67,108,864 words, host fill 174 to 178 ms</td></tr><tr><td>OpenCL through the clang emulation (<code>proto-opencl/emu/emu.sh</code>), sub-group 32 local exchange and wave64 sub-group shuffle</td><td><code>igneum-genesis-mh</code> and <code>igneum-devnet-v4-epoch0</code>: 96 of 96 in both configurations, fingerprints <code>f2a95d5bb84d961e</code> and <code>8e22ad069cb2a8c3</code> at 2^13, identical across configurations</td></tr><tr><td>Apple OpenCL on the M5 Max (<code>proto-opencl/host.c</code>)</td><td>all four packs: cache check PASS, 96 of 96 standalone and in batch (also <code>--group-warps 2</code>), fingerprint <code>f2a95d5bb84d961e</code> at 2^13 = the emulator's; 2^24 fingerprints <code>25f96e7dce90bd4e</code> (genesis-mh), <code>3cc4fbf90fa6366c</code> (devnet); rate 27.5 to 27.9 Mhash/s, 14.1 to 14.3 GB/s useful on every pack (version 1 genesis: 45.0 at 80 distinct loads; the census projected 28 at 128)</td></tr><tr><td>igneum-census, 20,000 programs, <code>--gen v2 --warps 64</code>, memory-hard day 2026-10-03, 8 threads, 254.8 s</td><td>rejected 5.225 percent (static 4.130, dynamic 1.095), 1.0551 candidates per epoch; accepted programs: distinct addresses per hash mean 127.887, min 120.127, p1 126.897, p50 127.999, max 128.000; static loads 128 on every program (<code>census-v2-20k.tsv</code> and its summary in the session scratchpad, not checked in). Against the 100,000-seed figure of 3 October: 5.14 percent</td></tr><tr><td>devnet-v4 node and miner (<code>vendor/igneum-node-v4</code>, path dependency bumped to igneum-pow 0.2.0, engine name v2)</td><td><code>cargo build --release -p kaspad -p igneum-miner --features igneum-pow</code> into <code>vendor/igneum-node/target-integration</code> (1 min 17 s warm): <code>release/igneumd</code> 40,480,112 B, <code>release/igneum-miner</code> 7,932,880 B (08:41 BST); <code>cargo test --release -p kaspa-pow --features igneum-pow</code> 11 of 11; Windows cross-build (<code>proto-cuda/windows-node/cross-build.sh vendor/igneum-node-v4 6</code>, 4 min 53 s): <code>target-integration/x86_64-pc-windows-gnu/release/igneumd.exe</code> 50,179,072 B, <code>igneum-miner.exe</code> 10,065,920 B (libstdc++-6.dll import as before)</td></tr><tr><td>2-node test network on the real engine (ports 29000 to 29012, <code>/tmp/igneum-gen2</code>, <code>igneum-devnet-900</code>, <code>IGNEUM_DEVNET_GENESIS_BITS=0x1f010000</code>, <code>IGNEUM_POW_EPOCH_BLOCKS=100</code>, <code>IGNEUM_POW_EPOCH_LEAD=20</code>, one 3-thread CPU miner per node for 300 s)</td><td>338 blocks accepted on both nodes, 0 rejected, 0 invalid, sink identical at 10 of 10 samples; four epochs crossed (DAA 0, 100, 200, 300; epoch seeds <code>234e08...</code>, <code>d3f427...</code>, <code>de316c...</code>, <code>971384...</code>, all attempt 0, ids <code>8f8806638d59850f</code>, <code>c015349db63beb2c</code>, <code>d7d52120407a0b69</code>, <code>512527bb7a528476</code>), program and cache ready in 192 to 284 ms on the miners, 4 cache builds per node; m1 158 and m2 180 blocks at 0.046 MH/s each; one WARN per node (eth JSON-RPC port 26790 held by the live devnet node, harmless)</td></tr></tbody></table></div>
<p>Not done: no NVIDIA or AMD hardware has run a version 2 pack (the RTX 5090's 192 of 192 and the gfx1036 run of 3 October were version 1; the kernel text is unchanged); the edge, stats, determinism and memcheck sections of TESTS.md were not re-run (they do not depend on the generator); the live devnet (v3, version 1 programs) was not touched, so the cut-over is where version 2 goes live; <code>proto-metal/main.swift</code> carries the version 2 port uncommitted next to the hot-swap working-tree changes (not in this agent's file list), and the Mac app's Metal worker must be rebuilt from it before the cut-over or Mac GPU shares will fail the CPU re-check; the v4 binaries above were built from the worktree as found, which also holds another agent's uncommitted finality floor change (2/3 of total, O-3.15); the GPU <code>prepare</code> hot-swap path was not exercised here (CPU miners only). The ten non-load weights and the 6-sigma bias threshold remain prototype values (spec 1.16).</p>
<h2 id="2026-10-04-finality-floor-2-3-the-total-weight-floor-raised-from-17-30-to-two-thirds-simulator-a-to-l-re-run-attack-scenarios-6a-and-6b-on-a-three-node-six-voter-network-cryptographer">2026-10-04 finality floor 2/3: the total-weight floor raised from 17/30 to two thirds, simulator A to L re-run, attack scenarios 6A and 6B on a three-node, six-voter network (cryptographer)</h2>
<p>Decision of 4 October 2026 (the project lead, O-3.15): a lock needs two thirds of all 30-day weight, and finality pauses whenever less than two thirds of that weight is connected and signing; the chain continues on proof of work meanwhile and the node reports it. Spec 3.3, 3.3.1, 3.7, 3.9, 3.11 rewritten; litepaper Finality and "What Igneum does not claim" updated; ledger F2, F9, F16, F18 restated and F21 added (the window bound of attack scenario 6A).</p>
<p>Node. Branch <code>devnet-v4</code> of <code>vendor/igneum-node</code> (worktree <code>vendor/igneum-node-v4</code>, from <code>dc749905</code>), commit <code>6457ca95</code>, two files: <code>consensus/core/src/finality.rs</code> (<code>FLOOR_NUM / FLOOR_DEN</code> 2/3, was 17/30; the Q3 arithmetic as <code>FinalityParams::{quorum_met, floor_met, locks}</code>, both comparisons inclusive) and <code>consensus/src/processes/finality.rs</code> (<code>lock_test</code> calls it). Build <code>CARGO_TARGET_DIR=target-integration nice -n 10 cargo build --release -j 6 -p kaspad --features kaspad/igneum-pow</code>, 3 min 17 s on a machine at load 3 to 13 (another agent's igneum-pow rebuild and the live devnet running). Tests <code>cargo test --release -j 6 -p kaspa-consensus-core -p kaspa-consensus -- finality</code>: 7 of 7 in consensus-core including the new <code>floor_is_two_thirds_of_total_and_inclusive</code> (4 of 6 locks, 3 of 6 does not, 2 of 3 locks, 67 of 100 locks, 66 does not, 57 does not; the total test implies the active test at every participation; a 3/3 side never locks whatever the other side's participation decays to), 2 of 2 in consensus (<code>no_certificate_while_the_window_is_filling</code> unchanged). The live devnet (26610, 26611, 26640, 26641, 28640, the seed relay on 26680 and <code>observer.mjs</code>) was never touched.</p>
<p>Simulator. <code>sim/finality_v2.py</code>: <code>--floor f</code> (a lock needs f x 2/3 of total; default 1.0 since this date, <code>--floor 0.85</code> reproduces the 3 October tables), the <code>+local</code> partition mode (a side's weight table counts only the blocks it has seen since the split, as a real node's window does; the 3 October tables kept weights global), scenario L (silent weight at 25 to 45%, churn, the poisoned eclipse, 12-day partitions with local weights), H widened to 30% and 33% attackers, I given the 34% case. A to G at <code>--quick</code> for seeds 7, 11, 13, 17, 19 (about 1 min a seed), H to L at full length for the same seeds (H 25 s, I 13 s, J 16 s, K 400 s, L 240 s), all at nice 10. Full tables and the 0.85 against 2/3 deltas in <code>sim/results_v2.md</code>, "Floor 2/3".</p>
<div class="tbl"><table><thead><tr><th>Simulator measure</th><th>Floor 0.85 (3 October)</th><th>Floor 2/3 (4 October)</th></tr></thead><tbody><tr><td>Smallest equivocator that splits a 50/50 honest partition (H, 150 and 360 min, retarget)</td><td>14% in some seeds, 20% in every seed from minute 12</td><td>34% (sides 67.0%): 2 to 54 conflicts, first at minute 2 to 77; 33% (66.5%) and below: 0 conflicts, no lock on either side, every seed</td></tr><tr><td>40/40 honest plus a 20% equivocator reaching both, sides 60/60 (I)</td><td>256 to 276 conflicts in 150 min</td><td>0 conflicts, no lock</td></tr><tr><td>Silent weight that keeps mining: where the pause begins (J, L1)</td><td>between 40% (13-min first lock) and 45% (never)</td><td>between 32% and 34%: 30% locks every checkpoint, 32% locks 69 to 100%, 33% locks 0 to 11%, 34% and above lock nothing for as long as they stay silent; first lock 0 min after the silent set returns</td></tr><tr><td>Churn, first lock (L2, D)</td><td>35%: 2 min; 50%: 4.1 days</td><td>35%: 1.7 days (analytic 1.4); 50%: 10.1 to 10.3 days (analytic 10.0)</td></tr><tr><td>Poisoned eclipse, 34% attacker plus a 20% pool, 1, 2, 4 h (L3)</td><td>0 conflicts</td><td>0 conflicts, 0 locks on the eclipsed side, every seed</td></tr><tr><td>Bought keys worth 40% that withhold their votes, 30 days (K)</td><td>305 to 1,085 stalls of 86,400</td><td>63,307 to 68,716 stalls: the pause lasts until the bought weight decays below one third, day 19 to 20</td></tr><tr><td>Long honest partition, each side counting only what it has seen (L4, 12 days)</td><td>50/50: both sides lock alone from day 4.1; 60/40: the 60 side at once, the 40 side from day 8.5</td><td>50/50: day 10.1 to 10.3 (predicted 10.0); 60/40: the 60 side from day 5.1 (predicted 5.0), the 40 side never in 12 days; 55/45: day 7.9 and 12.0</td></tr></tbody></table></div>
<p>Test network. Three <code>igneumd</code> (the floor-2/3 build) on <code>igneum-devnet-921</code> (6A), <code>-922</code> (6B) and <code>-923</code> (6A, long heal): n1 listens (gRPC 29210, p2p 29211, wRPC 29212), n2 dials n1 (29220 to 29222), n0 dials n1 through a TCP proxy on 29290 (29200 to 29202); cutting the proxy isolates n0 from {n1, n2}. Data under <code>/tmp/igneum-floor</code>; harness <code>/tmp/igneum-floor/harness/floor-run.mjs</code> over the env-driven <code>lib/</code> of the fin-fixes re-run (the repo harness <code>tools/finality-attacks</code> was not edited; its s6 still reads 56.7%). Override: <code>skip_proof_of_work</code>, interval 30, depth 20, window 1,800 DAA, dust 5, presence 20, 8 aggregators, ban 1,800, <code>min_daa</code> 1,800, fallback 15. Six vmine voters from the fin-attacks <code>igneum-miner</code> at share 1/6, 6 bps (each 611 to 639 blocks accepted over the run, 0 rejected from the miner's side), 390-s warmup (the window full at DAA 1,800, locks from about 300 s), 150-s split, 60-s heal window (300 s in the third run). Predicted bound for a 3/3 side on a full sliding window: share(T) = 1/2 + R T / (2 W), so two thirds at T* = W / (3 R) = 200 s at W = 1,800 and R = 3 blocks/s; the old floor's 17/30 at 2 W / (15 R) = 80 s.</p>
<div class="tbl"><table><thead><tr><th>Scenario</th><th>Criterion (spec Q3, 3.3.1)</th><th>Measured</th><th>Verdict</th></tr></thead><tbody><tr><td>6A, 3/3 (a0 a1 a2 on n0; b0 b1 on n1, b2 on n2), 150 s</td><td>zero new locks on any node during the split (each side under two thirds of its own table); no conflicting certificates; locks resume after the heal</td><td>cut at DAA 2,250 with 75 locks on all three nodes, window 1,800 of 1,800, side A at 48.3% and side B at 51.7% of every node's table; new locks during the split 0 / 0 / 0; shares at the end of the split 60.8% (A) and 62.7% (B), both still under the floor and both past the old 56.7% floor (B crossed it at about 76 s, A at about 106 s, so the 3 October rule would have locked on both sides inside this split); conflicting certificates 0 / 0 / 0; <code>finality_reason</code> stayed <code>active</code> throughout (a lock within the last 20 indices); after the heal n1 and n2 resumed (75 to 97 within 60 s), n0 did not redial the proxy within 60 s (the connection manager retries a <code>--connect</code> peer at 30 x 2^attempts seconds, so after four failed attempts during the split the next redial was minutes away); the third run below extends the heal window</td><td>PASS on the floor (0 locks either side, 0 conflicts); the 60-s heal window was too short for n0's redial, see 6A long heal</td></tr><tr><td>6B, 4/2 (p0 p1 on n1, p2 p3 on n2; q0 q1 on n0), 150 s</td><td>the 4 side locks on both its nodes, the 2 side does not; no conflicting certificates; identical locked hashes across nodes</td><td>cut at DAA 2,399 with 79 / 80 / 79 locks; the 4 side held 1,222 of 1,800 = 67.9% of every table (Poisson noise put it 1.2 points over the floor at the cut); first new lock on the 4 side 2 s (n2, index 80) and 8 s (n1, index 81) after the cut, signed 1,222, active 1,800, total 1,800: 67.9% of total and of active, 4 votes seen, the two silent keys still at participation 1 inside the presence window so the two tests bound at the same fraction; 20 and 21 new locks on the 4 side during the split, 0 on the 2 side (32.1% rising to 42.1% of its own table by the end); 0 conflicting certificates; 0 locked indices disagreeing across nodes; n1 and n2 at 109 after the heal</td><td>PASS</td></tr><tr><td>6A again, long heal (<code>igneum-devnet-923</code>), 150-s split, 300-s heal window</td><td>as 6A, with a heal window longer than the redial backoff</td><td>cut at DAA 2,279 with 75 / 76 / 75 locks, sides at 49.9% and 50.1%; new locks during the 150-s split 0 / 0 / 1, the one being n2 catching up to the common pre-split checkpoint 76 at the instant of the cut (signed by both sides, 67.7% of total), so 0 side-alone locks either side; shares at the end of the split 61.1% and 61.8%. The gate reopened at 150 s but n0's redial came at 209 s (the 30 x 2^attempts backoff), so the sides kept mining apart. Side B locked alone first at 205 s after the cut: checkpoint 96 (blue score 2,880, 601 own blocks after the cut) by its 3 keys at 1,206 of 1,800 = 67.0% of total, one lock over the floor, against the predicted bound W / (3 R) = 200 s. Side A (n0) locked alone from checkpoint 98 at 215 s, 6 s after its redial, by its 3 keys at 1,017 of a table of 1,362 = 74.7%: once side B's 600 post-split blocks arrived they were merged red, so in n0's view they count for nothing (W2 counts blue blocks) and its own share rose at once. From there each side's F1 pinned it to its own certified chain: 26 conflicting certificates logged on n0 (indices 98 to 123), 2 on n1 and 3 on n2 (indices 118 to 120, the first of n0's to reach them), 23 locked indices disagreeing across the three nodes at the end of the 300-s heal window, 39 / 42 / 42 locks in all, no equivocation and no strip (each key voted once per index, for its own side's checkpoint). The network healed and finality did not: a finality fork with no attacker, exactly the state 3.11.4 leaves to operators (F5)</td><td>PASS on the floor for 150 s (0 side-alone locks); the window bound crossed at 205 s against 200 predicted; the heal does not undo it (spec 3.7 item 9, ledger F21)</td></tr></tbody></table></div>
<p>The long-heal run is the measurement the 3 October harness could not make: the old floor fell at 84 s of a young window (S6A); the two-thirds floor on a full 1,800-DAA window held for 150 s and fell at 205 s, 3.25x later as the arithmetic says (2F / (13R) against F / (2R) on a young window, 2W / (15R) against W / (3R) on a full one), and it fell on both sides within 10 s of each other because a 50/50 split crosses the bound at the same moment from both ends. On mainnet the same bound is 10 days of a 30-day window at 50/50 (spec 3.3.1, <code>sim/results_v2.md</code> L4). What the DAG adds to the simulation: after the heal the losing side's blocks are red in the winner's view, so the crossing is sudden rather than gradual, and once either side has certified a checkpoint of its own F1 never lets it back, so the fork is permanent until an operator sets a trusted certificate (F5, not implemented).</p>
<p>The inclusive comparison at exactly two thirds is pinned by the integer test <code>3 x signed &gt;= 2 x total</code> and the unit test (4 of 6, 2 of 3 lock; the 3 October S6B run had already measured the active test passing at exactly 2/3 with 4 of 6 equal voters); the devnet's 4 side sat at 67.9% rather than 66.67% because block counts are Poisson, and locked at the first checkpoint after the cut.</p>
<p>Notes. (1) The v4 node logged "PoW rejected ... by igneum-lottery-v1-bound" about five times a second per node (2,952 lines on n0 over 6A) although the override carries <code>skip_proof_of_work</code> and the six miners saw every submission accepted; the window held exactly 1,800 blocks of weight on every node and each side's DAA advanced at 3 per second as planned, so the lines did not move the measurement, but what the v4 pipeline is re-checking there is a question for the consensus engineer before the v4 cut-over (30 of a sample of 200 rejected hashes were later accepted on the same node). (2) The repo harness <code>tools/finality-attacks/run.mjs</code> s6 criterion text and the s5 "below the 56.7% floor" pass test are now stale and should read two thirds. (3) Not done: the two-hour presence window and a 30-day window at mainnet length; the first-month gate under the new floor is unchanged (<code>min_daa</code> = window). (4) The harness's 60-s heal window (6A, 6B) is shorter than the connection manager's redial backoff for a <code>--connect</code> peer after a cut, so a healed proxy does not mean a reconnected n0 inside it; the long-heal run used 300 s and n0 redialled at 59 s after the gate reopened. (5) Stop everything: every node, miner and proxy of the three runs was stopped by the harness at the end of each run; ports 29200 to 29299 were free afterwards (<code>lsof</code> 0 listeners).</p>
<h2 id="4-october-2026-proving-v0-on-the-rtx-5090-first-gpu-proof-of-an-igneum-block-wsl2-sp1-6-8-1-cuda">4 October 2026, proving v0 on the RTX 5090: first GPU proof of an Igneum block (WSL2, SP1 6.8.1 cuda)</h2>
<p>Machine: the project lead's Windows 11 PC, RTX 5090 (32,607 MiB, driver 617.14), 16 cores and 45 GB visible to WSL2 Ubuntu 24.04, mining paused. Package <code>proving/windows-wsl2</code> (SETUP-PROVER then PROVE-BLOCK), host <code>igneum-prove-host</code> built with the <code>cuda</code> feature, <code>SP1_PROVER=cuda</code>, sp1-gpu-server 6.8.1 on device 0. Fixture <code>block-78-increment</code> (chain 4463, 2 transactions, 10 accounts). Run id <code>prove-&lt;pc&gt;-20261004-084838</code>, log intake id 10154.</p>
<div class="tbl"><table><thead><tr><th>Stage</th><th>RTX 5090</th><th>Apple M5 Max CPU (3 October, loaded)</th></tr></thead><tbody><tr><td>native re-execution</td><td>0.0003 s, state root MATCHES the fixture</td><td>0.0004 s, matches</td></tr><tr><td>setup (one per program id)</td><td>23.23 s</td><td>20.55 s on the PC's CPU; Mac not timed separately</td></tr><tr><td>execute</td><td>626,876 cycles, prover gas 844,704, 0.19 s, 14 cycles per EVM gas</td><td>626,246 cycles, 0.15 s on the PC's CPU</td></tr><tr><td>core proof</td><td>prove 1.4 s, 7,317,217 bytes, verify 0.221 s, VERIFIED</td><td>prove 22.0 s, 7.3 MB, verify 0.16 s</td></tr><tr><td>compressed proof</td><td>prove 2.7 s, 1,272,769 bytes, verify 0.038 s, VERIFIED</td><td>prove 55.7 s, 1.27 MB, verify 0.03 s</td></tr></tbody></table></div>
<p>Reading: 15.7x on core and 20.6x on compressed against a loaded laptop CPU. The block is far below one SP1 shard, so these are the fixed per-proof overheads of the proof system on this card; the throughput number needs the larger fixtures (proving e2e standard). Post-state root and receipts root identical to the node's on every stage. Two defects, neither in the proof: the host aborted (exit 134) AFTER writing and uploading the results, in <code>sp1-cuda</code>'s destructor outside a Tokio runtime; and the host was silent for ten minutes between the core and compressed stages with the card idle. Ledger P20. Setup on the PC needed three package fixes found only by running it on Windows: <code>protobuf-compiler</code> in the apt list, the WSL distro check (UTF-16 output), the elevated window closing; and WSL2 itself needed <code>bcdedit /set hypervisorlaunchtype auto</code> on a PC whose BIOS already had SVM on.</p>
<h2 id="4-october-2026-devnet-v4-cut-over-generator-v2-2-3-floor-three-nodes-and-a-seed-on-a-fresh-chain">4 October 2026, devnet v4 cut-over: generator v2, 2/3 floor, three nodes and a seed on a fresh chain</h2>
<p>Sequence (local time, UTC+1): seed re-staged from <code>6457ca95</code> (build 55 min on the 2-vCPU VM, 08:55 to 09:54); node 1 stopped and the v3 database moved aside at 10:05:30, <code>igneumd</code> v4 up at 10:05:31 with the execution layer; observer peer and <code>observer.mjs</code> restarted on fresh appdirs at 10:06:10; seed switched with <code>switch-v4.sh</code> at 10:06:28 and synced at 10:07:38 (<code>blocks=2</code>, <code>peers=1</code>); Mac Metal miner (generator v2 port, <code>prepare 1</code>) first accepted block at 10:07:35; the PC joined at 10:10:55 (protocol version 12) and its first blocks followed within the minute. Block rate went from about 1 per second (Mac alone, difficulty easing 9.1% a block from the 134M genesis value) to about 2 per second with the 5090; the live page followed from block 0 with 9 identities after five minutes. First NVIDIA card to mine a generator v2 program; CPU re-check passed on every Mac share. One launcher defect found only on Windows: <code>"$machine:$vendorName"</code> in <code>igneum-common.ps1</code> is a drive-qualified variable to PowerShell, so the file failed to parse (fixed, braces). The first finality lock is due at DAA 7,200, two hours after the v4 genesis; the first hourly swap at DAA 3,600.</p>
<h2 id="4-october-2026-one-click-windows-workers-what-the-mac-could-measure-no-nvidia-gpu-here">4 October 2026, one-click Windows workers: what the Mac could measure (no NVIDIA GPU here)</h2>
<p>Package 0.3.0 replaces the build-on-the-PC workers with two prebuilt exes (<code>proto-cuda/nvrtc/worker.cpp</code>: driver API + NVRTC loaded at run time; <code>proto-opencl/host.c --pack</code>: OpenCL.dll loaded at run time, pack read at run time). Both cross-compile with Homebrew mingw-w64 16.2 and import only KERNEL32 and the Universal CRT (1,117,184 and 78,336 bytes stripped). The NVRTC DLLs (12.8.93) add 93 MB to the zip. Checks run on the M5 Max, same protocol script for both (<code>proto-cuda/nvrtc/emu/serve-check.sh</code>: jobs on pack A, a background prepare of a second pack, the swap, a job after the swap, 15 to 17 found hashes against <code>igneum-pow hash-bound</code>):</p>
<div class="tbl"><table><thead><tr><th>Worker</th><th>How it ran here</th><th>First pack (cache / dataset / self-test)</th><th>Prepare of pack B (reported)</th><th>Verdict</th></tr></thead><tbody><tr><td>igneum-worker-cuda (emulation backend: pack kernels on host threads, NVRTC stand-in)</td><td><code>nvrtc/emu/test.sh</code></td><td>157 / 4,758 / 1,702 ms</td><td>8,413 ms (cache 132, dataset 4,759, check 1,671)</td><td>PASS, 17 of 17 sampled hashes, source check PASS for 4 files</td></tr><tr><td>igneum-bench-cl --pack (Apple OpenCL 1.2, built against a different placeholder pack)</td><td><code>proto-opencl/test-generic.sh</code></td><td>53 / 289 / 1,588 ms</td><td>2,083 ms (build 212, cache 25, dataset 197, check 1,387)</td><td>PASS, 15 of 15 sampled hashes</td></tr></tbody></table></div>
<p>The self-test times are dominated by reading the 256 MiB cache back and hashing it on one CPU thread (FNV-1a 64 over 2^26 words); the emulation's dataset build is 256 host threads over 2^24 items and says nothing about a GPU. Nothing NVIDIA was measured: the first NVRTC compile, cubin load and hash rate on the RTX 5090 are owed from the PC run (<code>proto-cuda/windows-app/TEST.md</code> lists the lines to copy here).</p>
<h2 id="4-october-2026-first-hourly-program-swap-on-the-live-devnet-compile-ahead-no-pause-two-cards">4 October 2026, first hourly program swap on the live devnet: compile-ahead, no pause, two cards</h2>
<p>Live devnet v4, epoch boundary at DAA 3,600 (11:05:07 BST). The node announced the next seed once the sink was 150 DAA past the seed score (<code>next_epoch_seed</code>, confirm = lead/4); each miner sent <code>prepare</code> to its worker and the worker built the next program in the background while the current one mined.</p>
<div class="tbl"><table><thead><tr><th>Machine</th><th>Prepare sent</th><th>Compile</th><th>Swap at 3,600</th><th>Restart</th><th>Rate before / after</th><th>Rejected</th></tr></thead><tbody><tr><td>Mac M5 Max (Metal, <code>prepare 1</code>)</td><td>449 DAA before the boundary</td><td>82 ms</td><td>0.01 ms, resident 2 programs</td><td>none</td><td>26.7 / 26.7 MH/s</td><td>0</td></tr><tr><td>RTX 5090 (CUDA, nvcc in the background)</td><td>same template</td><td>1,285 ms (nvcc 1,129, cache 4, dataset 23)</td><td>0.00 ms, resident 2 programs 2 datasets</td><td>none</td><td>121.8 / 123.4 MH/s</td><td>0</td></tr><tr><td>AMD Radeon integrated (OpenCL)</td><td>same</td><td>prepared</td><td>no restart</td><td>none</td><td>2.74 / 2.74 MH/s</td><td>0</td></tr></tbody></table></div>
<p>Reading: the compile-ahead rule (3 October 2026 decision: never restart every miner at once) holds on real values with three vendors; the hash rate is unbroken through the boundary and the launcher's exit-42 rebuild path was not used (<code>rebuilds 0</code>). The next boundary is DAA 7,200, which is also the first finality lock (weight window 7,200).</p>
<h2 id="4-october-2026-proving-devnet-v4-shards-on-the-apple-m5-max-cpu-loaded-machine-execution-engineer-proving">4 October 2026, proving: devnet v4 shards on the Apple M5 Max CPU, loaded machine (execution-engineer, proving)</h2>
<p>Machine: Apple M5 Max (18 cores, 64 GB), macOS Darwin 25.6.0, load average 38 to 47 during the runs (the live devnet node and Metal miner, another agent's cross-builds, this agent's exports), everything under <code>nice -n 19</code>. Toolchain: SP1 v6.8.1 (cargo-prove c84ada1, succinct rustc 1.96.0-dev, circuit v6.1.0), sp1-sdk 6.8.1 CPU prover, revm 43.0.3, alloy-trie 0.9.8. Code: <code>proving/igneum-prove</code> at the "Proving fixtures: blocks of one, two and four shards" commit: two guests (shard program id <code>0x7b274fc9...</code>, aggregator id <code>0x36e952f0...</code>), port of igneum-exec b7fca5a0. Fixtures: <code>proving/fixtures/block-{338,341,344}</code> cut by <code>igneum-prove-export</code> from <code>tools/prove-fixtures/seq.json</code> (a private one-node simnet, execution-layer b7fca5a0 binaries whose exec crate is byte-identical on devnet-v4; the devnet-v4 worktree had another agent's uncommitted edits and was not built), which replayed all 346 segments from genesis and matched every node state root (final root <code>0xe0f269dc...</code>); <code>block-56-transfers-3shards</code> is the v0 block 56 cut at a test budget of 200 pgas. <code>S_p</code> = 7,500,000 pgas (provisional, <code>B_p / 4</code>).</p>
<p>Statement per shard: from the carried-in position and a witness of the touched accounts, slots and trie nodes (checked against the pre-root), execute the shard's transactions and commit the post-root, the shard's receipts root, gas, pgas, the carry links and the prover's payout address; the aggregator verifies the shard proofs in order and commits the block. The host checks the native cut (shards chain and sum to the block) and rejects three tampered witnesses before any proof, on every fixture.</p>
<div class="tbl"><table><thead><tr><th>Fixture</th><th>Txs</th><th>EVM gas</th><th>pgas</th><th>Shards (pgas each)</th><th>Witness per shard: accounts / slots / leaves / hashes</th><th>Input bytes per shard</th></tr></thead><tbody><tr><td>block-338-shard1 (3 modexp calls of 652 iterations, 6 transfers, 2 Counter increments)</td><td>11</td><td>1,390,773</td><td>6,751,568</td><td>1 (6.75 M)</td><td>19 / 3 / 24 / 13</td><td>21,447</td></tr><tr><td>block-341-shards2</td><td>14</td><td>2,564,838</td><td>13,499,360</td><td>2 (6.75 M, 6.75 M)</td><td>11 / 1 / 12 / 17; 16 / 3 / 22 / 12</td><td>18,372; 20,335</td></tr><tr><td>block-344-shards4 (near <code>B_p</code>)</td><td>20</td><td>4,947,168</td><td>26,994,944</td><td>4 (6.75 M each)</td><td>11 / 1 / 12 / 17; 7 / 1 / 8 / 18; 7 / 1 / 7 / 21; 16 / 3 / 23 / 10</td><td>18,371; 17,125; 17,150; 20,362</td></tr><tr><td>block-56-transfers-3shards (test cut at 200 pgas)</td><td>3</td><td>63,000</td><td>600</td><td>3 (200 each)</td><td>4 / 0 / 5 / 0; 2 / 0 / 1 / 4; 2 / 0 / 1 / 5</td><td>4,298; 3,671; 3,720</td></tr></tbody></table></div>
<p>SP1 executor (<code>--mode execute</code>, no proof):</p>
<div class="tbl"><table><thead><tr><th>Shard</th><th>Cycles</th><th>Prover gas</th><th>Cycles per EVM gas</th><th>Cycles per pgas</th><th>Execute s</th></tr></thead><tbody><tr><td>block-344 shard 0 (6 txs)</td><td>60,015,755</td><td>49,669,617</td><td>48</td><td>9</td><td>6.9</td></tr><tr><td>block-344 shard 1 (3 modexp calls)</td><td>59,619,778</td><td>49,236,458</td><td>50</td><td>9</td><td>7.7</td></tr><tr><td>block-344 shard 2 (3 modexp calls)</td><td>59,628,334</td><td>49,243,900</td><td>50</td><td>9</td><td>4.6</td></tr><tr><td>block-344 shard 3 (8 txs)</td><td>60,417,382</td><td>50,172,279</td><td>46</td><td>9</td><td>6.5</td></tr><tr><td>block-344 aggregator over 4 shards (deferred verification off)</td><td>1,664,255</td><td></td><td></td><td></td><td>0.1</td></tr><tr><td>block-56 test shards 0, 1, 2 (one transfer each)</td><td>315,235; 271,190; 274,954</td><td></td><td>13 to 15</td><td>1,356 to 1,576</td><td>0.1</td></tr><tr><td>block-56 aggregator over 3 shards</td><td>1,544,681</td><td></td><td></td><td></td><td>0.1</td></tr></tbody></table></div>
<p>CPU proofs (<code>SP1_PROVER=cpu</code>), block-56-transfers-3shards:</p>
<div class="tbl"><table><thead><tr><th>Stage</th><th>Prove s</th><th>Proof bytes</th><th>Verify s</th><th>Verified</th></tr></thead><tbody><tr><td>setup (prover client plus two key setups)</td><td>39.4 to 60.6 (keys 3.3 + 3.2 of it; the rest is the client)</td><td></td><td></td><td></td></tr><tr><td>shard 0: core</td><td>83.1</td><td>7,310,257</td><td>0.368</td><td>yes</td></tr><tr><td>shard 0: compressed</td><td>272.3</td><td>1,272,897</td><td>0.075</td><td>yes</td></tr><tr><td>block mode, shard 0: compressed</td><td>336.9</td><td>1,272,897</td><td>0.068</td><td>yes</td></tr><tr><td>block mode, shard 1: compressed</td><td>305.4</td><td>1,272,897</td><td>0.066</td><td>yes</td></tr><tr><td>block mode, shard 2: compressed</td><td>245.3</td><td>1,272,897</td><td>0.064</td><td>yes</td></tr><tr><td>block mode, aggregation over the 3 shard proofs (recursion, deferred proofs)</td><td>244.5</td><td>1,272,909</td><td>0.084</td><td>yes, shard program id and claim checked</td></tr><tr><td>block mode, end to end (first shard proof to the verified block proof, 10:07:22 to 10:26:21 UTC)</td><td>1,139</td><td></td><td></td><td></td></tr></tbody></table></div>
<div class="tbl"><table><thead><tr><th>RTX 5090 (PROVE-SHARD.bat)</th><th>shard at <code>S_p</code>: execute, core, compressed</th><th>two-shard block end to end</th><th>four-shard block end to end</th></tr></thead><tbody><tr><td>pending</td><td></td><td></td><td></td></tr></tbody></table></div>
<p>Reading: the shard at <code>S_p</code> is 60 M cycles on the prototype table, 9 cycles per pgas against the unit's 1,000 (the modexp entry is about 100x its SP1 cost: R1, one number); a plain transfer shard is 1,400 to 1,600 cycles per pgas because the 200-pgas intrinsic charge carries the fixed cost of the witness check and the two root computations. The aggregator statement is 1.5 to 1.7 M cycles (bincode, an unpatched sha256 of each shard's public values, the keccaks), small next to a shard. The CPU proof times are 3.8x and 4.9x the 3 October v0 numbers on a comparable statement, on a machine three times as loaded; the GPU row stays empty until the PC runs. The devnet was not touched; the simnet ran on ports 29300, 29301 and 29390 and was stopped.</p>
<h2 id="2026-10-04-fast-time-60x-test-profile-linux-cross-compile-from-the-mac-ci-on-every-push-consensus-engineer">2026-10-04, fast time (60x test profile), Linux cross-compile from the Mac, CI on every push (consensus-engineer)</h2>
<p>Machine: Apple M5 Max, 64 GB, shared with four other agents and the live devnet (load 40 to 76 throughout), every job at nice 19 with at most 4 cargo jobs. The live devnet (26610/26611, 26640/26641, 28640), the Metal worker and the seed's <code>/opt/igneum/v4/bin</code> were not touched.</p>
<p>Fast time. <code>infra/fast-time/override-60x.json</code> is the devnet with every clock-like consensus parameter divided by 60 and every block count unchanged (<code>infra/fast-time/README.md</code> lists each field and why it scales or not). The hourly program epoch and its lead are now consensus parameters carried by the override file (<code>pow_epoch_blocks</code>, <code>pow_epoch_lead</code>, plus <code>pow_day_ms</code> for the dataset day; devnet-v4 <code>a5ef8b07</code>, devnet defaults unchanged: kaspa-consensus-core 79 tests, kaspa-pow 7, igneum-pow 39 pass, and a new test reads the 60x file and checks every rule against <code>DEVNET_PARAMS</code>). Proof (<code>infra/fast-time/simnet.mjs</code>, three devnet-v4 nodes on ports 29500+, <code>igneum-devnet-950</code>, three vmine voters sharing 1 block/s, one real-hash CPU miner thread): next epoch seed in the template at 56.1 s (DAA 53), program swap at 65.1 s wall (DAA 60; the CPU miner's new program and cache ready 397 ms later), first finality lock at 185.5 s wall (checkpoint 5, blue score 150, DAA 149; checkpoint 4 sat one DAA under <code>min_daa</code> 120), sinks identical on all three nodes. The devnet reaches the same two events at DAA 3,600 and DAA 7,200 plus a checkpoint.</p>
<div class="tbl"><table><thead><tr><th>Harness run, same binaries</th><th>Devnet profile</th><th>60x profile (<code>--fast-time</code>)</th></tr></thead><tbody><tr><td><code>tools/finality-attacks</code> s3, dishonest aggregators</td><td>catalogue 32 min at SCALE 0.6 on the 3 Oct node (above); on today's rule the window fills at DAA 7,200 = 20 min at 6 blocks/s before any lock</td><td>113 s wall: 16 locks per node, 0 conflicting certificates, lock hashes agree, median lock latency 1,018 ms, PASS</td></tr><tr><td><code>tools/harness</code> s3, partition and heal (in-process simulator of the devnet-v4 line)</td><td>983 s wall, four cuts of 120 / 600 / 1,800 / 3,700 virtual s (46 / 151 / 400 / 831 s wall), all PASS</td><td>43 s wall, three cuts of 10 / 30 / 62 virtual s (18 / 20 / 26 s wall), all PASS; the 62-s cut beyond the 60-s merge depth converged with a 34-block reorg</td></tr></tbody></table></div>
<p>Both harnesses take <code>--fast-time</code> (<code>tools/harness/lib/net.mjs</code>, <code>tools/finality-attacks/lib/net.mjs</code>): the node and the simulator then come from <code>vendor/igneum-node/target-integration</code> (the file carries fields only the devnet-v4 line knows), the merge-depth scenarios scale their cuts with the profile, and the finality runs default to the <code>--quick</code> scale.</p>
<p>Linux cross-compile. <code>infra/cross/build-linux.sh</code>: cargo-zigbuild 0.23.4 with zig 0.17.0 (<code>brew install zig</code>, <code>cargo install cargo-zigbuild</code>, <code>rustup target add x86_64-unknown-linux-gnu</code>), target <code>x86_64-unknown-linux-gnu.2.36</code> (Debian 12 on the servers), <code>-p kaspad -p igneum-miner --features kaspad/igneum-pow</code>, target dir <code>vendor/igneum-node/target-linux</code>. Cold build: 1,856 s (30 min 56 s) at 4 jobs, nice 19, on this loaded machine; rocksdb (librocksdb-sys C++), lz4, blst, secp256k1 and the execution layer's crates all linked through zig; no crate failed, so <code>cross</code> was not needed (Docker is not installed here anyway). Output: ELF x86-64 PIE, igneumd 46,883,304 B and igneum-miner 8,978,424 B, dynamically linked against libc and libm only. Verified on igneum-seed-1 (Debian 12, glibc 2.36, 2 vCPU) in <code>/root/xbuild-test</code>: <code>igneumd --version</code> = igneumd 2.1.0, <code>igneum-miner --help</code> prints its usage, and a 60-s run of the cross-compiled node on <code>igneum-devnet-951</code> (the 60x profile at genesis bits 2^16, real proof of work) with the cross-compiled miner on one CPU thread: the miner adopted the node's 60-block epoch from the template, built its 256 MiB cache in 395 ms, found 7 blocks at 0.011 MH/s, all 7 accepted by the node's own PoW check, 0 rejected (the x86 build of the lottery hash agrees between miner and node; neither binary exposes a standalone vector check). Against the alternatives: the seed's own build took 55 min on its 2 vCPU (4 Oct 2026, above), the builder VM 10 to 25 min on 8 vCPU plus its creation and deletion. <code>BIN_SOURCE=mac</code> is wired into <code>infra/cloud-devnet/provision.sh</code> (no builder VM, build/bin from the cross-compile) and <code>infra/seed-nodes/stage-v4.sh</code> (upload to /root/v4/out, install as before).</p>
<p>CI. <code>.github/workflows/ci.yml</code> runs on every push and pull request of the private repository: igneum-pow <code>cargo test --release</code> and the igneum-census build (49 s), the two simulators' <code>--quick</code> modes under a 120-s timeout (49 s for the job; <code>sim/finality_v2.py --quick</code> is now a true smoke run, 149 s at nice 19 on this loaded Mac and under 40 s on the runner, was 745 s; <code>sim/difficulty/sim.py --quick</code> is new, 36 s here), the site build, an internal link check of <code>site/*.html</code> (151 links, 0 broken) and a gh-free identity grep of the public export list after the generic scrub (<code>tools/ci/identity-check.sh</code>, <code>tools/ci/forbidden-strings.txt</code>: 157 files, 0 hits). First run green: https://github.com/igneum-network/igneum/actions/runs/37193811336, 54 s from trigger to completion. The node fork is gitignored and too big for the free runners today; the workflow says so.</p>
<p>Not done: <code>igneum-harness-sim</code>'s per-block cost on the devnet-v4 line (about 70 ms here against 3 ms on the ordering-layer branch, the execution layer's follower) is what still bounds the harness, not the clocks; the fast-time presence window floors at one checkpoint; the GPU workers were not run on fast time (the CPU miner proved the swap).</p></article>
</div>
<footer>© 2026 Igneum. Generated from the repository at build time. Nothing on this page is an offer to sell anything.</footer>
</div>
</body>
</html>