diff --git a/docs/design/latency-ladder-bench/20261006T220642Z/alone-173.txt b/docs/design/latency-ladder-bench/20261006T220642Z/alone-173.txt new file mode 100644 index 000000000..3088b26b4 --- /dev/null +++ b/docs/design/latency-ladder-bench/20261006T220642Z/alone-173.txt @@ -0,0 +1,8 @@ +igneum-pow bench: seed "igneum-genesis", day "2026-10-03", dataset 2^28 words (memory-hard) +cache: fill 362.8 ms on one core (2^26 words, 256 MiB, 65536 chains of 64 ChaCha12 blocks), FNV-1a 64 48c4f5bf24166b2e +program: class mx8+sh256x173, 128 loads/hash, 512 bytes/hash, widths (1,4,16 words) [16, 0, 0], 4096 items/warp, mixer x8 (72 mixers/item), cache 2^26 words, op mix load=16 add=8 shfl=8 xor=6 mad=5 mul=5 mulhi=5 sub=4 rotl=3 rotr=3 or=1; epoch built in 365.3 ms +shadow: 256 instructions x 173 passes per iteration, 354304 shadow instructions per hash, op mix add=47 rotl=30 xor=30 shfl=29 mad=27 mul=22 sub=21 rotr=20 mulhi=18 or=12 +warp base 0: single cold run 7.445 ms, 4096 items derived, lane0 74dbb9c7808b6c12 lane31 14a606e8cc2988ee +warp base 4096: single cold run 7.241 ms, 4096 items derived, lane0 361270e067803cb3 lane31 979c376d679d74b2 +warp base 1000000: single cold run 7.106 ms, 4096 items derived, lane0 173be6655be48c63 lane31 697bcf4f945c5bf9 +CPU verify: 7.004 ms per 32-lane warp, avg of 50 (checksum e9371a9db9fc1817) diff --git a/docs/design/latency-ladder-bench/20261006T220642Z/alone-267.txt b/docs/design/latency-ladder-bench/20261006T220642Z/alone-267.txt new file mode 100644 index 000000000..9d22d53ae --- /dev/null +++ b/docs/design/latency-ladder-bench/20261006T220642Z/alone-267.txt @@ -0,0 +1,8 @@ +igneum-pow bench: seed "igneum-genesis", day "2026-10-03", dataset 2^28 words (memory-hard) +cache: fill 363.1 ms on one core (2^26 words, 256 MiB, 65536 chains of 64 ChaCha12 blocks), FNV-1a 64 48c4f5bf24166b2e +program: class mx8+sh256x267, 128 loads/hash, 512 bytes/hash, widths (1,4,16 words) [16, 0, 0], 4096 items/warp, mixer x8 (72 mixers/item), cache 2^26 words, op mix load=16 add=8 shfl=8 xor=6 mad=5 mul=5 mulhi=5 sub=4 rotl=3 rotr=3 or=1; epoch built in 372.7 ms +shadow: 256 instructions x 267 passes per iteration, 546816 shadow instructions per hash, op mix add=47 rotl=30 xor=30 shfl=29 mad=27 mul=22 sub=21 rotr=20 mulhi=18 or=12 +warp base 0: single cold run 8.905 ms, 4096 items derived, lane0 14e8e692f6f1f094 lane31 a25fb8045d382322 +warp base 4096: single cold run 8.731 ms, 4096 items derived, lane0 eed0185d04e6d213 lane31 6b9af2c86bf9db03 +warp base 1000000: single cold run 8.723 ms, 4096 items derived, lane0 a38b5590afcba174 lane31 39664cb2e742eb67 +CPU verify: 8.714 ms per 32-lane warp, avg of 50 (checksum a30f9fb7574d0db3) diff --git a/docs/design/latency-ladder-bench/20261006T220642Z/alone-27.txt b/docs/design/latency-ladder-bench/20261006T220642Z/alone-27.txt new file mode 100644 index 000000000..3a7ae855d --- /dev/null +++ b/docs/design/latency-ladder-bench/20261006T220642Z/alone-27.txt @@ -0,0 +1,8 @@ +igneum-pow bench: seed "igneum-genesis", day "2026-10-03", dataset 2^28 words (memory-hard) +cache: fill 368.9 ms on one core (2^26 words, 256 MiB, 65536 chains of 64 ChaCha12 blocks), FNV-1a 64 48c4f5bf24166b2e +program: class mx8+sh256x27, 128 loads/hash, 512 bytes/hash, widths (1,4,16 words) [16, 0, 0], 4096 items/warp, mixer x8 (72 mixers/item), cache 2^26 words, op mix load=16 add=8 shfl=8 xor=6 mad=5 mul=5 mulhi=5 sub=4 rotl=3 rotr=3 or=1; epoch built in 365.5 ms +shadow: 256 instructions x 27 passes per iteration, 55296 shadow instructions per hash, op mix add=47 rotl=30 xor=30 shfl=29 mad=27 mul=22 sub=21 rotr=20 mulhi=18 or=12 +warp base 0: single cold run 5.089 ms, 4096 items derived, lane0 2576769ee4a14c8d lane31 c58ddcb717dd3370 +warp base 4096: single cold run 4.944 ms, 4096 items derived, lane0 1ce77a600ec573b4 lane31 03600a05ffba0055 +warp base 1000000: single cold run 4.935 ms, 4096 items derived, lane0 6b390e64bbdd91ce lane31 91c944d603539c62 +CPU verify: 4.934 ms per 32-lane warp, avg of 50 (checksum 17e36e7905b81375) diff --git a/docs/design/latency-ladder-bench/20261006T220642Z/alone-35.txt b/docs/design/latency-ladder-bench/20261006T220642Z/alone-35.txt new file mode 100644 index 000000000..e4f8b865c --- /dev/null +++ b/docs/design/latency-ladder-bench/20261006T220642Z/alone-35.txt @@ -0,0 +1,8 @@ +igneum-pow bench: seed "igneum-genesis", day "2026-10-03", dataset 2^28 words (memory-hard) +cache: fill 364.2 ms on one core (2^26 words, 256 MiB, 65536 chains of 64 ChaCha12 blocks), FNV-1a 64 48c4f5bf24166b2e +program: class mx8+sh256x35, 128 loads/hash, 512 bytes/hash, widths (1,4,16 words) [16, 0, 0], 4096 items/warp, mixer x8 (72 mixers/item), cache 2^26 words, op mix load=16 add=8 shfl=8 xor=6 mad=5 mul=5 mulhi=5 sub=4 rotl=3 rotr=3 or=1; epoch built in 368.1 ms +shadow: 256 instructions x 35 passes per iteration, 71680 shadow instructions per hash, op mix add=47 rotl=30 xor=30 shfl=29 mad=27 mul=22 sub=21 rotr=20 mulhi=18 or=12 +warp base 0: single cold run 5.201 ms, 4096 items derived, lane0 177dd71be37996bc lane31 792114b1b40ad79f +warp base 4096: single cold run 5.057 ms, 4096 items derived, lane0 fe6701666e2e483a lane31 fc2105cb89fcb8b7 +warp base 1000000: single cold run 5.059 ms, 4096 items derived, lane0 6401cfb6e9ddfed6 lane31 89445da95a2e7184 +CPU verify: 5.067 ms per 32-lane warp, avg of 50 (checksum 4632b2b31b01b311) diff --git a/docs/design/latency-ladder-bench/20261006T220642Z/alone-53.txt b/docs/design/latency-ladder-bench/20261006T220642Z/alone-53.txt new file mode 100644 index 000000000..1244db264 --- /dev/null +++ b/docs/design/latency-ladder-bench/20261006T220642Z/alone-53.txt @@ -0,0 +1,8 @@ +igneum-pow bench: seed "igneum-genesis", day "2026-10-03", dataset 2^28 words (memory-hard) +cache: fill 363.5 ms on one core (2^26 words, 256 MiB, 65536 chains of 64 ChaCha12 blocks), FNV-1a 64 48c4f5bf24166b2e +program: class mx8+sh256x53, 128 loads/hash, 512 bytes/hash, widths (1,4,16 words) [16, 0, 0], 4096 items/warp, mixer x8 (72 mixers/item), cache 2^26 words, op mix load=16 add=8 shfl=8 xor=6 mad=5 mul=5 mulhi=5 sub=4 rotl=3 rotr=3 or=1; epoch built in 366.6 ms +shadow: 256 instructions x 53 passes per iteration, 108544 shadow instructions per hash, op mix add=47 rotl=30 xor=30 shfl=29 mad=27 mul=22 sub=21 rotr=20 mulhi=18 or=12 +warp base 0: single cold run 5.512 ms, 4096 items derived, lane0 9771dab715d71261 lane31 525676cf580b6f11 +warp base 4096: single cold run 5.350 ms, 4096 items derived, lane0 1afa4ca3c965c3f5 lane31 4e238e93b5f0101d +warp base 1000000: single cold run 5.342 ms, 4096 items derived, lane0 21a07085d7c0bd82 lane31 39fd0197590efb2a +CPU verify: 5.342 ms per 32-lane warp, avg of 50 (checksum 2efdcc71de21f16f) diff --git a/docs/design/latency-ladder-bench/20261006T220642Z/alone-88.txt b/docs/design/latency-ladder-bench/20261006T220642Z/alone-88.txt new file mode 100644 index 000000000..7742b5d5a --- /dev/null +++ b/docs/design/latency-ladder-bench/20261006T220642Z/alone-88.txt @@ -0,0 +1,8 @@ +igneum-pow bench: seed "igneum-genesis", day "2026-10-03", dataset 2^28 words (memory-hard) +cache: fill 364.3 ms on one core (2^26 words, 256 MiB, 65536 chains of 64 ChaCha12 blocks), FNV-1a 64 48c4f5bf24166b2e +program: class mx8+sh256x88, 128 loads/hash, 512 bytes/hash, widths (1,4,16 words) [16, 0, 0], 4096 items/warp, mixer x8 (72 mixers/item), cache 2^26 words, op mix load=16 add=8 shfl=8 xor=6 mad=5 mul=5 mulhi=5 sub=4 rotl=3 rotr=3 or=1; epoch built in 365.0 ms +shadow: 256 instructions x 88 passes per iteration, 180224 shadow instructions per hash, op mix add=47 rotl=30 xor=30 shfl=29 mad=27 mul=22 sub=21 rotr=20 mulhi=18 or=12 +warp base 0: single cold run 6.044 ms, 4096 items derived, lane0 84beec0429c7ef83 lane31 334b204a4a68c30d +warp base 4096: single cold run 5.887 ms, 4096 items derived, lane0 32be0f5b7d992255 lane31 2c8dbd443403bc84 +warp base 1000000: single cold run 5.887 ms, 4096 items derived, lane0 78541813d6547a59 lane31 fd7fb2e05bbc316f +CPU verify: 5.900 ms per 32-lane warp, avg of 50 (checksum 77189c69c05913b5) diff --git a/docs/design/latency-ladder-bench/20261006T220642Z/loaded-173.txt b/docs/design/latency-ladder-bench/20261006T220642Z/loaded-173.txt new file mode 100644 index 000000000..884f144eb --- /dev/null +++ b/docs/design/latency-ladder-bench/20261006T220642Z/loaded-173.txt @@ -0,0 +1,8 @@ +igneum-pow bench: seed "igneum-genesis", day "2026-10-03", dataset 2^28 words (memory-hard) +cache: fill 542.3 ms on one core (2^26 words, 256 MiB, 65536 chains of 64 ChaCha12 blocks), FNV-1a 64 48c4f5bf24166b2e +program: class mx8+sh256x173, 128 loads/hash, 512 bytes/hash, widths (1,4,16 words) [16, 0, 0], 4096 items/warp, mixer x8 (72 mixers/item), cache 2^26 words, op mix load=16 add=8 shfl=8 xor=6 mad=5 mul=5 mulhi=5 sub=4 rotl=3 rotr=3 or=1; epoch built in 486.8 ms +shadow: 256 instructions x 173 passes per iteration, 354304 shadow instructions per hash, op mix add=47 rotl=30 xor=30 shfl=29 mad=27 mul=22 sub=21 rotr=20 mulhi=18 or=12 +warp base 0: single cold run 7.386 ms, 4096 items derived, lane0 74dbb9c7808b6c12 lane31 14a606e8cc2988ee +warp base 4096: single cold run 7.244 ms, 4096 items derived, lane0 361270e067803cb3 lane31 979c376d679d74b2 +warp base 1000000: single cold run 7.254 ms, 4096 items derived, lane0 173be6655be48c63 lane31 697bcf4f945c5bf9 +CPU verify: 7.231 ms per 32-lane warp, avg of 50 (checksum e9371a9db9fc1817) diff --git a/docs/design/latency-ladder-bench/20261006T220642Z/loaded-267.txt b/docs/design/latency-ladder-bench/20261006T220642Z/loaded-267.txt new file mode 100644 index 000000000..53f5f02b0 --- /dev/null +++ b/docs/design/latency-ladder-bench/20261006T220642Z/loaded-267.txt @@ -0,0 +1,8 @@ +igneum-pow bench: seed "igneum-genesis", day "2026-10-03", dataset 2^28 words (memory-hard) +cache: fill 541.5 ms on one core (2^26 words, 256 MiB, 65536 chains of 64 ChaCha12 blocks), FNV-1a 64 48c4f5bf24166b2e +program: class mx8+sh256x267, 128 loads/hash, 512 bytes/hash, widths (1,4,16 words) [16, 0, 0], 4096 items/warp, mixer x8 (72 mixers/item), cache 2^26 words, op mix load=16 add=8 shfl=8 xor=6 mad=5 mul=5 mulhi=5 sub=4 rotl=3 rotr=3 or=1; epoch built in 525.0 ms +shadow: 256 instructions x 267 passes per iteration, 546816 shadow instructions per hash, op mix add=47 rotl=30 xor=30 shfl=29 mad=27 mul=22 sub=21 rotr=20 mulhi=18 or=12 +warp base 0: single cold run 8.834 ms, 4096 items derived, lane0 14e8e692f6f1f094 lane31 a25fb8045d382322 +warp base 4096: single cold run 8.701 ms, 4096 items derived, lane0 eed0185d04e6d213 lane31 6b9af2c86bf9db03 +warp base 1000000: single cold run 8.717 ms, 4096 items derived, lane0 a38b5590afcba174 lane31 39664cb2e742eb67 +CPU verify: 8.712 ms per 32-lane warp, avg of 50 (checksum a30f9fb7574d0db3) diff --git a/docs/design/latency-ladder-bench/20261006T220642Z/loaded-27.txt b/docs/design/latency-ladder-bench/20261006T220642Z/loaded-27.txt new file mode 100644 index 000000000..09881c447 --- /dev/null +++ b/docs/design/latency-ladder-bench/20261006T220642Z/loaded-27.txt @@ -0,0 +1,8 @@ +igneum-pow bench: seed "igneum-genesis", day "2026-10-03", dataset 2^28 words (memory-hard) +cache: fill 540.1 ms on one core (2^26 words, 256 MiB, 65536 chains of 64 ChaCha12 blocks), FNV-1a 64 48c4f5bf24166b2e +program: class mx8+sh256x27, 128 loads/hash, 512 bytes/hash, widths (1,4,16 words) [16, 0, 0], 4096 items/warp, mixer x8 (72 mixers/item), cache 2^26 words, op mix load=16 add=8 shfl=8 xor=6 mad=5 mul=5 mulhi=5 sub=4 rotl=3 rotr=3 or=1; epoch built in 426.2 ms +shadow: 256 instructions x 27 passes per iteration, 55296 shadow instructions per hash, op mix add=47 rotl=30 xor=30 shfl=29 mad=27 mul=22 sub=21 rotr=20 mulhi=18 or=12 +warp base 0: single cold run 5.078 ms, 4096 items derived, lane0 2576769ee4a14c8d lane31 c58ddcb717dd3370 +warp base 4096: single cold run 4.936 ms, 4096 items derived, lane0 1ce77a600ec573b4 lane31 03600a05ffba0055 +warp base 1000000: single cold run 4.928 ms, 4096 items derived, lane0 6b390e64bbdd91ce lane31 91c944d603539c62 +CPU verify: 4.931 ms per 32-lane warp, avg of 50 (checksum 17e36e7905b81375) diff --git a/docs/design/latency-ladder-bench/20261006T220642Z/loaded-35.txt b/docs/design/latency-ladder-bench/20261006T220642Z/loaded-35.txt new file mode 100644 index 000000000..57baf5112 --- /dev/null +++ b/docs/design/latency-ladder-bench/20261006T220642Z/loaded-35.txt @@ -0,0 +1,8 @@ +igneum-pow bench: seed "igneum-genesis", day "2026-10-03", dataset 2^28 words (memory-hard) +cache: fill 542.5 ms on one core (2^26 words, 256 MiB, 65536 chains of 64 ChaCha12 blocks), FNV-1a 64 48c4f5bf24166b2e +program: class mx8+sh256x35, 128 loads/hash, 512 bytes/hash, widths (1,4,16 words) [16, 0, 0], 4096 items/warp, mixer x8 (72 mixers/item), cache 2^26 words, op mix load=16 add=8 shfl=8 xor=6 mad=5 mul=5 mulhi=5 sub=4 rotl=3 rotr=3 or=1; epoch built in 429.9 ms +shadow: 256 instructions x 35 passes per iteration, 71680 shadow instructions per hash, op mix add=47 rotl=30 xor=30 shfl=29 mad=27 mul=22 sub=21 rotr=20 mulhi=18 or=12 +warp base 0: single cold run 5.201 ms, 4096 items derived, lane0 177dd71be37996bc lane31 792114b1b40ad79f +warp base 4096: single cold run 5.070 ms, 4096 items derived, lane0 fe6701666e2e483a lane31 fc2105cb89fcb8b7 +warp base 1000000: single cold run 5.062 ms, 4096 items derived, lane0 6401cfb6e9ddfed6 lane31 89445da95a2e7184 +CPU verify: 5.055 ms per 32-lane warp, avg of 50 (checksum 4632b2b31b01b311) diff --git a/docs/design/latency-ladder-bench/20261006T220642Z/loaded-53.txt b/docs/design/latency-ladder-bench/20261006T220642Z/loaded-53.txt new file mode 100644 index 000000000..ee8d4fab0 --- /dev/null +++ b/docs/design/latency-ladder-bench/20261006T220642Z/loaded-53.txt @@ -0,0 +1,8 @@ +igneum-pow bench: seed "igneum-genesis", day "2026-10-03", dataset 2^28 words (memory-hard) +cache: fill 542.1 ms on one core (2^26 words, 256 MiB, 65536 chains of 64 ChaCha12 blocks), FNV-1a 64 48c4f5bf24166b2e +program: class mx8+sh256x53, 128 loads/hash, 512 bytes/hash, widths (1,4,16 words) [16, 0, 0], 4096 items/warp, mixer x8 (72 mixers/item), cache 2^26 words, op mix load=16 add=8 shfl=8 xor=6 mad=5 mul=5 mulhi=5 sub=4 rotl=3 rotr=3 or=1; epoch built in 436.9 ms +shadow: 256 instructions x 53 passes per iteration, 108544 shadow instructions per hash, op mix add=47 rotl=30 xor=30 shfl=29 mad=27 mul=22 sub=21 rotr=20 mulhi=18 or=12 +warp base 0: single cold run 5.497 ms, 4096 items derived, lane0 9771dab715d71261 lane31 525676cf580b6f11 +warp base 4096: single cold run 5.360 ms, 4096 items derived, lane0 1afa4ca3c965c3f5 lane31 4e238e93b5f0101d +warp base 1000000: single cold run 5.359 ms, 4096 items derived, lane0 21a07085d7c0bd82 lane31 39fd0197590efb2a +CPU verify: 5.350 ms per 32-lane warp, avg of 50 (checksum 2efdcc71de21f16f) diff --git a/docs/design/latency-ladder-bench/20261006T220642Z/loaded-88.txt b/docs/design/latency-ladder-bench/20261006T220642Z/loaded-88.txt new file mode 100644 index 000000000..6195d3bb8 --- /dev/null +++ b/docs/design/latency-ladder-bench/20261006T220642Z/loaded-88.txt @@ -0,0 +1,8 @@ +igneum-pow bench: seed "igneum-genesis", day "2026-10-03", dataset 2^28 words (memory-hard) +cache: fill 539.7 ms on one core (2^26 words, 256 MiB, 65536 chains of 64 ChaCha12 blocks), FNV-1a 64 48c4f5bf24166b2e +program: class mx8+sh256x88, 128 loads/hash, 512 bytes/hash, widths (1,4,16 words) [16, 0, 0], 4096 items/warp, mixer x8 (72 mixers/item), cache 2^26 words, op mix load=16 add=8 shfl=8 xor=6 mad=5 mul=5 mulhi=5 sub=4 rotl=3 rotr=3 or=1; epoch built in 450.4 ms +shadow: 256 instructions x 88 passes per iteration, 180224 shadow instructions per hash, op mix add=47 rotl=30 xor=30 shfl=29 mad=27 mul=22 sub=21 rotr=20 mulhi=18 or=12 +warp base 0: single cold run 6.066 ms, 4096 items derived, lane0 84beec0429c7ef83 lane31 334b204a4a68c30d +warp base 4096: single cold run 5.881 ms, 4096 items derived, lane0 32be0f5b7d992255 lane31 2c8dbd443403bc84 +warp base 1000000: single cold run 5.884 ms, 4096 items derived, lane0 78541813d6547a59 lane31 fd7fb2e05bbc316f +CPU verify: 5.829 ms per 32-lane warp, avg of 50 (checksum 77189c69c05913b5) diff --git a/docs/design/latency-ladder-bench/20261006T220642Z/meta.txt b/docs/design/latency-ladder-bench/20261006T220642Z/meta.txt new file mode 100644 index 000000000..41c153304 --- /dev/null +++ b/docs/design/latency-ladder-bench/20261006T220642Z/meta.txt @@ -0,0 +1,2 @@ +host igneum-build-1 load 26.61 13.24 8.88 freq40 1519940 kHz siblings 40,88 pow 3f7623f208335f5b +load after 19.77 12.55 8.74 diff --git a/docs/design/latency-ladder-bench/20261006T220642Z/sibling-173.txt b/docs/design/latency-ladder-bench/20261006T220642Z/sibling-173.txt new file mode 100644 index 000000000..6b3f9aae8 --- /dev/null +++ b/docs/design/latency-ladder-bench/20261006T220642Z/sibling-173.txt @@ -0,0 +1,8 @@ +igneum-pow bench: seed "igneum-genesis", day "2026-10-03", dataset 2^28 words (memory-hard) +cache: fill 364.1 ms on one core (2^26 words, 256 MiB, 65536 chains of 64 ChaCha12 blocks), FNV-1a 64 48c4f5bf24166b2e +program: class mx8+sh256x173, 128 loads/hash, 512 bytes/hash, widths (1,4,16 words) [16, 0, 0], 4096 items/warp, mixer x8 (72 mixers/item), cache 2^26 words, op mix load=16 add=8 shfl=8 xor=6 mad=5 mul=5 mulhi=5 sub=4 rotl=3 rotr=3 or=1; epoch built in 547.4 ms +shadow: 256 instructions x 173 passes per iteration, 354304 shadow instructions per hash, op mix add=47 rotl=30 xor=30 shfl=29 mad=27 mul=22 sub=21 rotr=20 mulhi=18 or=12 +warp base 0: single cold run 9.918 ms, 4096 items derived, lane0 74dbb9c7808b6c12 lane31 14a606e8cc2988ee +warp base 4096: single cold run 9.703 ms, 4096 items derived, lane0 361270e067803cb3 lane31 979c376d679d74b2 +warp base 1000000: single cold run 9.691 ms, 4096 items derived, lane0 173be6655be48c63 lane31 697bcf4f945c5bf9 +CPU verify: 10.870 ms per 32-lane warp, avg of 50 (checksum e9371a9db9fc1817) diff --git a/docs/design/latency-ladder-bench/20261006T220642Z/sibling-267.txt b/docs/design/latency-ladder-bench/20261006T220642Z/sibling-267.txt new file mode 100644 index 000000000..c2b49f5b4 --- /dev/null +++ b/docs/design/latency-ladder-bench/20261006T220642Z/sibling-267.txt @@ -0,0 +1,8 @@ +igneum-pow bench: seed "igneum-genesis", day "2026-10-03", dataset 2^28 words (memory-hard) +cache: fill 364.6 ms on one core (2^26 words, 256 MiB, 65536 chains of 64 ChaCha12 blocks), FNV-1a 64 48c4f5bf24166b2e +program: class mx8+sh256x267, 128 loads/hash, 512 bytes/hash, widths (1,4,16 words) [16, 0, 0], 4096 items/warp, mixer x8 (72 mixers/item), cache 2^26 words, op mix load=16 add=8 shfl=8 xor=6 mad=5 mul=5 mulhi=5 sub=4 rotl=3 rotr=3 or=1; epoch built in 545.8 ms +shadow: 256 instructions x 267 passes per iteration, 546816 shadow instructions per hash, op mix add=47 rotl=30 xor=30 shfl=29 mad=27 mul=22 sub=21 rotr=20 mulhi=18 or=12 +warp base 0: single cold run 11.728 ms, 4096 items derived, lane0 14e8e692f6f1f094 lane31 a25fb8045d382322 +warp base 4096: single cold run 11.491 ms, 4096 items derived, lane0 eed0185d04e6d213 lane31 6b9af2c86bf9db03 +warp base 1000000: single cold run 11.493 ms, 4096 items derived, lane0 a38b5590afcba174 lane31 39664cb2e742eb67 +CPU verify: 13.125 ms per 32-lane warp, avg of 50 (checksum a30f9fb7574d0db3) diff --git a/docs/design/latency-ladder-bench/20261006T220642Z/sibling-27.txt b/docs/design/latency-ladder-bench/20261006T220642Z/sibling-27.txt new file mode 100644 index 000000000..a6eae844d --- /dev/null +++ b/docs/design/latency-ladder-bench/20261006T220642Z/sibling-27.txt @@ -0,0 +1,8 @@ +igneum-pow bench: seed "igneum-genesis", day "2026-10-03", dataset 2^28 words (memory-hard) +cache: fill 365.9 ms on one core (2^26 words, 256 MiB, 65536 chains of 64 ChaCha12 blocks), FNV-1a 64 48c4f5bf24166b2e +program: class mx8+sh256x27, 128 loads/hash, 512 bytes/hash, widths (1,4,16 words) [16, 0, 0], 4096 items/warp, mixer x8 (72 mixers/item), cache 2^26 words, op mix load=16 add=8 shfl=8 xor=6 mad=5 mul=5 mulhi=5 sub=4 rotl=3 rotr=3 or=1; epoch built in 545.3 ms +shadow: 256 instructions x 27 passes per iteration, 55296 shadow instructions per hash, op mix add=47 rotl=30 xor=30 shfl=29 mad=27 mul=22 sub=21 rotr=20 mulhi=18 or=12 +warp base 0: single cold run 7.146 ms, 4096 items derived, lane0 2576769ee4a14c8d lane31 c58ddcb717dd3370 +warp base 4096: single cold run 6.915 ms, 4096 items derived, lane0 1ce77a600ec573b4 lane31 03600a05ffba0055 +warp base 1000000: single cold run 6.897 ms, 4096 items derived, lane0 6b390e64bbdd91ce lane31 91c944d603539c62 +CPU verify: 7.421 ms per 32-lane warp, avg of 50 (checksum 17e36e7905b81375) diff --git a/docs/design/latency-ladder-bench/20261006T220642Z/sibling-35.txt b/docs/design/latency-ladder-bench/20261006T220642Z/sibling-35.txt new file mode 100644 index 000000000..f84e1c90c --- /dev/null +++ b/docs/design/latency-ladder-bench/20261006T220642Z/sibling-35.txt @@ -0,0 +1,8 @@ +igneum-pow bench: seed "igneum-genesis", day "2026-10-03", dataset 2^28 words (memory-hard) +cache: fill 364.3 ms on one core (2^26 words, 256 MiB, 65536 chains of 64 ChaCha12 blocks), FNV-1a 64 48c4f5bf24166b2e +program: class mx8+sh256x35, 128 loads/hash, 512 bytes/hash, widths (1,4,16 words) [16, 0, 0], 4096 items/warp, mixer x8 (72 mixers/item), cache 2^26 words, op mix load=16 add=8 shfl=8 xor=6 mad=5 mul=5 mulhi=5 sub=4 rotl=3 rotr=3 or=1; epoch built in 547.5 ms +shadow: 256 instructions x 35 passes per iteration, 71680 shadow instructions per hash, op mix add=47 rotl=30 xor=30 shfl=29 mad=27 mul=22 sub=21 rotr=20 mulhi=18 or=12 +warp base 0: single cold run 7.291 ms, 4096 items derived, lane0 177dd71be37996bc lane31 792114b1b40ad79f +warp base 4096: single cold run 7.057 ms, 4096 items derived, lane0 fe6701666e2e483a lane31 fc2105cb89fcb8b7 +warp base 1000000: single cold run 7.070 ms, 4096 items derived, lane0 6401cfb6e9ddfed6 lane31 89445da95a2e7184 +CPU verify: 7.587 ms per 32-lane warp, avg of 50 (checksum 4632b2b31b01b311) diff --git a/docs/design/latency-ladder-bench/20261006T220642Z/sibling-53.txt b/docs/design/latency-ladder-bench/20261006T220642Z/sibling-53.txt new file mode 100644 index 000000000..dfc85778e --- /dev/null +++ b/docs/design/latency-ladder-bench/20261006T220642Z/sibling-53.txt @@ -0,0 +1,8 @@ +igneum-pow bench: seed "igneum-genesis", day "2026-10-03", dataset 2^28 words (memory-hard) +cache: fill 364.3 ms on one core (2^26 words, 256 MiB, 65536 chains of 64 ChaCha12 blocks), FNV-1a 64 48c4f5bf24166b2e +program: class mx8+sh256x53, 128 loads/hash, 512 bytes/hash, widths (1,4,16 words) [16, 0, 0], 4096 items/warp, mixer x8 (72 mixers/item), cache 2^26 words, op mix load=16 add=8 shfl=8 xor=6 mad=5 mul=5 mulhi=5 sub=4 rotl=3 rotr=3 or=1; epoch built in 547.1 ms +shadow: 256 instructions x 53 passes per iteration, 108544 shadow instructions per hash, op mix add=47 rotl=30 xor=30 shfl=29 mad=27 mul=22 sub=21 rotr=20 mulhi=18 or=12 +warp base 0: single cold run 7.627 ms, 4096 items derived, lane0 9771dab715d71261 lane31 525676cf580b6f11 +warp base 4096: single cold run 7.404 ms, 4096 items derived, lane0 1afa4ca3c965c3f5 lane31 4e238e93b5f0101d +warp base 1000000: single cold run 7.380 ms, 4096 items derived, lane0 21a07085d7c0bd82 lane31 39fd0197590efb2a +CPU verify: 8.023 ms per 32-lane warp, avg of 50 (checksum 2efdcc71de21f16f) diff --git a/docs/design/latency-ladder-bench/20261006T220642Z/sibling-88.txt b/docs/design/latency-ladder-bench/20261006T220642Z/sibling-88.txt new file mode 100644 index 000000000..abd5d89b7 --- /dev/null +++ b/docs/design/latency-ladder-bench/20261006T220642Z/sibling-88.txt @@ -0,0 +1,8 @@ +igneum-pow bench: seed "igneum-genesis", day "2026-10-03", dataset 2^28 words (memory-hard) +cache: fill 364.3 ms on one core (2^26 words, 256 MiB, 65536 chains of 64 ChaCha12 blocks), FNV-1a 64 48c4f5bf24166b2e +program: class mx8+sh256x88, 128 loads/hash, 512 bytes/hash, widths (1,4,16 words) [16, 0, 0], 4096 items/warp, mixer x8 (72 mixers/item), cache 2^26 words, op mix load=16 add=8 shfl=8 xor=6 mad=5 mul=5 mulhi=5 sub=4 rotl=3 rotr=3 or=1; epoch built in 544.8 ms +shadow: 256 instructions x 88 passes per iteration, 180224 shadow instructions per hash, op mix add=47 rotl=30 xor=30 shfl=29 mad=27 mul=22 sub=21 rotr=20 mulhi=18 or=12 +warp base 0: single cold run 8.290 ms, 4096 items derived, lane0 84beec0429c7ef83 lane31 334b204a4a68c30d +warp base 4096: single cold run 8.085 ms, 4096 items derived, lane0 32be0f5b7d992255 lane31 2c8dbd443403bc84 +warp base 1000000: single cold run 8.051 ms, 4096 items derived, lane0 78541813d6547a59 lane31 fd7fb2e05bbc316f +CPU verify: 8.846 ms per 32-lane warp, avg of 50 (checksum 77189c69c05913b5) diff --git a/docs/design/latency-ladder-bench/20261006T220642Z/table.md b/docs/design/latency-ladder-bench/20261006T220642Z/table.md new file mode 100644 index 000000000..21d142f34 --- /dev/null +++ b/docs/design/latency-ladder-bench/20261006T220642Z/table.md @@ -0,0 +1,10 @@ +host igneum-build-1 load 26.61 13.24 8.88 freq40 1519940 kHz siblings 40,88 pow 3f7623f208335f5b +load after 19.77 12.55 8.74 +| Rung | reps | Shadow instrs per hash | Counted ops (approx) | Cold, core alone (ms) | Avg of 50, alone (ms) | Cold, sibling loaded (ms) | Avg of 50, sibling loaded (ms) | Under 10 ms loaded | +|---|---|---|---|---|---|---|---|---| +| 0 | 27 | 55296 | 102,122 | 5.09 | 4.93 | 5.08 | 4.93 | yes | +| 1 | 35 | 71680 | 132,105 | 5.20 | 5.07 | 5.20 | 5.05 | yes | +| 2 | 53 | 108544 | 199,566 | 5.51 | 5.34 | 5.50 | 5.35 | yes | +| 3 | 88 | 180224 | 330,740 | 6.04 | 5.90 | 6.07 | 5.83 | yes | +| 4 | 173 | 354304 | 649,307 | 7.45 | 7.00 | 7.39 | 7.23 | yes | +| 5 | 267 | 546816 | 1,001,604 | 8.90 | 8.71 | 8.83 | 8.71 | yes | diff --git a/docs/design/latency-ladder-bench/20261006T221034Z/alone-173.txt b/docs/design/latency-ladder-bench/20261006T221034Z/alone-173.txt new file mode 100644 index 000000000..7042b0f6a --- /dev/null +++ b/docs/design/latency-ladder-bench/20261006T221034Z/alone-173.txt @@ -0,0 +1,8 @@ +igneum-pow bench: seed "igneum-genesis", day "2026-10-03", dataset 2^28 words (memory-hard) +cache: fill 363.1 ms on one core (2^26 words, 256 MiB, 65536 chains of 64 ChaCha12 blocks), FNV-1a 64 48c4f5bf24166b2e +program: class mx8+sh256x173, 128 loads/hash, 512 bytes/hash, widths (1,4,16 words) [16, 0, 0], 4096 items/warp, mixer x8 (72 mixers/item), cache 2^26 words, op mix load=16 add=8 shfl=8 xor=6 mad=5 mul=5 mulhi=5 sub=4 rotl=3 rotr=3 or=1; epoch built in 365.6 ms +shadow: 256 instructions x 173 passes per iteration, 354304 shadow instructions per hash, op mix add=47 rotl=30 xor=30 shfl=29 mad=27 mul=22 sub=21 rotr=20 mulhi=18 or=12 +warp base 0: single cold run 7.380 ms, 4096 items derived, lane0 74dbb9c7808b6c12 lane31 14a606e8cc2988ee +warp base 4096: single cold run 7.232 ms, 4096 items derived, lane0 361270e067803cb3 lane31 979c376d679d74b2 +warp base 1000000: single cold run 7.230 ms, 4096 items derived, lane0 173be6655be48c63 lane31 697bcf4f945c5bf9 +CPU verify: 7.233 ms per 32-lane warp, avg of 50 (checksum e9371a9db9fc1817) diff --git a/docs/design/latency-ladder-bench/20261006T221034Z/alone-267.txt b/docs/design/latency-ladder-bench/20261006T221034Z/alone-267.txt new file mode 100644 index 000000000..5836e81e2 --- /dev/null +++ b/docs/design/latency-ladder-bench/20261006T221034Z/alone-267.txt @@ -0,0 +1,8 @@ +igneum-pow bench: seed "igneum-genesis", day "2026-10-03", dataset 2^28 words (memory-hard) +cache: fill 364.0 ms on one core (2^26 words, 256 MiB, 65536 chains of 64 ChaCha12 blocks), FNV-1a 64 48c4f5bf24166b2e +program: class mx8+sh256x267, 128 loads/hash, 512 bytes/hash, widths (1,4,16 words) [16, 0, 0], 4096 items/warp, mixer x8 (72 mixers/item), cache 2^26 words, op mix load=16 add=8 shfl=8 xor=6 mad=5 mul=5 mulhi=5 sub=4 rotl=3 rotr=3 or=1; epoch built in 367.0 ms +shadow: 256 instructions x 267 passes per iteration, 546816 shadow instructions per hash, op mix add=47 rotl=30 xor=30 shfl=29 mad=27 mul=22 sub=21 rotr=20 mulhi=18 or=12 +warp base 0: single cold run 8.881 ms, 4096 items derived, lane0 14e8e692f6f1f094 lane31 a25fb8045d382322 +warp base 4096: single cold run 8.723 ms, 4096 items derived, lane0 eed0185d04e6d213 lane31 6b9af2c86bf9db03 +warp base 1000000: single cold run 8.707 ms, 4096 items derived, lane0 a38b5590afcba174 lane31 39664cb2e742eb67 +CPU verify: 8.499 ms per 32-lane warp, avg of 50 (checksum a30f9fb7574d0db3) diff --git a/docs/design/latency-ladder-bench/20261006T221034Z/alone-27.txt b/docs/design/latency-ladder-bench/20261006T221034Z/alone-27.txt new file mode 100644 index 000000000..43a552292 --- /dev/null +++ b/docs/design/latency-ladder-bench/20261006T221034Z/alone-27.txt @@ -0,0 +1,8 @@ +igneum-pow bench: seed "igneum-genesis", day "2026-10-03", dataset 2^28 words (memory-hard) +cache: fill 460.7 ms on one core (2^26 words, 256 MiB, 65536 chains of 64 ChaCha12 blocks), FNV-1a 64 48c4f5bf24166b2e +program: class mx8+sh256x27, 128 loads/hash, 512 bytes/hash, widths (1,4,16 words) [16, 0, 0], 4096 items/warp, mixer x8 (72 mixers/item), cache 2^26 words, op mix load=16 add=8 shfl=8 xor=6 mad=5 mul=5 mulhi=5 sub=4 rotl=3 rotr=3 or=1; epoch built in 368.6 ms +shadow: 256 instructions x 27 passes per iteration, 55296 shadow instructions per hash, op mix add=47 rotl=30 xor=30 shfl=29 mad=27 mul=22 sub=21 rotr=20 mulhi=18 or=12 +warp base 0: single cold run 5.141 ms, 4096 items derived, lane0 2576769ee4a14c8d lane31 c58ddcb717dd3370 +warp base 4096: single cold run 4.954 ms, 4096 items derived, lane0 1ce77a600ec573b4 lane31 03600a05ffba0055 +warp base 1000000: single cold run 4.969 ms, 4096 items derived, lane0 6b390e64bbdd91ce lane31 91c944d603539c62 +CPU verify: 4.946 ms per 32-lane warp, avg of 50 (checksum 17e36e7905b81375) diff --git a/docs/design/latency-ladder-bench/20261006T221034Z/alone-35.txt b/docs/design/latency-ladder-bench/20261006T221034Z/alone-35.txt new file mode 100644 index 000000000..cf4d5753f --- /dev/null +++ b/docs/design/latency-ladder-bench/20261006T221034Z/alone-35.txt @@ -0,0 +1,8 @@ +igneum-pow bench: seed "igneum-genesis", day "2026-10-03", dataset 2^28 words (memory-hard) +cache: fill 456.9 ms on one core (2^26 words, 256 MiB, 65536 chains of 64 ChaCha12 blocks), FNV-1a 64 48c4f5bf24166b2e +program: class mx8+sh256x35, 128 loads/hash, 512 bytes/hash, widths (1,4,16 words) [16, 0, 0], 4096 items/warp, mixer x8 (72 mixers/item), cache 2^26 words, op mix load=16 add=8 shfl=8 xor=6 mad=5 mul=5 mulhi=5 sub=4 rotl=3 rotr=3 or=1; epoch built in 437.3 ms +shadow: 256 instructions x 35 passes per iteration, 71680 shadow instructions per hash, op mix add=47 rotl=30 xor=30 shfl=29 mad=27 mul=22 sub=21 rotr=20 mulhi=18 or=12 +warp base 0: single cold run 5.571 ms, 4096 items derived, lane0 177dd71be37996bc lane31 792114b1b40ad79f +warp base 4096: single cold run 5.327 ms, 4096 items derived, lane0 fe6701666e2e483a lane31 fc2105cb89fcb8b7 +warp base 1000000: single cold run 5.203 ms, 4096 items derived, lane0 6401cfb6e9ddfed6 lane31 89445da95a2e7184 +CPU verify: 5.521 ms per 32-lane warp, avg of 50 (checksum 4632b2b31b01b311) diff --git a/docs/design/latency-ladder-bench/20261006T221034Z/alone-53.txt b/docs/design/latency-ladder-bench/20261006T221034Z/alone-53.txt new file mode 100644 index 000000000..2584866e3 --- /dev/null +++ b/docs/design/latency-ladder-bench/20261006T221034Z/alone-53.txt @@ -0,0 +1,8 @@ +igneum-pow bench: seed "igneum-genesis", day "2026-10-03", dataset 2^28 words (memory-hard) +cache: fill 368.7 ms on one core (2^26 words, 256 MiB, 65536 chains of 64 ChaCha12 blocks), FNV-1a 64 48c4f5bf24166b2e +program: class mx8+sh256x53, 128 loads/hash, 512 bytes/hash, widths (1,4,16 words) [16, 0, 0], 4096 items/warp, mixer x8 (72 mixers/item), cache 2^26 words, op mix load=16 add=8 shfl=8 xor=6 mad=5 mul=5 mulhi=5 sub=4 rotl=3 rotr=3 or=1; epoch built in 364.8 ms +shadow: 256 instructions x 53 passes per iteration, 108544 shadow instructions per hash, op mix add=47 rotl=30 xor=30 shfl=29 mad=27 mul=22 sub=21 rotr=20 mulhi=18 or=12 +warp base 0: single cold run 5.569 ms, 4096 items derived, lane0 9771dab715d71261 lane31 525676cf580b6f11 +warp base 4096: single cold run 5.352 ms, 4096 items derived, lane0 1afa4ca3c965c3f5 lane31 4e238e93b5f0101d +warp base 1000000: single cold run 5.366 ms, 4096 items derived, lane0 21a07085d7c0bd82 lane31 39fd0197590efb2a +CPU verify: 5.351 ms per 32-lane warp, avg of 50 (checksum 2efdcc71de21f16f) diff --git a/docs/design/latency-ladder-bench/20261006T221034Z/alone-88.txt b/docs/design/latency-ladder-bench/20261006T221034Z/alone-88.txt new file mode 100644 index 000000000..8c99e7388 --- /dev/null +++ b/docs/design/latency-ladder-bench/20261006T221034Z/alone-88.txt @@ -0,0 +1,8 @@ +igneum-pow bench: seed "igneum-genesis", day "2026-10-03", dataset 2^28 words (memory-hard) +cache: fill 360.9 ms on one core (2^26 words, 256 MiB, 65536 chains of 64 ChaCha12 blocks), FNV-1a 64 48c4f5bf24166b2e +program: class mx8+sh256x88, 128 loads/hash, 512 bytes/hash, widths (1,4,16 words) [16, 0, 0], 4096 items/warp, mixer x8 (72 mixers/item), cache 2^26 words, op mix load=16 add=8 shfl=8 xor=6 mad=5 mul=5 mulhi=5 sub=4 rotl=3 rotr=3 or=1; epoch built in 361.8 ms +shadow: 256 instructions x 88 passes per iteration, 180224 shadow instructions per hash, op mix add=47 rotl=30 xor=30 shfl=29 mad=27 mul=22 sub=21 rotr=20 mulhi=18 or=12 +warp base 0: single cold run 6.073 ms, 4096 items derived, lane0 84beec0429c7ef83 lane31 334b204a4a68c30d +warp base 4096: single cold run 5.900 ms, 4096 items derived, lane0 32be0f5b7d992255 lane31 2c8dbd443403bc84 +warp base 1000000: single cold run 5.883 ms, 4096 items derived, lane0 78541813d6547a59 lane31 fd7fb2e05bbc316f +CPU verify: 5.892 ms per 32-lane warp, avg of 50 (checksum 77189c69c05913b5) diff --git a/docs/design/latency-ladder-bench/20261006T221034Z/loaded-173.txt b/docs/design/latency-ladder-bench/20261006T221034Z/loaded-173.txt new file mode 100644 index 000000000..d923374f4 --- /dev/null +++ b/docs/design/latency-ladder-bench/20261006T221034Z/loaded-173.txt @@ -0,0 +1,8 @@ +igneum-pow bench: seed "igneum-genesis", day "2026-10-03", dataset 2^28 words (memory-hard) +cache: fill 548.6 ms on one core (2^26 words, 256 MiB, 65536 chains of 64 ChaCha12 blocks), FNV-1a 64 48c4f5bf24166b2e +program: class mx8+sh256x173, 128 loads/hash, 512 bytes/hash, widths (1,4,16 words) [16, 0, 0], 4096 items/warp, mixer x8 (72 mixers/item), cache 2^26 words, op mix load=16 add=8 shfl=8 xor=6 mad=5 mul=5 mulhi=5 sub=4 rotl=3 rotr=3 or=1; epoch built in 551.8 ms +shadow: 256 instructions x 173 passes per iteration, 354304 shadow instructions per hash, op mix add=47 rotl=30 xor=30 shfl=29 mad=27 mul=22 sub=21 rotr=20 mulhi=18 or=12 +warp base 0: single cold run 12.383 ms, 4096 items derived, lane0 74dbb9c7808b6c12 lane31 14a606e8cc2988ee +warp base 4096: single cold run 11.970 ms, 4096 items derived, lane0 361270e067803cb3 lane31 979c376d679d74b2 +warp base 1000000: single cold run 12.177 ms, 4096 items derived, lane0 173be6655be48c63 lane31 697bcf4f945c5bf9 +CPU verify: 12.091 ms per 32-lane warp, avg of 50 (checksum e9371a9db9fc1817) diff --git a/docs/design/latency-ladder-bench/20261006T221034Z/loaded-267.txt b/docs/design/latency-ladder-bench/20261006T221034Z/loaded-267.txt new file mode 100644 index 000000000..571144b4d --- /dev/null +++ b/docs/design/latency-ladder-bench/20261006T221034Z/loaded-267.txt @@ -0,0 +1,8 @@ +igneum-pow bench: seed "igneum-genesis", day "2026-10-03", dataset 2^28 words (memory-hard) +cache: fill 548.8 ms on one core (2^26 words, 256 MiB, 65536 chains of 64 ChaCha12 blocks), FNV-1a 64 48c4f5bf24166b2e +program: class mx8+sh256x267, 128 loads/hash, 512 bytes/hash, widths (1,4,16 words) [16, 0, 0], 4096 items/warp, mixer x8 (72 mixers/item), cache 2^26 words, op mix load=16 add=8 shfl=8 xor=6 mad=5 mul=5 mulhi=5 sub=4 rotl=3 rotr=3 or=1; epoch built in 556.3 ms +shadow: 256 instructions x 267 passes per iteration, 546816 shadow instructions per hash, op mix add=47 rotl=30 xor=30 shfl=29 mad=27 mul=22 sub=21 rotr=20 mulhi=18 or=12 +warp base 0: single cold run 14.959 ms, 4096 items derived, lane0 14e8e692f6f1f094 lane31 a25fb8045d382322 +warp base 4096: single cold run 14.619 ms, 4096 items derived, lane0 eed0185d04e6d213 lane31 6b9af2c86bf9db03 +warp base 1000000: single cold run 14.437 ms, 4096 items derived, lane0 a38b5590afcba174 lane31 39664cb2e742eb67 +CPU verify: 14.580 ms per 32-lane warp, avg of 50 (checksum a30f9fb7574d0db3) diff --git a/docs/design/latency-ladder-bench/20261006T221034Z/loaded-27.txt b/docs/design/latency-ladder-bench/20261006T221034Z/loaded-27.txt new file mode 100644 index 000000000..b9e4a648f --- /dev/null +++ b/docs/design/latency-ladder-bench/20261006T221034Z/loaded-27.txt @@ -0,0 +1,8 @@ +igneum-pow bench: seed "igneum-genesis", day "2026-10-03", dataset 2^28 words (memory-hard) +cache: fill 551.2 ms on one core (2^26 words, 256 MiB, 65536 chains of 64 ChaCha12 blocks), FNV-1a 64 48c4f5bf24166b2e +program: class mx8+sh256x27, 128 loads/hash, 512 bytes/hash, widths (1,4,16 words) [16, 0, 0], 4096 items/warp, mixer x8 (72 mixers/item), cache 2^26 words, op mix load=16 add=8 shfl=8 xor=6 mad=5 mul=5 mulhi=5 sub=4 rotl=3 rotr=3 or=1; epoch built in 561.9 ms +shadow: 256 instructions x 27 passes per iteration, 55296 shadow instructions per hash, op mix add=47 rotl=30 xor=30 shfl=29 mad=27 mul=22 sub=21 rotr=20 mulhi=18 or=12 +warp base 0: single cold run 8.769 ms, 4096 items derived, lane0 2576769ee4a14c8d lane31 c58ddcb717dd3370 +warp base 4096: single cold run 8.503 ms, 4096 items derived, lane0 1ce77a600ec573b4 lane31 03600a05ffba0055 +warp base 1000000: single cold run 8.469 ms, 4096 items derived, lane0 6b390e64bbdd91ce lane31 91c944d603539c62 +CPU verify: 8.674 ms per 32-lane warp, avg of 50 (checksum 17e36e7905b81375) diff --git a/docs/design/latency-ladder-bench/20261006T221034Z/loaded-35.txt b/docs/design/latency-ladder-bench/20261006T221034Z/loaded-35.txt new file mode 100644 index 000000000..2b588fd27 --- /dev/null +++ b/docs/design/latency-ladder-bench/20261006T221034Z/loaded-35.txt @@ -0,0 +1,8 @@ +igneum-pow bench: seed "igneum-genesis", day "2026-10-03", dataset 2^28 words (memory-hard) +cache: fill 549.4 ms on one core (2^26 words, 256 MiB, 65536 chains of 64 ChaCha12 blocks), FNV-1a 64 48c4f5bf24166b2e +program: class mx8+sh256x35, 128 loads/hash, 512 bytes/hash, widths (1,4,16 words) [16, 0, 0], 4096 items/warp, mixer x8 (72 mixers/item), cache 2^26 words, op mix load=16 add=8 shfl=8 xor=6 mad=5 mul=5 mulhi=5 sub=4 rotl=3 rotr=3 or=1; epoch built in 554.8 ms +shadow: 256 instructions x 35 passes per iteration, 71680 shadow instructions per hash, op mix add=47 rotl=30 xor=30 shfl=29 mad=27 mul=22 sub=21 rotr=20 mulhi=18 or=12 +warp base 0: single cold run 8.867 ms, 4096 items derived, lane0 177dd71be37996bc lane31 792114b1b40ad79f +warp base 4096: single cold run 8.505 ms, 4096 items derived, lane0 fe6701666e2e483a lane31 fc2105cb89fcb8b7 +warp base 1000000: single cold run 8.517 ms, 4096 items derived, lane0 6401cfb6e9ddfed6 lane31 89445da95a2e7184 +CPU verify: 8.510 ms per 32-lane warp, avg of 50 (checksum 4632b2b31b01b311) diff --git a/docs/design/latency-ladder-bench/20261006T221034Z/loaded-53.txt b/docs/design/latency-ladder-bench/20261006T221034Z/loaded-53.txt new file mode 100644 index 000000000..bc44389b4 --- /dev/null +++ b/docs/design/latency-ladder-bench/20261006T221034Z/loaded-53.txt @@ -0,0 +1,8 @@ +igneum-pow bench: seed "igneum-genesis", day "2026-10-03", dataset 2^28 words (memory-hard) +cache: fill 541.1 ms on one core (2^26 words, 256 MiB, 65536 chains of 64 ChaCha12 blocks), FNV-1a 64 48c4f5bf24166b2e +program: class mx8+sh256x53, 128 loads/hash, 512 bytes/hash, widths (1,4,16 words) [16, 0, 0], 4096 items/warp, mixer x8 (72 mixers/item), cache 2^26 words, op mix load=16 add=8 shfl=8 xor=6 mad=5 mul=5 mulhi=5 sub=4 rotl=3 rotr=3 or=1; epoch built in 549.4 ms +shadow: 256 instructions x 53 passes per iteration, 108544 shadow instructions per hash, op mix add=47 rotl=30 xor=30 shfl=29 mad=27 mul=22 sub=21 rotr=20 mulhi=18 or=12 +warp base 0: single cold run 9.233 ms, 4096 items derived, lane0 9771dab715d71261 lane31 525676cf580b6f11 +warp base 4096: single cold run 8.869 ms, 4096 items derived, lane0 1afa4ca3c965c3f5 lane31 4e238e93b5f0101d +warp base 1000000: single cold run 8.943 ms, 4096 items derived, lane0 21a07085d7c0bd82 lane31 39fd0197590efb2a +CPU verify: 8.908 ms per 32-lane warp, avg of 50 (checksum 2efdcc71de21f16f) diff --git a/docs/design/latency-ladder-bench/20261006T221034Z/loaded-88.txt b/docs/design/latency-ladder-bench/20261006T221034Z/loaded-88.txt new file mode 100644 index 000000000..f4ea243c2 --- /dev/null +++ b/docs/design/latency-ladder-bench/20261006T221034Z/loaded-88.txt @@ -0,0 +1,8 @@ +igneum-pow bench: seed "igneum-genesis", day "2026-10-03", dataset 2^28 words (memory-hard) +cache: fill 542.3 ms on one core (2^26 words, 256 MiB, 65536 chains of 64 ChaCha12 blocks), FNV-1a 64 48c4f5bf24166b2e +program: class mx8+sh256x88, 128 loads/hash, 512 bytes/hash, widths (1,4,16 words) [16, 0, 0], 4096 items/warp, mixer x8 (72 mixers/item), cache 2^26 words, op mix load=16 add=8 shfl=8 xor=6 mad=5 mul=5 mulhi=5 sub=4 rotl=3 rotr=3 or=1; epoch built in 556.0 ms +shadow: 256 instructions x 88 passes per iteration, 180224 shadow instructions per hash, op mix add=47 rotl=30 xor=30 shfl=29 mad=27 mul=22 sub=21 rotr=20 mulhi=18 or=12 +warp base 0: single cold run 10.081 ms, 4096 items derived, lane0 84beec0429c7ef83 lane31 334b204a4a68c30d +warp base 4096: single cold run 9.845 ms, 4096 items derived, lane0 32be0f5b7d992255 lane31 2c8dbd443403bc84 +warp base 1000000: single cold run 9.944 ms, 4096 items derived, lane0 78541813d6547a59 lane31 fd7fb2e05bbc316f +CPU verify: 9.842 ms per 32-lane warp, avg of 50 (checksum 77189c69c05913b5) diff --git a/docs/design/latency-ladder-bench/20261006T221034Z/meta.txt b/docs/design/latency-ladder-bench/20261006T221034Z/meta.txt new file mode 100644 index 000000000..8e0abb2cb --- /dev/null +++ b/docs/design/latency-ladder-bench/20261006T221034Z/meta.txt @@ -0,0 +1,2 @@ +host igneum-build-1 load 25.39 17.85 11.63 freq40 1500000 kHz siblings 40,88 pow 3f7623f208335f5b +load after 23.60 18.46 12.05 diff --git a/docs/design/latency-ladder-bench/20261006T221034Z/sibling-173.txt b/docs/design/latency-ladder-bench/20261006T221034Z/sibling-173.txt new file mode 100644 index 000000000..ed3b85a9e --- /dev/null +++ b/docs/design/latency-ladder-bench/20261006T221034Z/sibling-173.txt @@ -0,0 +1,7 @@ +igneum-pow bench: seed "igneum-genesis", day "2026-10-03", dataset 2^28 words (memory-hard) +cache: fill 363.1 ms on one core (2^26 words, 256 MiB, 65536 chains of 64 ChaCha12 blocks), FNV-1a 64 48c4f5bf24166b2e +program: class mx8+sh256x173, 128 loads/hash, 512 bytes/hash, widths (1,4,16 words) [16, 0, 0], 4096 items/warp, mixer x8 (72 mixers/item), cache 2^26 words, op mix load=16 add=8 shfl=8 xor=6 mad=5 mul=5 mulhi=5 sub=4 rotl=3 rotr=3 or=1; epoch built in 364.9 ms +shadow: 256 instructions x 173 passes per iteration, 354304 shadow instructions per hash, op mix add=47 rotl=30 xor=30 shfl=29 mad=27 mul=22 sub=21 rotr=20 mulhi=18 or=12 +warp base 0: single cold run 7.394 ms, 4096 items derived, lane0 74dbb9c7808b6c12 lane31 14a606e8cc2988ee +warp base 4096: single cold run 7.241 ms, 4096 items derived, lane0 361270e067803cb3 lane31 979c376d679d74b2 +warp base 1000000: single cold run 7.269 ms, 4096 items derived, lane0 173be6655be48c63 lane31 697bcf4f945c5bf9 diff --git a/docs/design/latency-ladder-bench/20261006T221034Z/sibling-267.txt b/docs/design/latency-ladder-bench/20261006T221034Z/sibling-267.txt new file mode 100644 index 000000000..f850bee90 --- /dev/null +++ b/docs/design/latency-ladder-bench/20261006T221034Z/sibling-267.txt @@ -0,0 +1,7 @@ +igneum-pow bench: seed "igneum-genesis", day "2026-10-03", dataset 2^28 words (memory-hard) +cache: fill 365.1 ms on one core (2^26 words, 256 MiB, 65536 chains of 64 ChaCha12 blocks), FNV-1a 64 48c4f5bf24166b2e +program: class mx8+sh256x267, 128 loads/hash, 512 bytes/hash, widths (1,4,16 words) [16, 0, 0], 4096 items/warp, mixer x8 (72 mixers/item), cache 2^26 words, op mix load=16 add=8 shfl=8 xor=6 mad=5 mul=5 mulhi=5 sub=4 rotl=3 rotr=3 or=1; epoch built in 372.0 ms +shadow: 256 instructions x 267 passes per iteration, 546816 shadow instructions per hash, op mix add=47 rotl=30 xor=30 shfl=29 mad=27 mul=22 sub=21 rotr=20 mulhi=18 or=12 +warp base 0: single cold run 8.930 ms, 4096 items derived, lane0 14e8e692f6f1f094 lane31 a25fb8045d382322 +warp base 4096: single cold run 8.716 ms, 4096 items derived, lane0 eed0185d04e6d213 lane31 6b9af2c86bf9db03 +warp base 1000000: single cold run 8.762 ms, 4096 items derived, lane0 a38b5590afcba174 lane31 39664cb2e742eb67 diff --git a/docs/design/latency-ladder-bench/20261006T221034Z/sibling-27.txt b/docs/design/latency-ladder-bench/20261006T221034Z/sibling-27.txt new file mode 100644 index 000000000..ea662305b --- /dev/null +++ b/docs/design/latency-ladder-bench/20261006T221034Z/sibling-27.txt @@ -0,0 +1,7 @@ +igneum-pow bench: seed "igneum-genesis", day "2026-10-03", dataset 2^28 words (memory-hard) +cache: fill 369.7 ms on one core (2^26 words, 256 MiB, 65536 chains of 64 ChaCha12 blocks), FNV-1a 64 48c4f5bf24166b2e +program: class mx8+sh256x27, 128 loads/hash, 512 bytes/hash, widths (1,4,16 words) [16, 0, 0], 4096 items/warp, mixer x8 (72 mixers/item), cache 2^26 words, op mix load=16 add=8 shfl=8 xor=6 mad=5 mul=5 mulhi=5 sub=4 rotl=3 rotr=3 or=1; epoch built in 370.0 ms +shadow: 256 instructions x 27 passes per iteration, 55296 shadow instructions per hash, op mix add=47 rotl=30 xor=30 shfl=29 mad=27 mul=22 sub=21 rotr=20 mulhi=18 or=12 +warp base 0: single cold run 5.129 ms, 4096 items derived, lane0 2576769ee4a14c8d lane31 c58ddcb717dd3370 +warp base 4096: single cold run 4.944 ms, 4096 items derived, lane0 1ce77a600ec573b4 lane31 03600a05ffba0055 +warp base 1000000: single cold run 4.947 ms, 4096 items derived, lane0 6b390e64bbdd91ce lane31 91c944d603539c62 diff --git a/docs/design/latency-ladder-bench/20261006T221034Z/sibling-35.txt b/docs/design/latency-ladder-bench/20261006T221034Z/sibling-35.txt new file mode 100644 index 000000000..83a8bee28 --- /dev/null +++ b/docs/design/latency-ladder-bench/20261006T221034Z/sibling-35.txt @@ -0,0 +1,7 @@ +igneum-pow bench: seed "igneum-genesis", day "2026-10-03", dataset 2^28 words (memory-hard) +cache: fill 366.7 ms on one core (2^26 words, 256 MiB, 65536 chains of 64 ChaCha12 blocks), FNV-1a 64 48c4f5bf24166b2e +program: class mx8+sh256x35, 128 loads/hash, 512 bytes/hash, widths (1,4,16 words) [16, 0, 0], 4096 items/warp, mixer x8 (72 mixers/item), cache 2^26 words, op mix load=16 add=8 shfl=8 xor=6 mad=5 mul=5 mulhi=5 sub=4 rotl=3 rotr=3 or=1; epoch built in 368.5 ms +shadow: 256 instructions x 35 passes per iteration, 71680 shadow instructions per hash, op mix add=47 rotl=30 xor=30 shfl=29 mad=27 mul=22 sub=21 rotr=20 mulhi=18 or=12 +warp base 0: single cold run 5.247 ms, 4096 items derived, lane0 177dd71be37996bc lane31 792114b1b40ad79f +warp base 4096: single cold run 5.071 ms, 4096 items derived, lane0 fe6701666e2e483a lane31 fc2105cb89fcb8b7 +warp base 1000000: single cold run 5.063 ms, 4096 items derived, lane0 6401cfb6e9ddfed6 lane31 89445da95a2e7184 diff --git a/docs/design/latency-ladder-bench/20261006T221034Z/sibling-53.txt b/docs/design/latency-ladder-bench/20261006T221034Z/sibling-53.txt new file mode 100644 index 000000000..50d108cf2 --- /dev/null +++ b/docs/design/latency-ladder-bench/20261006T221034Z/sibling-53.txt @@ -0,0 +1,7 @@ +igneum-pow bench: seed "igneum-genesis", day "2026-10-03", dataset 2^28 words (memory-hard) +cache: fill 362.3 ms on one core (2^26 words, 256 MiB, 65536 chains of 64 ChaCha12 blocks), FNV-1a 64 48c4f5bf24166b2e +program: class mx8+sh256x53, 128 loads/hash, 512 bytes/hash, widths (1,4,16 words) [16, 0, 0], 4096 items/warp, mixer x8 (72 mixers/item), cache 2^26 words, op mix load=16 add=8 shfl=8 xor=6 mad=5 mul=5 mulhi=5 sub=4 rotl=3 rotr=3 or=1; epoch built in 364.4 ms +shadow: 256 instructions x 53 passes per iteration, 108544 shadow instructions per hash, op mix add=47 rotl=30 xor=30 shfl=29 mad=27 mul=22 sub=21 rotr=20 mulhi=18 or=12 +warp base 0: single cold run 5.481 ms, 4096 items derived, lane0 9771dab715d71261 lane31 525676cf580b6f11 +warp base 4096: single cold run 5.346 ms, 4096 items derived, lane0 1afa4ca3c965c3f5 lane31 4e238e93b5f0101d +warp base 1000000: single cold run 5.349 ms, 4096 items derived, lane0 21a07085d7c0bd82 lane31 39fd0197590efb2a diff --git a/docs/design/latency-ladder-bench/20261006T221034Z/sibling-88.txt b/docs/design/latency-ladder-bench/20261006T221034Z/sibling-88.txt new file mode 100644 index 000000000..54d39cd6f --- /dev/null +++ b/docs/design/latency-ladder-bench/20261006T221034Z/sibling-88.txt @@ -0,0 +1,7 @@ +igneum-pow bench: seed "igneum-genesis", day "2026-10-03", dataset 2^28 words (memory-hard) +cache: fill 367.6 ms on one core (2^26 words, 256 MiB, 65536 chains of 64 ChaCha12 blocks), FNV-1a 64 48c4f5bf24166b2e +program: class mx8+sh256x88, 128 loads/hash, 512 bytes/hash, widths (1,4,16 words) [16, 0, 0], 4096 items/warp, mixer x8 (72 mixers/item), cache 2^26 words, op mix load=16 add=8 shfl=8 xor=6 mad=5 mul=5 mulhi=5 sub=4 rotl=3 rotr=3 or=1; epoch built in 364.7 ms +shadow: 256 instructions x 88 passes per iteration, 180224 shadow instructions per hash, op mix add=47 rotl=30 xor=30 shfl=29 mad=27 mul=22 sub=21 rotr=20 mulhi=18 or=12 +warp base 0: single cold run 6.058 ms, 4096 items derived, lane0 84beec0429c7ef83 lane31 334b204a4a68c30d +warp base 4096: single cold run 5.909 ms, 4096 items derived, lane0 32be0f5b7d992255 lane31 2c8dbd443403bc84 +warp base 1000000: single cold run 5.896 ms, 4096 items derived, lane0 78541813d6547a59 lane31 fd7fb2e05bbc316f diff --git a/docs/design/latency-ladder-bench/20261006T221034Z/table.md b/docs/design/latency-ladder-bench/20261006T221034Z/table.md new file mode 100644 index 000000000..452d06524 --- /dev/null +++ b/docs/design/latency-ladder-bench/20261006T221034Z/table.md @@ -0,0 +1,10 @@ +host igneum-build-1 load 25.39 17.85 11.63 freq40 1500000 kHz siblings 40,88 pow 3f7623f208335f5b +load after 23.60 18.46 12.05 +| Rung | reps | Shadow instrs per hash | Counted ops (approx) | Cold, core alone (ms) | Avg of 50, alone (ms) | Cold, sibling loaded (ms) | Avg of 50, sibling loaded (ms) | Under 10 ms loaded | +|---|---|---|---|---|---|---|---|---| +| 0 | 27 | 55296 | 102,122 | 5.14 | 4.95 | 8.77 | 8.67 | yes | +| 1 | 35 | 71680 | 132,105 | 5.57 | 5.52 | 8.87 | 8.51 | yes | +| 2 | 53 | 108544 | 199,566 | 5.57 | 5.35 | 9.23 | 8.91 | yes | +| 3 | 88 | 180224 | 330,740 | 6.07 | 5.89 | 10.08 | 9.84 | NO | +| 4 | 173 | 354304 | 649,307 | 7.38 | 7.23 | 12.38 | 12.09 | NO | +| 5 | 267 | 546816 | 1,001,604 | 8.88 | 8.50 | 14.96 | 14.58 | NO | diff --git a/docs/design/latency-ladder.md b/docs/design/latency-ladder.md new file mode 100644 index 000000000..2ca8adc72 --- /dev/null +++ b/docs/design/latency-ladder.md @@ -0,0 +1,115 @@ +# The latency ladder: N as a verifier-bounded genesis ladder stepped by miner signal + +6 October 2026, night UK. Horizon finding 4 (`docs/analysis/horizon/algorithm.md` 5.3a and proposal 5; `frontier.md` 2.3). Branch `ladder` (repo) and `ladder-node` (fork, from `ca3-v4-0316` 8dbb7a23). Status: implemented behind `latency_ladder_activation_daa` (never until set; 0 on the testnet when the project lead says), ships in the 0.3.17 feature tree (0.3.16 went out on 6 October 2026 as a corrective cut of 0.3.15, node f1ea7a38, and carries none of this); not in `docs/spec` until adopted. + +## 1. The finding in one line + +The chip that matters stores the dataset; the reserve and the era draw buy nothing against it; the one lever is the latency-shadow size N, and an unconditional doubling per era retires the M5 Max at era 1 (-10.5 percent at 200,000 ops). So N moves inside a ladder fixed at genesis, one rung at a time, only when 90 percent of mining weight asks for it, never past the rung the reference verifier can still check under 10 ms. + +## 2. Where N lives + +| Item | Place | Value | +|---|---|---| +| The unit | `igneum_pow::ShadowClass { instrs: 256, reps }`: class v4 is `mx8` plus a 256-instruction shadow block run `reps` times per iteration; N in counted ops = 930 + 2,048 x reps x 1.83 (the convention of `latency-shadow-2026-10-06.md` s1) | reps is what the generator consumes; the ops label is derived (`igneum::latency_ladder_counted_ops`) | +| The ladder | `Params::latency_ladder` (`consensus/core/src/config/params.rs`), a genesis list of six rungs `{ reps, admissible }`, in the override file as `latency_ladder: [{"reps": 27, "admissible": true}, ...]`, in the digest once the activation is set | reps 27, 35, 53, 88, 173, 267 = about 102,100, 132,100, 199,600, 330,700, 649,400, 1,001,600 counted ops: the measured rungs of 5.3a (35 passes is the integer rung nearest the 130,000 interpolation), then the two doublings; admissible tonight: rungs 0, 1, 2 (section 5) | +| The switch | `Params::latency_ladder_activation_daa` (`u64::MAX` never; in the digest once set, the 0.3.15 rule, so a binary that carries the field peers with one that does not until a file sets it) | never on every network; 0 on the testnet when the project lead says | +| The window | `Params::latency_ladder_window_daa` (one window of the seven; in the digest once the activation is set) | 86,400 DAA (one day); 120 on the fast-time profile, the class window's rounding | +| The step | consensus state derived from signals, never stored: `processes::latency_ladder::step_of_epoch` walks the epochs' seed blocks down the selected chain exactly as `class_signal::program_class_of_epoch` does, memoised per seed block | rung 0 before the activation and at it | +| The era draw | draws 8 (`epoch_len`) and 9 (the ladder) of the era stream are consumed and not used (`igneum_pow::era_draw`), so a later use of either slot changes no other parameter; the first seven draws and every pinned pack are unchanged | the value is set by signal, not by the draw, as `epoch_len` is (spec 01 1.13.1) | +| The program | `EpochSeeds.shadow_reps` (0 = the class's own) → `Epoch::chain_program_shadow` → `generate_from_seed_bytes_program_class_shadow`: class v4 at rung r is `LoadClass::era(v4_class_at(reps_r), era, &V3_ALLOWED)`, generator 4; rung 0 is `V4_CLASS` byte for byte | the base program, the 16 loads, the day cache and the era draw are class v3's; only the block count moves | + +## 3. The step rule (`igneum::latency_ladder_step_signalled`, pure; the tally shared with the class signal) + +1. The carrier: two bits of the header `version` high byte. Bit 15 = up, bit 14 = down, both = no signal; the object version (the class signal) keeps bits 8 to 13 (`class_signal_of` masks with 0x3f; every value written so far is 4, so nothing changes). Stamped by the node from `Params::latency_ladder_signal` (`IGNEUM_LADDER_SIGNAL=up|down|none` on devnet and simnet; the app's toggle later) only while the ladder is active; the header version rule accepts the high byte while the class signal or the ladder is active. +2. The weight and the windows: blue blocks, the finality rule's convention, `CLASS_SIGNAL_WINDOWS = 7` consecutive windows of `latency_ladder_window_daa` ending at the epoch's seed block `S_e`, one walk bucketed per window (`class_signal::tally_window` with the ladder predicate). The number the decision rests on is the weakest of the seven. +3. The decision for epoch e, from the step of epoch e - 1: up one rung when every one of the seven windows is full, each has at least `LADDER_SIGNAL_THRESHOLD_BPS = 9,000` of its blue blocks signalling up, the rung above exists and is admissible, and the oldest window begins at or after the DAA score where the current step took effect; down one rung symmetrically (never below rung 0). Otherwise the step stands. 90 percent, not 95: the ladder is a parameter the genesis rules leave to miners within fixed bounds (the `epoch_len` precedent, spec 01 1.13.1), not a class change; 60 percent (a parameter the rules leave open) is too low for a move that retires cards. +4. Where it lands: at the epoch boundary, decided at the seed block, so the miner learns the next epoch's rung one lead (600 DAA) before it, with `next_latency_ladder_reps` in every template; a class never changes inside an epoch, nor does a rung. +5. The cool-down (the two-step guard): because the seven windows must lie wholly after the last step, the earliest next decision is 7 x W (seven days) after a step, and every counted signal was cast under the rung it moves from. A miner majority cannot jump two rungs in one decision and cannot take two decisions inside seven days. + +## 4. The verifier bound + +A rung is admissible only if the cold verify of one 32-lane warp on the reference core stays under 10 ms with the SMT sibling loaded. Measured as lane 2 measured class v4: `igneum-pow bench --seed igneum-genesis --day 2026-10-03 --class mx8+sh256x --warps 50` on igneum-build-1 (EPYC 9454P, core 40 at 3.8 GHz under schedutil, `nice -n 19 taskset -c 40`), the figure is `warp base 0: single cold run` with the same bench on core 88 (the sibling, `thread_siblings_list` 40,88) at the same time; the average of 50 warps and the quiet-core cold run are recorded beside it. The measurement runs under the box's `measure` hold (`remote-run.sh BR_MEASURE=1`: every build slot waits, the capacity layer yields), `tools/ladder/verify-bench-remote.sh`. The table is section 5; a rung over 10 ms is `admissible: false` in the genesis list itself, and the step rule never enters it. Changing a flag after genesis is a code change under the 90 percent upgrade path (spec 05 5.7); the owed O-1.14 laptop run can only tighten the list before genesis. + +## 5. The measured table (igneum-build-1, 6 to 7 October 2026) + +Run 2 (22:10Z, `docs/design/latency-ladder-bench/20261006T221034Z`, `tools/ladder/verify-bench-remote.sh` under the measure hold; core 40 read 1,500,000 kHz at the start under schedutil; box load average 25.4 before and 23.6 after from other agents' processes the hold does not exclude; igneum-pow sha256 3f7623f2...; every rung's lane-0 hash equal across the three runs of a rung): + +| Rung | reps | Shadow instrs per hash | Counted ops (approx) | Cold, core alone (ms) | Avg of 50, alone (ms) | Cold, sibling loaded (ms) | Avg of 50, sibling loaded (ms) | Admissible | +|---|---|---|---|---|---|---|---|---| +| 0 | 27 | 55,296 | 102,100 | 5.14 | 4.95 | 8.77 | 8.67 | yes (class v4 today) | +| 1 | 35 | 71,680 | 132,100 | 5.57 | 5.52 | 8.87 | 8.51 | yes | +| 2 | 53 | 108,544 | 199,600 | 5.57 | 5.35 | 9.23 | 8.91 | yes | +| 3 | 88 | 180,224 | 330,700 | 6.07 | 5.89 | 10.08 | 9.84 | NO (by 0.08 ms under a load of 25; a quiet re-run before genesis may admit it) | +| 4 | 173 | 354,304 | 649,400 | 7.38 | 7.23 | 12.38 | 12.09 | NO | +| 5 | 267 | 546,816 | 1,001,600 | 8.88 | 8.50 | 14.96 | 14.58 | NO | + +Reading: the loaded slope is about 12.6 us per 1,000 extra shadow instructions (8.77 to 14.96 over 491,520), lane 2's 12.1; rung 0 reads 8.77 against lane 2's 8.23 (its load average was 2.3), so the box's other work costs about 0.5 ms and rung 3 would read about 9.6 ms on a quiet box. The rule is the measurement, not the estimate: the genesis list carries rung 3 as inadmissible tonight, and a re-measurement on a quiet box (or the O-1.14 laptop) before the testnet genesis file is written can admit it. Per tier what the flags mean: the ladder as it ships can take N from 102,100 to 199,600 ops (two steps, each at least seven windows apart); at the top rung the Apple tier has given up about 10 percent of rate, the 5090 2.7 percent at its cap, the 4070 pays 21 W more, and a node on a 2019-class core still verifies a warp inside 10 ms with its sibling busy. + +Lane 2's slopes predicted 8.23 + 12.1 us per 1,000 extra shadow instructions on the loaded core: 8.4, 8.9, 9.7, 11.9, 14.2 ms for rungs 1 to 5, so rungs 4 and 5 were expected out before the run. The genesis list in `params.rs` carries the flags this table gives. First run (22:06Z, `docs/design/latency-ladder-bench/20261006T220642Z`): the "loaded" column was NOT loaded (the known-failed case of the script: a 50-warp sibling finished during the measured run's own cache fill, which the fill time showed, 540 against 366 ms, while the warps then ran alone: 5.08, 5.20, 5.50, 6.07, 7.39, 8.83 ms); the script now keeps the sibling busy for the whole run and the table above is the second run's. + +Also recorded in the first run: the quiet-core cold figures 5.09, 5.20, 5.51, 6.04, 7.45, 8.90 ms for rungs 0 to 5 (lane 2's class v4 cold figure was 5.06), so on the quiet reference core every rung including 1.0 M ops passes 10 ms; the loaded column decides. + +## 5a. The chip anchor: what a determined vendor did to RandomX in seven years (coordinator's data point, 6 October 2026, night) + +Bitmain's Antminer X9 is a shipping RandomX ASIC: about 1 MH/s at 2,472 W (2.47 J per KH), custom RISC-V cores, about USD 5,600, shipping July 2026 (github.com/monero-project/monero/issues/10270; every figure approximate, read from the issue by the coordinator). A Zen 4 desktop CPU at roughly 20 KH/s and 150 W is 7.5 J per KH (approximate), so the chip's per-joule edge over the honest device RandomX was built for is about 3x, seven years after launch. Two consequences for this project's own text and model: + +| Item | Before | After | +|---|---|---| +| The precedent sentence | `latency-shadow-2026-10-06.md` s5 and `site/litepaper.html`: "no RandomX chip has beaten a CPU per joule", "no chip publicly shipped" | false since July 2026; corrected in the litepaper tonight (section 10 below) and owed in `latency-shadow-2026-10-06.md` and `chip-model-v3.md` | +| The k column | k = 0.3 was "the attacker's claim" (a wide-SIMD fixed-datapath array at N5); k = 1 was "ours" and the headline (2.1x over the 5090 at class v4) | a shipped product reached about 1/3 of its honest device's energy on a latency-bound random program, so k about 0.33 is a measured class, not a claim; the honest headline is the range, 2.1x (k = 1) to 3.9x (k = 0.33) over the 5090 at rung 0, with the X9 bracket named. Approximate in both directions: the 5090's ALU is already a wide-SIMD part at 11 pJ per op, so a chip's gain over it on random ALU work should be under its gain over a CPU core, and the memory side (the f = 1 chip) is priced separately and is not moved by k | +| The 5.7x | the f = 1 GDDR7 chip's per-joule edge at class v3 (memory only) | unchanged: no core work in it, k does not enter | + +What N buys against a vendor at the X9's scale (energy per hash = memory + N x 11 pJ x k, `latency-shadow-2026-10-06.md` s5; the 5090 and M5 Max rows measured, algorithm.md 5.3a; k = 0.33; approximate): + +| Rung | N (approx) | f = 1 GDDR7 chip, uJ | Edge over the 5090 at its 431 W cap | Edge over the M5 Max | +|---|---|---|---|---| +| 0 | 102,100 | 0.84 | 3.9x | 1.7x | +| 1 | 132,100 | 0.95 | 3.4x | 1.5x | +| 2 | 199,600 | 1.19 | 2.8x | 1.3x | +| 3 | 330,700 | 1.67 | 3.0x (the 5090 itself is compute-bound at this rung, 86 MH/s) | 1.1x | + +So against a vendor at the X9's scale the ladder narrows the chip's edge over the 5090 from about 3.9x to about 2.8x by rung 2 (the top admissible rung tonight) and does not close it; against the Apple tier it takes the edge to about 1.3x at rung 2 and would take it to about 1.1x at rung 3, where the Mac has given up 21 percent of its rate and which the verifier measurement keeps out until a quiet re-run. The lever that moves every row by the same factor is the honest card's own watts (algorithm.md proposal 7), and the public text says so. The ladder is the chain's only automatic answer; it is not the whole answer. + +## 6. What moves the digest and the wire + +| Change | Digest | Wire | +|---|---|---| +| The binary carrying the fields, nothing set | no (the 0.3.15 rule: the activation and the window enter only once set; the ladder list enters only once the activation is set) | header version unchanged; `PowEpochInfo` gains fields 25 to 36 with defaults, so an old miner reads reps 0 (the class's own) | +| The file setting `latency_ladder_activation_daa` (and the window, and the list) | yes, once, at publish | the high byte may carry bits 14 and 15; a node without the ladder refuses the version when class signalling is inactive, which is the point of the rollout order (binary first, file second) | +| A step | no | the program of the epoch changes: `program_id_class` with the `shadow/` bytes (rung 0 keeps `program_id(4, seed, attempt)`), the pack's `IGNEUM_SHADOW_REPS` and `seeds.txt`'s `shadow_reps` line (absent at rung 0, so every pack and job line written today is unchanged; the job line gains no token, because the three hosts strip only `class=` and `era=` from its tail) | + +## 7. Hostile review + +| Question | Answer | +|---|---| +| Can a chip-holder vote the ladder down? | Down needs 90 percent of blue blocks in each of seven windows. A chip at 10 percent of hash cannot; at 90 percent the chain has a bigger problem than N, and the honest 10 percent still blocks the move (a 10 percent minority stops any move either way). | +| Can a chip-holder stall it? | Yes, with over 10 percent of weight for the whole week: the status quo is kept. The status quo is an admissible rung the cards already run, so a stall costs the chain nothing it does not have today; the ladder is a lever miners pull, not a schedule a chip can be late for. The one defence it does not give is a forced move against a chip that holds 10 percent: that is the honest-card watts and the price per joule (algorithm.md 5.1). | +| Can a miner majority jump two steps? | No: one rung per decision, and the next decision's seven windows must begin after the step took effect, so two moves are at least 7 x W apart (`latency_ladder_step_signalled`, tested). | +| Can a week be bought? | 90 percent of blue blocks for seven consecutive days is 90 percent of hash for a week, in public, with the pending count on every console (`latency_ladder_up_weakest_bps`); the class signal's argument (one day can be bought, seven cannot unnoticed). | +| What happens to a miner mid-step? | The rung is decided at the seed block, 600 DAA before the boundary; the template carries `next_latency_ladder_reps` and the miner prepares the next pack with it (the 2.0 era-boundary shape: a boundary that decides otherwise costs one refused pair and one prepare). A miner on a binary without the ladder keeps mining rung 0 after the step and loses every block until it updates; it can be at most 10 percent of hash by construction, and it had seven days of a rising count to see it. | +| An inadmissible rung by a 90 percent majority? | Impossible: the rule reads the flag; the flag is a genesis constant in the digest. | +| A proof-synced node | holds no headers below its pruning point, so an epoch whose windows reach below it cannot be tallied; it takes rung 0 (the floor) and logs that a ladder witness is owed, the class signal's shape (counter-asic-3-node.md 6.3). The witness in the proof format is the next node item, shared with the class witness. | +| The program id | rung 0 keeps today's id; a rung above goes through `program_id_class`, which carries the shadow size, so two rungs of one seed never share an id and a pack of another rung is refused by the id check as a pack of another class is. | + +## 8. Consequences per tier (measured rungs from algorithm.md 5.3a; the step is the miners' to take) + +| Step | M5 Max | RTX 5090 at 431 W | RTX 4070 at 160 W | RX 9070 XT | Pool user | Verifier (half-core) | +|---|---|---|---|---|---|---| +| 0 → 1 (102,100 → 132,100) | -3.3 points of rate, 0 W more | 0 | 0 | 0 | 0 | +0.2 ms | +| 1 → 2 (→ 199,600) | -6 more points | -2.7 percent | +21 W | 0 | 0 | +0.5 ms | +| 2 → 3 (→ 330,700) | -21 percent | -35 percent (compute-bound at the cap) | -12 percent | +3.6 percent | 0 | +0.9 ms | +| 3 → 4, 4 → 5 | unmeasured on cards; the verifier decides admissibility (section 5) | | | | | | + +A chip's shadow core grows with N at k x 11 pJ per op; at rung 1 the f = 1 GDDR7 chip's edge over the 5090 at k = 1 falls 2.1x to 1.7x, at rung 2 to 1.3x. Which is why the step is the miners' who pay for it: rung 3 is a step the signal would refuse until cards change. + +## 9. What is implemented, tested, owed + +Public text: section 10. + +Implemented: the ladder, activation, window and signal in `Params`, `OverrideParams`, the digest and the 60x file; the pure rule and the bits in `igneum.rs`; `processes::latency_ladder` (the walk, the memo, the step); `EpochSeeds.shadow_reps` through the engine, the header path, the miner and the pack; `PowEpochInfo` and the gRPC fields; the daemon lines; `igneum-pow`: `v4_class_at`, the shadow program path, the id rule, the consumed draws, `--shadow-reps` on the CLI. Tests, the known-failed case first: a changed N today is a hard fork (the same seeds at reps 35 hash another program with the same class and, before the ladder, the same id); the step moves only by signal; 8,999 bps does not move it; a two-step jump is impossible; down never below 0; an inadmissible rung is never entered; rung 0 is `V4_CLASS` byte for byte. Fast-time harness `infra/fast-time/latency-ladder.mjs` (three cases and the failed case, the class harness's shape). Owed: the ladder witness in the pruning proof; the app's signal toggle and its consequences line; a rung check in the three hosts (today a host compiles whatever `IGNEUM_SHADOW_REPS` the pack carries and the Rust pack check binds the pack to its seeds, so a stale pack of another rung fails the miner's own check, not the host's); the O-1.14 laptop run. + +## 10. Public text (the litepaper's hash-class paragraph, `site/litepaper.html`, Mining section) and the ledger row + +The paragraph as placed in the litepaper: "**The work that waits can grow.** Class v4 adds a block of latency-shadow arithmetic to every hash, about 100,000 integer operations that run while the memory reads are in flight, so a chip that stores the whole dataset still has to pay for a core. That size sits on a ladder fixed at genesis, six rungs from about 100,000 to about 1,000,000 operations, and it moves one rung at a time only when 90 percent of blue blocks in each of seven consecutive days ask for it; it can never move two rungs inside a week and never past a rung the reference verifier cannot check under 10 ms with its sibling thread busy (measured on the build server: every rung's cold verify stays under that gate on the quiet core, 5.1 to 8.9 ms). What it buys, on the measured cards: against a dataset-storing chip whose core costs what an RTX 5090's does per operation, the chip's per-joule edge falls from 2.1x at the first rung to 1.3x at the third; against a core as good as the shipping RandomX chip's (Bitmain's Antminer X9, about 3x per joule over a desktop CPU after seven years, approximate), from 3.9x to 2.8x. What it costs, per rung, is measured too: the Apple tier gives up 3 points of rate at the first step and 6 more at the second, the RTX 5090 nothing until the second; so the miners who pay for a step are the ones who take it." The sentence "Monero has run on RandomX since 2019 with no chip publicly shipped" is replaced by the X9 fact. + +The ledger row is M34 in `docs/fud-ledger.md`. diff --git a/docs/fud-ledger.md b/docs/fud-ledger.md index e8e808d52..910cef6ff 100644 --- a/docs/fud-ledger.md +++ b/docs/fud-ledger.md @@ -182,6 +182,15 @@ Answer: Correct. The measured 2.4 G/s had been read on 6 October as a mapping ar Evidence: `docs/analysis/horizon/algorithm.md` section 5.1 (the ceiling table: measured 2.4, tFAW-bound 2.3, tRRD-bound 2.8, bank-bound 11.4, O'Connor 10.7 G reads/s, and the F2 measurement plan), 6 October 2026; JEDEC HBM2 timings as carried by ICCAD 2021 Table I; Shuhai, FCCM 2020, Fig 7. +### M34. The shadow size N is a constant of the binary, so the one lever against the dataset-storing chip needs a fork to move +"Your own Horizon lane says the reserve and the era draw buy nothing against a chip that stores the dataset, and that the only lever is the latency-shadow size N. N is 27 passes of a 256-instruction block, hard-coded in `V4_CLASS`. So when HBM4 doubles a chip's rate per stack in 2028, your answer is a hard fork, and a fork that retires the M5 Max at the first doubling. And now there is a shipping RandomX ASIC." + +Status: Conceded, implemented (6 October 2026, night; `docs/design/latency-ladder.md`, branch `ladder`, fork branch `ladder-node`, the 0.3.17 feature tree, behind `latency_ladder_activation_daa`, never until set, 0 on the testnet when the project lead says): N is a genesis ladder of six rungs (27, 35, 53, 88, 173, 267 passes; about 102,100 to 1,001,600 counted ops) with a measured admissibility flag per rung (cold verify under 10 ms on the reference core with its SMT sibling loaded, igneum-build-1, 6 October 2026: rungs 0 to 2 pass at 8.77, 8.87 and 9.23 ms, rung 3 misses by 0.08 ms under a box load of 25 and is out until a quiet re-run, rungs 4 and 5 are out at 12.38 and 14.96), and the step is consensus state derived from two bits of the header version: up one rung when 90 percent of blue blocks in each of seven consecutive windows ask for it and the rung above is admissible, down one rung symmetrically, never two rungs inside seven windows (the oldest window must begin after the last step took effect), never unconditionally. Tests, the known-failed case first: a changed N today hashes another program under the same program id (a hard fork no pack line told apart); after, rung 0 is class v4 byte for byte, a rung above carries its pass count in the id, 8,999 bps in one window of seven does not move the step, a two-step jump is impossible, down never passes rung 0, an inadmissible rung is never entered. Stated in `site/litepaper.html`, Mining section ("The work that waits can grow"). + +Answer: Correct on both counts, and the second was the sharper one. The X9 (Bitmain, about 1 MH/s at 2,472 W, approximate, github.com/monero-project/monero/issues/10270) is a shipped 3x per-joule edge over a desktop CPU on the best-known latency-bound random-program design, seven years after launch; it makes the k = 0.3 column of the chip model a product class rather than an attacker's claim, and the public headline is now the range 2.1x (k = 1) to 3.9x (k = 0.33) over the RTX 5090 at class v4, with the ladder taking the X9 bracket to about 2.8x by rung 2 and the Apple tier's to about 1.1x by rung 3. The ladder does not close the gap; it is the chain's only automatic answer, it moves at the pace of the cards that pay for it, and the honest card's watts remain the lever that moves every row (algorithm lane proposal 7). What the ladder gives up by design: a chip holding over 10 percent of weight can stall it, and the status quo it stalls is a rung the cards already run. + +Evidence: `docs/design/latency-ladder.md` (the rule, the hostile review, the measured verifier table, the X9 arithmetic); `igneum-pow/src/generator.rs` test `latency_ladder_known_failed_a_changed_n_was_a_hard_fork_and_rungs_are_class_v4`; the fork's `consensus/core/src/igneum.rs` test `latency_ladder_rule`, `consensus/pow/src/igneum.rs` test `latency_ladder_rungs_are_programs_of_their_own_over_one_day_cache`; `infra/fast-time/latency-ladder.mjs` (the step, no-step and known-failed cases). + ## 2. Finality and attacks ### F1. Finality is attackable for the first month diff --git a/igneum-pow/src/generator.rs b/igneum-pow/src/generator.rs index b55e2c954..8bbc72c9f 100644 --- a/igneum-pow/src/generator.rs +++ b/igneum-pow/src/generator.rs @@ -326,6 +326,11 @@ pub fn era_draw(era_bytes: &[u8], allowed: &[u8]) -> EraParams { let j = i + (r[i] % n as u64) as usize; c.swap(i, j); } + // Draws 8 and 9, consumed and not used (spec 01 section 1.13.1 for `epoch_len`; `docs/design/latency-ladder.md` + // section 2 for the latency ladder): both parameters are set by miner signal, and consuming their slots here means a + // later use of either changes no other draw. Nothing below reads them, so every value drawn above is what it was. + let _epoch_len_draw = s.next(); + let _latency_ladder_draw = s.next(); let mut chosen: Vec = c[..free].to_vec(); chosen.sort_unstable(); let mut pos = [0u8; 4]; @@ -804,7 +809,43 @@ pub const V3_CLASS: LoadClass = LoadClass { era: None, hot: None, ..LoadClass::M /// of 256 ALU instructions run 27 times per iteration ("mx8+sh256x27", 55,296 shadow instructions per hash). The /// base program, the 16 loads, the item construction, the cache growth rule and the era draw are class v3's, draw /// for draw, so a v4 epoch's day cache and dataset are the v3 day's. Composed with the era exactly as V3 is. -pub const V4_CLASS: LoadClass = LoadClass { shadow: Some(ShadowClass { instrs: 256, reps: 27 }), ..V3_CLASS }; +pub const V4_CLASS: LoadClass = LoadClass { shadow: Some(ShadowClass { instrs: V4_SHADOW_INSTRS, reps: V4_SHADOW_REPS }), ..V3_CLASS }; + +/// The shadow block size of class v4 at every rung of the latency ladder (`docs/design/latency-ladder.md`): 256 +/// instructions. The ladder moves the pass count alone. +pub const V4_SHADOW_INSTRS: u16 = 256; + +/// The shadow passes of class v4 at rung 0 of the latency ladder: 27 (`mx8+sh256x27`, about 102,100 counted ops). +pub const V4_SHADOW_REPS: u16 = 27; + +/// Class v4 at a rung of the latency ladder (`docs/design/latency-ladder.md` section 2): [`V4_CLASS`] with the +/// 256-instruction shadow block run `reps` times per iteration. `reps` 0 means the class's own count, so +/// `v4_class_at(0) == v4_class_at(27) == V4_CLASS` and a caller that knows no rung changes nothing. +pub fn v4_class_at(reps: u16) -> LoadClass { + if reps == 0 { + V4_CLASS + } else { + LoadClass { shadow: Some(ShadowClass { instrs: V4_SHADOW_INSTRS, reps }), ..V3_CLASS } + } +} + +/// The shadow passes of a class v4 load class at any rung of the ladder, the era draw set aside (`Some(27)` for +/// [`V4_CLASS`] itself); `None` for every other class, a measurement class with another block size included. +pub fn v4_rung_reps(class: &LoadClass) -> Option { + let base = LoadClass { era: None, ..*class }; + match base.shadow { + Some(ShadowClass { instrs: V4_SHADOW_INSTRS, reps }) if LoadClass { shadow: None, ..base } == V3_CLASS => Some(reps), + _ => None, + } +} + +/// Counted integer ops per hash of class v4 at `reps` shadow passes, the convention of +/// `docs/analysis/latency-shadow-2026-10-06.md` section 1: 930 for the base program and its loads, 1.83 ops per +/// shadow instruction (137 / 75 over the non-load weights), 8 iterations x 256 instructions x `reps` shadow +/// instructions. Approximate by construction; the label of a rung, never a consensus value. +pub fn v4_counted_ops(reps: u16) -> u64 { + 930 + (ITERATIONS as u64 * V4_SHADOW_INSTRS as u64 * reps as u64 * 183).div_ceil(100) +} /// The width set class v3's era draw chooses from: 4 bytes only (the read-width decision of 5 October 2026; the /// draw is consumed, so widening the set at genesis keeps the derivation). @@ -1009,7 +1050,12 @@ impl Program { /// || attempt_le32`. Written into every pack so a version 1 program, or another attempt of the same seed, /// can never be mistaken for this one. pub fn program_id(&self) -> u64 { - if self.class.is_v2() || self.generator == GENERATOR_VERSION_V3 || self.generator == GENERATOR_VERSION_V4 { + // Latency ladder (docs/design/latency-ladder.md section 7): a class v4 program above rung 0 carries its shadow + // size in the id (`program_id_class`, the "shadow/" bytes), so two rungs of one seed never share an id and a + // pack of another rung is refused as a pack of another class is. Rung 0 keeps `program_id(4, seed, attempt)` + // byte for byte, so every v4 id written before the ladder stands. + let v4_rung_0 = self.generator == GENERATOR_VERSION_V4 && LoadClass { era: None, ..self.class } == V4_CLASS; + if self.class.is_v2() || self.generator == GENERATOR_VERSION_V3 || v4_rung_0 { // Spec 01 section 1.4.6: a class v3 program's id is `program_id(3, seed, attempt)`, a class v4 program's // `program_id(4, seed, attempt)` (Counter ASIC 3.0); the generator version in the preimage separates // them from every version 2 program of the same seed @@ -1378,6 +1424,27 @@ pub fn generate_from_seed_bytes_program_class(seed_string: &str, seed_bytes: &[u p } +/// [`generate_from_seed_bytes_program_class`] at a rung of the latency ladder (`docs/design/latency-ladder.md` +/// section 2): `shadow_reps` is the shadow pass count the chain's step gives the epoch, 0 for the class's own. Class +/// v4 at a rung above 0 draws from [`v4_class_at`] with the era inside and generator 4 stamped; every other class, +/// and class v4 at rung 0, is [`generate_from_seed_bytes_program_class`] byte for byte. The base program, the 16 loads +/// and the era draw do not move with the rung: only the pass count of the shadow block does. +pub fn generate_from_seed_bytes_program_class_shadow(seed_string: &str, seed_bytes: &[u8], class: ProgramClass, era_bytes: Option<&[u8]>, shadow_reps: u16) -> Program { + if class != ProgramClass::V4 || shadow_reps == 0 || shadow_reps == V4_SHADOW_REPS { + return generate_from_seed_bytes_program_class(seed_string, seed_bytes, class, era_bytes); + } + let base = v4_class_at(shadow_reps); + match era_bytes { + Some(era) => generate_era_generator(seed_string, seed_bytes, base, era, &V3_ALLOWED, GENERATOR_VERSION_V4), + None => { + let mut p = generate_from_seed_bytes_class(seed_string, seed_bytes, base); + p.generator = GENERATOR_VERSION_V4; + p.era_bytes = None; + p + } + } +} + /// The program of a seed string (its UTF-8 bytes are the program seed). pub fn generate(seed_string: &str) -> Program { generate_from_seed_bytes(seed_string, seed_string.as_bytes()) @@ -2027,4 +2094,72 @@ mod tests { assert_eq!(again.shadow, sh.shadow); assert_eq!(crate::verify::hash_warp(&again, 0, &ds), h1); } + + /// Latency ladder (`docs/design/latency-ladder.md`), the known-failed case first: before the ladder a changed N + /// was a hard fork. Two nodes drawing class v4 at 27 and at 35 passes from the same seeds build the same base + /// program and the same shadow block, carry generator 4 on both and, under the id rule as it stood, the SAME id + /// (`program_id(4, seed, attempt)` reads no shadow size), yet their warps hash differently: every block of one is + /// invalid to the other and no pack line told them apart. After: the rung is a parameter of the chain's step, + /// rung 0 is `V4_CLASS` byte for byte, and a rung above carries its pass count in the id. + #[test] + fn latency_ladder_known_failed_a_changed_n_was_a_hard_fork_and_rungs_are_class_v4() { + let era = [7u8; 32]; + let today = generate_from_seed_bytes_program_class("igneum-genesis", b"igneum-genesis", ProgramClass::V4, Some(&era)); + let r0 = generate_from_seed_bytes_program_class_shadow("igneum-genesis", b"igneum-genesis", ProgramClass::V4, Some(&era), 0); + let r27 = generate_from_seed_bytes_program_class_shadow("igneum-genesis", b"igneum-genesis", ProgramClass::V4, Some(&era), V4_SHADOW_REPS); + assert_eq!(r0, today, "rung 0 is class v4 byte for byte"); + assert_eq!(r27, today, "27 passes is rung 0"); + let r1 = generate_from_seed_bytes_program_class_shadow("igneum-genesis", b"igneum-genesis", ProgramClass::V4, Some(&era), 35); + // the failed case, as it stood: the same base, the same block, the same seed words, generator 4 on both, one id + assert_eq!(r1.instrs, today.instrs, "the base program is the class's, draw for draw"); + assert_eq!(r1.shadow, today.shadow, "the block is the same draw; only the pass count moves"); + assert_eq!((r1.seed, r1.attempt, r1.generator, r1.era_bytes.clone()), (today.seed, today.attempt, today.generator, today.era_bytes.clone())); + assert_eq!(program_id(GENERATOR_VERSION_V4, &r1.seed, r1.attempt), program_id(GENERATOR_VERSION_V4, &today.seed, today.attempt), "the old rule gave both nodes one id"); + assert_eq!(r1.shadow_reps(), 35); + assert_eq!(r1.shadow_instrs_per_hash(), ITERATIONS * 256 * 35); + let ds = crate::verify::DatasetSource::new("2026-10-06", crate::verify::DatasetMode::ClosedForm, 20); + let h0 = crate::verify::hash_warp(&today, 0, &ds); + let h1 = crate::verify::hash_warp(&r1, 0, &ds); + assert_ne!(h0, h1, "a changed N is another hash: before the ladder, a hard fork"); + // after: the rung is in the id above rung 0; rung 0 keeps the id written before the ladder + assert_ne!(r1.program_id(), today.program_id(), "the rung is in the id"); + assert_eq!(r1.program_id(), program_id_class(GENERATOR_VERSION_V4, &r1.seed, r1.attempt, &r1.class)); + assert_eq!(today.program_id(), program_id(GENERATOR_VERSION_V4, &today.seed, today.attempt), "rung 0 keeps the v4 id"); + assert_eq!(r1.program_class(), ProgramClass::V4, "a rung is class v4"); + assert_eq!(v4_rung_reps(&r1.class), Some(35)); + assert_eq!(v4_rung_reps(&today.class), Some(27)); + assert_eq!(v4_rung_reps(&V4_CLASS), Some(27)); + assert_eq!(v4_rung_reps(&V3_CLASS), None); + assert_eq!(v4_rung_reps(&LoadClass::V2), None); + assert_eq!(v4_rung_reps(&LoadClass::MX8.with_shadow(64, 52)), None, "another block size is a measurement class, not a rung"); + assert_eq!(v4_class_at(0), V4_CLASS); + assert_eq!(v4_class_at(27), V4_CLASS); + assert_eq!(v4_class_at(35), LoadClass::parse("mx8+sh256x35").unwrap()); + assert_eq!(LoadClass { era: None, ..r1.class }, v4_class_at(35), "the era rides inside the rung's class"); + assert_eq!(r1.class.era, today.class.era, "the same era draw at every rung"); + assert!(check(&r1).is_ok(), "the acceptance rule reads the base program, which did not move"); + // every rung of the designed ladder is another program with its own id + let rungs = [27u16, 35, 53, 88, 173, 267]; + let ids: Vec = rungs.iter().map(|&r| generate_from_seed_bytes_program_class_shadow("igneum-genesis", b"igneum-genesis", ProgramClass::V4, Some(&era), r).program_id()).collect(); + for i in 0..ids.len() { + for j in 0..i { + assert_ne!(ids[i], ids[j], "rungs {} and {} share an id", rungs[i], rungs[j]); + } + } + // the ops labels of the rungs, within 1 percent of the measured rungs of algorithm.md 5.3a + for (r, ops) in [(27u16, 102_100u64), (35, 132_100), (53, 199_600), (88, 330_700), (173, 649_400), (267, 1_001_600)] { + let got = v4_counted_ops(r); + assert!(got.abs_diff(ops) * 100 < ops, "reps {r}: {got} counted ops against the label {ops}"); + } + // other classes ignore the rung; class v4 without an era takes it + assert_eq!(generate_from_seed_bytes_program_class_shadow("igneum-genesis", b"igneum-genesis", ProgramClass::V3, Some(&era), 35), generate_from_seed_bytes_program_class("igneum-genesis", b"igneum-genesis", ProgramClass::V3, Some(&era))); + assert_eq!(generate_from_seed_bytes_program_class_shadow("igneum-genesis", b"igneum-genesis", ProgramClass::V2, None, 35), generate_from_seed_bytes_program_class("igneum-genesis", b"igneum-genesis", ProgramClass::V2, None)); + let bare = generate_from_seed_bytes_program_class_shadow("igneum-genesis", b"igneum-genesis", ProgramClass::V4, None, 35); + assert_eq!((bare.generator, bare.shadow_reps(), bare.era_bytes.is_none()), (GENERATOR_VERSION_V4, 35, true)); + // the era stream consumes draws 8 and 9 after the seven it uses, so the seven are what they were: the pinned era + // packs of tests/packs.rs hold the values; here, the draw is a function of the bytes and the set alone + let e = era_draw(&era, &V3_ALLOWED); + assert_eq!(e, era_draw(&era, &V3_ALLOWED)); + assert_eq!(e.width_words, 1); + } } diff --git a/igneum-pow/src/lib.rs b/igneum-pow/src/lib.rs index 1b398fab5..244c4ab17 100644 --- a/igneum-pow/src/lib.rs +++ b/igneum-pow/src/lib.rs @@ -34,7 +34,7 @@ pub mod verify; pub use bind::{block_init_words, day_bytes, pow256_from_lane, target64_from_le256}; pub use accept::{check as accept_program, AcceptReport, Reject}; -pub use generator::{generate, generate_from_seed_bytes, generate_from_seed_bytes_program_class, Instr, LoadClass, Op, Program, ProgramClass, GENERATOR_VERSION, GENERATOR_VERSION_V3, GENERATOR_VERSION_V4, V3_CLASS, V4_CLASS}; +pub use generator::{generate, generate_from_seed_bytes, generate_from_seed_bytes_program_class, generate_from_seed_bytes_program_class_shadow, v4_class_at, v4_counted_ops, v4_rung_reps, Instr, LoadClass, Op, Program, ProgramClass, GENERATOR_VERSION, GENERATOR_VERSION_V3, GENERATOR_VERSION_V4, V3_CLASS, V4_CLASS, V4_SHADOW_INSTRS, V4_SHADOW_REPS}; pub use memhard::{cache_log2_words, dataset_log2_words, days_since_genesis, growth_doublings, Cache, MemhardCpu, MixParams, Shape}; pub use seed::{fnv1a64, seed_words, SplitMix64}; pub use verify::{hash_warp, interpret_warp_init, verify_block, DatasetMode, DatasetSource, Epoch}; diff --git a/igneum-pow/src/main.rs b/igneum-pow/src/main.rs index 3d32504ee..be02cad05 100644 --- a/igneum-pow/src/main.rs +++ b/igneum-pow/src/main.rs @@ -44,6 +44,9 @@ struct Args { days: u64, /// The program class (Counter ASIC 2.0 seam): v2 (default), v3 (V3_CLASS, generator 3) or v4 (V4_CLASS, generator 4). program_class: Option, + /// The shadow pass count of a class v4 program at a rung of the latency ladder (`--shadow-reps`, 0 = the class's + /// own 27; `docs/design/latency-ladder.md`); read under `--program-class v4` only. + shadow_reps: u16, /// The era seed bytes a class v3 chain program records (`--era-hex`). era_hex: Option, /// Era layout: `--era igneum-era-test/` or `--era :<64 hex>` composes the era class over `--class` with @@ -99,6 +102,7 @@ fn usage() -> ! { \x20 also: w4, w16, w64, w64x4, p4,p16,p64[xN], m[g], +shx (latency-shadow block of S ALU instructions x R passes per iteration, Counter ASIC 3.0 item 8)\n\ \x20 --days N days since genesis for the cache growth rule of a class with it (default 0: the 2^26-word cache)\n\ \x20 --program-class v2|v3|v4 the program class of the seam (v3 = generator 3 on V3_CLASS, v4 = generator 4 on V4_CLASS = mx8+sh256x27, the chain's own derivation; --era-hex records the era seed)\n\ + \x20 --shadow-reps N class v4 at a rung of the latency ladder: the shadow block's pass count (0 = the class's own 27; docs/design/latency-ladder.md), with --program-class v4\n\ \x20 --era E era layout over --class: igneum-era-test/ or :<64 hex> (the 32-byte era seed E_n)\n\ \x20 --era-widths 4[,16,64] the width set the era draws from, in bytes (default 4: pinned; more lets the era draw it)" ); @@ -123,6 +127,7 @@ fn parse() -> Args { days: 0, program_class: None, era_hex: None, + shadow_reps: 0, era: None, era_widths: vec![1], }; @@ -146,6 +151,7 @@ fn parse() -> Args { "--days" => a.days = val().parse().unwrap_or_else(|_| usage()), "--program-class" => a.program_class = Some(ProgramClass::parse(&val()).unwrap_or_else(|| usage())), "--era-hex" => a.era_hex = Some(val()), + "--shadow-reps" => a.shadow_reps = val().parse().unwrap_or_else(|_| usage()), "--era" => a.era = Some(parse_era(&val()).unwrap_or_else(|| usage())), "--era-widths" => a.era_widths = parse_widths(&val()).unwrap_or_else(|| usage()), _ => usage(), @@ -222,7 +228,7 @@ fn epoch_of_class(a: &Args, mode: DatasetMode) -> (Epoch, String) { let label = format!("igneum-epoch/{eh}/day/{dh}"); let e = match a.program_class { Some(pc) => Epoch { - program: Epoch::chain_program(&eb, era.as_deref(), pc, &label), + program: Epoch::chain_program_shadow(&eb, era.as_deref(), pc, a.shadow_reps, &label), dataset: Epoch::chain_dataset_day(&db, pc, a.days, a.dataset_log2), }, None => Epoch::from_seed_bytes_day(&eb, &db, &label, a.class, a.days, a.dataset_log2), @@ -232,7 +238,7 @@ fn epoch_of_class(a: &Args, mode: DatasetMode) -> (Epoch, String) { _ => { let e = match a.program_class { Some(pc) => { - let program = igneum_pow::generator::generate_from_seed_bytes_program_class(&a.seed, a.seed.as_bytes(), pc, era.as_deref()); + let program = igneum_pow::generator::generate_from_seed_bytes_program_class_shadow(&a.seed, a.seed.as_bytes(), pc, era.as_deref(), a.shadow_reps); let lc = pc.load_class(); let shape = Shape::for_class_day(&lc, a.days); let log2 = if lc.growth { igneum_pow::memhard::dataset_log2_words(a.dataset_log2, a.days) } else { a.dataset_log2 }; diff --git a/igneum-pow/src/verify.rs b/igneum-pow/src/verify.rs index 4b22630c4..34692b684 100644 --- a/igneum-pow/src/verify.rs +++ b/igneum-pow/src/verify.rs @@ -626,6 +626,13 @@ impl Epoch { crate::generator::generate_from_seed_bytes_program_class(label, epoch_seed, class, era_bytes) } + /// [`Epoch::chain_program`] at a rung of the latency ladder (`docs/design/latency-ladder.md`): `shadow_reps` is + /// the shadow pass count the chain's step gives the epoch (0 = the class's own, which is [`Epoch::chain_program`] + /// byte for byte). The node's engine and the miner's pack export call this with the step the template names. + pub fn chain_program_shadow(epoch_seed: &[u8], era_bytes: Option<&[u8]>, class: ProgramClass, shadow_reps: u16, label: &str) -> Program { + crate::generator::generate_from_seed_bytes_program_class_shadow(label, epoch_seed, class, era_bytes, shadow_reps) + } + /// The day's cache and dataset of [`Epoch::from_chain_seeds`], the one entry the node's engine builds a day /// cache through. The class is an argument because the Counter ASIC 2.0 integration gives class v3 its own item /// construction (the mixer multiplier) and cache size schedule (ca2-mixer); today both classes build the day of diff --git a/infra/fast-time/README.md b/infra/fast-time/README.md index cb74fd381..e27543f74 100644 --- a/infra/fast-time/README.md +++ b/infra/fast-time/README.md @@ -36,6 +36,13 @@ Class v4 switch gate (Counter ASIC 3.0 G4): `node infra/fast-time/class-v4.mjs` known-failed case, which must report FAIL with no v4 epoch; `--metal ` puts a real Metal miner on node 0 for G4b; see `docs/plans/counter-asic-3-node.md`). +Latency ladder gate (`docs/design/latency-ladder.md` section 9): `node infra/fast-time/latency-ladder.mjs --signal up,up,up --expect step` +(3 nodes on ports 29720 and up, `igneum-devnet-972`, data `/tmp/igneum-fast-time-ladder`, class v4 from genesis with class +signalling off, the ladder active from DAA 0 with a 60-DAA window, each node's bits from `IGNEUM_LADDER_SIGNAL`; the step to +rung 1 at epoch 8, the first with seven full windows, the rung-1 program ids against the CLI's `--shadow-reps 35`, and no +second step inside the next two epochs; `--signal up,up,none --expect no-step` holds rung 0 for 10 epochs; `--signal +up,up,none --expect step` is the known-failed case and must report FAIL). + Miners: `igneum-miner` follows the epoch length and lead its node reports in every template (`pow_epoch`), no flag. The dataset day is not in the template, so a real-hash miner on a fast-time network takes `IGNEUM_POW_DAY_MS=1440000` in its environment (the node reads it from the file). The environment variables `IGNEUM_POW_EPOCH_BLOCKS`, @@ -70,6 +77,8 @@ Time parameters, divided by 60 (devnet value, 60x value): | `program_class_v3_activation_daa` | never | never | a height, not a clock: the lottery hash draws programs from class v3 (Counter ASIC 2.0, 5 Oct 2026) from the first EPOCH whose start is at or above this DAA score (rounded up to an epoch boundary: at 60 DAA per epoch, 150 means epoch 3 at DAA 180); `infra/fast-time/class-v3.mjs` sets it a few epochs ahead in its merged file | | `program_class_v4_activation_daa` | never | never | a height, not a clock: the lottery hash draws programs from class v4 (Counter ASIC 3.0, 6 Oct 2026: class v3 plus the latency-shadow block) from the first EPOCH whose start is at or above this DAA score, rounded up like the v3 switch; `infra/fast-time/class-v4.mjs` sets it a few epochs ahead in its merged file | | `program_class_v4_signal_window_daa` | 86,400 | 120 | a DAA window (one day of blocks), divided by 60 and rounded to two epochs: the class v4 signal tally (PROPOSED, `docs/plans/counter-asic-3-node.md` section 6) over the blue blocks below each epoch's seed block; 0 = off; `infra/fast-time/class-v4-signal.mjs` is its gate | +| `latency_ladder_window_daa` | 86,400 | 120 | a DAA window (one day of blocks), divided by 60 and rounded to two epochs as the class window is: one of the seven windows of the latency ladder's step rule (`docs/design/latency-ladder.md`); `infra/fast-time/latency-ladder.mjs` is its gate and passes `--window 60` by default | +| `latency_ladder_activation_daa`, `latency_ladder` | never, the designed six rungs | the same | a height and a genesis list, not clocks: the ladder runs from the first epoch at or above the height (rung 0 until a signal moves it); the harness sets 0 in its merged file | | `proving_v1_fresh_rule_daa`, `exec_restart_number`, `exec_restart_trust_daa`, `exec_restart_hash` | never, never, never, "" | the same | heights and a hash, not clocks (the 0.3.12 and 0.3.13 switches); present so the fork's every-field test (`fast_time_60x_file_is_the_devnet_at_60x`) holds; added 6 Oct 2026 with the class v4 field | Unchanged, and why: diff --git a/infra/fast-time/latency-ladder.mjs b/infra/fast-time/latency-ladder.mjs new file mode 100644 index 000000000..0dbb1f0ae --- /dev/null +++ b/infra/fast-time/latency-ladder.mjs @@ -0,0 +1,275 @@ +#!/usr/bin/env node +// The latency ladder's fast-time gate (docs/design/latency-ladder.md section 9; the class-v4-signal.mjs shape): a 3-node +// network on override-60x.json, class v4 from genesis (v3 and the v4 floor at 0, the class window 0: class signalling off, +// so the ladder opens the header's high byte on its own), the ladder active from DAA 0 with one window of --window DAA +// (default 60, one epoch; seven windows = 420 DAA, so the first epoch whose seed block has seven full windows below it is +// epoch 8 at DAA 480), each node's ladder signal set by IGNEUM_LADDER_SIGNAL (--signal a,b,c of up|down|none), one real CPU +// miner per node. Ports 29720 and up, network igneum-devnet-972, data /tmp/igneum-fast-time-ladder. +// +// The cases and the known-failed case: +// --signal up,up,none --expect no-step two of three miners signal up: about 67 percent, rung 0 must hold (run 10 epochs) +// --signal up,up,up --expect step all three: rung 1 (35 shadow passes) from epoch 8, the first with seven full +// windows, every miner's rung-1 program id equal to the CLI's --shadow-reps 35 id and +// unequal to the rung-0 id, and NO second step inside the next two epochs +// --signal up,up,none --expect step the known-failed case: the harness must report FAIL (no step happened) +// +// node infra/fast-time/latency-ladder.mjs --signal a,b,c --expect step|no-step [--window 60] [--secs 900] [--epochs 10] +// IGNEUMD, IGNEUM_MINER, IGNEUM_POW name the binaries (defaults: the ladder fork worktree's target/release and +// igneum-pow/target/release/igneum-pow, the layout on igneum-build-1 under /srv/builds/igneum-wt-ladder). + +import { spawn, spawnSync } from 'node:child_process'; +import { mkdirSync, rmSync, writeFileSync, readFileSync, openSync, existsSync } from 'node:fs'; +import { connectRpc } from '../../tools/finality-attacks/lib/rpc.mjs'; +import { devAddress } from '../../tools/harness/lib/address.mjs'; + +const ROOT = new URL('../../', import.meta.url).pathname; +const FILE = `${ROOT}infra/fast-time/override-60x.json`; +const BIN = process.env.IGNEUM_LADDER_BIN || `${ROOT}vendor/igneum-node-ladder/target/release`; +const IGNEUMD = process.env.IGNEUMD || `${BIN}/igneumd`; +const CPU_MINER = process.env.IGNEUM_MINER || `${BIN}/igneum-miner`; +const IGNEUM_POW = process.env.IGNEUM_POW || `${ROOT}igneum-pow/target/release/igneum-pow`; +const TMP = process.env.IGNEUM_LADDER_TMP || '/tmp/igneum-fast-time-ladder'; +const BASE = 29720, SUFFIX = 972; +const NEVER = '18446744073709551615'; +const RUNG0 = 27, RUNG1 = 35, WINDOWS = 7, THRESHOLD = 9000; +const args = process.argv.slice(2); +const flag = (name, dflt) => { const i = args.indexOf(`--${name}`); return i >= 0 ? +args[i + 1] : dflt; }; +const sflag = (name) => { const i = args.indexOf(`--${name}`); return i >= 0 ? args[i + 1] : null; }; +const GENESIS_BITS = flag('genesis-bits', 0x1f010000); +const SECS = flag('secs', 900); +const EPOCHS = flag('epochs', 10); +const WINDOW = flag('window', 60); +const SIGNAL = (sflag('signal') || 'up,up,up').split(',').map(s => s.trim().toLowerCase()); +const EXPECT = sflag('expect') || 'step'; +if (!['step', 'no-step'].includes(EXPECT) || SIGNAL.length !== 3 || !SIGNAL.every(s => ['up', 'down', 'none'].includes(s))) { console.error('usage: --signal a,b,c (up|down|none) --expect step|no-step'); process.exit(2); } +const started = []; +const log = (...a) => console.log(new Date().toISOString().slice(11, 23), ...a); +const sleep = (ms) => new Promise(r => setTimeout(r, ms)); +for (const b of [IGNEUMD, CPU_MINER]) if (!existsSync(b)) { console.error(`missing ${b}`); process.exit(2); } + +rmSync(TMP, { recursive: true, force: true }); mkdirSync(TMP, { recursive: true }); +const baseText = readFileSync(FILE, 'utf8'); +const field = (name) => { const m = new RegExp(`"${name}":\\s*([0-9]+)`).exec(baseText); return m ? +m[1] : undefined; }; +const EPOCH = field('pow_epoch_blocks'); +const LEAD = field('pow_epoch_lead'); +const DAY_MS = field('pow_day_ms'); +// the first epoch whose seed block (the last chain block below L*e - lead) can have DAA >= 7 x WINDOW: L*e - lead - 1 >= 7W +let FIRST_STEP_EPOCH = 0; +while (FIRST_STEP_EPOCH * EPOCH - LEAD - 1 < WINDOWS * WINDOW) FIRST_STEP_EPOCH++; +function mergeOverrideText(text, fields) { + let out = text; + for (const k of Object.keys(fields)) out = out.replace(new RegExp(`\\s*"${k}":\\s*[^,}\\n]+,?`), ''); + const extra = Object.entries(fields).map(([k, v]) => `"${k}": ${typeof v === 'string' && !/^\d+$/.test(v) ? JSON.stringify(v) : v}`).join(', '); + return out.replace(/,?\s*}\s*$/, `,\n ${extra}\n}\n`); +} +const override = `${TMP}/override.json`; +writeFileSync(override, mergeOverrideText(baseText, { + genesis_bits: GENESIS_BITS, skip_proof_of_work: false, + program_class_v3_activation_daa: '0', program_class_v4_activation_daa: '0', program_class_v4_signal_window_daa: '0', + latency_ladder_activation_daa: '0', latency_ladder_window_daa: String(WINDOW), +})); +log(`signals ${SIGNAL.join('/')}, expect ${EXPECT}; class v4 from genesis, the ladder active from DAA 0, window ${WINDOW} DAA x ${WINDOWS} (the first epoch that can step is ${FIRST_STEP_EPOCH}, DAA ${FIRST_STEP_EPOCH * EPOCH}); ${EPOCH} DAA per epoch, lead ${LEAD}; run ${SECS} s or ${EPOCHS} epochs`); + +class Node { + constructor(i, connect = []) { + this.i = i; this.grpcPort = BASE + i * 10; this.p2pPort = BASE + i * 10 + 1; this.jsonPort = BASE + i * 10 + 2; + this.connect = connect; this.dir = `${TMP}/n${i}`; this.logFile = `${this.dir}/node.log`; + } + get grpc() { return `grpc://127.0.0.1:${this.grpcPort}`; } + async start() { + mkdirSync(this.dir, { recursive: true }); + const a = ['--devnet', `--devnet-suffix=${SUFFIX}`, '--nodnsseed', '--disable-upnp', '--nologfiles', '--enable-unsynced-mining', '--utxoindex', + `--appdir=${this.dir}`, `--rpclisten=127.0.0.1:${this.grpcPort}`, `--rpclisten-json=127.0.0.1:${this.jsonPort}`, + `--listen=127.0.0.1:${this.p2pPort}`, `--override-params-file=${override}`, '--loglevel=info', '--yes']; + if (this.connect.length) a.push(`--connect=${this.connect.join(',')}`); else a.push('--outpeers=0'); + const out = openSync(this.logFile, 'a'); + // the node's own ladder signal: what its templates carry in bits 15 and 14 + this.proc = spawn(IGNEUMD, a, { stdio: ['ignore', out, out], env: { ...process.env, IGNEUM_LADDER_SIGNAL: SIGNAL[this.i] } }); + started.push(this.proc); + await sleep(1200); + this.rpc = await connectRpc(`ws://127.0.0.1:${this.jsonPort}`); + log(`n${this.i} up pid ${this.proc.pid} json ${this.jsonPort} p2p ${this.p2pPort}, signals ${SIGNAL[this.i]}`); + return this; + } + grepLog(re) { try { return readFileSync(this.logFile, 'utf8').split('\n').filter(l => re.test(l)); } catch { return []; } } +} +function miner(bin, argv, name, env = {}) { + const out = openSync(`${TMP}/${name}.log`, 'a'); + const p = spawn(bin, argv, { stdio: ['ignore', out, out], env: { ...process.env, ...env } }); + started.push(p); + return p; +} +async function stopAll() { + for (const p of started.reverse()) { try { p.kill('SIGINT'); } catch { } } + await sleep(1500); + for (const p of started) { try { p.kill('SIGKILL'); } catch { } } +} +process.on('SIGINT', async () => { await stopAll(); process.exit(130); }); +process.on('unhandledRejection', async (e) => { log(`FAILED: ${e?.stack || e}`); await stopAll(); process.exit(3); }); +const minerLog = (i) => { try { return readFileSync(`${TMP}/cpu${i}.log`, 'utf8').split('\n'); } catch { return []; } }; +const STEP_LINE = /Latency ladder step by miner signal: epoch (\d+) moves to rung (\d+) \((\d+) shadow passes, from rung (\d+)\): (up|down) in each of (\d+) consecutive windows of (\d+) DAA .*weakest up (\d+) bps, weakest down (\d+) bps/; +const LADDER_LINE = /Latency ladder from the override file/; +const ACTIVE_LINE = /Latency ladder active: rungs/; +const OWN_LINE = /Latency ladder signal from IGNEUM_LADDER_SIGNAL: this node signals (\w+)/; + +const t0 = Date.now(); +const since = () => ((Date.now() - t0) / 1000).toFixed(1); +const n0 = await new Node(0).start(); +const n1 = await new Node(1, [`127.0.0.1:${n0.p2pPort}`]).start(); +const n2 = await new Node(2, [`127.0.0.1:${n0.p2pPort}`]).start(); +const nodes = [n0, n1, n2]; +for (const n of nodes) log(`n${n.i}: ${n.grepLog(LADDER_LINE).map(l => l.replace(/^.*?(Latency ladder from)/, '$1'))[0] || '(no ladder line)'} | ${n.grepLog(OWN_LINE).map(l => l.replace(/^.*?(this node signals)/, '$1'))[0] || '(no signal line)'}`); +log(`n0 digest: ${n0.grepLog(/Consensus params digest/).map(l => l.replace(/^.*?digest: /, '').slice(0, 16)).join(' ')}`); +nodes.forEach((n, i) => miner(CPU_MINER, ['mine', n.grpc, '1', String(SECS), `cpu${i}`, '--engine', 'igneum-pow', '--payout-label', `cpu${i}`, '--status-secs', '30', '--no-vote'], `cpu${i}`, { IGNEUM_POW_DAY_MS: String(DAY_MS) })); +const pay = devAddress('fast-time-ladder'); + +const epochs = new Map(); +let firstStep = null, lastEpoch = -1, lastReport = 0, lastDaa = 0, endAt = null; +const samples = []; +while (Date.now() - t0 < SECS * 1000) { + await sleep(1000); + let daa = null, epoch = null, cls = null, reps = null, nextReps = null, step = null, nextStep = null, up = null, upWeak = null, down = null, sig = null, stepEpoch = null, eraSeed = null; + try { + const t = await n0.rpc.call('getBlockTemplate', { payAddress: pay, extraData: [] }); + const pe = t.powEpoch || t.pow_epoch || {}; + daa = pe.virtualDaaScore ?? t.block?.header?.daaScore; epoch = pe.epochIndex; cls = pe.programClass; eraSeed = pe.eraSeed; + reps = pe.latencyLadderReps; nextReps = pe.nextLatencyLadderReps; step = pe.latencyLadderStep; nextStep = pe.nextLatencyLadderStep; + up = pe.latencyLadderUpBps; upWeak = pe.latencyLadderUpWeakestBps; down = pe.latencyLadderDownBps; sig = pe.latencyLadderSignal; stepEpoch = pe.latencyLadderStepEpoch; + } catch (e) { log(`template: ${e.message}`); } + if (epoch != null && epoch !== lastEpoch) { + epochs.set(epoch, { class: cls, reps, step, firstSeenDaa: daa, at: +since(), eraSeed: eraSeed == null ? null : String(eraSeed), up_bps: up, up_weakest_bps: upWeak, down_bps: down, step_epoch: stepEpoch ?? null }); + log(`epoch ${lastEpoch} -> ${epoch} at daa ${daa}, ${since()} s: template class ${cls} rung ${step} (${reps} passes), next rung ${nextStep} (${nextReps}), up ${up} bps (weakest of ${WINDOWS}: ${upWeak}), down ${down} bps, this node signals ${sig}, step took effect at epoch ${stepEpoch ?? 'none'}`); + if (firstStep == null && step > 0) { firstStep = { epoch, daa, step, reps, at: +since() }; log(`LADDER STEP: the template is rung ${step} (${reps} shadow passes) from epoch ${epoch} (daa ${daa}) at ${since()} s wall`); } + lastEpoch = epoch; + } + lastDaa = daa ?? lastDaa; + if (Date.now() - lastReport > 15000) { + lastReport = Date.now(); + const counts = await Promise.all(nodes.map(async n => { try { const d = await n.rpc.call('getBlockDagInfo'); return `${d.blockCount}/${String(d.sink).slice(0, 8)}`; } catch { return '?'; } })); + log(`t=${since()} s daa ${daa} epoch ${epoch} rung ${step} (${reps}) up ${up} bps weakest ${upWeak} blocks/sink per node ${counts.join(' ')}`); + samples.push({ t: +since(), daa, epoch, step, reps, up_bps: up, up_weakest_bps: upWeak, nodes: counts }); + } + // the end: two epochs after a step (to show no second step), or --epochs epochs when no step is expected + if (firstStep != null && daa != null && daa >= (firstStep.epoch + 2) * EPOCH + LEAD) { endAt = +since(); break; } + if (firstStep == null && daa != null && daa >= EPOCHS * EPOCH) { endAt = +since(); break; } +} +await sleep(3000); + +const dag = await Promise.all(nodes.map(async n => { try { return await n.rpc.call('getBlockDagInfo'); } catch (e) { return { error: e.message }; } })); +const genesis = dag[0].pruningPointHash; +async function allBlocks(n) { + const out = []; let low = genesis; const seen = new Set(); + for (let round = 0; round < 500; round++) { + const r = await n.rpc.call('getBlocks', { lowHash: low, includeBlocks: true, includeTransactions: false }); + const blocks = r.blocks || []; + let added = 0; + for (const b of blocks) { const h = b.verboseData?.hash || b.header?.hash; if (seen.has(h)) continue; seen.add(h); out.push({ hash: h, daa: +b.header.daaScore, version: +b.header.version, chain: !!b.verboseData?.isChainBlock }); added++; } + if (!blocks.length || added === 0) break; + low = (r.blockHashes || []).at(-1) || blocks.at(-1).verboseData?.hash; if (!low) break; + } + return out; +} +let blocks = []; +try { blocks = await allBlocks(n0); } catch (e) { log(`getBlocks: ${e.message}`); } +const BOUNDARY = firstStep ? firstStep.epoch * EPOCH : Infinity; +const before = blocks.filter(b => b.daa < BOUNDARY), after = blocks.filter(b => b.daa >= BOUNDARY); +// the ladder bits on the chain: bit 15 up, bit 14 down; the object byte (bits 8 to 13) must be 0 (class signalling off) +const bitsOf = (v) => (v & 0x8000) ? 'up' : (v & 0x4000) ? 'down' : 'none'; +const ladderBits = blocks.reduce((m, b) => { const k = bitsOf(b.version); m[k] = (m[k] || 0) + 1; return m; }, {}); +const upShareOnChain = blocks.length ? Math.round(10000 * (blocks.filter(b => bitsOf(b.version) === 'up').length) / blocks.length) : 0; +const objectBytes = blocks.reduce((m, b) => { const v = (b.version >> 8) & 0x3f; m[v] = (m[v] || 0) + 1; return m; }, {}); +const lowBytes = new Set(blocks.map(b => b.version & 0xff)); + +const programs = new Map(); +for (const i of [0, 1, 2]) for (const l of minerLog(i)) { + const m = /epoch seed ([0-9a-f]{64}) day (\d+) \(daa (\d+)\): program and 256 MiB cache ready in ([\d.]+) ms; class (v\d) program id ([0-9a-f]{16})/.exec(l); + if (!m) continue; + const k = m[1]; const e = programs.get(k) || { seed: k.slice(0, 16), epoch: Math.floor(+m[3] / EPOCH), class: m[5], id: m[6], miners: new Set() }; + if (e.id !== m[6] || e.class !== m[5]) e.disagree = true; + e.miners.add(i); programs.set(k, e); +} +const programRows = [...programs.values()].sort((a, b) => a.epoch - b.epoch).map(p => ({ epoch: p.epoch, class: p.class, program_id: p.id, seed: p.seed, miners: p.miners.size, disagree: !!p.disagree })); +function cliId(seedHex, eraHex, reps) { + if (!existsSync(IGNEUM_POW)) return null; + const r = spawnSync(IGNEUM_POW, ['show', '--epoch-hex', seedHex, '--program-class', 'v4', '--era-hex', eraHex, '--shadow-reps', String(reps)], { encoding: 'utf8' }); + const m = /program id ([0-9a-f]{16})/.exec(r.stdout || ''); + return m ? m[1] : null; +} +const idRows = []; +for (const [k, e] of programs) { + const ep = epochs.get(e.epoch); + if (!ep || ep.eraSeed == null) continue; + const reps = ep.reps ?? 0; + idRows.push({ epoch: e.epoch, seed: e.seed, reps, miners_id: e.id, miners: e.miners.size, cli_rung0: cliId(k, ep.eraSeed, 0), cli_at_reps: cliId(k, ep.eraSeed, reps) }); +} +const steppedRows = idRows.filter(r => r.reps !== RUNG0 && r.reps !== 0); +const accepted = [0, 1, 2].map(i => minerLog(i).filter(l => /ACCEPTED block/.test(l)).length); +const rejectedMiner = [0, 1, 2].map(i => minerLog(i).filter(l => /rejected nonce=|submit error/.test(l))); +const rejectedNode = nodes.map(n => n.grepLog(/PoW rejected|Rejected block|rejected block/i)); +const stepLines = nodes.map(n => n.grepLog(STEP_LINE).map(l => l.replace(/^.*?(Latency ladder step by miner signal)/, '$1'))); +const firstStepLine = stepLines.map(ls => ls[0] || null); +const stepEpochs = firstStepLine.map(l => { const m = l && STEP_LINE.exec(l); return m ? +m[1] : null; }); +const stepRungs = firstStepLine.map(l => { const m = l && STEP_LINE.exec(l); return m ? +m[2] : null; }); +const stepWeakestUp = firstStepLine.map(l => { const m = l && STEP_LINE.exec(l); return m ? +m[8] : null; }); +const sinks = dag.map(d => String(d.sink || '?').slice(0, 16)); +const counts = dag.map(d => d.blockCount ?? '?'); +const maxEpochSeen = Math.max(-1, ...epochs.keys()); +const repsSeen = [...epochs.values()].map(e => e.reps); +const afterStep = firstStep ? [...epochs.entries()].filter(([e]) => e > firstStep.epoch).map(([, v]) => v.step) : []; + +const common = { + zero_rejected_by_miners: rejectedMiner.every(r => r.length === 0), + zero_rejected_by_nodes: rejectedNode.every(r => r.length === 0), + sinks_agree: new Set(sinks).size === 1, + block_counts_agree: new Set(counts.map(String)).size === 1, + miners_agree_on_every_program: programRows.every(p => !p.disagree), + ladder_line_on_every_node: nodes.every(n => n.grepLog(LADDER_LINE).length > 0 && n.grepLog(ACTIVE_LINE).length > 0), + every_node_signals_its_bits: nodes.every((n, i) => n.grepLog(OWN_LINE).some(l => OWN_LINE.exec(l)[1] === SIGNAL[i])), + every_epoch_class_v4: [...epochs.values()].every(e => e.class === 4), + rung0_ids_equal_the_cli_rung0_id: idRows.filter(r => r.reps === RUNG0).length > 0 && idRows.filter(r => r.reps === RUNG0).every(r => r.cli_rung0 != null && r.cli_rung0 === r.miners_id), +}; +// every block carries block version 2, an object byte of 0 (class signalling off) and the ladder bits of one of the three +// nodes; genesis, made before any node, is the one bit-less block when every node signals +const noneNodes = SIGNAL.filter(s => s === 'none').length; +common.chain_carries_the_bits = blocks.length > 0 && [...lowBytes].every(v => v === 2) && Object.keys(objectBytes).every(v => +v === 0) + && Object.keys(ladderBits).every(k => SIGNAL.includes(k) || k === 'none') && (noneNodes > 0 || (ladderBits.none || 0) === 1); +let checks; +if (EXPECT === 'step') { + checks = { + ...common, + template_stepped_to_rung_1: firstStep != null && firstStep.step === 1 && firstStep.reps === RUNG1, + stepped_at_the_first_full_window_epoch: firstStep != null && firstStep.epoch === FIRST_STEP_EPOCH, + step_line_on_every_node_same_epoch: stepEpochs.every(e => e != null) && new Set(stepEpochs).size === 1 && stepEpochs[0] === (firstStep && firstStep.epoch) && stepRungs.every(r => r === 1), + weakest_up_at_or_above_threshold: stepWeakestUp.every(s => s != null && s >= THRESHOLD), + no_second_step_inside_seven_windows: firstStep != null && afterStep.length >= 2 && afterStep.every(s => s === 1) && stepLines.every(ls => ls.length === 1), + blocks_on_both_sides: before.length > 0 && after.length > 0, + rung1_ids_equal_the_cli_rung1_id: steppedRows.length > 0 && steppedRows.every(r => r.reps === RUNG1 && r.cli_at_reps != null && r.cli_at_reps === r.miners_id && r.miners === 3), + rung1_ids_differ_from_the_same_seed_rung0_id: steppedRows.length > 0 && steppedRows.every(r => r.cli_rung0 != null && r.cli_rung0 !== r.miners_id), + }; +} else { + checks = { + ...common, + template_never_above_rung_0: firstStep == null && repsSeen.every(r => r === RUNG0 || r === 0), + no_step_line_on_any_node: stepLines.every(ls => ls.length === 0), + ran_the_epochs: maxEpochSeen >= EPOCHS - 1, + passed_the_first_full_window_epoch: maxEpochSeen >= FIRST_STEP_EPOCH, + up_share_under_threshold_on_chain: upShareOnChain < THRESHOLD, + }; +} +const pass = Object.values(checks).every(Boolean); +const summary = { + pass, expect: EXPECT, signals: SIGNAL, checks, window: WINDOW, windows: WINDOWS, threshold_bps: THRESHOLD, epoch_blocks: EPOCH, lead: LEAD, first_step_epoch: FIRST_STEP_EPOCH, + node: IGNEUMD, miner: CPU_MINER, pow: IGNEUM_POW, template_step: firstStep, run_ended_at_s: endAt, final_daa: lastDaa, max_epoch_seen: maxEpochSeen, + epochs: Object.fromEntries([...epochs.entries()].map(([k, v]) => [k, v])), + blocks: { total: blocks.length, before_boundary: before.length, after_boundary: after.length, ladder_bits: ladderBits, object_bytes: objectBytes, up_share_bps_on_chain: upShareOnChain }, + programs: programRows, program_id_rows: idRows, accepted_per_miner: accepted, + rejected_by_miners: rejectedMiner.map(r => r.length), rejected_by_nodes: rejectedNode.map(r => r.length), + sinks, block_counts: counts, step_lines: stepLines, samples, +}; +writeFileSync(`${TMP}/summary.json`, JSON.stringify(summary, null, 2)); +log(`SUMMARY ${pass ? 'PASS' : 'FAIL'} (expect ${EXPECT}, signals ${SIGNAL.join('/')}): ${firstStep ? `rung ${firstStep.step} (${firstStep.reps} passes) from epoch ${firstStep.epoch} at DAA ${firstStep.daa}` : 'no step'}; epochs seen ${[...epochs.entries()].map(([e, v]) => `e${e}:r${v.step}:${v.up_weakest_bps}bps`).join(' ')}; chain bits ${JSON.stringify(ladderBits)} (${upShareOnChain} bps up); blocks ${before.length} / ${after.length}; rejected miners ${rejectedMiner.map(r => r.length).join('/')} nodes ${rejectedNode.map(r => r.length).join('/')}; sinks ${sinks.join(' ')} at ${counts.join('/')}`); +for (const r of idRows) log(`PROGRAM ID epoch ${r.epoch} seed ${r.seed} reps ${r.reps}: miners ${r.miners_id} (${r.miners} of 3) cli at reps ${r.cli_at_reps} cli rung 0 ${r.cli_rung0}`); +for (const [k, v] of Object.entries(checks)) if (!v) log(`FAILED CHECK ${k}`); +log(`summary: ${TMP}/summary.json`); +await stopAll(); +process.exit(pass ? 0 : 1); diff --git a/infra/fast-time/override-60x.json b/infra/fast-time/override-60x.json index f232111df..38c080d74 100644 --- a/infra/fast-time/override-60x.json +++ b/infra/fast-time/override-60x.json @@ -52,11 +52,16 @@ "pow_epoch_lead": 10, "pow_day_ms": 1440000, "difficulty_v2_activation_daa": 18446744073709551615, + "difficulty_v3_activation_daa": 18446744073709551615, + "finality_daa_rule_activation_daa": 18446744073709551615, "proving_v0_activation_daa": 18446744073709551615, "finality_v3_activation_daa": 18446744073709551615, "program_class_v3_activation_daa": 18446744073709551615, "program_class_v4_activation_daa": 18446744073709551615, "program_class_v4_signal_window_daa": 120, + "latency_ladder": [{"reps": 27, "admissible": true}, {"reps": 35, "admissible": true}, {"reps": 53, "admissible": true}, {"reps": 88, "admissible": false}, {"reps": 173, "admissible": false}, {"reps": 267, "admissible": false}], + "latency_ladder_activation_daa": 18446744073709551615, + "latency_ladder_window_daa": 120, "proving_v1_fresh_rule_daa": 18446744073709551615, "exec_restart_number": 18446744073709551615, "exec_restart_hash": "", diff --git a/site/ledger.html b/site/ledger.html index 8b814893e..eaa5b7e0e 100644 --- a/site/ledger.html +++ b/site/ledger.html @@ -4,13 +4,13 @@ Igneum ledger: every criticism, answered - + - + @@ -18,7 +18,7 @@ - + @@ -51,10 +51,21 @@ @@ -121,20 +134,20 @@ details{margin-top:8px;font-size:14px;color:var(--ash)}summary{cursor:pointer;co
-
Ledger · 179 entries · regenerated from the repository
+
Ledger · 180 entries · regenerated from the repository

Every criticism, answered or conceded

-

This is every criticism the project expects, in the critic's words, with what was done about it and the date. 179 entries since 3 October 2026. Entries are never deleted; a status that changes keeps its history on the line. Where the critic was right the entry says Conceded. Where nothing has been done it says Open and names what settles it. The founder mined through the GPU years. Ethereum's move to proof of stake in September 2022 ended that income and the miners' place in that chain. This is one person building, with AI systems doing the engineering, the coin he wanted to exist for miners: GPU-mined, the miners are the provers, no founder allocation, every cost stated. Help is welcome and a team is wanted: cryptographers, node engineers, miners who will test. This ledger is the application form: pick an open row and write to hello@igneum.network with its id.

+

This is every criticism the project expects, in the critic's words, with what was done about it and the date. 180 entries since 3 October 2026. Entries are never deleted; a status that changes keeps its history on the line. Where the critic was right the entry says Conceded. Where nothing has been done it says Open and names what settles it. The founder mined through the GPU years. Ethereum's move to proof of stake in September 2022 ended that income and the miners' place in that chain. This is one person building, with AI systems doing the engineering, the coin he wanted to exist for miners: GPU-mined, the miners are the provers, no founder allocation, every cost stated. Help is welcome and a team is wanted: cryptographers, node engineers, miners who will test. This ledger is the application form: pick an open row and write to hello@igneum.network with its id.

- + - +
CountStatusMeaning
7Nothing has settled it yet. The entry names what will
62The critic is right. "Stated" means the public text says so; "not yet stated" means it does not yet
63The critic is right. "Stated" means the public text says so; "not yet stated" means it does not yet
57A code, spec or text change answers it, with the commit or the page named
27A consensus rule or a decision by the owner answers it, dated
13A measurement or a simulation exists and is named
13A design rule answers it; no measurement is possible yet
179Every entry. The sections: Mining and chips, Finality and attacks, Proving and the zkEVM, Economics and the coin, Governance and the founders, Comparisons, Legal and regulatory, Launch and operations, Builders
180Every entry. The sections: Mining and chips, Finality and attacks, Proving and the zkEVM, Economics and the coin, Governance and the founders, Comparisons, Legal and regulatory, Launch and operations, Builders

Mining and chips

@@ -228,6 +241,12 @@ details{margin-top:8px;font-size:14px;color:var(--ash)}summary{cursor:pointer;co
Conceded, stated 6 October 2026, evening; the Horizon lane analysis a repository file section 5.1, the FPGA lane): the public FPGA line carries only the measured row, 2.4 G reads/s per card and 0.30x to 0.39x of the RTX 5090 per watt (Shuhai, FCCM 2020 Fig 7; the tFAW arithmetic from ICCAD 2021 Table I), and the 11.4 G bank-bound row and the 12.2 G ceiling are marked unmeasured until an AWS F2 hour measures them. Stated in a repository file section 5.3 (the activate-bound row marked UNMEASURED with the JEDEC figure beside it, and the FPGA paragraph after the table). The epoch-length analysis's 12.2 row is not on master yet and is corrected when it lands.
The answer as first written

Correct. The measured 2.4 G/s had been read on 6 October as a mapping artefact ("the paper's point is that this mapping is the wrong one for random access"); the activate window says it is the DRAM's own limit, and a bank-interleaved mapping does not lift it because tFAW is enforced per channel by the die. The measurement that settles it is one AWS F2 hour (f2.6xlarge, Virtex UltraScale+ VU47P, 16 GB HBM2 in 2 stacks, 32 pseudo-channels, USD 1.98 an hour on demand): the chase kernel of a repository file 2.2 ported to a Vitis HLS AXI master over the HBM IP at 1 GiB across all 32 pseudo-channels, 256 to 4,096 lanes in flight, board power at 1 Hz; pass line 15 to 25 M reads/s/W (0.3x to 0.5x of the 5090), alarm 27 (0.5x), over 54 (1.0x) a Counter ASIC 4.0 item. Consequence per tier: none today (no FPGA mines); on the measured row a soft-overlay FPGA mines at an RX 9070 XT's rate per watt for about 7x the price (approximate), so no home or rig tier is displaced.

+
+
M34

The shadow size N is a constant of the binary, so the one lever against the dataset-storing chip needs a fork to move

6 October 2026
+
Your own Horizon lane says the reserve and the era draw buy nothing against a chip that stores the dataset, and that the only lever is the latency-shadow size N. N is 27 passes of a 256-instruction block, hard-coded in V4_CLASS. So when HBM4 doubles a chip's rate per stack in 2028, your answer is a hard fork, and a fork that retires the M5 Max at the first doubling. And now there is a shipping RandomX ASIC.
+
Conceded, implemented 6 October 2026, night; a repository file, branch ladder, fork branch ladder-node, the 0.3.17 feature tree, behind latency_ladder_activation_daa, never until set, 0 on the testnet when the owner says): N is a genesis ladder of six rungs (27, 35, 53, 88, 173, 267 passes; about 102,100 to 1,001,600 counted ops) with a measured admissibility flag per rung (cold verify under 10 ms on the reference core with its SMT sibling loaded, igneum-build-1, 6 October 2026: rungs 0 to 2 pass at 8.77, 8.87 and 9.23 ms, rung 3 misses by 0.08 ms under a box load of 25 and is out until a quiet re-run, rungs 4 and 5 are out at 12.38 and 14.96), and the step is consensus state derived from two bits of the header version: up one rung when 90 percent of blue blocks in each of seven consecutive windows ask for it and the rung above is admissible, down one rung symmetrically, never two rungs inside seven windows (the oldest window must begin after the last step took effect), never unconditionally. Tests, the known-failed case first: a changed N today hashes another program under the same program id (a hard fork no pack line told apart); after, rung 0 is class v4 byte for byte, a rung above carries its pass count in the id, 8,999 bps in one window of seven does not move the step, a two-step jump is impossible, down never passes rung 0, an inadmissible rung is never entered. Stated in a repository file, Mining section ("The work that waits can grow").
+
The answer as first written

Correct on both counts, and the second was the sharper one. The X9 (Bitmain, about 1 MH/s at 2,472 W, approximate, github.com/monero-project/monero/issues/10270) is a shipped 3x per-joule edge over a desktop CPU on the best-known latency-bound random-program design, seven years after launch; it makes the k = 0.3 column of the chip model a product class rather than an attacker's claim, and the public headline is now the range 2.1x (k = 1) to 3.9x (k = 0.33) over the RTX 5090 at class v4, with the ladder taking the X9 bracket to about 2.8x by rung 2 and the Apple tier's to about 1.1x by rung 3. The ladder does not close the gap; it is the chain's only automatic answer, it moves at the pace of the cards that pay for it, and the honest card's watts remain the lever that moves every row (algorithm lane proposal 7). What the ladder gives up by design: a chip holding over 10 percent of weight can stall it, and the status quo it stalls is a rung the cards already run.

+

Finality and attacks

F1

Finality is attackable for the first month

5 October 2026
diff --git a/site/litepaper.html b/site/litepaper.html index 54d21ad53..124c5b0d2 100644 --- a/site/litepaper.html +++ b/site/litepaper.html @@ -344,7 +344,8 @@ body.all .pager{display:none}

Three ideas carry the chip resistance. The hash rewrites itself. A new program every hour, drawn from the chain. Its memory pattern changes with it. The rules change on a schedule fixed at launch. No release, no vote. These are automatic schedule changes: they defeat a chip wired for one datapath and they need no human fork. Against a chip that stores the dataset every drawn parameter is firmware, and what meets that chip is the latency-shadow work (class v4) and the price per joule (the Horizon lane analysis, 6 October 2026, section 5.4; ledger M32). It waits on memory, not maths. Every hash is a chain of random reads into a table too big for a chip to carry. The wait is the same physics for everyone. Miners hold the switch. Spare defences are written into the rules, switched off. A miner signal turns one on, at the class-change threshold: miners signal three things at three thresholds, 60 percent of blue blocks over two weeks for a parameter genesis leaves open, 90 percent for an upgrade (new code), and 95 percent with a floor height for a class change. No fork.

-

No hash has stayed free of chips forever. Igneum does not claim to. It states the gain its own model finds, the response takes a week, and both are measured. The model is public: the numbers; the claim is tested by paid independent cryptanalysis and the public benchmark. Monero has run on RandomX since 2019 with no chip publicly shipped, approximate; that is precedent, not proof.

+

The work that waits can grow. Class v4 adds a block of latency-shadow arithmetic to every hash, about 100,000 integer operations that run while the memory reads are in flight, so a chip that stores the whole dataset still has to pay for a core. That size sits on a ladder fixed at genesis, six rungs from about 100,000 to about 1,000,000 operations, and it moves one rung at a time only when 90 percent of blue blocks in each of seven consecutive days ask for it; it can never move two rungs inside a week and never past a rung the reference verifier cannot check under 10 ms with its sibling thread busy (measured on the build server, 6 October 2026: the first three rungs pass at 8.8, 8.9 and 9.2 ms, the fourth misses by 0.08 ms on a loaded box and stays out until a quiet re-measurement, the two doublings are out at 12.4 and 15.0 ms). What it buys, on the measured cards: against a dataset-storing chip whose core costs what an RTX 5090's does per operation, the chip's per-joule edge falls from 2.1x at the first rung to 1.3x at the third; against a core as good as the shipping RandomX chip's (Bitmain's Antminer X9, about 3x per joule over a desktop CPU after seven years, approximate), from 3.9x to 2.8x. What it costs, per rung, is measured too: the Apple tier gives up 3 points of rate at the first step and 6 more at the second, the RTX 5090 nothing until the second; so the miners who pay for a step are the ones who take it (ledger M34).

+

No hash has stayed free of chips forever. Igneum does not claim to. It states the gain its own model finds, the response takes a week, and both are measured. The model is public: the numbers; the claim is tested by paid independent cryptanalysis and the public benchmark. Monero ran on RandomX from 2019 until a chip shipped in July 2026 (Bitmain's Antminer X9, about 3x per joule over a desktop CPU, approximate); seven years is the precedent for what a latency-bound random-program design buys, not proof of more.

One thing takes a person, here and on every chain that exists: writing new code. A chain cannot safely write its own generator, and it cannot safely tell a chip from a wave of honest new cards by hashrate alone. If the design above ever failed, anyone could publish a new generator and miners would switch it on by signalling, as Monero's community can fork. Igneum is built to make that day unlikely, and does not depend on avoiding it.

diff --git a/tools/ladder/verify-bench-remote.sh b/tools/ladder/verify-bench-remote.sh new file mode 100755 index 000000000..35adfc35f --- /dev/null +++ b/tools/ladder/verify-bench-remote.sh @@ -0,0 +1,81 @@ +#!/usr/bin/env bash +# The latency ladder's verifier bound, measured on igneum-build-1 (docs/design/latency-ladder.md section 4): for every rung +# of the ladder, the cold verify of one 32-lane warp of class v4 at that rung on the reference core (core 40, 3.8 GHz under +# schedutil, nice 19), alone and with its SMT sibling (core 88) running the same bench, plus the average of 50 warps. The +# figure a rung's admissibility reads is the cold run with the sibling loaded; the gate is 10 ms. +# +# tools/ladder/verify-bench-remote.sh [--rungs "27 35 53 88 173 267"] [--pow ] [--out ] +# +# Runs ON the box under its measure hold (infra/build-server/remote-run.sh BR_MEASURE=1: waits for every running build, blocks +# new ones and the capacity layer until it ends; one JSONL line of kind measure in /srv/builds/_log/builds.jsonl). The binary is +# the box's own build of igneum-pow from this worktree (tools/build-remote.sh from igneum-pow/ puts it at +# /srv/builds//igneum-pow/target/release/igneum-pow); the results come back as one text file per run plus a table. +# A number taken beside another build is not a number (CLAUDE.md), which is what the hold is for. +set -euo pipefail +HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +BS_TOOL=ladder-verify-bench +# shellcheck source=../../infra/build-server/lib.sh +. "$HERE/../../infra/build-server/lib.sh" +RUNGS="27 35 53 88 173 267"; POW=""; OUT="" +while [ $# -gt 0 ]; do + case "$1" in + --rungs) RUNGS="$2"; shift 2 ;; --pow) POW="$2"; shift 2 ;; --out) OUT="$2"; shift 2 ;; + *) echo "unknown argument $1" >&2; exit 2 ;; + esac +done +bs_host +# the context of the igneum-pow crate (BS_WT, BS_WT_ROOT, branch and sha for the JSONL line); the binary is already on the box +( cd "$HERE/../../igneum-pow" ) || bs_die "no igneum-pow beside tools/" +pushd "$HERE/../../igneum-pow" > /dev/null; bs_context; popd > /dev/null +WT_ROOT="$BS_WT_ROOT" +WT_NAME="$BS_WT" +POW="${POW:-/srv/builds/$WT_NAME/igneum-pow/target/release/igneum-pow}" +OUT="${OUT:-$WT_ROOT/docs/design/latency-ladder-bench}" +STAMP=$(date -u +%Y%m%dT%H%M%SZ) +REMOTE_OUT="/srv/builds/_log/ladder-bench/$STAMP" +mkdir -p "$OUT" +# the command remote-run.sh evals under the measure hold: per rung, the quiet-core run, then the two siblings at once +read -r -d '' CMD </dev/null || echo ?) kHz siblings \$(cat /sys/devices/system/cpu/cpu40/topology/thread_siblings_list) pow \$(sha256sum '$POW' | cut -c1-16)" > meta.txt +for r in $RUNGS; do + nice -n 19 taskset -c 40 '$POW' bench --seed igneum-genesis --day 2026-10-03 --class mx8+sh256x\$r --warps 50 > alone-\$r.txt 2>&1 + # the sibling's load must outlast the measured run (the first run of this script, 22:06Z: a 50-warp sibling finished during + # the measured run's own cache fill, so the "loaded" warps ran alone; 4,000 warps is 20 to 40 s, killed when the run ends) + nice -n 19 taskset -c 88 '$POW' bench --seed igneum-genesis --day 2026-10-03 --class mx8+sh256x\$r --warps 4000 > sibling-\$r.txt 2>&1 & + sib=\$! + sleep 1.5 + nice -n 19 taskset -c 40 '$POW' bench --seed igneum-genesis --day 2026-10-03 --class mx8+sh256x\$r --warps 50 > loaded-\$r.txt 2>&1 + kill \$sib 2>/dev/null; wait \$sib 2>/dev/null + echo "rung reps=\$r done \$(date -u +%H:%M:%SZ)" +done +echo "load after \$(cut -d' ' -f1-3 /proc/loadavg)" >> meta.txt +EOF +bs_log "measuring rungs $RUNGS on $BS_HOST under the measure hold (pow $POW); results to $OUT" +BR_MEASURE=1 BR_KIND=measure BR_COMMAND="igneum-pow bench per ladder rung (core 40 alone, then with core 88 loaded)" BR_TARGET=x86_64-unknown-linux-gnu \ + bs_remote_run "$(dirname "$POW")" "latency ladder verifier bench rungs $RUNGS" "$CMD" +bs_rsync -a "$BS_HOST:$REMOTE_OUT/" "$OUT/$STAMP/" +# the table: cold = the "warp base 0: single cold run" line, avg = the "CPU verify" line +python3 - "$OUT/$STAMP" $RUNGS <<'PY' +import re, sys, os +d = sys.argv[1]; rungs = sys.argv[2:] +def read(name): + try: t = open(os.path.join(d, name)).read() + except FileNotFoundError: return (None, None, None) + cold = re.search(r"warp base 0: single cold run ([\d.]+) ms", t) + avg = re.search(r"CPU verify: ([\d.]+) ms per 32-lane warp", t) + shadow = re.search(r"(\d+) shadow instructions per hash", t) + return (float(cold.group(1)) if cold else None, float(avg.group(1)) if avg else None, int(shadow.group(1)) if shadow else None) +out = [open(os.path.join(d, "meta.txt")).read().strip()] +out.append("| Rung | reps | Shadow instrs per hash | Counted ops (approx) | Cold, core alone (ms) | Avg of 50, alone (ms) | Cold, sibling loaded (ms) | Avg of 50, sibling loaded (ms) | Under 10 ms loaded |") +out.append("|---|---|---|---|---|---|---|---|---|") +for i, r in enumerate(rungs): + a = read(f"alone-{r}.txt"); l = read(f"loaded-{r}.txt") + ops = 930 + (8 * 256 * int(r) * 183 + 99) // 100 + f = lambda x: "?" if x is None else f"{x:.2f}" + ok = "?" if l[0] is None else ("yes" if l[0] < 10.0 else "NO") + out.append(f"| {i} | {r} | {a[2] or '?'} | {ops:,} | {f(a[0])} | {f(a[1])} | {f(l[0])} | {f(l[1])} | {ok} |") +print("\n".join(out)) +open(os.path.join(d, "table.md"), "w").write("\n".join(out) + "\n") +PY +bs_log "done; raw lines in $OUT/$STAMP"