tools/harness runs the standard consensus-attack catalogue against a private test network of our own igneumd nodes (127.0.0.1 ports 27200+, /tmp/igneum-harness, never the live devnet or the PC node), with a pass criterion per scenario from the spec and a measured result each. Built on the node fork's own crates (igneum-harness-sim on kaspa_utils::sim as simpa does; igneum-p2p-probe for the wire). Scenarios: 1 withholding, 2 timestamp edges and drift, 3 partition and heal, 4 eclipse, 5 malformed and boundary inputs on every p2p and RPC surface, 6 resource exhaustion, 7 fast-miner flood. Finality and difficulty-controller scenarios are stubs with their criteria written. bench-log: one dated entry, a row per scenario (criterion, measured, pass or fail). First run: 19 of 20 measured rows pass. Findings recorded in the entry: scenario 5 reproduces ledger M15 on HEAD (bogus past-day or DAA headers build a 256 MiB cache before rejection; the r3-fixes branch removes it); scenario 1 at 45% hash with burst withholding shows a selfish-mining blue-share gain (50.7% of blues), the one failing row. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
71 KiB
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.
| Seed | Loads/hash | Compile ms | Mhash/s | GB/s useful | CPU verify ms/warp | Verify |
|---|---|---|---|---|---|---|
| igneum-genesis | 104 | 49.6 (cold) | 45.2 | 18.8 | 0.015 | PASS |
| igneum-genesis/epoch1 | 104 | 20.3 | 48.4 | 20.1 | 0.019 | PASS |
| igneum-second-seed | 104 | 46.6 (cold) | 35.5 | 14.8 | 0.016 | PASS |
| igneum-second-seed/epoch1 | 144 | 18.7 | 35.4 | 20.4 | 0.017 | PASS |
| igneum-hourly | 128 | 52.0 (cold) | 36.6 | 18.7 | 0.021 | PASS |
| igneum-hourly/epoch1 | 128 | 21.6 | 37.5 | 19.2 | 0.017 | PASS |
| igneum-hourly/epoch2 | 120 | 23.7 | 36.6 | 17.6 | 0.016 | PASS |
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)
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.
| Pack | Loads/hash | Op mix | CPU interpreter vs Metal GPU (3 warps) | CUDA text in CPU emulation (clang, 32 threads/warp) |
|---|---|---|---|---|
| igneum-genesis | 104 | load=13 xor=13 sub=7 shfl=6 add=5 mulhi=5 mad=4 rotr=4 mul=3 rotl=3 or=1 | PASS 3/3 | PASS: dataset self-test, 3/3 warps standalone, warps 0 and 4096 in batch at 1 and 2 warps/block |
| igneum-hourly | 128 | load=16 add=8 xor=7 mad=5 mul=5 mulhi=5 shfl=5 rotl=4 or=3 rotr=3 sub=3 | PASS 3/3 | PASS: 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.
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)
Pack igneum-genesis, dataset 1024 MiB, 5 batches x 2^24 hashes, 1 warp per block.
| Card | Mhash/s at 1 GiB | GB/s useful | random loads/s | dataset fill | vectors |
|---|---|---|---|---|---|
| NVIDIA RTX 5090 (170 SMs, 32 GB) | 228.1 | 94.9 | 23.7 G | 0.66 ms, 1638 GB/s | 96/96 PASS, standalone and in batch |
| Apple M5 Max (40 GPU cores, same program, same day) | 45.2 | 18.8 | 4.6 G | 2.34 ms, 427 GB/s | 96/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.
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 MiB | Mhash/s | GB/s useful | random loads/s (G) |
|---|---|---|---|
| 4 | 1339.8 | 557 | 139.3 |
| 64 | 1352.7 | 563 | 140.7 |
| 256 | 269.8 | 112 | 28.1 |
| 512 | 241.8 | 101 | 25.2 |
| 1024 | 228.7 | 95 | 23.8 |
Second program igneum-hourly (128 loads per hash): 96/96 vectors PASS, 185.3 Mhash/s at 1 GiB, 23.7 G random loads/s.
Reading: the 5090 carries 96 MiB of L2. At 4 and 64 MiB the dataset sits inside it and the program runs about 5.8x faster than at 1 GiB. Past the L2 the rate settles at about 23.7 G random loads/s for both programs regardless of loads per hash (104 vs 128 loads gives 228 vs 185 Mhash/s, proportional), so the program is random-access bound once the dataset exceeds on-chip cache. Each 4-byte random load moves a 32-byte sector, so DRAM traffic is roughly 760 GB/s, approximate, against a quoted peak near 1.8 TB/s for this card. Design consequence: the dataset must stay well above any plausible on-chip cache, which the 2 GB genesis size and the growth schedule provide; a chip would need gigabytes of on-chip memory to escape the random-access limit. Still prototype numbers: dataset derivation remains closed-form until the 256 MB cache construction lands.
2026-10-03 sim/finality_v2.py, finality rule V2 with latency, partitions and eclipses (model, not hardware)
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)
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)
| Check | Result |
|---|---|
| 256 MiB cache, GPU vs host, all 67,108,864 words | PASS, FNV-1a 48c4f5bf24166b2e matches the Mac |
| Cache fill | 0.67 ms GPU, 223 ms one host thread |
| Dataset build from the cache, 1 GiB | 13.4 ms, 1,253 M items/s |
| Vectors, 3 warps, standalone and in batch | 96/96 PASS |
| Hash rate at 1 GiB | 228.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.
2026-10-03 igneum-pow: Rust crate bit-exact with proto-metal (consensus-engineer)
Machine: Apple M5 Max, one performance core, rustc 1.99.0 (rustup), release build with LTO. Crate at igneum-pow/ (seed, generator, memhard, verify, emit; CLI bench, export, hash), standard library only, serde_json as a dev-dependency for the pack tests.
Agreement with the Swift through proto-cuda/packs/: program.json instruction by instruction for igneum-genesis, igneum-genesis-mh and igneum-hourly (3 x 64 match); mixer rot/mul/rc match; cache head, last line and FNV-1a 64 48c4f5bf24166b2e match; dataset head, [MASK] and 64 sampled words match in all three packs; hash vectors 96/96 for igneum-genesis-mh (memory-hard) and 96/96 each for the two closed-form packs. 23 tests, all pass.
Emitted sources: kernel.cu, program.metal, kernel.cl and program.h byte-identical for all three packs, memhard.h and memhard.metal byte-identical for igneum-genesis-mh; igneum-pow export then diff -r against the packs differs only in the provenance string of vectors.json/vectors.h.
Found: proto-cuda/packs/igneum-genesis-mh/program.json is not valid JSON (main.swift line 1291 writes jhex(cacheLineMask) inside the "item" string). The Rust emitter writes the mask bare and the test normalises that line; fix pending in the Swift.
Cache fill, 256 MiB on one core: 175 to 181 ms in Rust (5 runs) against 184.5 to 190.6 ms Swift and 161.5 ms C++ host reference.
CPU verify per 32-lane warp, avg of 20, 1 GiB dataset: igneum-genesis 0.441 ms (Swift 0.649), /epoch1 0.411 (0.631), /epoch2 0.488 (0.701), igneum-second-seed 0.482 (0.801), igneum-second-seed/epoch1 at 144 loads and 4,608 items 0.579 (1.205). Cold single warps 0.41 to 0.87 ms (Swift 1.16 to 2.11). Closed form 0.002 ms (Swift 0.017).
Reading: Rust is 1.4x to 2.1x faster than the Swift verifier per warp with the same algorithm (register-major lanes, 32-lane interleaved item derivation); 10 ms gate margin about 17x steady, 11x on the worst cold warp. Cache fill is within 5 percent of the Swift and 10 percent slower than clang C++.
API for the fork: Epoch::memory_hard(seed, day) once per epoch (fills the cache), then epoch.hash(nonce), epoch.hash_warp(base), epoch.verify_block(nonce, target); emit::export_pack(&epoch, day, source) for miner programs. seed::seed_words_from_bytes is the boundary for the VDF output.
Not done: no GPU run from Rust; the 256-bit target mapping stays in the fork; the seed is still a string.
3 October 2026, proto-opencl: OpenCL path built and proven without AMD silicon (Apple OpenCL 1.2, pocl, CPU emulator)
Machine: the same Apple M5 Max. New: proto-opencl/host.c (C99, OpenCL 1.2 API), kernel.cl in every pack from --export-pack (same emitter, OpenCL C dialect; memory-hard core emitted in three dialects), WAVEFRONT.md, CPU emulator with a 32- or 64-wide sub-group. The AMD rig has not arrived; no AMD compiler or device has touched this code.
Exchange rule: sub_group_shuffle_xor only when the device lists cl_khr_subgroup_shuffle, the work-group is exactly 32 and the queried sub-group size for a 32-item work-group is exactly 32; otherwise a __local memory exchange with one barrier per exchange (two alternating buffers). Wave64 hardware (GCN, CDNA, RDNA in wave64) therefore takes the local-memory path and the hash never depends on the wave width.
Apple OpenCL 1.2 runtime, Apple M5 Max (40 CUs, OpenCL C 1.2, no sub-group extension, local-memory path): cache check PASS (all 2^26 words, FNV-1a 64 48c4f5bf24166b2e = Mac), dataset self-test PASS, 96/96 vectors standalone and in batch for igneum-genesis-mh; also 96/96 at --exchange local --group-warps 2 and --group-warps 4; closed-form packs igneum-genesis 96/96 and igneum-hourly 96/96.
Apple OpenCL hash rate (wall time; Apple's event timestamps are unusable), pack igneum-genesis-mh, 1 GiB, 5 x 2^24: 45.03 Mhash/s, 18.73 GB/s useful (repeat run 44.58). igneum-genesis 45.17, igneum-hourly 36.33 (128 loads). Metal on the same chip: 45.2. This is Apple's deprecated OpenCL on the M5 Max, NOT an AMD number.
Apple OpenCL sweep (3 batches): 4 MiB 573.7 Mhash/s, 64 MiB 178.9, 256 MiB 94.3, 512 MiB 68.8, 1024 MiB 45.0 (Metal sweep shape reproduced).
pocl 7.2 CPU device (OpenCL 3.0, LLVM 23, Khronos ICD loader, brew install pocl, needs SDKROOT): --exchange auto and --exchange subgroup built with -cl-std=CL3.0 -D IGNEUM_EXCHANGE=1 and ran the real sub_group_shuffle_xor text: cache FNV = Mac, 96/96 PASS. pocl's clGetKernelSubGroupInfoKHR returns CL_INVALID_OPERATION, so the new probe kernel (igneum_probe_subgroup, reports get_sub_group_size() 32) decided; --exchange local also 96/96.
CPU emulator (proto-opencl/emu, kernel.cl compiled as C++, 1 GiB dataset built on 256 host threads): 7 configurations all PASS with the identical batch fingerprint f99fb375b3abeaf5 over 2^13 outputs: exchange 0 with work-group 32/sub-group 32, 64/64, 32/64; exchange 1 (sub-group shuffles) with 32/32, 32/64, 64/64 (wave64 carrying two 32-lane units in one shuffle domain), 64/32.
Cross-implementation fingerprint at --batch-log2 13, base nonce 0, igneum-genesis-mh: Apple OpenCL f99fb375b3abeaf5, pocl sub-group f99fb375b3abeaf5, pocl local f99fb375b3abeaf5, emulator f99fb375b3abeaf5 (all 7). At 2^24 Apple OpenCL prints 98af644e993239e2 (reference for the AMD run). proto-cuda emulator re-run after the header changes (program.h, vectors.h, memhard.h now C99-safe): PASS.
Not demonstrated: any AMD compile or run, any AMD hash rate, the cost of the local-memory exchange on AMD, whether RDNA compiles igneum_hash as wave32 or wave64. Next: run the seven commands in proto-opencl/README.md on the AMD rig and paste the logs.
3 October 2026, AMD gfx1036 (Ryzen 7 9800X3D integrated RDNA 2 graphics, 1 compute unit), AMD OpenCL 2.1 driver 3652.0
Pack igneum-genesis-mh, memory-hard dataset, 1024 MiB, exchange via local memory (the driver lists no sub-group shuffle extension), wavefront 32.
| Check | Result |
|---|---|
| 256 MiB cache, device vs host vs Mac | PASS, 17.5 ms device fill |
| Dataset build from the cache, 1 GiB | 392 ms, 42.8 M items/s |
| Dataset self-test, 4 checks | PASS |
| Vectors, 3 warps, standalone and in batch | 96/96 PASS |
| Hash rate | 4.38 Mhash/s on one compute unit, 1.82 GB/s useful |
Reading: the third GPU vendor. The same memory-hard program now produces identical hashes on Apple Metal, NVIDIA CUDA, Apple OpenCL and AMD OpenCL, cache and dataset included. The AMD number is from a two-CU integrated chip sharing system memory and is a correctness result only; the discrete AMD card is still to come. The local-memory exchange path, which wave64 cards will also use, is now proven on AMD silicon.
3 October 2026, RTX 5090 through NVIDIA OpenCL (fourth compiler path on the same card)
Pack igneum-genesis-mh, 1024 MiB, local-memory exchange (NVIDIA's OpenCL lists no sub-group shuffle extension).
| Check | Result |
|---|---|
| Cache check and dataset self-test | PASS |
| Vectors | 96/96 PASS, batch fingerprint 98af644e993239e2, identical to the AMD gfx1036 run |
| Hash rate | 219.6 Mhash/s via OpenCL against 229.0 via CUDA, about 4% apart, approximate |
Reading: NVIDIA's OpenCL compiler and NVIDIA's CUDA compiler agree with each other, with AMD's OpenCL, with Apple's Metal and OpenCL, and with the CPU reference. The batch fingerprint over 16.7 million consecutive nonces is identical on the AMD integrated chip and the 5090, which is a far stronger statement than the 96 vectors alone. The local-memory exchange costs about 4% against CUDA's warp shuffle on this card, approximate.
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 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.
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.
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.
- Before (the pre-fix order, replayed by calling the engine with the header's own unvalidated day seed): 50 cold 256 MiB builds, 10,595 ms, and the honest day's entry is evicted (
KEEPwas 3). - After (the new order): 0 builds, all 50 rejected (
TimeTooOldorUnexpectedHeaderDaaScore) in 14 ms total. The live day stays resident. Tests: kaspa-pow--features igneum-pow8 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_processorcheap_checks_run_before_the_pow_enginepass; kaspa-p2p-flowspow_guard2 pass; the full kaspa-consensus release suite otherwise unchanged. M16 Metal note (R3.5, cheap reconfirmation only): the Mac--inline-datasetshortcut 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 inproto-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 = 24inproto-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 withskip_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).
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.
| Scenario | Criterion (spec) | Measured | Pass |
|---|---|---|---|
| 5 malformed and boundary inputs on every p2p message and RPC method the fork touches | rejected 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 s | pass |
| 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 s | pass |
| 1 withhold a=0.1 release every 5 | attacker blue share <= 0.1 + 2 sigma (0.014) over 1927 blues | blue share 5.4% (104 blue, 81 red of 186 made); honest reorgs depth:count 1:2 2:5 3:2 4:2 5:2 8:2, max 8 | pass |
| 1 withhold a=0.1 release every 20 | attacker blue share <= 0.1 + 2 sigma (0.014) over 1858 blues | blue share 1.2% (23 blue, 157 red of 186 made); honest reorgs depth:count 1:1 2:1 5:1, max 5 | pass |
| 1 withhold a=0.25 release every 5 | attacker blue share <= 0.25 + 2 sigma (0.020) over 1942 blues | blue share 22.0% (428 blue, 42 red of 474 made); honest reorgs depth:count 1:11 2:11 3:6 4:17 5:6 6:9 7:5 8:6 9:2 10:1, max 10 | pass |
| 1 withhold a=0.25 release every 20 | attacker blue share <= 0.25 + 2 sigma (0.021) over 1693 blues | blue share 12.6% (214 blue, 266 red of 489 made); honest reorgs depth:count 1:3 3:3 4:1 5:1 6:1 7:1 8:2 11:2 13:1 14:2 16:1 25:1 30:1, max 30 | pass |
| 1 withhold a=0.33 release every 5 | attacker blue share <= 0.33 + 2 sigma (0.021) over 2039 blues | blue share 31.5% (643 blue, 17 red of 663 made); honest reorgs depth:count 1:15 2:18 3:21 4:21 5:15 6:14 7:10 8:5 12:1 13:1, max 13 | pass |
| 1 withhold a=0.33 release every 20 | attacker blue share <= 0.33 + 2 sigma (0.024) over 1561 blues | blue share 27.4% (428 blue, 212 red of 640 made); honest reorgs depth:count 2:2 3:1 4:1 5:1 6:1 7:1 8:2 12:1 13:1 15:1 19:1 22:1 23:3 25:2 26:1 28:2 29:1 32:2 33:1, max 33 | pass |
| 1 withhold a=0.45 release every 5 | attacker blue share <= 0.45 + 2 sigma (0.022) over 2002 blues | blue share 44.2% (885 blue, 0 red of 886 made); honest reorgs depth:count 1:24 2:27 3:23 4:29 5:22 6:16 7:7 8:8 9:2 13:2 14:1 15:1, max 15 | pass |
| 1 withhold a=0.45 release every 20 | attacker blue share <= 0.45 + 2 sigma (0.024) over 1657 blues | blue share 50.7% (840 blue, 40 red of 886 made); honest reorgs depth:count 3:1 8:2 9:1 11:3 12:1 13:1 15:1 16:5 17:2 18:2 19:3 20:3 21:1 22:4 23:3 25:3 26:2 28:1 29:1 31:1 32:2 36:1, max 36 | FAIL |
| 3 partition 120 s | one chain after the merge-depth rule; reorg depth and time to heal recorded | one chain: true (blue scores within 3 at the end); healed in 10 s; losing-side reorg at heal 41 chain blocks (per node 41/41/0/2); rejects none | pass |
| 3 partition 600 s | one chain after the merge-depth rule; reorg depth and time to heal recorded | one chain: true (blue scores within 4 at the end); healed in 10 s; losing-side reorg at heal 234 chain blocks (per node 0/0/233/234); rejects none | pass |
| 3 partition 1800 s | one chain after the merge-depth rule; reorg depth and time to heal recorded | one chain: true (blue scores within 4 at the end); healed in 10 s; losing-side reorg at heal 920 chain blocks (per node 0/0/920/920); rejects none | pass |
| 3 partition 3700 s (beyond merge depth) | one chain after the merge-depth rule; reorg depth and time to heal recorded | one chain: true (blue scores within 4 at the end); healed in 10 s; losing-side reorg at heal 2160 chain blocks (per node 0/5/2160/2159); rejects MissingParents:1; MissingParents:1; MissingParents:5; MissingParents:5 | pass |
| 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 sink | honest template p95 worst 3.7 ms across loads (baseline 33.2 ms); template 500ps 500/s, submit 50ps 50/s, mempool 500ps 500/s; RSS growth template +4MB, submit +11MB, mempool +14MB; alive true; same sink true | pass |
| 4 eclipse 600 s | victim rejoins the honest chain on reconnection within the merge-depth bound; reorg depth recorded | victim rejoined 10 s after reconnection (blue-score gap to honest 3 at the end); victim reorg depth 149 chain blocks; adversary built 242 blocks that never entered the honest chain | pass |
| 4 eclipse 1800 s | victim rejoins the honest chain on reconnection within the merge-depth bound; reorg depth recorded | victim rejoined 10 s after reconnection (blue-score gap to honest 0 at the end); victim reorg depth 447 chain blocks; adversary built 497 blocks that never entered the honest chain | pass |
| 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 s | pass |
| 7 fast-miner flood, live (50 blocks/s from one peer) | node stays responsive: honest template p95 < 200 ms, both nodes alive, same sink | flood accepted 721 blocks in 60 s (12.0/s); honest template p50/p95/max 0.4/0.8/1.3 ms under flood (baseline 0.4/0.8/1.2); rss a 303->333 MB, b 305->332 MB; alive true; same sink true | pass |
| 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 exercised | stub: run s3 partition with voting miners on both sides; record every FinalityLock notification and assert no locked checkpoint changes hash after the heal. Needs the finality branch. | 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 lock | stub: run s4 eclipse with voting miners; assert the victim never reports a lock on the adversary chain that the honest chain does not also certify. Needs the finality branch. | 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).