diff --git a/site/.gitignore b/site/.gitignore index 245259b69..46802b5eb 100644 --- a/site/.gitignore +++ b/site/.gitignore @@ -1,2 +1,3 @@ .vercel .env* +.bench-scrubbed.md diff --git a/site/bench.html b/site/bench.html index d14d85c20..6c1e9a061 100644 --- a/site/bench.html +++ b/site/bench.html @@ -69,25 +69,25 @@ footer{border-top:1px solid var(--line);padding-block:32px 48px;font-size:13px;c

Engineering log

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.

- +

Igneum bench log

Append-only. Every number here was measured on the machine named, on the date given.

2026-10-03 proto-metal / igneum-bench, first run

Machine: Apple M5 Max, 40 GPU cores, 64 GB unified memory, macOS Darwin 25.6.0, Swift 5.8.1, Metal 4. Build: swiftc -O -o igneum-bench main.swift -framework Metal. Source: proto-metal/main.swift. 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.

SeedLoads/hashCompile msMhash/sGB/s usefulCPU verify ms/warpVerify
igneum-genesis10449.6 (cold)45.218.80.015PASS
igneum-genesis/epoch110420.348.420.10.019PASS
igneum-second-seed10446.6 (cold)35.514.80.016PASS
igneum-second-seed/epoch114418.735.420.40.017PASS
igneum-hourly12852.0 (cold)36.618.70.021PASS
igneum-hourly/epoch112821.637.519.20.017PASS
igneum-hourly/epoch212023.736.617.60.016PASS

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 proto-metal/README.md.

-

2026-10-03 proto-cuda / program pack export (Mac side only; RTX 5090 run pending)

+

2026-10-03 proto-cuda / program pack export (Apple M5 Max side only; RTX 5090 run pending)

Machine: the same Apple M5 Max. No CUDA toolchain exists on it, so nothing below is an NVIDIA measurement. Added --export-pack <dir> to proto-metal/main.swift. It writes, per seed, the CUDA kernel (kernel.cu), C headers (program.h, vectors.h), JSON twins, and the Metal source, into proto-cuda/packs/<seed>/. 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.

PackLoads/hashOp mixCPU interpreter vs Metal GPU (3 warps)CUDA text in CPU emulation (clang, 32 threads/warp)
igneum-genesis104load=13 xor=13 sub=7 shfl=6 add=5 mulhi=5 mad=4 rotr=4 mul=3 rotl=3 or=1PASS 3/3PASS: dataset self-test, 3/3 warps standalone, warps 0 and 4096 in batch at 1 and 2 warps/block
igneum-hourly128load=16 add=8 xor=7 mad=5 mul=5 mulhi=5 shfl=5 rotl=4 or=3 rotr=3 sub=3PASS 3/3PASS: dataset self-test, 3/3 warps standalone, warps 0 and 4096 in batch at 1 warp/block
-

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: proto-cuda/host.cu, build.sh, build.bat, README.md, CHECKLIST.md, emu/. Pending: build and run on the project's RTX 5090 (CUDA 12.8 or newer, -arch=sm_120). No NVIDIA hash rate exists yet. Emulation rates are not recorded because they measure the Mac's CPU, not a GPU.

+

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: proto-cuda/host.cu, build.sh, build.bat, README.md, CHECKLIST.md, emu/. Pending: build and run on the project's RTX 5090 (CUDA 12.8 or newer, -arch=sm_120). No NVIDIA hash rate exists yet. Emulation rates are not recorded because they measure the Apple M5 Max's CPU, not a GPU.

2026-10-03 sim/finality_sim.py, sustained-mining finality vote weight (model, not hardware)

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.

2026-10-03 proto-metal hardening tests (correctness and soundness of the lottery hash, Metal only)

Machine: the same Apple M5 Max. Added --fuzz, --edge, --stats, --determinism, --memcheck, --inline-dataset to proto-metal/main.swift. Full tables and commands in proto-metal/TESTS.md. 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 dataset[ in the MSL is dataset[rN & MASK] (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). --export-pack igneum-genesis re-run is byte-identical to the existing pack. SHORTCUT MEASURED: --inline-dataset 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.

-

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 (an RTX 5090 on Windows, CUDA 12.8 runtime, driver 13.4, Visual Studio 2026 with the 14.30 toolset selected via vcvarsall -vcvars_ver=14.30)

Pack igneum-genesis, dataset 1024 MiB, 5 batches x 2^24 hashes, 1 warp per block.

CardMhash/s at 1 GiBGB/s usefulrandom loads/sdataset fillvectors
NVIDIA RTX 5090 (170 SMs, 32 GB)228.194.923.7 G0.66 ms, 1638 GB/s96/96 PASS, standalone and in batch
Apple M5 Max (40 GPU cores, same program, same day)45.218.84.6 G2.34 ms, 427 GB/s96/96 PASS
-

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.

+

Result: the same hourly program, generated on the Apple M5 Max, 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.

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 "C:\Program Files\Microsoft Visual Studio\18\Community\VC\Auxiliary\Build\vcvarsall.bat" x64 -vcvars_ver=14.30, then build normally.

RTX 5090, dataset sweep and second program (same session)

dataset MiBMhash/sGB/s usefulrandom loads/s (G)
41339.8557139.3
641352.7563140.7
256269.811228.1
512241.810125.2
1024228.79523.8
@@ -97,10 +97,10 @@ footer{border-top:1px solid var(--line);padding-block:32px 48px;font-size:13px;c

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% > 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.

2026-10-03 proto-metal memory-hard dataset (cache + 8 dependent reads), Metal only; CUDA pack emulated

Machine: the same Apple M5 Max (one performance core for the CPU figures). Construction, every table and the commands are in proto-metal/MEMHARD.md. Default dataset is now memory-hard; --closed-form 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] & (2^22-1), xor) plus a final mixer; dataset[w] = item(w >> 4)[w & 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.

-

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 Apple M5 Max (consensus-engineer, pre-fork proof)

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 brew install protobuf (protoc was missing); Apple clang 14.0.3 already present. Nothing else was needed. Source: vendor/rusty-kaspa at commit 01b532e8b553523216471682649693af92f0fd16 (v2.1.0, 2026-09-22). cargo build --release --bin kaspad: 2 min 36 s cold, binary 35,405,104 bytes (34 MB), 131 compiler warnings, zero errors. Devnet: three kaspad --devnet --nodnsseed --disable-upnp --enable-unsynced-mining --yes --loglevel=info nodes, separate --appdir, P2P 16611/16621/16631, gRPC 16610/16620/16630, nodes 2 and 3 --connect 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 kaspa-pow::State (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 docs/fork-map.md (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 testing/integration/src/common/utils.rs:271 if needed.

3 October 2026, RTX 5090, memory-hard dataset (pack igneum-genesis-mh)

-
CheckResult
256 MiB cache, GPU vs host, all 67,108,864 wordsPASS, FNV-1a 48c4f5bf24166b2e matches the Mac
Cache fill0.67 ms GPU, 223 ms one host thread
Dataset build from the cache, 1 GiB13.4 ms, 1,253 M items/s
Vectors, 3 warps, standalone and in batch96/96 PASS
Hash rate at 1 GiB228.95 Mhash/s, 95.2 GB/s useful, 23.8 G random loads/s
+
CheckResult
256 MiB cache, GPU vs host, all 67,108,864 wordsPASS, FNV-1a 48c4f5bf24166b2e matches the Apple M5 Max
Cache fill0.67 ms GPU, 223 ms one host thread
Dataset build from the cache, 1 GiB13.4 ms, 1,253 M items/s
Vectors, 3 warps, standalone and in batch96/96 PASS
Hash rate at 1 GiB228.95 Mhash/s, 95.2 GB/s useful, 23.8 G random loads/s

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.

2026-10-03 proto-vdf, Wesolowski VDF between the certified checkpoint and the program seed (epoch 10 min, era 1 h)

Machine: Apple M5 Max (18 logical cores), rustc 1.69.0, GMP 6.3.0 via rug 1.19. Source proto-vdf/, details in proto-vdf/README.md. 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) -> (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.

@@ -119,22 +119,22 @@ footer{border-top:1px solid var(--line);padding-block:32px 48px;font-size:13px;c

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)

Machine: Apple M5 Max (18 logical cores), rustc 1.99.0, fork vendor/igneum-node at commit "Build: forward the igneum-pow feature" on top of rusty-kaspa v2.1.0 01b532e8. Release build of kaspad and igneum-miner; the kHeavyHash stub engine (default feature set); cargo check -p kaspad --features igneum-pow also builds. Network: three kaspad --devnet --nodnsseed --disable-upnp --enable-unsynced-mining nodes, P2P 26611/26621/26631, gRPC 26610/26620/26630, nodes 2 and 3 --connect to node 1 and node 3 also to node 2; network name igneum-devnet, k 18, merge depth 3,600 blocks, DAA 661 samples x 4 blocks, genesis bits 0x1e020000 (2^23 expected hashes per block). Miners: three igneum-miner mine 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: igneum-miner inspect 40 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 igneum-proving-pool-v0 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 --features igneum-pow (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).

3 October 2026, first devnet blocks on the real lottery hash: CPU, then Metal GPU, three worker implementations (consensus-engineer)

-

Machine: Apple M5 Max (18 logical cores, 40 GPU cores), rustc 1.99.0, Swift 5.8.1, fork vendor/igneum-node at "PoW: header-bound lottery engine, real-hash miner modes, genesis bits 0x1e400000" plus the overnight genesis commit; igneum-pow at "igneum-pow: header binding ...". Release builds with --features igneum-pow. Binding (spec 01 section 1.6, O-1.9, igneum-pow/src/bind.rs): 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 <= target is exactly lane <= target >> 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 igneum-pow/README.md; 29 crate tests pass; the 288 pack vectors and the pack files are unchanged, program_bound.metal, kernel_bound.cu and kernel_bound.cl 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 igneum-miner mine --engine igneum-pow, 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 <hash> by igneum-lottery-v1-bound" 833 times, 0 rejections during the run. igneum-miner bad-nonce: Reject(BlockInvalid) and a "PoW rejected ... by igneum-lottery-v1-bound" line. inspect 30: 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 (proto-metal/igneum-bench --serve, runtime-compiled igneum_hash_bound, init words in buffer 3, 2^22 nonces per dispatch, CPU-side scan of the 64-bit outputs) driven by igneum-miner --worker 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 -> 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 -> 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 (igneum-bench 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, caffeinate -dims, logs /tmp/igneum-devnet/node1.log) with the Metal worker (/tmp/igneum-devnet/metal-worker.log): 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 igneum-pow hash-bound 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 (proto-opencl/host.c --serve, Apple OpenCL 1.2 on the M5 Max, local-memory exchange, --vendor device filter added), CUDA (proto-cuda/host.cu --serve with the pack's kernel_bound.cu, 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). igneum-miner.exe 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 ~/Desktop/igneum-mine-test.zip 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.

+

Machine: Apple M5 Max (18 logical cores, 40 GPU cores), rustc 1.99.0, Swift 5.8.1, fork vendor/igneum-node at "PoW: header-bound lottery engine, real-hash miner modes, genesis bits 0x1e400000" plus the overnight genesis commit; igneum-pow at "igneum-pow: header binding ...". Release builds with --features igneum-pow. Binding (spec 01 section 1.6, O-1.9, igneum-pow/src/bind.rs): 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 <= target is exactly lane <= target >> 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 igneum-pow/README.md; 29 crate tests pass; the 288 pack vectors and the pack files are unchanged, program_bound.metal, kernel_bound.cu and kernel_bound.cl 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 igneum-miner mine --engine igneum-pow, 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 <hash> by igneum-lottery-v1-bound" 833 times, 0 rejections during the run. igneum-miner bad-nonce: Reject(BlockInvalid) and a "PoW rejected ... by igneum-lottery-v1-bound" line. inspect 30: 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 (proto-metal/igneum-bench --serve, runtime-compiled igneum_hash_bound, init words in buffer 3, 2^22 nonces per dispatch, CPU-side scan of the 64-bit outputs) driven by igneum-miner --worker 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 -> 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 -> 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 (igneum-bench 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 19:07 UTC): 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, caffeinate -dims, logs /tmp/igneum-devnet/node1.log) with the Metal worker (/tmp/igneum-devnet/metal-worker.log): 31 blocks in 273 s = 0.11 blocks/s at 31.1 MH/s, as expected for 2^28 until the RTX 5090 machine joins. Worker implementations, all bit-exact with igneum-pow hash-bound 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 (proto-opencl/host.c --serve, Apple OpenCL 1.2 on the M5 Max, local-memory exchange, --vendor device filter added), CUDA (proto-cuda/host.cu --serve with the pack's kernel_bound.cu, 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). igneum-miner.exe 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 igneum-mine-test.zip 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.

3 October 2026, Windows node package: igneumd cross-compiled for x86_64-pc-windows-gnu, two-peer sync test (consensus-engineer)

-

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 vendor/igneum-node-win, branch windows-node at 352495f6 (the rename commit d62708a8 plus one miner change: watch prints blue= and synced= after sink=). Cross-compile: cargo build --release -j 6 -p kaspad -p igneum-miner --features igneum-pow --target x86_64-pc-windows-gnu at nice -n 19, 8 min 25 s from a clean target directory (recipe in proto-cuda/windows-node/cross-build.sh). librocksdb-sys (bindgen plus the bundled rocksdb and snappy C++) built with LIBCLANG_PATH=/opt/homebrew/opt/llvm/lib, BINDGEN_EXTRA_CLANG_ARGS_x86_64_pc_windows_gnu="--target=x86_64-w64-mingw32 --sysroot=<mingw sysroot> -I<mingw include>" and CXX_x86_64_pc_windows_gnu=x86_64-w64-mingw32-g++; zstd, lz4, bzip2, zlib, secp256k1 and mimalloc built with the mingw gcc. igneumd.exe 43,172,352 bytes, igneum-miner.exe 9,391,104 bytes. The exe imports libstdc++-6.dll: -static-libstdc++ is ignored by the gcc driver rustc links with, and -C link-arg=-static (second full build, 8 min) does not cover a library rustc passes as -Bdynamic; the package ships libstdc++-6.dll, libgcc_s_seh-1.dll and libwinpthread-1.dll next to the exe (fix for next time: empty CXXSTDLIB_x86_64_pc_windows_gnu plus -Wl,-Bstatic -lstdc++). 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 (--addpeer=192.168.68.64:26611 --listen=0.0.0.0:27001 --nodnsseed --disable-upnp --nologfiles) 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, synced=true); every header checked by igneum-lottery-v1-bound. Live node peers 1 -> 2 -> 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 --addpeer plus --listen accepts inbound connections (--connect would set the inbound limit to 0, kaspad/src/daemon.rs). SIGINT stopped both cleanly. Package: ~/Desktop/igneum-node-windows.zip from proto-cuda/windows-node/make-package.sh (exe, miner exe, src.zip snapshot of the worktree HEAD plus igneum-pow/, scripts, README). Build on the PC (BUILD-NODE.bat) estimated at 15 to 25 minutes on the 9800X3D, approximate, untested.

+

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 vendor/igneum-node-win, branch windows-node at 352495f6 (the rename commit d62708a8 plus one miner change: watch prints blue= and synced= after sink=). Cross-compile: cargo build --release -j 6 -p kaspad -p igneum-miner --features igneum-pow --target x86_64-pc-windows-gnu at nice -n 19, 8 min 25 s from a clean target directory (recipe in proto-cuda/windows-node/cross-build.sh). librocksdb-sys (bindgen plus the bundled rocksdb and snappy C++) built with LIBCLANG_PATH=/opt/homebrew/opt/llvm/lib, BINDGEN_EXTRA_CLANG_ARGS_x86_64_pc_windows_gnu="--target=x86_64-w64-mingw32 --sysroot=<mingw sysroot> -I<mingw include>" and CXX_x86_64_pc_windows_gnu=x86_64-w64-mingw32-g++; zstd, lz4, bzip2, zlib, secp256k1 and mimalloc built with the mingw gcc. igneumd.exe 43,172,352 bytes, igneum-miner.exe 9,391,104 bytes. The exe imports libstdc++-6.dll: -static-libstdc++ is ignored by the gcc driver rustc links with, and -C link-arg=-static (second full build, 8 min) does not cover a library rustc passes as -Bdynamic; the package ships libstdc++-6.dll, libgcc_s_seh-1.dll and libwinpthread-1.dll next to the exe (fix for next time: empty CXXSTDLIB_x86_64_pc_windows_gnu plus -Wl,-Bstatic -lstdc++). Not run on Windows yet. Sync test (native build of the same worktree, 20:44 to 20:46 UTC, live node on 26610/26611 untouched): node A on 27000/27001 with START-NODE.bat's flags (--addpeer=<lan-ip>:26611 --listen=0.0.0.0:27001 --nodnsseed --disable-upnp --nologfiles) 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, synced=true); every header checked by igneum-lottery-v1-bound. Live node peers 1 -> 2 -> 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 --addpeer plus --listen accepts inbound connections (--connect would set the inbound limit to 0, kaspad/src/daemon.rs). SIGINT stopped both cleanly. Package: igneum-node-windows.zip from proto-cuda/windows-node/make-package.sh (exe, miner exe, src.zip snapshot of the worktree HEAD plus igneum-pow/, scripts, README). Build on the RTX 5090 machine (BUILD-NODE.bat) estimated at 15 to 25 minutes on the 9800X3D, approximate, untested.

3 October 2026, R3.26 / M15: PoW checked after the cheap checks, cache-build cap, attack before and after (consensus-engineer)

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 vendor/igneum-node-r3, branch r3-fixes at 5166ee26 on top of the rename commit d62708a8. Release builds; the real engine needs --features igneum-pow. Fix: validate_header 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 KEEP = 4 (epoch seed, day) 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 PowCacheQueueFull, 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 IGNEUM_POW_STRIKES (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; proto-metal 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 measure_m15_attack_before_and_after, release, --features igneum-pow, validate_and_insert_block, the path submit_block 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.

-

Tests: kaspa-pow --features igneum-pow 8 pass (engine smoke, one_build_per_seed_pair_under_contention, live_days_survive_off_day_builds, build_queue_is_bounded, index and live-day helpers, shared-engine, stub); kaspa-consensus header_processor cheap_checks_run_before_the_pow_engine pass; kaspa-p2p-flows pow_guard 2 pass; the full kaspa-consensus release suite otherwise unchanged. M16 Metal note (R3.5, cheap reconfirmation only): the Mac --inline-dataset 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 proto-metal/MEMHARD.md (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, cacheLog2Words = 24 in proto-metal/main.swift) is the RTX 5090 run reserved for Josh'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 skip_proof_of_work); 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.

+

Tests: kaspa-pow --features igneum-pow 8 pass (engine smoke, one_build_per_seed_pair_under_contention, live_days_survive_off_day_builds, build_queue_is_bounded, index and live-day helpers, shared-engine, stub); kaspa-consensus header_processor cheap_checks_run_before_the_pow_engine pass; kaspa-p2p-flows pow_guard 2 pass; the full kaspa-consensus release suite otherwise unchanged. M16 Metal note (R3.5, cheap reconfirmation only): the Apple M5 Max --inline-dataset 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 proto-metal/MEMHARD.md (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, cacheLog2Words = 24 in proto-metal/main.swift) is the RTX 5090 run reserved for the maintainers' PC, as R3.5 states; it is not done here and the Apple M5 Max 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 skip_proof_of_work); 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.

3 October 2026, difficulty controller: devnet record, simulator, Igneum dual-lane rule, 3-node CPU test network (consensus-engineer)

Machine: the same Apple M5 Max, shared with two other build agents (load average 60 to 98 during the Rust builds). Fork worktree vendor/igneum-node-diff, branch difficulty from d62708a8. Everything in docs/analysis/difficulty-2026-10-03.md; raw outputs in sim/difficulty/results.md. Record: 3,682 headers of the overnight devnet pulled read-only through the observer node's wRPC JSON (ws://127.0.0.1:28640, getBlocks from genesis) into sim/difficulty/devnet-2026-10-03.csv. 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 sim/difficulty/sim.py (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 estimateNetworkHashesPerSecond 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: DifficultyRule { KaspaSampled, IgneumDual } as a network parameter (IgneumDual on all four networks, "difficulty_rule": "kaspa-sampled" in the override file selects Kaspa's), OverrideParams.genesis_bits (genesis hash recomputed) for test networks, SampledDifficultyManager::igneum_difficulty_bits over a selected-chain walk plus the in-epoch samples of the existing window, pure integer core igneum_target (Uint320). cargo test -p kaspa-consensus --lib difficulty: 9 pass (hold, steady state, 3% harden, 10% ease, trigger, epoch shrinkage, max target, Kaspa's two level-work tests); cargo test -p kaspa-consensus-core --lib params: 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 difficulty_rule and genesis_bits 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.

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)

-

Machine: Apple M5 Max, rustc 1.99.0, fork vendor/igneum-node at commits "Finality: BLS12-381 vote keys ..." through "Miner: BLS identity ..." (four commits on top of the rename). Builds in target-finality (CARGO_TARGET_DIR=target-finality cargo build --release --features igneum-pow): igneumd 36 MB (66 MB with line tables for the stall hunt), igneum-miner 7.6 MB; Windows cross-builds with Homebrew mingw-w64 in target-finality/x86_64-pc-windows-gnu/release/: igneum-miner.exe 9.7 MB and igneumd.exe 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: docs/spec/03-finality.md section 3.10 and docs/fork-divergence.md "Finality v2". Crypto: blst min-pubkey BLS12-381, votes over "igneum-vote-v1/" || chain_id || 0 || index || hash, 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 (--devnet --devnet-suffix=7, network id igneum-devnet-7, genesis bits 0x1e400000 through --override-params-file, 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 --addpeer node 1, node 3 also node 2), peers at protocol version 12; four 4-thread CPU igneum-miner mine --engine igneum-pow 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 (Finality: vote key revealed), 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 <= 8); aggregators 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 --equivocate 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 accepted=false equivocation=true, every node logged EQUIVOCATION by key 56da130c... at index 43 ... weight stripped until daa 8512, and from checkpoint 44 the key is voter false, stripped_until 8931 (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 (evidence 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. finality_active 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: tools/observer/observer.mjs with LIVE_TABLE_PREFIX=fintest_ against 28650 wrote fintest_live_checkpoints (49 rows, 42 locked at the first sample) and checkpoint_locked events ("checkpoint 42 locked (76.2% of weight, 96.5% of active, 3 votes of 4 voters) at block 0d1405d0"), from both the FinalityLock subscription and the 2-s poll; live_state.finality carried the weights snapshot. site/api/live.mjs adds the live_checkpoints query and locked/final flags per block; site/live.html 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 (igneumd v2 on gRPC 26690, p2p 26691, JSON 28690, --connect=127.0.0.1:26611, 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, finality_active 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 (compute_weights taking the state lock its callers hold) was found and fixed before that stall, and Router::enqueue 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).

+

Machine: Apple M5 Max, rustc 1.99.0, fork vendor/igneum-node at commits "Finality: BLS12-381 vote keys ..." through "Miner: BLS identity ..." (four commits on top of the rename). Builds in target-finality (CARGO_TARGET_DIR=target-finality cargo build --release --features igneum-pow): igneumd 36 MB (66 MB with line tables for the stall hunt), igneum-miner 7.6 MB; Windows cross-builds with Homebrew mingw-w64 in target-finality/x86_64-pc-windows-gnu/release/: igneum-miner.exe 9.7 MB and igneumd.exe 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: docs/spec/03-finality.md section 3.10 and docs/fork-divergence.md "Finality v2". Crypto: blst min-pubkey BLS12-381, votes over "igneum-vote-v1/" || chain_id || 0 || index || hash, 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 (--devnet --devnet-suffix=7, network id igneum-devnet-7, genesis bits 0x1e400000 through --override-params-file, 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 --addpeer node 1, node 3 also node 2), peers at protocol version 12; four 4-thread CPU igneum-miner mine --engine igneum-pow identities m1, m2 (node 1), m3 (node 2), m4 (node 3); 20:59:48 to 21:53 UTC. 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 21:35 UTC; difficulty 149,037 at DAA 2,193. Key reveal: all four keys revealed from the first block of each identity (Finality: vote key revealed), 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 <= 8); aggregators 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 --equivocate 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 accepted=false equivocation=true, every node logged EQUIVOCATION by key 56da130c... at index 43 ... weight stripped until daa 8512, and from checkpoint 44 the key is voter false, stripped_until 8931 (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 (evidence 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. finality_active 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: tools/observer/observer.mjs with LIVE_TABLE_PREFIX=fintest_ against 28650 wrote fintest_live_checkpoints (49 rows, 42 locked at the first sample) and checkpoint_locked events ("checkpoint 42 locked (76.2% of weight, 96.5% of active, 3 votes of 4 voters) at block 0d1405d0"), from both the FinalityLock subscription and the 2-s poll; live_state.finality carried the weights snapshot. site/api/live.mjs adds the live_checkpoints query and locked/final flags per block; site/live.html draws the locked-checkpoint ring, the dashed "final" line at the newest lock and the locked counter. Live devnet follower (igneumd v2 on gRPC 26690, p2p 26691, JSON 28690, --connect=127.0.0.1:26611, 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, finality_active 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 20:48:43 UTC 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 (compute_weights taking the state lock its callers hold) was found and fixed before that stall, and Router::enqueue 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).

3 October 2026, execution layer devnet v3: revm over the selected chain, 3-node simnet, viem smoke test (execution-engineer)

Machine: the same Apple M5 Max (18 cores), shared with two other agents' builds (load 15 to 55). Branch execution-layer in vendor/igneum-node-exec, worktree of vendor/igneum-node from d62708a8; 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 CARGO_TARGET_DIR=target cargo build --release -p kaspad -p igneum-miner --features igneum-pow: first full build with the new crates about 20 min under nice -n 10 -j 10 on the loaded machine; incremental igneumd rebuilds 35 s to 4 min. Test network: 3 igneumd --simnet 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 igneum-miner --engine stub --hold-ms 2500 (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 (node tools/evm-smoke/smoke.mjs, 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 increment(5): 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 deployContract: gasUsed 185,948, pgasUsed 1,438, registry creatorOf = deployer and payeeOf = constructor argument (executor CREATE rule plus register). eth_estimateGas reports the revert of increment(0); eth_call hashLoop(50) returns; eth_estimateGas hashLoop(200) = 98,900 with the fold; eth_getLogs 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 NonceTooLow { expected: 18, got: 17 }; 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 (igneum_getTransactionStatus: includedIn [], executed false). Execution time per chain block (node 1, igneum.executionMicros, 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 (igneum-exec-diff seq.json, 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. cargo test -p igneum-evm-types: 3 passed. Not done: on-disk state and incremental trie (state rebuilt from genesis at start), header fields utxo_commitment and accepted_id_merkle_root kept (proofs_root is an RPC placeholder), body miner field and proofs 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.

2026-10-03, consensus attack harness (consensus-engineer), catalogue run on the ordering-layer node

-

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.

-
ScenarioCriterion (spec)MeasuredPass
5 malformed and boundary inputs on every p2p message and RPC method the fork touchesrejected without a crash or a cache build (spec 02 2.4; fork-divergence header and RPC rows; ledger M15)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).pass
2 timestamp boundaries (live)rejected at ts <= past median, accepted at pmt+1; accepted below now+132 s, rejected above (spec 02 section 2.3)past: pmt-1=rejected, pmt=rejected, pmt+1=accepted, pmt+2=accepted; future flip between +132.00 s and +132.01 spass
2 timestamp stretch drift (sim)controller response to a 33% miner stretching timestamps inside the rules is measured (blocks per second drift against an honest run)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 spass
1 withhold a=0.1 release every 5attacker blue share <= 0.1 + 2 sigma (0.014) over 1927 bluesblue 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 8pass
1 withhold a=0.1 release every 20attacker blue share <= 0.1 + 2 sigma (0.014) over 1858 bluesblue share 1.2% (23 blue, 157 red of 186 made); honest reorgs depth:count 1:1 2:1 5:1, max 5pass
1 withhold a=0.25 release every 5attacker blue share <= 0.25 + 2 sigma (0.020) over 1942 bluesblue 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 10pass
1 withhold a=0.25 release every 20attacker blue share <= 0.25 + 2 sigma (0.021) over 1693 bluesblue 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 30pass
1 withhold a=0.33 release every 5attacker blue share <= 0.33 + 2 sigma (0.021) over 2039 bluesblue 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 13pass
1 withhold a=0.33 release every 20attacker blue share <= 0.33 + 2 sigma (0.024) over 1561 bluesblue 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 33pass
1 withhold a=0.45 release every 5attacker blue share <= 0.45 + 2 sigma (0.022) over 2002 bluesblue 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 15pass
1 withhold a=0.45 release every 20attacker blue share <= 0.45 + 2 sigma (0.024) over 1657 bluesblue 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 36FAIL
3 partition 120 sone chain after the merge-depth rule; reorg depth and time to heal recordedone 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 nonepass
3 partition 600 sone chain after the merge-depth rule; reorg depth and time to heal recordedone 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 nonepass
3 partition 1800 sone chain after the merge-depth rule; reorg depth and time to heal recordedone 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 nonepass
3 partition 3700 s (beyond merge depth)one chain after the merge-depth rule; reorg depth and time to heal recordedone 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:5pass
6 resource exhaustion (50x template, submit and mempool floods from one peer)honest template p95 < 200 ms and both nodes under baseline RSS + 512 MB, alive, one sinkhonest 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 truepass
4 eclipse 600 svictim rejoins the honest chain on reconnection within the merge-depth bound; reorg depth recordedvictim 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 chainpass
4 eclipse 1800 svictim rejoins the honest chain on reconnection within the merge-depth bound; reorg depth recordedvictim 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 chainpass
7 fast-miner flood, controller trajectory (sim, Kaspa sampled DAA on HEAD)trajectory recorded for the difficulty branch (bits, blocks per second, settle times)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 spass
7 fast-miner flood, live (50 blocks/s from one peer)node stays responsive: honest template p95 < 200 ms, both nodes alive, same sinkflood 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->333 MB, b 305->332 MB; alive true; same sink truepass
1b withhold vs finality weight (finality branch)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)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.stub
3b partition vs finality lock (finality branch)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 exercisedstub: 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.stub
4b eclipse vs finality presence window (finality branch)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 lockstub: 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.stub
2b difficulty controller under timestamp stretch (difficulty branch)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)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.stub
7b fast-miner flood on the dual-lane controller (difficulty branch)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)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.stub
+

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 RTX 5090 node were not touched. Harness: tools/harness/, node fork worktree vendor/igneum-node-harness.

+
ScenarioCriterion (spec)MeasuredPass
5 malformed and boundary inputs on every p2p message and RPC method the fork touchesrejected without a crash or a cache build (spec 02 2.4; fork-divergence header and RPC rows; ledger M15)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).pass
2 timestamp boundaries (live)rejected at ts <= past median, accepted at pmt+1; accepted below now+132 s, rejected above (spec 02 section 2.3)past: pmt-1=rejected, pmt=rejected, pmt+1=accepted, pmt+2=accepted; future flip between +132.00 s and +132.01 spass
2 timestamp stretch drift (sim)controller response to a 33% miner stretching timestamps inside the rules is measured (blocks per second drift against an honest run)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 spass
1 withhold a=0.1 release every 5attacker blue share <= 0.1 + 2 sigma (0.014) over 1927 bluesblue 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 8pass
1 withhold a=0.1 release every 20attacker blue share <= 0.1 + 2 sigma (0.014) over 1858 bluesblue share 1.2% (23 blue, 157 red of 186 made); honest reorgs depth:count 1:1 2:1 5:1, max 5pass
1 withhold a=0.25 release every 5attacker blue share <= 0.25 + 2 sigma (0.020) over 1942 bluesblue 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 10pass
1 withhold a=0.25 release every 20attacker blue share <= 0.25 + 2 sigma (0.021) over 1693 bluesblue 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 30pass
1 withhold a=0.33 release every 5attacker blue share <= 0.33 + 2 sigma (0.021) over 2039 bluesblue 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 13pass
1 withhold a=0.33 release every 20attacker blue share <= 0.33 + 2 sigma (0.024) over 1561 bluesblue 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 33pass
1 withhold a=0.45 release every 5attacker blue share <= 0.45 + 2 sigma (0.022) over 2002 bluesblue 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 15pass
1 withhold a=0.45 release every 20attacker blue share <= 0.45 + 2 sigma (0.024) over 1657 bluesblue 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 36FAIL
3 partition 120 sone chain after the merge-depth rule; reorg depth and time to heal recordedone 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 nonepass
3 partition 600 sone chain after the merge-depth rule; reorg depth and time to heal recordedone 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 nonepass
3 partition 1800 sone chain after the merge-depth rule; reorg depth and time to heal recordedone 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 nonepass
3 partition 3700 s (beyond merge depth)one chain after the merge-depth rule; reorg depth and time to heal recordedone 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:5pass
6 resource exhaustion (50x template, submit and mempool floods from one peer)honest template p95 < 200 ms and both nodes under baseline RSS + 512 MB, alive, one sinkhonest 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 truepass
4 eclipse 600 svictim rejoins the honest chain on reconnection within the merge-depth bound; reorg depth recordedvictim 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 chainpass
4 eclipse 1800 svictim rejoins the honest chain on reconnection within the merge-depth bound; reorg depth recordedvictim 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 chainpass
7 fast-miner flood, controller trajectory (sim, Kaspa sampled DAA on HEAD)trajectory recorded for the difficulty branch (bits, blocks per second, settle times)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 spass
7 fast-miner flood, live (50 blocks/s from one peer)node stays responsive: honest template p95 < 200 ms, both nodes alive, same sinkflood 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->333 MB, b 305->332 MB; alive true; same sink truepass
1b withhold vs finality weight (finality branch)spec 03: a withholder gains no vote weight beyond its hash share; under the 56.7% total floor, 0 conflicting locks (the design document, ledger F18)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.stub
3b partition vs finality lock (finality branch)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 exercisedstub: 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.stub
4b eclipse vs finality presence window (finality branch)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 lockstub: 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.stub
2b difficulty controller under timestamp stretch (difficulty branch)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)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.stub
7b fast-miner flood on the dual-lane controller (difficulty branch)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)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.stub

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).

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).

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)

@@ -165,7 +165,7 @@ footer{border-top:1px solid var(--line);padding-block:32px 48px;font-size:13px;c

Differential: igneum-exec-diff over the test network's export, segments 0 to 176, 17 executed transactions compared (one pgasAborted), 6 skipped copies confirmed, 11 accounts compared, 0 mismatches.

Not changed: the pgas table magnitudes (prototype), B_p = 30 M (prototype). Open: the admission estimate runs under the RPC's state read lock, so a flood of heavy eth_sendRawTransaction calls delays the follower by up to B_p of simulation each (same shape as the eth_call 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.

3 October 2026, per-identity hash rate "decay" on the RTX 5090: diagnosis and Metal reproduction (miner-community-lead)

-

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 nice -n 19. Binaries: HEAD proto-metal/main.swift built with swiftc -O into the scratchpad (465,529 bytes, the same size as proto-metal/igneum-bench), vendor/igneum-node-diff/target/release/igneumd and igneum-miner (22:38 and 22:17 BST, the difficulty worktree pair; the miner's Seeder 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 /tmp/igneum-decay-test, all stopped afterwards. Full write-up: docs/analysis/hashrate-decay-2026-10-03.md; proposed fix: docs/analysis/hashrate-decay-2026-10-03.patch (not applied; git apply --check passes against vendor/igneum-node). PC data (node tools/logs.mjs <run_id> --all, 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 proto-cuda/host.cu, proto-opencl/host.c or proto-metal/main.swift serve loops (tables in the analysis); the miner's only per-job growth is time in Seeder::seeds_for (memo keyed by (epoch, sink), one getBlock RPC per block from the sink to the epoch start on every miss, 1,274 to 3,313 calls on the PC). cudaDeviceSynchronize at the default schedule spins one thread per worker (Josh's 6.2% per process); the hot-swap working tree sets cudaDeviceScheduleBlockingSync and swaps clFinish for clWaitForEvents. 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: skip_proof_of_work 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 difficulty worktree's node panics at consensus/src/processes/difficulty.rs:431 ("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); skip_proof_of_work 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 seeds_for (the client is concrete).

+

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 nice -n 19. Binaries: HEAD proto-metal/main.swift built with swiftc -O into the scratchpad (465,529 bytes, the same size as proto-metal/igneum-bench), vendor/igneum-node-diff/target/release/igneumd and igneum-miner (22:38 and 21:17 UTC, the difficulty worktree pair; the miner's Seeder 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 /tmp/igneum-decay-test, all stopped afterwards. Full write-up: docs/analysis/hashrate-decay-2026-10-03.md; proposed fix: docs/analysis/hashrate-decay-2026-10-03.patch (not applied; git apply --check passes against vendor/igneum-node). PC data (node tools/logs.mjs <run_id> --all, 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 proto-cuda/host.cu, proto-opencl/host.c or proto-metal/main.swift serve loops (tables in the analysis); the miner's only per-job growth is time in Seeder::seeds_for (memo keyed by (epoch, sink), one getBlock RPC per block from the sink to the epoch start on every miss, 1,274 to 3,313 calls on the RTX 5090 machine). cudaDeviceSynchronize at the default schedule spins one thread per worker (the maintainers' 6.2% per process); the hot-swap working tree sets cudaDeviceScheduleBlockingSync and swaps clFinish for clWaitForEvents. 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: skip_proof_of_work 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 difficulty worktree's node panics at consensus/src/processes/difficulty.rs:431 ("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); skip_proof_of_work nodes still log "PoW rejected ... by igneum-lottery-v1-bound" for every block they accept. Not done: the fix applied and measured on the RTX 5090 machine (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 seeds_for (the client is concrete).

2026-10-04 finality v2 attack harness: seven hostile scenarios on a six-voter private test network (consensus test engineer, cryptographer)

Machine: Apple M5 Max, rustc stable, macOS Darwin 25.6.0. Fork: worktree vendor/igneum-node-fin-attacks, branch fin-attacks on master c6d47547 to 2a00ff55 (BLS votes, certificates in coinbase extra data, p2p message 70, the finality RPCs). Build: CARGO_TARGET_DIR=target nice -n 19 cargo build --release -j 4 -p kaspad -p igneum-miner --features kaspad/igneum-pow. Tool: tools/finality-attacks (run.mjs, lib/, README with the proposed fixes). Network: igneum-devnet-800, ports 27800 and up, data /tmp/igneum-fin-attacks, skip_proof_of_work (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).

Hostile pieces are test-only flags of igneum-miner, never honest node or consensus code: vmine (Poisson submit at a chosen hash share, decoupled 0.5-s voter), --equivocate, --sybil b:bb:a:ab (one miner mints many vote keys), --drop-votes (strips the node's finality section from its coinbase so its blocks carry no votes or certificates while it still votes over RPC), --pulse burst:on:period, and fin-rpc-attack (malformed, mis-signed, replayed, oversized and non-hex votes over submitFinalityVote). Six voters throughout; three nodes for the cross-node scenarios, two nodes over a TCP proxy for the partitions.

@@ -187,16 +187,16 @@ footer{border-top:1px solid var(--line);padding-block:32px 48px;font-size:13px;c

4 October 2026, difficulty rule: timestamp attack fixed (tight bounds, sanitised clock, target floor), simulator regression, 3-node forger test (consensus-engineer)

Machine: the same Apple M5 Max, shared with other agents' simulations (load 6 to 10). Worktree vendor/igneum-node-diff, branch difficulty, commit "Difficulty: timestamp rules 10 s both ways, sanitised clock per header, target floor 2^128" on 3ea7a3e3; built with CARGO_TARGET_DIR=target nice -n 19 cargo ... -j 4 on the rustup toolchain (cargo 1.99; the Homebrew cargo 1.69 in PATH cannot read the 2024 edition). Everything in docs/analysis/difficulty-2026-10-03.md section 11 and spec 2.3; ledger M23. The change, the three parts of sim/difficulty/attacks/README.md as proposed: (A) FUTURE_TOLERANCE_MS 10 s in isolation and BACK_TOLERANCE_MS 10 s behind the selected parent in context (new RuleError::TimeTooFarBehindParent), 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 (stores::clock, prefix IgneumClock 62, written in commit_header, 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) bound_target floors both rules at 2^128. sim/difficulty/sim.py class Igneum carries the same clock and floor. Unit tests: cargo test --release -p kaspa-consensus --lib difficulty 12 pass (the diff-attacks flood test with should_panic removed: every output at or above 2^128, floor reached between blocks 2,000 and 3,000, work under 2^129; both_rules_share_the_target_floor; igneum_clock_steps_pay_a_forgery_back: 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); cargo test --release -p kaspa-consensus-core --lib igneum 9 pass (sanitised_clock_telescopes_a_forged_stamp). Simulator, seeds 7 to 9 (attacks.py --scenario ts --ts-rules tight), 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 (--scenario hop, 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 igneumd nodes from the fix plus the diff-attacks template hook on a scratch branch, ports 28500 to 28521, appdir /tmp/igneum-diff-fix, genesis bits 0x1f010000, 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 /tmp/igneum-diff-fix/ts-past-igneum/record.csv and ts-future-igneum/record.csv (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.

4 October 2026, devnet-v4 integration: nine branches merged, 3-node test network on the merged node, Windows cross-build (release engineer)

-

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 vendor/igneum-node/target-integration. Branch devnet-v4 of vendor/igneum-node, head dc749905; merge order, conflicts and the cut-over commands in docs/fork-divergence.md, "Integration 4 Oct 2026". The hot swap was not in master (no pow_epoch in 2a00ff55); it was captured from the uncommitted hotswap worktree as a4224689 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 (proto-cuda/windows-node/cross-build.sh vendor/igneum-node-v4 6). Tests: 628 passed, 0 failed, 24 ignored across the 21 touched crates (--no-fail-fast), then kaspa-p2p-flows 30 of 30 after the estimated_header_size fix (the header's voteKeyHash field was not counted since 815cd00f). 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 BlockBody. Test network, 02:02:27 to 02:18:53 BST: 3 igneumd on igneum-devnet-880 (gRPC 28800/28810/28820, p2p 28801/28811/28821, wRPC JSON 28802/28812/28822, eth RPC 28803/28813/28823; node 2 and 3 --addpeer node 1, node 3 also node 2), override file genesis_bits 0x1f010000 (2^16) and finality interval 30, depth 20, window 300 DAA, dust 5, presence 20, aggregators 8, ban 300, min_daa 300, fallback 15; IGNEUM_POW_EPOCH_BLOCKS=300, IGNEUM_POW_EPOCH_LEAD=60 (a short epoch so the hourly swap crosses boundaries inside the run; the devnet values are 3,600 and 600). Miners m1, m2, m3 (igneum-miner mine ... 3 960 --engine igneum-pow --payout-label mN --evm-address 0x7099...79C8), 0.078 MH/s each (3 threads on the loaded machine), 375 / 342 / 338 blocks found, 0 rejected.

+

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 vendor/igneum-node/target-integration. Branch devnet-v4 of vendor/igneum-node, head dc749905; merge order, conflicts and the cut-over commands in docs/fork-divergence.md, "Integration 4 Oct 2026". The hot swap was not in master (no pow_epoch in 2a00ff55); it was captured from the uncommitted hotswap worktree as a4224689 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 (proto-cuda/windows-node/cross-build.sh vendor/igneum-node-v4 6). Tests: 628 passed, 0 failed, 24 ignored across the 21 touched crates (--no-fail-fast), then kaspa-p2p-flows 30 of 30 after the estimated_header_size fix (the header's voteKeyHash field was not counted since 815cd00f). 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 BlockBody. Test network, 01:02:27 to 01:18:53 UTC: 3 igneumd on igneum-devnet-880 (gRPC 28800/28810/28820, p2p 28801/28811/28821, wRPC JSON 28802/28812/28822, eth RPC 28803/28813/28823; node 2 and 3 --addpeer node 1, node 3 also node 2), override file genesis_bits 0x1f010000 (2^16) and finality interval 30, depth 20, window 300 DAA, dust 5, presence 20, aggregators 8, ban 300, min_daa 300, fallback 15; IGNEUM_POW_EPOCH_BLOCKS=300, IGNEUM_POW_EPOCH_LEAD=60 (a short epoch so the hourly swap crosses boundaries inside the run; the devnet values are 3,600 and 600). Miners m1, m2, m3 (igneum-miner mine ... 3 960 --engine igneum-pow --payout-label mN --evm-address 0x7099...79C8), 0.078 MH/s each (3 threads on the loaded machine), 375 / 342 / 338 blocks found, 0 rejected.

MeasureValue
Blocks accepted per node (PoW accepted lines)1,055 / 1,055 / 1,055, 0 rejected, 0 invalid
Block rate95 to 1,026 blocks between the 0-s and 900-s samples: 1.03 blocks/s; 1,055 in 960 s
Sink identical on all 3 nodes31 of 31 samples; peers 2 on every node at every sample; max tips 2
Difficulty (dual-lane rule)56,268 at 20 s, 155,835 peak at 90 s, 110,533 at 900 s
Epoch boundaries (DAA 300, 600, 900)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
Caches built per node4 (genesis day plus the three epoch seeds); miners' CPU program and cache for a new seed ready in 191 to 212 ms
Finalitywindow 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)
Lock latency (miner, proposed to locked over RPC)median 1,011 / 1,012 / 1,010 ms, max 1,988 / 2,716 / 1,965 ms; 34 of 34 votes accepted per miner
EVM smoke (tools/evm-smoke, copy outside the repo, IGNEUM_RPCS on 28803/28813/28823)84 of 85 checks in 118 s: chain id 4463, miner balance 428.5 IGN at chain block 113 (the IGNA 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)
igneum-exec-diff on the smoke exportsegments 0 to 198, 59 transactions compared, 8 accounts, 0 mismatches
Harness scenario 5 (ports 28900+, copy of tools/harness)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
Harness scenario 2 (copy adapted to the merged rules; the repo copy probes 132 s and pmt+1)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
-

Binaries: 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}; target-integration/x86_64-pc-windows-gnu/release/{igneumd.exe 50,169,344 B, igneum-miner.exe 10,045,440 B}; package /tmp/igneum-integration/igneum-node-windows-v4.zip (32,946,104 B). Not done: the GPU prepare 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).

+

Binaries: 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}; target-integration/x86_64-pc-windows-gnu/release/{igneumd.exe 50,169,344 B, igneum-miner.exe 10,045,440 B}; package /tmp/igneum-integration/igneum-node-windows-v4.zip (32,946,104 B). Not done: the GPU prepare hot-swap path on the merged miner (CPU miners only here), the cut-over itself, v4 builds for the Apple M5 Max seed relay and the seed node, 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).

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)

-

Machine: Apple M5 Max, idle at the start (load 3), rustc 1.99.0 (rustup), Swift 5.8.1, builds at nice 10. Decision (Josh, this morning): adopt the census's generator rule before any public vector ships. Rule as implemented (igneum-pow/src/generator.rs, src/accept.rs, spec 01 sections 1.4.2, 1.4.3 and 1.4.6; mirrored in proto-metal/main.swift as generateProgramV2 and acceptProgram 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 dst 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 SplitMix64(FNV-1a-64("igneum-accept/" || seed words LE)) 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 seed_words_from_bytes(seed || k_le32), k = 1, 2, ..., 32 consecutive rejections a consensus fault. Every pack carries generator 2, the attempt and the program id FNV-1a-64("igneum-program/" || 2_le32 || seed words LE || attempt_le32); igneum-pow is 0.2.0 and the node's engine reports igneum-lottery-v2-bound. The crate is now the pack source (igneum-pow export); the Swift exporter is the Metal cross-check. Version 1 stays as generate_v1 for the census and MEMHARD.md levers; its vectors are retired.

+

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 maintainers, this morning): adopt the census's generator rule before any public vector ships. Rule as implemented (igneum-pow/src/generator.rs, src/accept.rs, spec 01 sections 1.4.2, 1.4.3 and 1.4.6; mirrored in proto-metal/main.swift as generateProgramV2 and acceptProgram 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 dst 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 SplitMix64(FNV-1a-64("igneum-accept/" || seed words LE)) 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 seed_words_from_bytes(seed || k_le32), k = 1, 2, ..., 32 consecutive rejections a consensus fault. Every pack carries generator 2, the attempt and the program id FNV-1a-64("igneum-program/" || 2_le32 || seed words LE || attempt_le32); igneum-pow is 0.2.0 and the node's engine reports igneum-lottery-v2-bound. The crate is now the pack source (igneum-pow export); the Swift exporter is the Metal cross-check. Version 1 stays as generate_v1 for the census and MEMHARD.md levers; its vectors are retired.

Packs regenerated (proto-cuda/packs/): igneum-genesis and igneum-hourly (closed form), igneum-genesis-mh (memory-hard, day 2026-10-03, cache FNV unchanged 48c4f5bf24166b2e), and new igneum-devnet-v4-epoch0 (epoch seed = devnet genesis hash edc4fa84...fb07, day bytes igneum-day/20730, cache FNV 448274a57f508cbc). igneum-genesis attempt 0, program id bcc1248b10cc90f2, op mix load=16 add=8 shfl=8 xor=6 mad=5 mul=5 mulhi=5 sub=4 rotl=3 rotr=3 or=1, lane 0 at base 0 42246ba99fc58e4f, lane 31 b08446b1f2de7793; devnet pack id 4be132dd1f2ff270, lane 0 285a83011e7ac3fc. Bound vectors re-cut (igneum-pow/README.md: H zero, nonce 0 gives 746c567b090acf6a). cargo test --release: 39 of 39 (28 unit, 11 pack).

-
CheckResult
Rust CPU reference (igneum-pow)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
Apple Metal, natively (proto-metal/igneum-bench, Swift v2 generator)--export-pack igneum-genesis 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 igneum-genesis, igneum-hourly, igneum-census-2026-10-03/22, /37, /51 (the last three have attempt 0 rejected: (b) r7, (c) 119.74 distinct, (b) r4; attempt 1 accepted, ids 22ed0609d079f4cf, 947705cc4eb1df0a, 9869afcc028bf9f1): instructions, seed words and 96 vectors identical to Rust on all five; fuzz --fuzz 2000 --fuzz-seed igneum-fuzz-gen2-2026-10-04: 2,000 of 2,000 PASS, 8,000 warps, loads per hash 128 to 128, compile avg 21.8 ms, wall 91.4 s (proto-metal/TESTS.md section 9)
CUDA through the clang emulation shim (proto-cuda/emu/emu.sh, --batch-log2 13 --block-warps 2)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
OpenCL through the clang emulation (proto-opencl/emu/emu.sh), sub-group 32 local exchange and wave64 sub-group shuffleigneum-genesis-mh and igneum-devnet-v4-epoch0: 96 of 96 in both configurations, fingerprints f2a95d5bb84d961e and 8e22ad069cb2a8c3 at 2^13, identical across configurations
Apple OpenCL on the M5 Max (proto-opencl/host.c)all four packs: cache check PASS, 96 of 96 standalone and in batch (also --group-warps 2), fingerprint f2a95d5bb84d961e at 2^13 = the emulator's; 2^24 fingerprints 25f96e7dce90bd4e (genesis-mh), 3cc4fbf90fa6366c (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)
igneum-census, 20,000 programs, --gen v2 --warps 64, memory-hard day 2026-10-03, 8 threads, 254.8 srejected 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 (census-v2-20k.tsv and its summary in the session scratchpad, not checked in). Against the 100,000-seed figure of 3 October: 5.14 percent
devnet-v4 node and miner (vendor/igneum-node-v4, path dependency bumped to igneum-pow 0.2.0, engine name v2)cargo build --release -p kaspad -p igneum-miner --features igneum-pow into vendor/igneum-node/target-integration (1 min 17 s warm): release/igneumd 40,480,112 B, release/igneum-miner 7,932,880 B (08:41 BST); cargo test --release -p kaspa-pow --features igneum-pow 11 of 11; Windows cross-build (proto-cuda/windows-node/cross-build.sh vendor/igneum-node-v4 6, 4 min 53 s): target-integration/x86_64-pc-windows-gnu/release/igneumd.exe 50,179,072 B, igneum-miner.exe 10,065,920 B (libstdc++-6.dll import as before)
2-node test network on the real engine (ports 29000 to 29012, /tmp/igneum-gen2, igneum-devnet-900, IGNEUM_DEVNET_GENESIS_BITS=0x1f010000, IGNEUM_POW_EPOCH_BLOCKS=100, IGNEUM_POW_EPOCH_LEAD=20, one 3-thread CPU miner per node for 300 s)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 234e08..., d3f427..., de316c..., 971384..., all attempt 0, ids 8f8806638d59850f, c015349db63beb2c, d7d52120407a0b69, 512527bb7a528476), 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)
-

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; proto-metal/main.swift 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 prepare 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).

+
CheckResult
Rust CPU reference (igneum-pow)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
Apple Metal, natively (proto-metal/igneum-bench, Swift v2 generator)--export-pack igneum-genesis 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 igneum-genesis, igneum-hourly, igneum-census-2026-10-03/22, /37, /51 (the last three have attempt 0 rejected: (b) r7, (c) 119.74 distinct, (b) r4; attempt 1 accepted, ids 22ed0609d079f4cf, 947705cc4eb1df0a, 9869afcc028bf9f1): instructions, seed words and 96 vectors identical to Rust on all five; fuzz --fuzz 2000 --fuzz-seed igneum-fuzz-gen2-2026-10-04: 2,000 of 2,000 PASS, 8,000 warps, loads per hash 128 to 128, compile avg 21.8 ms, wall 91.4 s (proto-metal/TESTS.md section 9)
CUDA through the clang emulation shim (proto-cuda/emu/emu.sh, --batch-log2 13 --block-warps 2)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
OpenCL through the clang emulation (proto-opencl/emu/emu.sh), sub-group 32 local exchange and wave64 sub-group shuffleigneum-genesis-mh and igneum-devnet-v4-epoch0: 96 of 96 in both configurations, fingerprints f2a95d5bb84d961e and 8e22ad069cb2a8c3 at 2^13, identical across configurations
Apple OpenCL on the M5 Max (proto-opencl/host.c)all four packs: cache check PASS, 96 of 96 standalone and in batch (also --group-warps 2), fingerprint f2a95d5bb84d961e at 2^13 = the emulator's; 2^24 fingerprints 25f96e7dce90bd4e (genesis-mh), 3cc4fbf90fa6366c (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)
igneum-census, 20,000 programs, --gen v2 --warps 64, memory-hard day 2026-10-03, 8 threads, 254.8 srejected 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 (census-v2-20k.tsv and its summary in the session scratchpad, not checked in). Against the 100,000-seed figure of 3 October: 5.14 percent
devnet-v4 node and miner (vendor/igneum-node-v4, path dependency bumped to igneum-pow 0.2.0, engine name v2)cargo build --release -p kaspad -p igneum-miner --features igneum-pow into vendor/igneum-node/target-integration (1 min 17 s warm): release/igneumd 40,480,112 B, release/igneum-miner 7,932,880 B (07:41 UTC); cargo test --release -p kaspa-pow --features igneum-pow 11 of 11; Windows cross-build (proto-cuda/windows-node/cross-build.sh vendor/igneum-node-v4 6, 4 min 53 s): target-integration/x86_64-pc-windows-gnu/release/igneumd.exe 50,179,072 B, igneum-miner.exe 10,065,920 B (libstdc++-6.dll import as before)
2-node test network on the real engine (ports 29000 to 29012, /tmp/igneum-gen2, igneum-devnet-900, IGNEUM_DEVNET_GENESIS_BITS=0x1f010000, IGNEUM_POW_EPOCH_BLOCKS=100, IGNEUM_POW_EPOCH_LEAD=20, one 3-thread CPU miner per node for 300 s)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 234e08..., d3f427..., de316c..., 971384..., all attempt 0, ids 8f8806638d59850f, c015349db63beb2c, d7d52120407a0b69, 512527bb7a528476), 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)
+

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; proto-metal/main.swift carries the version 2 port uncommitted next to the hot-swap working-tree changes (not in this agent's file list), and the Apple M5 Max 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 prepare 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).

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)

-

Decision of 4 October 2026 (Josh, 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).

+

Decision of 4 October 2026 (the maintainers, 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).

Node. Branch devnet-v4 of vendor/igneum-node (worktree vendor/igneum-node-v4, from dc749905), commit 6457ca95, two files: consensus/core/src/finality.rs (FLOOR_NUM / FLOOR_DEN 2/3, was 17/30; the Q3 arithmetic as FinalityParams::{quorum_met, floor_met, locks}, both comparisons inclusive) and consensus/src/processes/finality.rs (lock_test calls it). Build CARGO_TARGET_DIR=target-integration nice -n 10 cargo build --release -j 6 -p kaspad --features kaspad/igneum-pow, 3 min 17 s on a machine at load 3 to 13 (another agent's igneum-pow rebuild and the live devnet running). Tests cargo test --release -j 6 -p kaspa-consensus-core -p kaspa-consensus -- finality: 7 of 7 in consensus-core including the new floor_is_two_thirds_of_total_and_inclusive (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 (no_certificate_while_the_window_is_filling unchanged). The live devnet (26610, 26611, 26640, 26641, 28640, the seed relay on 26680 and observer.mjs) was never touched.

Simulator. sim/finality_v2.py: --floor f (a lock needs f x 2/3 of total; default 1.0 since this date, --floor 0.85 reproduces the 3 October tables), the +local 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 --quick 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 sim/results_v2.md, "Floor 2/3".

Simulator measureFloor 0.85 (3 October)Floor 2/3 (4 October)
Smallest equivocator that splits a 50/50 honest partition (H, 150 and 360 min, retarget)14% in some seeds, 20% in every seed from minute 1234% (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
40/40 honest plus a 20% equivocator reaching both, sides 60/60 (I)256 to 276 conflicts in 150 min0 conflicts, no lock
Silent weight that keeps mining: where the pause begins (J, L1)between 40% (13-min first lock) and 45% (never)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
Churn, first lock (L2, D)35%: 2 min; 50%: 4.1 days35%: 1.7 days (analytic 1.4); 50%: 10.1 to 10.3 days (analytic 10.0)
Poisoned eclipse, 34% attacker plus a 20% pool, 1, 2, 4 h (L3)0 conflicts0 conflicts, 0 locks on the eclipsed side, every seed
Bought keys worth 40% that withhold their votes, 30 days (K)305 to 1,085 stalls of 86,40063,307 to 68,716 stalls: the pause lasts until the bought weight decays below one third, day 19 to 20
Long honest partition, each side counting only what it has seen (L4, 12 days)50/50: both sides lock alone from day 4.1; 60/40: the 60 side at once, the 40 side from day 8.550/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
@@ -206,17 +206,17 @@ footer{border-top:1px solid var(--line);padding-block:32px 48px;font-size:13px;c

The inclusive comparison at exactly two thirds is pinned by the integer test 3 x signed >= 2 x total 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.

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 skip_proof_of_work 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 tools/finality-attacks/run.mjs 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 (min_daa = window). (4) The harness's 60-s heal window (6A, 6B) is shorter than the connection manager's redial backoff for a --connect 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 (lsof 0 listeners).

4 October 2026, proving v0 on the RTX 5090: first GPU proof of an Igneum block (WSL2, SP1 6.8.1 cuda)

-

Machine: Josh'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 proving/windows-wsl2 (SETUP-PROVER then PROVE-BLOCK), host igneum-prove-host built with the cuda feature, SP1_PROVER=cuda, sp1-gpu-server 6.8.1 on device 0. Fixture block-78-increment (chain 4463, 2 transactions, 10 accounts). Run id prove-<pc>-20261004-084838, log intake id 10154.

-
StageRTX 5090Apple M5 Max CPU (3 October, loaded)
native re-execution0.0003 s, state root MATCHES the fixture0.0004 s, matches
setup (one per program id)23.23 s20.55 s on the PC's CPU; Mac not timed separately
execute626,876 cycles, prover gas 844,704, 0.19 s, 14 cycles per EVM gas626,246 cycles, 0.15 s on the PC's CPU
core proofprove 1.4 s, 7,317,217 bytes, verify 0.221 s, VERIFIEDprove 22.0 s, 7.3 MB, verify 0.16 s
compressed proofprove 2.7 s, 1,272,769 bytes, verify 0.038 s, VERIFIEDprove 55.7 s, 1.27 MB, verify 0.03 s
-

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 sp1-cuda'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: protobuf-compiler in the apt list, the WSL distro check (UTF-16 output), the elevated window closing; and WSL2 itself needed bcdedit /set hypervisorlaunchtype auto on a PC whose BIOS already had SVM on.

+

Machine: the maintainers' 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 proving/windows-wsl2 (SETUP-PROVER then PROVE-BLOCK), host igneum-prove-host built with the cuda feature, SP1_PROVER=cuda, sp1-gpu-server 6.8.1 on device 0. Fixture block-78-increment (chain 4463, 2 transactions, 10 accounts). Run id prove-<pc>-20261004-084838.

+
StageRTX 5090Apple M5 Max CPU (3 October, loaded)
native re-execution0.0003 s, state root MATCHES the fixture0.0004 s, matches
setup (one per program id)23.23 s20.55 s on the RTX 5090 machine's CPU; Mac not timed separately
execute626,876 cycles, prover gas 844,704, 0.19 s, 14 cycles per EVM gas626,246 cycles, 0.15 s on the RTX 5090 machine's CPU
core proofprove 1.4 s, 7,317,217 bytes, verify 0.221 s, VERIFIEDprove 22.0 s, 7.3 MB, verify 0.16 s
compressed proofprove 2.7 s, 1,272,769 bytes, verify 0.038 s, VERIFIEDprove 55.7 s, 1.27 MB, verify 0.03 s
+

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 sp1-cuda'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 RTX 5090 machine needed three package fixes found only by running it on Windows: protobuf-compiler in the apt list, the WSL distro check (UTF-16 output), the elevated window closing; and WSL2 itself needed bcdedit /set hypervisorlaunchtype auto on a PC whose BIOS already had SVM on.

4 October 2026, devnet v4 cut-over: generator v2, 2/3 floor, three nodes and a seed on a fresh chain

-

Sequence (local time, UTC+1): seed re-staged from 6457ca95 (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, igneumd v4 up at 10:05:31 with the execution layer; observer peer and observer.mjs restarted on fresh appdirs at 10:06:10; seed switched with switch-v4.sh at 10:06:28 and synced at 10:07:38 (blocks=2, peers=1); Mac Metal miner (generator v2 port, prepare 1) 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: "$machine:$vendorName" in igneum-common.ps1 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.

-

4 October 2026, one-click Windows workers: what the Mac could measure (no NVIDIA GPU here)

+

Sequence (UTC): seed re-staged from 6457ca95 (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, igneumd v4 up at 10:05:31 with the execution layer; observer peer and observer.mjs restarted on fresh appdirs at 10:06:10; seed switched with switch-v4.sh at 10:06:28 and synced at 10:07:38 (blocks=2, peers=1); Mac Metal miner (generator v2 port, prepare 1) first accepted block at 10:07:35; the RTX 5090 machine 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: "$machine:$vendorName" in igneum-common.ps1 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.

+

4 October 2026, one-click Windows workers: what the Apple M5 Max could measure (no NVIDIA GPU here)

Package 0.3.0 replaces the build-on-the-PC workers with two prebuilt exes (proto-cuda/nvrtc/worker.cpp: driver API + NVRTC loaded at run time; proto-opencl/host.c --pack: 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 (proto-cuda/nvrtc/emu/serve-check.sh: jobs on pack A, a background prepare of a second pack, the swap, a job after the swap, 15 to 17 found hashes against igneum-pow hash-bound):

WorkerHow it ran hereFirst pack (cache / dataset / self-test)Prepare of pack B (reported)Verdict
igneum-worker-cuda (emulation backend: pack kernels on host threads, NVRTC stand-in)nvrtc/emu/test.sh157 / 4,758 / 1,702 ms8,413 ms (cache 132, dataset 4,759, check 1,671)PASS, 17 of 17 sampled hashes, source check PASS for 4 files
igneum-bench-cl --pack (Apple OpenCL 1.2, built against a different placeholder pack)proto-opencl/test-generic.sh53 / 289 / 1,588 ms2,083 ms (build 212, cache 25, dataset 197, check 1,387)PASS, 15 of 15 sampled hashes
-

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 (proto-cuda/windows-app/TEST.md lists the lines to copy here).

+

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 RTX 5090 machine run (proto-cuda/windows-app/TEST.md lists the lines to copy here).

4 October 2026, first hourly program swap on the live devnet: compile-ahead, no pause, two cards

-

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 (next_epoch_seed, confirm = lead/4); each miner sent prepare to its worker and the worker built the next program in the background while the current one mined.

+

Live devnet v4, epoch boundary at DAA 3,600 (10:05:07 UTC). The node announced the next seed once the sink was 150 DAA past the seed score (next_epoch_seed, confirm = lead/4); each miner sent prepare to its worker and the worker built the next program in the background while the current one mined.

MachinePrepare sentCompileSwap at 3,600RestartRate before / afterRejected
Mac M5 Max (Metal, prepare 1)449 DAA before the boundary82 ms0.01 ms, resident 2 programsnone26.7 / 26.7 MH/s0
RTX 5090 (CUDA, nvcc in the background)same template1,285 ms (nvcc 1,129, cache 4, dataset 23)0.00 ms, resident 2 programs 2 datasetsnone121.8 / 123.4 MH/s0
AMD Radeon integrated (OpenCL)samepreparedno restartnone2.74 / 2.74 MH/s0

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 (rebuilds 0). The next boundary is DAA 7,200, which is also the first finality lock (weight window 7,200).

4 October 2026, proving: devnet v4 shards on the Apple M5 Max CPU, loaded machine (execution-engineer, proving)

@@ -228,28 +228,40 @@ footer{border-top:1px solid var(--line);padding-block:32px 48px;font-size:13px;c

CPU proofs (SP1_PROVER=cpu), block-56-transfers-3shards:

StageProve sProof bytesVerify sVerified
setup (prover client plus two key setups)39.4 to 60.6 (keys 3.3 + 3.2 of it; the rest is the client)
shard 0: core83.17,310,2570.368yes
shard 0: compressed272.31,272,8970.075yes
block mode, shard 0: compressed336.91,272,8970.068yes
block mode, shard 1: compressed305.41,272,8970.066yes
block mode, shard 2: compressed245.31,272,8970.064yes
block mode, aggregation over the 3 shard proofs (recursion, deferred proofs)244.51,272,9090.084yes, shard program id and claim checked
block mode, end to end (first shard proof to the verified block proof, 10:07:22 to 10:26:21 UTC)1,139
RTX 5090 (PROVE-SHARD.bat)shard at S_p: execute, core, compressedtwo-shard block end to endfour-shard block end to end
pending
-

Reading: the shard at S_p 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.

-

2026-10-04, fast time (60x test profile), Linux cross-compile from the Mac, CI on every push (consensus-engineer)

-

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 /opt/igneum/v4/bin were not touched.

+

Reading: the shard at S_p 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 RTX 5090 machine runs. The devnet was not touched; the simnet ran on ports 29300, 29301 and 29390 and was stopped.

+

2026-10-04, fast time (60x test profile), Linux cross-compile from the Apple M5 Max, CI on every push (consensus-engineer)

+

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 <server path> were not touched.

Fast time. infra/fast-time/override-60x.json is the devnet with every clock-like consensus parameter divided by 60 and every block count unchanged (infra/fast-time/README.md 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 (pow_epoch_blocks, pow_epoch_lead, plus pow_day_ms for the dataset day; devnet-v4 a5ef8b07, 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 DEVNET_PARAMS). Proof (infra/fast-time/simnet.mjs, three devnet-v4 nodes on ports 29500+, igneum-devnet-950, 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 min_daa 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.

Harness run, same binariesDevnet profile60x profile (--fast-time)
tools/finality-attacks s3, dishonest aggregatorscatalogue 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 lock113 s wall: 16 locks per node, 0 conflicting certificates, lock hashes agree, median lock latency 1,018 ms, PASS
tools/harness s3, partition and heal (in-process simulator of the devnet-v4 line)983 s wall, four cuts of 120 / 600 / 1,800 / 3,700 virtual s (46 / 151 / 400 / 831 s wall), all PASS43 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

Both harnesses take --fast-time (tools/harness/lib/net.mjs, tools/finality-attacks/lib/net.mjs): the node and the simulator then come from vendor/igneum-node/target-integration (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 --quick scale.

-

Linux cross-compile. infra/cross/build-linux.sh: cargo-zigbuild 0.23.4 with zig 0.17.0 (brew install zig, cargo install cargo-zigbuild, rustup target add x86_64-unknown-linux-gnu), target x86_64-unknown-linux-gnu.2.36 (Debian 12 on the servers), -p kaspad -p igneum-miner --features kaspad/igneum-pow, target dir vendor/igneum-node/target-linux. 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 cross 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 /root/xbuild-test: igneumd --version = igneumd 2.1.0, igneum-miner --help prints its usage, and a 60-s run of the cross-compiled node on igneum-devnet-951 (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. BIN_SOURCE=mac is wired into infra/cloud-devnet/provision.sh (no builder VM, build/bin from the cross-compile) and infra/seed-nodes/stage-v4.sh (upload to /root/v4/out, install as before).

+

Linux cross-compile. infra/cross/build-linux.sh: cargo-zigbuild 0.23.4 with zig 0.17.0 (brew install zig, cargo install cargo-zigbuild, rustup target add x86_64-unknown-linux-gnu), target x86_64-unknown-linux-gnu.2.36 (Debian 12 on the servers), -p kaspad -p igneum-miner --features kaspad/igneum-pow, target dir vendor/igneum-node/target-linux. 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 cross 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 the seed node (Debian 12, glibc 2.36, 2 vCPU) in <server path>: igneumd --version = igneumd 2.1.0, igneum-miner --help prints its usage, and a 60-s run of the cross-compiled node on igneum-devnet-951 (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. BIN_SOURCE=mac is wired into infra/cloud-devnet/provision.sh (no builder VM, build/bin from the cross-compile) and infra/seed-nodes/stage-v4.sh (upload to <server path>, install as before).

CI. .github/workflows/ci.yml runs on every push and pull request of the private repository: igneum-pow cargo test --release and the igneum-census build (49 s), the two simulators' --quick modes under a 120-s timeout (49 s for the job; sim/finality_v2.py --quick 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; sim/difficulty/sim.py --quick is new, 36 s here), the site build, an internal link check of site/*.html (151 links, 0 broken) and a gh-free identity grep of the public export list after the generic scrub (tools/ci/identity-check.sh, tools/ci/forbidden-strings.txt: 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.

Not done: igneum-harness-sim'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).

4 October 2026, first finality lock on the live devnet: checkpoint 242 at 77.4% of all weight, two hours after genesis

-

Live devnet v4 (genesis 10:05 BST). The weight window and min_daa are 7,200 DAA seconds, so no checkpoint could lock before DAA 7,200. The first checkpoint past it, index 242 (block 59b4a314, blue score 7,261), locked at 12:03:44 BST with 77.4% of total weight and 77.4% of active weight signed, 12 aggregated votes from 17 vote keys (two RTX 5090 machines with 8 identities each, the Mac's Metal miner, the integrated AMD chip's identities), floor 2/3. The certificate (23 headers) was stored and carried in block 1e2439a1; observer.mjs logged checkpoint_locked 0.7 s after the miner's own LOCK line. The floor was raised to 2/3 this morning (O-3.15); this is its first live lock. Difficulty at the moment of the lock was mid-oscillation (93M to 99M, see the oscillation finding), which did not affect voting.

-

4 October 2026, the gfx1036 worker fault and what the Mac could and could not reproduce

+

Live devnet v4 (genesis 09:05 UTC). The weight window and min_daa are 7,200 DAA seconds, so no checkpoint could lock before DAA 7,200. The first checkpoint past it, index 242 (block 59b4a314, blue score 7,261), locked at 11:03:44 UTC with 77.4% of total weight and 77.4% of active weight signed, 12 aggregated votes from 17 vote keys (two RTX 5090 machines with 8 identities each, the Apple M5 Max's Metal miner, the integrated AMD chip's identities), floor 2/3. The certificate (23 headers) was stored and carried in block 1e2439a1; observer.mjs logged checkpoint_locked 0.7 s after the miner's own LOCK line. The floor was raised to 2/3 this morning (O-3.15); this is its first live lock. Difficulty at the moment of the lock was mid-oscillation (93M to 99M, see the oscillation finding), which did not affect voting.

+

4 October 2026, the gfx1036 worker fault and what the Apple M5 Max could and could not reproduce

PC 2 (RTX 5090 plus the Ryzen's integrated gfx1036), package 0.3.0 prebuilt workers. The CUDA worker compiled the pack with NVRTC (after -default-device) and mined at 124.2 MH/s, equal to the nvcc-built worker, 0 rejected, CPU re-check clean. The OpenCL worker (igneum-worker-opencl.exe --pack, path prebuilt-generic) self-tested PASS and mined correctly at 3.3 MH/s for 577 s (8 accepted blocks), then from about 600 s every job "completed" in 0.5 ms with no hash: 906 jobs became 56,384 within 30 s, the miner reported 4.3 GH/s inside jobs and the dashboard over 1 GH/s, with no error line, no exit and no restart. PC 1's cl.exe-built worker on the same host.c serve loop ran over an hour without this.

Root cause, as far as it can be stated: the AMD runtime kept answering clEnqueueNDRangeKernel, clWaitForEvents and the blocking clEnqueueReadBuffer with CL_SUCCESS while running nothing, so the loop walked its chunks at memory speed and reported the stale output buffer as a finished job. What flipped the runtime into that state at 600 s is not visible in the logs and the job path itself leaks nothing (one event per chunk, created and released; verified below). The two plausible triggers are a device reset of the integrated GPU with the runtime swallowing it (the generic path is the only one that self-tests, which reads 256 MiB back and runs the three vector warps at start; an hourly prepare would do the same work again on a second queue while jobs run) and a runtime limit reached after about 900 jobs. Neither reproduces on Apple OpenCL:

Run on the M5 Max (Apple OpenCL 1.2, pack-a, 2^22 nonces per job)JobsJob time ms (mean, min, max)FaultsLive objects at the end
20-minute soak of the shipped generic worker3,365412 / 305 / 5910not counted (that build had no counters)
1,200-job soak of the hardened worker (events and buffers counted)1,200411 / 315 / 54900 events, 4 buffers (cache, dataset, out, init words); 1,200 events created and released
the same worker with IGNEUM_FAULT_TEST=6 (the dispatch skipped from chunk 6 on, the runtime "succeeding")6 real + 11: the output buffer is unchanged since the previous dispatch, exit 3
-

So the fix is defensive at three levels (commit 112acf6 and vendor devnet-v4 f9392600): the worker treats every OpenCL error in the job path as fatal, requires CL_COMPLETE on the dispatch event, refuses a chunk 20x faster per nonce than the running mean or an output buffer unchanged since the previous dispatch, prints live object counts every 200 jobs and exits 3 on any of these; the miner kills and restarts a worker whose job time per hash drops under 1/20 of the mean or whose interval rate exceeds 10x the mean before it, rolls its counters back to the last report and prints WORKER FAULT; the launcher shows worker fault and restarting for that card instead of a rate. The next gfx1036 run says which guard fires first; that line is the diagnosis the Mac cannot give.

+

So the fix is defensive at three levels (commit 112acf6 and vendor devnet-v4 f9392600): the worker treats every OpenCL error in the job path as fatal, requires CL_COMPLETE on the dispatch event, refuses a chunk 20x faster per nonce than the running mean or an output buffer unchanged since the previous dispatch, prints live object counts every 200 jobs and exits 3 on any of these; the miner kills and restarts a worker whose job time per hash drops under 1/20 of the mean or whose interval rate exceeds 10x the mean before it, rolls its counters back to the last report and prints WORKER FAULT; the launcher shows worker fault and restarting for that card instead of a rate. The next gfx1036 run says which guard fires first; that line is the diagnosis the Apple M5 Max cannot give.

4 October 2026, a node 60 s behind the clock is silently dead (PC 2's first app install)

PC 2 came back from a power cut with its clock 60 s slow. igneumd connected, then logged HandleRelayInvsFlow flow error: the block timestamp is too far into the future: block timestamp is ... but maximum timestamp allowed is ... for every relayed block, processed 0 blocks and the app sat on "waiting for a peer" with nothing to say. The consensus rule is right (a header may not be ahead of the node's clock by more than the tolerance, check_block_timestamp_in_isolation); the reporting was not. The node prints one WARN per relayed block with two millisecond numbers and never the one line a person needs.

-
WhatWhereNow
The app reads that WARN, takes block timestamp - maximum allowed as a lower bound and shows "Your clock is at least N seconds behind the network; mining cannot start until it is fixed" on the node card with a Sync clock button (macOS sntp -sS time.apple.com under an administrator prompt, Windows w32tm /resync elevated, Linux chronyc makestep / timedatectl) and the manual stepsapp/igneum-app/src/engine.rs node_line, resolve_clock; platform.rs sync_clockdone
Independent of the node: once blocks arrive the engine samples the latest block's timestamp through the node's Ethereum JSON-RPC every 10 s and takes the median of local minus block time over the last 9 (behind only; a stalled chain reads as ahead); and an HTTPS Date header from dl.igneum.network at start and every 10 min (either direction, 1 s resolution). Over 5 s: a warning. Over 10 s (the consensus bound): the Start button is blocked and running miners are heldengine.rs watch_line, update.rs latest_block_time, https_timedone
The node itself should log one clear line at WARN, once, not per block: clock skew: local time is N s behind the median peer block time (and the same for ahead, when its own templates are refused by peers). Filed for the devnet-v4 worktree owner; the app does not patch the nodedocs/plans/node-changes.mdfiled
-

Checked on the Mac with a fake 60 s skew (IGNEUM_APP_FAKE_SKEW=-60): the banner, the node card, the blocked Start button and the held miner all showed; with the real clock the HTTPS source read +0.4 s and the block source agreed.

+
WhatWhereNow
The app reads that WARN, takes block timestamp - maximum allowed as a lower bound and shows "Your clock is at least N seconds behind the network; mining cannot start until it is fixed" on the node card with a Sync clock button (macOS sntp -sS time.apple.com under an administrator prompt, Windows w32tm /resync elevated, Linux chronyc makestep / timedatectl) and the manual stepsapp/igneum-app/src/engine.rs node_line, resolve_clock; platform.rs sync_clockdone
Independent of the node: once blocks arrive the engine samples the latest block's timestamp through the node's Ethereum JSON-RPC every 10 s and takes the median of local minus block time over the last 9 (behind only; a stalled chain reads as ahead); and an HTTPS Date header from the downloads host at start and every 10 min (either direction, 1 s resolution). Over 5 s: a warning. Over 10 s (the consensus bound): the Start button is blocked and running miners are heldengine.rs watch_line, update.rs latest_block_time, https_timedone
The node itself should log one clear line at WARN, once, not per block: clock skew: local time is N s behind the median peer block time (and the same for ahead, when its own templates are refused by peers). Filed for the devnet-v4 worktree owner; the app does not patch the nodedocs/plans/node-changes.mdfiled
+

Checked on the Apple M5 Max with a fake 60 s skew (IGNEUM_APP_FAKE_SKEW=-60): the banner, the node card, the blocked Start button and the held miner all showed; with the real clock the HTTPS source read +0.4 s and the block source agreed.

4 October 2026, first machine on the Igneum Miner app: PC 2's RTX 5090 at 118 MH/s, via Setup.exe

-

Josh's second PC (a clone of the first; the app's per-install machine id 1ccfe586 keeps its keys apart), installed from the runner-built Igneum-Miner-Setup-0.3.0.exe (unsigned, SmartScreen "run anyway"), the one-click package: prebuilt NVRTC worker, no toolchain on the machine. First attempt sat at "waiting for peer": the PC's clock was 62 s slow after a power cut and igneumd rejected every relayed block ("the block timestamp is too far into the future"; the 10-s skew bound from the hardened timestamp rule) and processed 0 blocks for 12 minutes with no visible reason. Clock set by hand; the node caught up (46 blocks in the next 10 s at 12:27:53 BST), the 5090 started inside the app and ran at 117 to 119 MH/s with 34 accepted blocks in the first minute, CPU re-check OK on every share, the integrated AMD chip at 3.3 MH/s beside it. Two defects from the run, both fixed in the app the same hour: no clock-skew warning (now detected from the node's warning, block timestamps and an HTTPS Date header; Start is blocked above 10 s), and the node card stayed on "syncing" after the late catch-up while the miner was already accepted (state now re-derived every poll).

+

the maintainers' second PC (a clone of the first; the app's per-install machine id 1ccfe586 keeps its keys apart), installed from the runner-built Igneum-Miner-Setup-0.3.0.exe (unsigned, SmartScreen "run anyway"), the one-click package: prebuilt NVRTC worker, no toolchain on the machine. First attempt sat at "waiting for peer": the RTX 5090 machine's clock was 62 s slow after a power cut and igneumd rejected every relayed block ("the block timestamp is too far into the future"; the 10-s skew bound from the hardened timestamp rule) and processed 0 blocks for 12 minutes with no visible reason. Clock set by hand; the node caught up (46 blocks in the next 10 s at 11:27:53 UTC), the 5090 started inside the app and ran at 117 to 119 MH/s with 34 accepted blocks in the first minute, CPU re-check OK on every share, the integrated AMD chip at 3.3 MH/s beside it. Two defects from the run, both fixed in the app the same hour: no clock-skew warning (now detected from the node's warning, block timestamps and an HTTPS Date header; Start is blocked above 10 s), and the node card stayed on "syncing" after the late catch-up while the miner was already accepted (state now re-derived every poll).

+

4 October 2026, difficulty rule v2: the live oscillation, its cause, the DAG replay, the fix behind a height switch (consensus-engineer)

+

Machine: Apple M5 Max shared with four other agents' builds (load average 7 at the start, 184 to 442 from 11:40 UTC on); every simulation and build at nice 19, cargo at 4 jobs; the live devnet (26610/26611, 26640/28640, observer.mjs, the Metal miner) untouched, read through the observer node's wRPC only. Worktree vendor/igneum-node-v4, branch devnet-v4, built in its own target/. Everything in docs/analysis/difficulty-2026-10-04-oscillation.md; ledger M24; spec 2.3 revised. Live finding (devnet v4, UTC): PC 1 (RTX 5090, 122 MH/s, 8 identities through its own node) with the Apple M5 Max (26.7 MH/s) and a Radeon (2.8) from 09:11; PC 2 (RTX 5090, 124 MH/s, 8 identities through the Apple M5 Max node) from 10:12:17, off 10:31:25 to 10:35:19; both PCs restarted at 11:00 for the machine-id package (they are clones with one computer name and had signed with the same vote keys). Difficulty (node convention): flat 56.7M to 67M within 1% per minute from 09:24 to 10:04; 70M to 144M in 90 s after the join; then 101.8M to 164.3M from 10:17 to 10:32 (5 peaks of 1.33x spaced 132 chain blocks, std of log difficulty 0.123, 54 to 81 blocks a minute) and 103M to 150M from 10:37 to 10:54 (4 peaks of 1.35x, std 0.072, a floor rising 110M to 127M) against a true 139M; after the epoch boundary at DAA 7,200 (11:01:52) 69.0M to 72.7M within 1.3% per minute. Stacked clamps in the observer's 2-s polls: "up 15.9%" = five 3% hardens, "down 24.3%" = three 10% eases. DAG: 6,741 blocks to 10:54, 38% of chain blocks merging two or more blues, every merged block blue. Records sim/difficulty/records/live-2026-10-04.csv (8,090 headers, pull_live.py) and live-2026-10-04-hashrate.csv (587 worker STATUS lines from the log intake, by run id and node). Cause: spec 2.3's reference lane is the whole epoch, so a step 7 minutes into the hour left it polluted for the hour; the short lane read 11 to 25% above it; the 25% trigger flipped on the short lane's noise; the clamps turned each flip into a ramp. The DAG's bursts widen the short lane's noise and the stacked clamps steepen each flip; neither starts it. The 3 October simulator stepped at epoch boundaries, so it never saw it. Replay: sim.py --live, a DAG model (miners on two nodes with igneum-miner's template staleness, templates stamped by the node, GHOSTDAG, the rule as igneum_difficulty_bits runs it, the sampled window per mergeset), one scale fitted to the merge fraction. Join window 10:20 to 10:31, 3 seeds: std of log difficulty 0.115 against the record's 0.134, 4.3 peaks of 1.31x against 4 of 1.37x, 110.7M to 167.2M against 101.8M to 164.3M, 56.7 to 73.7 blocks a minute against 59 to 72, 22 lane flips, the short lane ruling 38% of the time. Whole polluted window: 0.118 against 0.123, 5.3 peaks against 5. The chain-only simulator with a 1.85x step 10 minutes into an epoch: std 0.136 to 0.189 with 96 to 142 flips (v1). Candidates on the live replay (join window, 3 seeds, std of log difficulty / lane flips): v1 0.115 / 22; short lane 240 0.125 / 10; 360 0.107 / 11; ease clamp 3% 0.113 / 20; clamp once per DAA second 0.132 / 21; hysteresis leave at 10% 0.090 / 2 (short lane ruling 78%); median of three 0.124 / 15; soft trigger 0.110 / 13; reference window 1,200 0.108, 900 0.081, 720 0.055, 600 0.024 / 0, 480 0.021 / 0. Adopted: v2 = the reference lane is the epoch lane over the newest 600 DAA score of the epoch, the sampled long lane not consulted: 0.026 / 0, mean 142.6M against 139M true; rejoin window 0.031 / 0; chain-only 0.022 to 0.081. Cost (synthetic set seed 7, v1 / v2): x50 settled 61.7 / 65.5 s (standard under 90), /50 628 / 753 s (worst gap 32 / 62 s), epoch +-30% settled 144 / 143 s, hop10 211 / 200 s, polluted overshoot 0.180 / 0.089, warm-ups equal, steady std 0.038 / 0.049 with blocks-per-minute CV 0.135 / 0.130. Attacks (seeds 7 to 9, v1 / v2): greedy hopper at most +1.5% / +2.5%, with a 60 s dwell -4.0% / -1.5% at 100%; pulsed rental -96.4% / -96.3% with weight per hash 0.262 / 0.262; forger drift +0.4 to +1.1% / -0.8 to +0.5% (worst seed 2.7% / 1.5%); short-lane oscillation gain 3.75 / 3.29; epoch games 0.0 to +0.7% / 0.0 to +0.4%; polluted window settled 287 to 329 s / 288 to 331 s; base profiles 3-seed up50 154 / 150 s, down50 762 / 822 s, epoch30 88 / 88 s, hop10 245 / 233 s, polluted 75 / 76 s, steady std 0.042 / 0.053. Implementation (devnet-v4): difficulty_v2_activation_daa in Params (every network u64::MAX), OverrideParams, override_params, the daemon's file parser (prints the height), SampledDifficultyManager (new field, reference_window(daa_score, epoch_blocks, activation)), REF_WINDOW_V2 = 600, IgneumInputs.k_ref; infra/fast-time/override-60x.json carries the field as never. cargo test --release -p kaspa-consensus --lib difficulty: 15 pass (12 of 3 and 4 October plus reference_window_switches_at_the_activation_height, v2_reference_window_follows_a_step_inside_the_epoch_where_v1_eases_into_it, v1_and_v2_agree_in_a_steady_epoch); -p kaspa-consensus-core --lib params: 7 pass (override_params_carry_the_difficulty_v2_activation, the fast-time file test extended). Build 2 min incremental for igneumd and igneum-miner. Test network (sim/difficulty/testnet_v2.py, 3 nodes on 29600 to 29622, the 60x file with the devnet epoch, genesis bits 2^16, activation 900 on nodes 1 and 2, node 3 without it; CPU miners A from 0, B from minute 4, off at 19, back at 23): node 1 reached DAA 900 at 1,022 s; node 3 rejected the first v2 block ("difficulty of 520437997 is not the expected value of 520406991"), banned its peer and stayed at DAA 900 (901 headers, a prefix of node 1's 1,472); nodes 1 and 2 agreed on every header and the sink. Under v2 the leave eased 6,589 to 5,972 over 180 s (std 0.036, no peak), the rejoin hardened 6,154 to 8,312 within 60 s and held within 3%. The v1 phase is not readable: the load swung the CPU miners' delivered hash rate 2x on its own (difficulty fell 40% after B joined). Record records/testnet-v2-2026-10-04.csv. Repeat on a quiet machine, 30 minutes. Rollout: only igneumd changes (the Apple M5 Max build, infra/cross/build-linux.sh for the seed and the the cloud provider nodes, the Windows package for PC 1's node); every node of a chain needs the same "difficulty_v2_activation_daa": N in its override file before the height or it forks off there. First the 12 the cloud provider nodes on their own chain (N = current DAA + 1,800, restart one by one, a miner joins inside an epoch, no flips after the height), then the devnet with N about two hours ahead: observer node, seed, Mac node 1, PC 1's node, in that order, by the maintainers. A new network sets 0. Not done: a quiet-machine test-network run; the DAG model's red blocks and the Apple M5 Max's log series; v2 with +-500 ms stamp jitter.

+

4 October 2026, the observer stored nothing for 78 minutes, then 7,022 blocks in two minutes

+

Mac, load average 200 to 300 from other agents' simulations (uptime at 13:21 UTC: 83 / 224 / 206). The observer (tools/observer/observer.mjs, reading the Apple M5 Max's non-mining peer on wRPC 28640) kept writing live_state every 2 s, so the page said LIVE with age_s 0.1 while its newest stored block was 4,868 s old; the DAG panel showed "waiting for the first block" and one identity while PC 1 mined at 122 MH/s.

+

Measured from live_blocks (received_at minus the header timestamp, UTC):

+
WindowBlocks storedMean lag sMax lag s
11:30 to 11:55, five-minute slots150 to 451 each4 to 1112 to 61
11:57 to 13:150
13:15 slot (the restart at 13:18)7,0232,4874,911
13:20 slot1351084
+

The 7,022 catch-up blocks carried header times spread evenly over the gap (66 to 95 per minute by header time), and blocks_per_minute was bucketed by processing time, so the hour chart showed 3,762 and 3,260 for 13:18 and 13:19 against a true 76 and 63. The lag was already 4 to 11 s on average before the gap, and the observer's colour marking from this morning did a getBlock per chain block inline on the same timers as the block flush.

+

What changed (commit "Observer: decoupled ingest, lag metric, per-minute by header time, feed self-check, restart loop"):

+
ChangeWhereMeasured after
Notifications only enqueue; a drain loop handles them in bounded batches; block flush, colour marking and certificate work on separate timers, none waits on anotherobserver.mjsqueue_depth 0
Mergesets taken from each notification's verbose data (bounded cache of 4,000); getBlock only on a miss, four in flightobserver.mjs0 RPC fetch failures in the first 5 min
live_state.observer_lag_s (now minus newest stored header time) and queue_depth; served by /api/live; the page shows "observer N s behind" past 30 s instead of "waiting for the first block"observer.mjs, site/api/live.mjs, site/live.htmllag 0.7 s at 13:27 UTC
blocks_per_minute and blocks_60s bucketed by the block's own timestamp, reseeded from the table on startobserver.mjs13:18 = 76, 13:19 = 63; max in the hour 110
Self-check: no blockAdded for 60 s while the node's block_count advances resubscribes; two failed attempts exit 2observer.mjsnot yet triggered
tools/observer/run.sh: restart loop, same env and log (/tmp/igneum-devnet/observer-mjs-v4.out)newrunning since 13:26 UTC
+

Open: the gap itself. Zero blocks for 78 minutes followed by every missed block arriving with its original header time is also what a stalled observer node delivering its own catch-up looks like; the lag metric now makes either case visible on the page within 30 s, and the self-check covers the dead-subscription case. Node logs for 11:57 to 13:18 UTC would settle which it was.

+

4 October 2026, PC 2 at the 14:20 boundary: a worker stuck on the previous epoch (root cause from the uploads)

+

Run win-1ccfe586-20261004-132055 (the Igneum Miner app, package 0.3.0 workers). Sequence from the node and miner uploads: the app reinstalled and its node restarted at 13:20:29 UTC in IBD from DAA 17,881, inside epoch 4 (seed 57ac7663...); the app exported packs\devnet from that node's template at once, so both workers started on 57ac. The boundary at DAA 18,000 passed about a minute later. The CUDA miner's first templates still carried next_epoch_seed c23e65dd... within lead: PREPARE sent at 14:20:57, prepared 0.9 s later (NVRTC 151 ms, cache 68, dataset 113, self-test 511 ms), switched to the prepared pair at 14:21:28, then 60 MH/s with 8 identities and 101 accepted blocks in 271 s. The OpenCL worker reported ready 43 s after the CUDA one (14:21:39); by then every template was on c23e as the current pair and no next epoch was within lead, so the miner never sent a prepare, and the worker answered 514 jobs in a row with epoch seed mismatch (one every 0.5 s, the miner's error back-off) for the rest of the run. The message text "holds prepared epoch 57ac..." is host.c's wording for a pack read at run time, which is why the stuck worker looked like a wrong prediction: PC 2's node announced the same next epoch (c23e) as the chain. No node on this PC predicted a different epoch, and the 57ac pack was simply the previous epoch's. Fixes: devnet-v4 miner 3bfe346f (prepare the current pair after a need line or three mismatches; exit 42 without prepare support; restart a ready worker with jobs queued and no job done for 60 s), workers emit need <epoch> <day> before the error. Not measured here: the swap time of the forced prepare on the RTX 5090 machine; the OpenCL run's "2 jobs in 154 s" were the two jobs before the first mismatch and are not a rate.

diff --git a/site/build.mjs b/site/build.mjs index d6d69ee3e..023147efb 100644 --- a/site/build.mjs +++ b/site/build.mjs @@ -3,6 +3,7 @@ // is never rendered here. // Runs on every deploy (Vercel build command) and locally with `node build.mjs`. import { readFileSync, writeFileSync, existsSync } from 'node:fs'; +import { execSync } from 'node:child_process'; import { join, dirname } from 'node:path'; import { fileURLToPath } from 'node:url'; @@ -134,7 +135,11 @@ footer{border-top:1px solid var(--line);padding-block:32px 48px;font-size:13px;c let built = []; if (existsSync(join(docs, 'bench-log.md'))) { - const src = readFileSync(join(docs, 'bench-log.md'), 'utf8'); + // the public /bench page renders a SCRUBBED copy (site/scrub-bench.sh: the public repository's export rules plus + // site-only ones); the build fails rather than publish a private name, host or address (review round 4, R4.6.1) + const scrubbed = join(here, '.bench-scrubbed.md'); + execSync(`"${join(here, 'scrub-bench.sh')}" "${join(docs, 'bench-log.md')}" "${scrubbed}"`, { stdio: 'inherit' }); + const src = readFileSync(scrubbed, 'utf8'); const { html, toc } = md(src); writeFileSync(join(here, 'bench.html'), page('Igneum engineering log', 'Every Igneum benchmark and test, with the hardware and the commands that produced it. Prototype numbers are labelled as such.', html, toc, '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.', diff --git a/site/journey.json b/site/journey.json index 1fbf25c08..9de3049f2 100644 --- a/site/journey.json +++ b/site/journey.json @@ -50,6 +50,31 @@ } ], "log": [ + { + "date": "2026-10-04", + "text": "One-click Windows workers: what the Apple M5 Max could measure", + "short": "One-click Windows workers" + }, + { + "date": "2026-10-04", + "text": "The gfx1036 worker fault and what the Apple M5 Max could and could not reproduce", + "short": "The gfx1036 worker fault and what the Apple M5 Max could and could…" + }, + { + "date": "2026-10-04", + "text": "Difficulty rule v2: the live oscillation, its cause, the DAG replay, the fix behind a height switch", + "short": "Difficulty rule v2" + }, + { + "date": "2026-10-04", + "text": "The observer stored nothing for 78 minutes, then 7,022 blocks in two minutes", + "short": "The observer stored nothing for 78 minutes, then 7,022 blocks in two…" + }, + { + "date": "2026-10-04", + "text": "PC 2 at the 14:20 boundary: a worker stuck on the previous epoch", + "short": "PC 2 at the 14:20 boundary: a worker stuck on the previous epoch" + }, { "date": "2026-10-04", "text": "The gfx1036 worker fault and what the Mac could and could not reproduce", @@ -224,11 +249,6 @@ "date": "2026-10-03", "text": "First prototype of the random-program lottery hash ran on an Apple M5 Max. GPU and CPU agreed bit for bit.", "short": "First prototype of the lottery hash on Apple silicon" - }, - { - "date": "2026-10-03", - "text": "Igneum named. Litepaper, brand and wallet designs published.", - "short": "Igneum named. Litepaper, brand and wallet designs published" } ] } diff --git a/site/scrub-bench.sh b/site/scrub-bench.sh new file mode 100755 index 000000000..a8e5519a8 --- /dev/null +++ b/site/scrub-bench.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash +# Scrubs a copy of docs/bench-log.md for the public /bench page: the same rules the public repository export applies +# (tools/sync.sh scrub + scrub_benchlog + the private sync.local.sed), plus the site-only rules below. Exits 1 if any +# forbidden string survives, so build.mjs refuses to render a page that names private machines, hosts or people. +# site/scrub-bench.sh +set -euo pipefail +IN="$1"; OUT="$2"; PUB="${IGNEUM_PUBLIC_REPO:-$HOME/Projects/igneum-public}" +cp "$IN" "$OUT" +if [ -f "$PUB/tools/sync.sh" ]; then + FNS="$(mktemp)"; sed -n '/^scrub() {/,/^}/p; /^scrub_benchlog() {/,/^}/p' "$PUB/tools/sync.sh" > "$FNS" + # shellcheck disable=SC1090 + source "$FNS"; rm -f "$FNS" + scrub "$OUT"; scrub_benchlog "$OUT" + [ -f "$PUB/tools/sync.local.sed" ] && perl -pi -f "$PUB/tools/sync.local.sed" "$OUT" +else + echo "scrub-bench: public repository scrub rules not found at $PUB; refusing to render /bench" >&2; exit 1 +fi +perl -pi -e ' + s/\bJosh\x27s\b/the maintainers\x27/g; s/\bJosh\b/the maintainers/g; + s/igneum-seed-\d+/the seed node/g; s/\bHetzner\b/the cloud provider/g; s/\bhcloud\b/the cloud CLI/g; + s/DESKTOP-[A-Z0-9]{7}//g; s#/root/[A-Za-z0-9_./-]*##g; s#/opt/igneum[A-Za-z0-9_./-]*##g; + s/dl\.igneum\.network[^ )`]*/the downloads host/g; s/,? ?log intake id \d+//g; s/\bintake id \d+\b/intake/g; + s/\bCLAUDE\.md\b/the design document/g; s/\(CLAUDE\.md, /(the design document, /g; + s/\b(\d{1,2}):(\d{2})(:\d{2})? BST\b/sprintf("%02d:%s%s UTC",($1+23)%24,$2,$3\/\/"")/ge; s/\(local time, UTC\+1\)/(UTC)/g; s/\bBST\b/UTC/g; + s/\b(\d+) (?:cloud provider|Hetzner) nodes\b/$1 cloud nodes/g; +' "$OUT" +PAT="$(dirname "$0")/../tools/ci/forbidden-strings.txt" +EXTRA='Josh|Hetzner|igneum-seed|/root/|/opt/igneum|DESKTOP-[A-Z0-9]{7}|dl\.igneum|intake id|CLAUDE\.md|\bBST\b|192\.168\.|DESKTOP-KMCV' +PATS="$(mktemp)"; [ -f "$PAT" ] && grep -vE '^\s*(#|$)' "$PAT" > "$PATS" || true; echo "$EXTRA" | tr '|' '\n' >> "$PATS" +HITS=$(grep -nE -f "$PATS" "$OUT" || true); rm -f "$PATS" +if [ -n "$HITS" ]; then echo "scrub-bench: forbidden strings remain:" >&2; echo "$HITS" | head -20 >&2; exit 1; fi +echo "scrub-bench: clean ($(wc -l < "$OUT") lines)"