Merge remote-tracking branch 'box/master' into enforced-proving

This commit is contained in:
igneum-labs 2026-10-08 17:34:34 +00:00
commit 38ba56f5c0
34 changed files with 17320 additions and 84 deletions

View file

@ -0,0 +1,145 @@
# Binding review: template, nonce, work and result (October 2026)
Igneum 2.0 register row (plan p. 10, missed pins): "Cryptographic review of the template, nonce, work and result
binding: no expensive intermediate reused across cheap winning attempts." Adversarial seat, 8 October 2026 (evening,
UK), with the hash lane for the generator's side. Sources: the crate (`igneum-pow` at the mirror's master 494fb981),
the fork's node line (`release-2.0.0-node` 9fc9f42a on the box mirror; the Mac's vendor copy is the 7 October master
and is not used where the two differ), spec 01, 02, 04 and 09, and the attack pass of 7 to 8 October
(`docs/analysis/attack-pass-2026-10.md`, rows F3, F8, F9, F1 and their records). Every number is either from a
record named beside it or marked Designed. No consensus code is changed by this document.
The question in one line: for every quantity that is expensive to compute, which inputs is it a function of, and
can a miner hold it fixed while it varies something cheap that still produces distinct winning attempts? A "winning
attempt" is a 64-bit lane hash at or below the target for a header the chain accepts.
## 1. What the hash commits to, from the code
### 1.1 The chain of bindings
| Step | Function of | Where | Cost |
|---|---|---|---|
| The pre-PoW hash `H` | every header field but the nonce: version, parents (every level), `hash_merkle_root`, `accepted_id_merkle_root`, `utxo_commitment`, timestamp, bits, `daa_score`, `blue_score`, `blue_work`, `pruning_point`, `vote_key_hash`; the nonce field zeroed | fork `consensus/core/src/hashing/header.rs:7` (`hash_override_nonce_time(header, 0, header.timestamp)`), `consensus/pow/src/igneum.rs:115` (`header_prehash`) | one BLAKE2b over about 200 bytes per template |
| The init words `I[0..7]` | `seed_words_from_bytes("igneum-block/" || H || nonce_hi_le32)` | `igneum-pow/src/bind.rs:48` (`block_init_bytes`, 49 bytes), `:57` | four FNV-1a-64 passes over 49 bytes per `(H, nonce_hi)`: about 200 integer ops |
| The lane registers at start | `r[i] = splitmix32((n XOR I[i]) + 0x9e3779b9 (i + 1)) XOR I[(i + 1) AND 7]` for lane nonce `n` (the low 32 bits of the header nonce) | `verify.rs:570` (`interpret_warp_core`); spec 1.6 | 8 splitmix32 per lane |
| The program | the epoch seed: on the node line, the hash of the last selected-chain block below `epoch x 3,600 - 600` on the header's own selected-parent chain (`pow_epoch_seed_score`, `epoch_seed`), attempt by attempt through acceptance (spec 1.4.6.6); the era seed from the era VDF (spec 4.4) | fork `consensus/core/src/igneum.rs:343`, `consensus/src/pipeline/header_processor/processor.rs:365` (release-2.0.0-node); `generator.rs`, `accept.rs` | once per epoch per node: the draw plus acceptance, about 1.8 core-s per candidate at (c'') and (c''') (attack pass, F9 lane (d)), 3.2 candidates per seed |
| The dataset | the day key `seed_words_from_bytes("igneum-day/" || day_le64)` with `day = header.timestamp_ms / 86,400,000` (class v5 adds the epoch's state leaves) | `bind.rs:62`, fork `pre_ghostdag_validation.rs:147` (`day: day_index(header.timestamp)`); spec 1.8 | once per day per node: the 256 MiB cache fill (175 ms on one core) and, for a miner, the 1 to 2 GiB dataset |
| One lane hash | the program applied to the 32 lanes of the aligned group `n AND NOT 31`, 8 iterations of 64 base instructions plus the 256-instruction shadow block 27 times, 16 loads per iteration reading `dataset[load_index(x)]`, the output fold over `r0..r7` | `verify.rs:555` to `:654` | 55,296 instructions and 128 dependent dataset reads per lane; the warp is the unit |
| The verdict | `hash_bound(H, nonce) <= target64(bits)`, `target64` the top 64 bits of the 256-bit target | `bind.rs:128`, `igneum.rs:120`, `:173` | one 64-bit compare |
So a winning attempt is a function of `(H, nonce_hi, n, program(e), dataset(day), target)`, and every one of `H`,
`e` (through `daa_score` and the parents, both in `H`) and `day` (through the timestamp, in `H`) is under proof of
work. The expensive quantities are three: the program (per epoch), the dataset (per day), and the hash itself (per
warp). The cheap variables are the nonce's two halves and anything in the template the miner may change.
### 1.2 What is in the template that a miner may change, and what each change costs
| Field | Who sets it | Freedom | What changes downstream |
|---|---|---|---|
| nonce, low 32 bits (`n`) | the worker | 2^32 per `I`, iterated 32 at a time | the lane registers only; the warp's 32 lanes are hashed together |
| nonce, high 32 bits | the miner (`main.rs:565`, a random `hi`, incremented when the lane space wraps) | 2^32 per template | `I`, hence every register of every lane: a new warp space |
| timestamp | the miner, within Kaspa's bounds (10 s future tolerance, above the sampled past median) | about 10 s of milliseconds, 10,000 values | `H`, hence `I`; and the DAY at a day boundary (1.4) |
| the coinbase and transactions | the miner (mode A or C) or the pool | unbounded | `hash_merkle_root`, hence `H` |
| parents | the miner, among known tips within the merge bound | a few | `H`, `daa_score`, `blue_work`, `blue_score`, and the epoch seed if the selected parent's chain differs below the seed score |
| `vote_key_hash` | the member's client (spec 9.4.1 item 1) | one per key the miner holds | `H` |
| bits, `daa_score`, `blue_score`, `blue_work`, `pruning_point`, `accepted_id_merkle_root`, `utxo_commitment` | the node from the parents | none | |
Every one of these is an "extra nonce": it changes `H`, so `I`, so every register from the first instruction. None
of them is free of the hash: a template change costs the whole warp again.
## 2. The nonce's place in the chain and the warp as the unit
The lane nonce enters once, at register initialisation, XORed with each init word before splitmix32 (`verify.rs:570`).
Nothing later reads the nonce. So the hash of lane `n` is the composition `fold(program^8(init(n, I)))` and the only
state shared across lanes is what `shfl` moves (lane `l` reads lane `l XOR mask`), which couples the 32 lanes of the
aligned group into one computation. The consequences:
- A warp's 32 hashes are 32 attempts for the price of 32 lanes of work; no lane's result is reusable for another
nonce because every register of every lane starts from the nonce.
- The high half of the nonce is one `I` per 2^32 lanes. A miner that changes `nonce_hi` pays one FNV over 49 bytes
(about 200 ops, `bind.rs:48`) and gets a fresh warp space; it does not need to, since 2^32 lane nonces at 141.8
MH/s on an RTX 5090 (attack pass F9, the card row) is 30 s of work per `I`, 30 templates' worth at 1 block per
second. The crate's own test `bind::bound_hash_properties` pins that two `nonce_hi` values give different warps
under one `H` and that the lane hash depends on `H` (the fork's engine test pins the timestamp, `igneum.rs:237`).
- The pool's share check recomputes `hash_bound(job.prehash, nonce)` for the claimed nonce (`pool/src/verify.rs:52`),
so a share binds the whole template through `H`; a nonce under another template "hashes differently and is
refused as a wrong hash" (the crate's test `the same nonce on another template`, `verify.rs:101`).
## 3. What the 256 loads and the shadow block compute from
### 3.1 The loads
Each of the 16 load sites per iteration (128 per hash) reads `dataset[load_index(era, site, x)]` where `x` is the
source register's value in that lane at that instruction (`verify.rs:759`): `idx = x AND MASK` without an era, or
`rotl(x M, R)` masked into the site's window under an era (`load_index`, `:18`). The address is therefore a function
of the lane's register state, which from the first instruction is a function of `(I, n)` and, after the first load,
of dataset words too. The attack pass measured the part that is a function of `(I, n)` alone (F9, sub-row (c), taint
analysis `init_determined_sites`): on the devnet epoch-0 program the loads at instructions 7, 8, 9, 10 and 31 of
iteration 0; over 2,000 seeds 1 to 7 sites per program, median 3, always in iteration 0 only. Everything after the
first dataset word is data-dependent.
The 256 loads of a hash's "working set" are the 128 reads of its own lane plus the reads the warp's other lanes
make, and they are distinct per hash by construction: the acceptance rule refuses a program whose site reads fewer
than 120 distinct addresses per hash on average (`MIN_DISTINCT_SUM`, spec 1.4.6.4), whose 32 lanes compute one
address at any site (`LaneConstantSite`), whose site's distinct-index ratio over 2^20 evaluations falls under 0.98
(class v4, (c'')) or 0.995 (class v5, (c''')).
### 3.2 The shadow block
The shadow block is 256 ALU instructions from the ten non-load families (`generator.rs:413`), applied 27 times after
instruction 63 of every iteration with the iteration's `sel` (`r0` at the iteration's start; `verify.rs:609`,
`:631`). It has no loads. Its input is the lane's eight registers at that point and `sel`; its output is the
registers for the next iteration. It is therefore a per-lane function of the lane's state, with `shfl` coupling
lanes inside it as in the base block. Nothing in it is shared across nonces, iterations or templates: the same block
applied 27 times to a different register state each time. The attack pass's F1 row measured what an honest compiler
can remove from it (at most 4.688 percent of its instructions on the frozen class v5 tip at 10^5 programs, two
programs at 5.078 percent in the 2.2 x 10^5 partial, both at the honest-compiler parity the ruling AP-F1-1 names), so
the block's cost per application is fixed to within that margin for every implementer.
## 4. Where a result could be reused, each with its cost and the rule or the finding
The seven places an expensive intermediate could be shared across cheap attempts, in the order a miner would try
them. "Rule" names what forbids the reuse or makes it worthless; "cost" is what the reuse would save against what it
costs.
| # | Reuse | What is shared | Across what | Cost to the attacker | Rule or finding |
|---|---|---|---|---|---|
| R1 | The dataset | the day's 1 to 2 GiB and its 256 MiB cache | every hash of the day, every miner | nothing: this is the design (the memory-hard dataset is the shared intermediate on purpose) | Intended. The question is whether a SMALLER shared structure serves: the hot set (R5) and partial storage (R6) |
| R2 | The compiled program | the epoch's kernel | every hash of the epoch, every miner | nothing: intended; one compile per epoch per card | Intended. A miner that grinds the epoch seed to get a favourable program is R7 |
| R3 | The init words `I` | one FNV result | 2^32 lane nonces | nothing to gain: `I` costs about 200 ops and a warp costs 1.8 million lane-instructions (F9's count) | No rule needed; the ratio is 10^-4 |
| R4 | The register prefix before the first load | the first instructions of iteration 0, a function of `(I, n)` | nothing: `n` is per lane, so there is no second attempt with the same prefix | zero reuse: each lane's prefix is its own | Bound by construction (spec 1.6: the nonce enters every register) |
| R5 | A hot set of items | a few MB that serves a large share of reads | every hash of the epoch | the attack pass's F8 and F9 (b): at the frozen tip no program's top 0.1 percent of items carries more than 0.23 percent of reads (256 seeds, 245 within 1.2x of the window model), hot-set verdict clear on every seed; the exemplar that reads 3.35 percent (class v4 seed 100767) is refused by (c''') | AP-F8-1: the residue is a quarter-bit bucket concentration at one narrow-window site, named by mechanism, no cacheable hot set; the per-site largest-bucket bound is a next class's item |
| R6 | A partial dataset or cache | a stored fraction `f` of the cache lines, the rest recomputed | every hash | F3: every cache line costs exactly `j + 1` block evaluations from nothing (0 of 1,024 lines under the bound), the storage-against-recompute curve at f = 1/8 is 3.17 ops per read optimal against the 3.5 the funding note assumed, and a partial-cache chip is worse than the full mirror at every f below 1 | F3 PASS; the chip edge is the latency ladder's business, not a binding hole |
| R7 | The epoch seed | the program of epoch `e + 1` | every hash of that epoch | a miner who produces the seed block (the last selected-chain block below `3,600 (e + 1) - 600`) picks among the candidate blocks it can produce; each candidate costs a full block's work, and the 600-score lead gives every miner the same 10 minutes to compile; under the devnet stand-in there is no VDF on the epoch seed (spec 4.3 Designed, the era VDF Implemented) | Spec 4.3 (the 1,200-s lead and the 600-s VDF) is the rule; on the node line the lead is 600 and the VDF is not in the node. The grind's value is bounded by the program-to-program variance of hash rate: the attack pass's F8 and F1 rows put every accepted program within a few percent of the mean on the card (the per-program spread is the F10 ladder's reading), so a candidate block burned to pick a 2 percent better program is a bad trade at any hashrate under 50 percent. Finding, minor: the VDF of 4.3 is the stated defence and is not implemented; the lead alone does not stop the pick. Routed to the Counter ASIC lane's D1 list as a 2.0 item (6) |
| R8 | The day | the dataset of day `d` | every hash of the day | the day is keyed on the header TIMESTAMP (`pre_ghostdag_validation.rs:147`), which the miner sets within about 10 s; at a day boundary a miner may hash on either day's dataset for about 10 s, and a verifier must hold both caches across the boundary (the engine's `note_chain_day`, `:139`, builds the next in the background) | No gain: both datasets are full datasets. Observation: spec 1.12 keys the day on DAA score and the node keys it on the timestamp; one of the two texts moves (6) |
| R9 | The attempt chain | the acceptance work of attempts 0 to k-1 | every node and miner of the epoch | nothing to a miner: attempts are deterministic from the seed; a miner cannot choose the attempt. A verifier's cost is the row below | No rule needed |
| R10 | The warp | one hash computation | 32 nonces | intended: the 32 lanes are 32 attempts for 32 lanes of work, coupled by `shfl` so no lane is computable alone | Intended (spec 1.9). The verifier dedupes items within a warp (`verify.rs`, the 4,096 derivations bound); a miner that finds warps whose loads coincide is F9 (c): one coalesced pair per 3,400 tries is worth 0.2 percent of one warp and costs 2.4 hashes of search; five sites fully coalesced would be worth 43 percent and has probability 2^-115 per draw |
| R11 | The template | one `H` | 2^64 nonces | intended; a template change is a new `H` and costs nothing but one BLAKE2b; it buys nothing, since the nonce space under one `H` is 2^64 | No rule needed |
| R12 | The vote key | one `vote_key_hash` | every block of the key | a miner with many keys hashes under one `H` per key; keys are free (spec 03 W6) and weight is blocks, so more keys is more templates, not more hashes per hash | No gain; the pool design document (pool-vote-key-commitment.md) covers the pool case |
The cheapest reuse that survives as a gain is R10's coalesced pair at 0.2 percent of one warp per 2.4 hashes of
search, which is a loss, and R5's quarter-bit bucket, which is under the window model's own spread. No path was found
by which an intermediate computed once serves a second winning attempt at less than the honest cost of that attempt.
## 5. Known-failed tests and measured lines
| Test | Known-pass | Known-fail | Where | Result |
|---|---|---|---|---|
| The lane hash depends on `H` and on `nonce_hi` | two headers differ in one field: different warps | the same `(H, nonce_hi)`: the same warp, bit for bit | `igneum-pow/src/bind.rs`, `bound_hash_properties`, `bound_vectors`; the fork's `igneum.rs:200` smoke test (timestamp bound, `:237`) | see the run line below |
| A nonce on another template is refused | the pool's check against its own job | the same nonce on another template: `wrong_hash` | `pool/src/verify.rs:101`, `the same nonce on another template hashes differently` | in the pool crate's suite (pool.md section 4) |
| A stateless hasher is wrong on every class v5 item | the dataset built with the leaves | the dataset built without them: every item differs | `igneum-pow/src/state.rs:200`, `a_stateless_hasher_is_wrong_on_every_item` | see the run line below |
| No cache line costs less than `j + 1` | the real chain | a flattened chain (the F3 harness's known-fail) | `docs/analysis/attack-pass/f3-cache.md`, the two firings | PASS, 7 October |
| No hot set on the frozen tip | the window-model control | a planted hot site (F8's known-fail) | `f8-uniform.md` sections (d) and (e) | PASS, 61 of 64 and 245 of 256 within 1.2x |
| Init-determined sites are in iteration 0 only | the taint trace | a program with a load whose address never absorbs a word (F9's planted case) | `f9-grind.md` sub-row (c) | 1 to 7 sites, iteration 0 only, 2,000 seeds |
Run line (box 2 through `tools/build-remote.sh`, `cargo test --release -p igneum-pow -- bind::
state::a_stateless_hasher_is_wrong_on_every_item`): 8 October 2026, 19:0x UK, box 2 (igneum-build-2) through `tools/build-remote.sh --box 2`, the crate at the mirror's master 494fb981: `bind::tests::bound_hash_properties` ok, `bound_vectors` ok, `init_bytes_layout` ok, `day_bytes_layout` ok, `pow256_and_target64` ok, `closed_form_bound_also_works` ok (6 passed, 0 failed, 0.43 s); `state::tests::a_stateless_hasher_is_wrong_on_every_item` ok (1 passed, 0 failed, 2.15 s). The pool crate's `the same nonce on another template hashes differently` and the fork engine's timestamp-bound test are in their crates' suites (pool.md section 4; `igneum.rs:200`), not re-run here.
## 6. Open questions, named for main
| Id | Question | What closes it |
|---|---|---|
| B1 | The epoch-seed VDF (spec 4.3, 600 s of delay on the seed block's hash) is Designed and not in the node line; the era VDF is. Without it the seed-block producer picks among the candidates it can afford to burn (R7). Is the epoch VDF a 2.0 item, or does the measured program-to-program hash-rate spread (the F10 ladder's reading) close it as not worth a block? | A ruling, with the F10 spread as the number |
| B2 | The day is keyed on the header timestamp in the node (`day_index(header.timestamp)`) and on DAA score in spec 1.12 (R8). Which text moves? The timestamp key makes the day boundary a miner's choice within 10 s and costs the verifier two caches across it; the DAA key makes it a function of the past alone | A ruling; a one-line spec or code change |
| B3 | `seed_source` and `proof_ref` (spec 2.4, fork point a5) are not in the node line's header; the epoch seed is bound through `daa_score` and the parents instead, which is sufficient for binding but leaves the spec's field table ahead of the code | The spec's table marked to the code, or the fields added when the chunked proving protocol needs `proof_ref` |
| B4 | A verifier syncing from genesis derives every epoch's program (the draw plus acceptance, about 6 core-s per epoch at 3.2 candidates): about 15 core-hours per year of chain on one core, parallel across epochs | The IBD cost measured on the node line with the memo (`epoch_seed_memo`) and a per-epoch program cache; a number, not a rule |
| B5 | R10's coalesced-pair search (F9 (c)) is measured as a loss on the RTX 5090; it is not measured on a card whose load completes per lane rather than per warp (an AMD wave64 under the local-memory exchange) | One run of the F9 `grind` per-warp table on a 9070 XT |

View file

@ -70,7 +70,10 @@ Instrument: the class v5 nvcc harness (`proto-newpow/class-v5/bench.cu` on `box/
| RTX 5090, the same card | v5-genesis | 65.30 | 574.8 | 8.803 | 48 | 0 | 24 | ae74193ddad19e19 |
| RTX 4090 (RunPod 386yytbh4bkfnz, driver 580.159.04, 450 W cap), stock | cs64s27x16 | 62.44 | 268.8 | 4.305 | 87 | 0 | 20 | ad0cec2a42c84aff |
| RTX 4090, the same card | v5-genesis | 62.44 | 271.3 | 4.345 | 32 | 0 | 24 | ae74193ddad19e19 |
| RTX 5090 on PC 1 at the 1,300 MHz lock | both | OWED: PC 1 booked to 17:40 BST; the bound pack and the kit are at build-1:/srv/builds/igneum-wt-connected/cs-kit (sha c9aff54aaf10d79e) and the hash lane publishes the job when PC 1 frees | | | | | | |
| RTX 5090 on PC 1 at the 1,300 MHz core lock (the hash lane's job run-ca3-pc1-cs64-5090-20261008, 17:50 to 17:53 BST, the class v5 kit's CUDA worker, 250 x 2^24, nvidia-smi 1 Hz; PCIe gen 4 x16 since the eGPU swap) | cs64s27x16 (bound pack) | 132.4 | 316.4 | 2.390 | 80 | 0 | | ad0cec2a42c84aff |
| PC 1, the same lock | v5-genesis | 132.3 | 311.4 | 2.354 | 48 | 0 | | ae74193ddad19e19 |
| PC 1 unlocked (the same job) | cs64s27x16 | 139.9 | 492.6 | 3.521 | 80 | 0 | | ad0cec2a42c84aff |
| PC 1 unlocked | v5-genesis | 139.8 | 465.2 | 3.328 | 48 | 0 | | ae74193ddad19e19 |
The kit worker (the brief's instrument, `igneum-worker-cuda --bench --batch-log2 24 --batches 250`, the class v5 kit's NVRTC worker of 7 October loading the pack's `kernel_bound.cu`; check PASS on both packs):
@ -83,7 +86,7 @@ The kit worker (the brief's instrument, `igneum-worker-cuda --bench --batch-log2
Both instruments agree with each other on each card (the harness and the worker within 3 percent of rate) and agree on the comparison: the window moves energy per hash by +0.6 percent on the 5090 (harness and worker alike) and by -0.9 percent on the 4090 (harness and worker alike), inside the run-to-run noise of a power reading. The 4090 is not at its cap (269 W of 450) and both packs read the same rate to three figures, so there the hash is bound by the memory chain, not the ALU or the register file; the 5090 is at its 575 W cap and the window costs under 1 percent of rate. (The fingerprints are the same on both cards and both instruments: ad0cec2a42c84aff for cs64, ae74193ddad19e19 for v5-genesis.)
Meaning: at stock the window costs the 5090 0.6 percent of energy per hash against a 10 percent budget; the compiled allocation is 80 registers per thread with no spill, so the window is in registers, not local memory, and the occupancy under this harness is the same as v5's. The harness reads 65 MH/s where the NVRTC worker reads about twice that on a 5090 (one warp per block, 24 resident blocks); the ratio between two packs on the same harness is the measurement, the absolute rate is not. The lock row is where the energy comparison binds (the 5090 at the lock reads 2.33 microjoules per hash on v5); the stock rows say the card is bound by its power cap in both cases and the window moves the rate by under 1 percent.
Meaning: at stock the window costs the 5090 0.6 percent of energy per hash against a 10 percent budget; the compiled allocation is 80 registers per thread with no spill, so the window is in registers, not local memory, and the occupancy under this harness is the same as v5's. The harness reads 65 MH/s where the NVRTC worker reads about twice that on a 5090 (one warp per block, 24 resident blocks); the ratio between two packs on the same harness is the measurement, the absolute rate is not. At the lock, where the energy comparison binds, the window costs the 5090 +1.6 percent of energy per hash (2.39 against 2.35 microjoules, the rate equal); unlocked on PC 1 it costs +5.9 percent (3.52 against 3.33, the rate equal, 27 W more at the same clock): the window's register traffic is a power term the lock hides and the rented cards' caps hid. Both inside the 10 percent budget; the card side never decided this lane.
## 5. The chip side
@ -100,15 +103,15 @@ What the adversary's re-optimisation did to each part of the structure: the gate
## 6. The score and the verdict
E_GPU over E_adversary, absolute convention, GDDR7 board (E_mem 0.466 microjoules per hash), E_chip = E_mem + 55,296 x e_chip, E_GPU = the 5090 at the lock (2.33 microjoules per hash on class v5) x 1.006 for the window (the stock rows of section 4; the lock row is owed):
E_GPU over E_adversary, absolute convention, GDDR7 board (E_mem 0.466 microjoules per hash), E_chip = E_mem + 55,296 x e_chip, E_GPU = the 5090 at the lock (2.33 microjoules per hash on class v5) x 1.016 for the window (the PC 1 lock row of section 4; the table was first written at x 1.006 from the stock rows and the amendment moved the ratios from 1.10x and 1.08x to 1.08x and 1.07x):
| Core | Node-for-node (N5) | A node ahead (N3) | Two nodes (N2) |
|---|---|---|---|
| cs64s27x16, re-optimised | 2.344 / (0.466 + 0.243) = 3.3x | 2.344 / (0.466 + 0.177) = 3.6x | 2.344 / (0.466 + 0.127) = 4.0x |
| cs64s27x16, re-optimised | 2.367 / (0.466 + 0.243) = 3.3x | 2.367 / (0.466 + 0.177) = 3.7x | 2.367 / (0.466 + 0.127) = 4.0x |
| the genesis window (the control) | 2.33 / (0.466 + 0.177) = 3.6x | 2.33 / (0.466 + 0.127) = 3.9x | 2.33 / (0.466 + 0.088) = 4.2x |
| the window's effect on the chip's edge | 1.10x | 1.08x | 1.05x |
| the window's effect on the chip's edge | 1.08x | 1.07x | 1.05x |
On the placed figures (both rows 64 percent higher) the pair reads about 2.2x and 2.4x node-for-node and the ratio stays near 1.1x. The gate was 1.25x node-for-node for the 1.5x one-node-ahead ambition; the row reads 1.10x node-for-node and 1.08x a node ahead.
On the placed figures (both rows 64 percent higher) the pair reads about 2.2x and 2.4x node-for-node and the ratio stays near 1.1x. The gate was 1.25x node-for-node for the 1.5x one-node-ahead ambition; the row reads 1.08x node-for-node and 1.07x a node ahead.
**Verdict: KILL as a class.** The hypothesis was that a connected organisation of the same work, with the window independently necessary across the whole chain, would deny a specialist its separation of storage, arithmetic and scheduling. It does not: the liveness rows show the window is necessary (63 of 64 at every address) and the chip answers with a clock-gated file that pays per write, not per live register, so necessity costs it nothing; the only term that reaches the chip is the window's own width (+0.14 k at the lock), which the design document already holds as its one robust core knob, and the connected structure adds about 0.1 pJ around it. The GPU side passes its budget with room (+0.6 percent of energy per hash at stock on the 5090, -0.9 percent on the 4090, 80 to 87 registers per thread with no spill), and the census passes every instrument with fewer attempts than v5; neither moves the score. The founder's accepted review stands in a sharper form than before: a specialist's edge against this family is a per-op energy ratio on a known op mix, and reorganising the dependency graph of the same ops does not change what an op costs on either side.
@ -120,5 +123,4 @@ What is kept: the generator variant and the liveness tool (research class, behin
- A wider per-step chain: a spine of depth 8 to 16 so `dep` rises from about 11 toward the window, at the cost of ILP on the card (measure the rate first; the chain per step is what a two-level file exploits).
- The window at 32 and 16 (`cs32s27x16`, `cs16s27x16`) for the k curve, and the block at 16 x 27 (`cs64s16x27`, text 272) if the imem matters to the re-optimised core.
- The op-mix re-weight of the census lane at this structure (the two closing instructions already take the injecting table).
- The PC 1 lock row (informational now: the verdict does not turn on it; it lands as an amendment if PC 1 runs it).
- A knob that reaches a gated file: not more live state but more WRITES per op the chip cannot skip (every op writing two registers, or a window write per load), priced against the card's own write cost first; the k lane's placed row at 21:00 says whether even that moves k.

View file

@ -327,9 +327,9 @@ The research lane's close names, from the k lane's mix optimiser over the band,
| The attempts census, width 4 (the or-floored copy, fg8) | 2,497 eras read (1,500 plus 997 of a second 1,500 on build-3 before it stopped answering ssh at 16:4x BST); `logs/ktable-w4-*`, `logs/ktable-w4b-*` | r = 0.329 per candidate (the band 0.62, the shipped draw 0.68); mean attempt 0.49, max 10, 0 past attempt 31, 0 exhausted; of the 3,011 candidates reaching the 2^20 pass (c'') refuses 1.5 percent and (c''') 15.6 (12.6 percent of all candidates); the accepted draw's minimum ratio p1 0.9960, median 0.9999 | the draw is half the band's cost (the lossy share 6 of 75 against 18 cuts (a') from 76 to 58 percent of rejections); the (c''') cost at width 4 is the width's (15.6 against the band's 14.6 percent of reaching candidates), not the table's |
| The attempts census, width 1 (fg8) | 1,500 eras; `logs/ktable-w1-*` | r = 0.191; mean attempt 0.24, max 4, 0 exhausted; (c'') 1.2 and (c''') 1.0 percent of reaching candidates (0.9 percent of all); accepted min ratio p1 0.9960 | the band at width 1: r = 0.565, (c''') 1.8 percent of reaching candidates |
| The index-bit read (the fg8 rows) | the same eras | over 6 sigma at one site in 40.5 percent of eras at width 4 (51.1 at width 1), over 100 sigma 22.6 (36.3), over 300 sigma 9.3 (22.6); the bucket excess over +8 sigma 9.8 percent (18.7), p99 +28 (+50) | the band: 44.9 / 26.5 / 9.4 at width 4, 47.7 / 31.1 / 15.7 at width 1. The table's products (mad 11, mul 4, mulhi 2 against the base's 8, 8, 6) keep the bit-R mechanism where it was: this table does not change the standing fact of section 6.8 and does not worsen it |
| The attempts census at the EXACT table (or = 0, fg9), widths 4 and 1 | OWED: 466 (width 4) and 401 (width 1) of 1,500 eras were done on build-3 when it stopped answering ssh at 16:4x BST (unreachable at 17:52); the fallback on build-2 (`fg-chain13.sh`, the fg9 build) waited 70 minutes on a full pool and had not started by the clock | lands as an amendment line when either box gives cores | one point of `or` (1.3 percent of instructions) separates these from the fg8 rows above, so the verdict does not turn on them |
| The attempts census at the EXACT table (or = 0), widths 4 and 1 | DONE 18:20 BST on build-4, on the harness ported to class-v6 13885e16f (fold off, rw 0: the class v5 acceptance, the port reproducing section 6.8's p4, p8 and p10 rows to the last digit); 1,500 eras per width; `logs/ktable0-w4-*`, `logs/ktable0-w1-*` | width 4: r = 0.286, mean attempt 0.40, max 8, 0 exhausted; (c'') 1.3 and (c''') 13.9 percent of the 1,768 candidates reaching the 2^20 pass (11.7 percent of all); accepted min ratio p1 0.9960; the bit read over 6 sigma 38.7 percent of eras, over 300 sigma 9.1; bucket over +8 sigma 8.7. Width 1: r = 0.155, mean 0.18, max 5, 0 exhausted; (c'') 0.3 and (c''') 0.8 percent; bit over 6 sigma 47.5, over 300 19.5 | the exact table draws cheaper still than the or-floored copy (r 0.29 against 0.33 at width 4; `or` gone takes the last 1.3 percent of lossy instructions out) and reads the same at the floors and on the bit level: the verdict stands on the exact table |
VERDICT (18:00 BST, to the coordinator, the hash lane, the census lane and the research lane): PASS. The table halves the draw's cost against the band (the lossy share 6 of 75 cuts (a') from 76 to 58 percent of rejections), costs nothing at the floors beyond the width's own cost, adds no hot set and no seed over 1.2x on the live item map at 2^20, and leaves the bit-R mechanism of section 6.8 where every table of the day left it. The census lane's neighbouring table (add 13, xor 11, mul 6, mad 10, shfl 8, rotl 8, sub 7, mulhi 2, rotr 6, or 4) stays the fallback, unused.
VERDICT (18:00 BST, to the coordinator, the hash lane, the census lane and the research lane; the exact-table rows in at 18:20): PASS. The table halves the draw's cost against the band (the lossy share 6 of 75 cuts (a') from 76 to 58 percent of rejections), costs nothing at the floors beyond the width's own cost, adds no hot set and no seed over 1.2x on the live item map at 2^20, and leaves the bit-R mechanism of section 6.8 where every table of the day left it. The census lane's neighbouring table (add 13, xor 11, mul 6, mad 10, shfl 8, rotl 8, sub 7, mulhi 2, rotr 6, or 4) stays the fallback, unused.
## 7. The 16:30 BST report (the founder's clock, pulled from 09:00; a partial carries its count)
@ -339,7 +339,7 @@ VERDICT (18:00 BST, to the coordinator, the hash lane, the census lane and the r
4. DONE 15:1x BST: the x4, x8 and x16 verifier rows (section 6.7, build-3 one core: 3.73 / 4.12 / 7.13 ms per warp; x16 outside the band on the 10 ms gate by scaling to the proxies).
5. DONE 14:4x BST: the width-4 plus shape-64 crossing (build-4, 3,000 eras: r = 0.654, 0 exhausted, (c''') 5.3 percent of candidates, the width's floor cost again) and section 6.8, the F8 label space's p2 to p65 and p212 to p225 through the sigma form at the shipped parameters (build-2, queued 14:0x BST), so the two F8-256 attributions the hash lane added to layer 4's bucket-bound row (p212, site 9 at 1.75x its window expectation; p225, a value-level concentration at a mad-written site with the bucket at expectation) and the morning's four tail seeds are read as known-failed cases of the bucket-sigma and bit-bias tests on one scale; and the bucket bound in sigma (from the bit-clean spread: p99 +6.8, p99.9 +16.5 to +20.1, max +35 over 9,409 eras; a band at +8 refuses 0.3 to 0.4 percent of bit-clean programs and 11 to 18 percent of all, which is the biased population, so the bucket bound and the bit read are one rule).
6. The gate-record form: `logs/gate-records-lossy-base.jsonl` (3,000 records of the band stratum in the section 4.3 shape, one per line, 3.6 MB) and `logs/fg-records.py`, which derives every other stratum's records from its TSV rows in this directory.
7. DONE 18:0x BST, section 6.9 (the Igneum 2.0 pause of 17:10 BST keeps this row as D1's op-mix acceptance and pauses the rest of the family gate; this report stands as the no-rescue test's control document): the research lane's best-mix genesis table (add 16, xor 14, mad 12, rotl 11, sub 10, rotr 10, shfl 4, mul 4, mulhi 2, or 0, sum 83; renormalised to 75 by largest remainder as 14, 13, 4, 11, 3, 10, 9, 2, 9, 0 in the generator's order) through the acceptance at the shipped parameters: the attempts census with the refused-ratio column at widths 4 and 1 (1,500 eras each, build-3, the fg8 harness), the index-bit read, and attack-f8 at 2^20 on 64 seeds; running from 15:5x BST, the verdict to the coordinator, the hash, census and research lanes with its minute.
7. DONE 18:0x BST, section 6.9 (the Igneum 2.0 pause of 17:10 BST keeps this row as D1's op-mix acceptance and pauses the rest of the family gate; this report stands as the no-rescue test's control document): the research lane's best-mix genesis table (add 16, xor 14, mad 12, rotl 11, sub 10, rotr 10, shfl 4, mul 4, mulhi 2, or 0, sum 83; renormalised to 75 by largest remainder as 14, 13, 4, 11, 3, 10, 9, 2, 9, 0 in the generator's order) through the acceptance at the shipped parameters: the attempts census with the refused-ratio column at widths 4 and 1, the index-bit read, and attack-f8 at 2^20 on 64 seeds: PASS, the exact-table rows in at 18:20 BST. The harness is also ported onto class-v6 (13885e16f; the diff `logs/harness-v6-13885e16f.diff`, reproducing the class-v5 rows to the last digit) for the hash lane's layer-8-off acceptance due 12:00 BST on 9 October.
8. Not in this report: the mixer ladder re-run at m = 4 and 16 (adv-mixer-3's harness rows; the per-family rows of section 4), the F8 census at D = 29 (the stand-in gap row), the 10^5-program verifier census at the top corner (F6 per family): each a named per-family row with its hours in section 4.1.
## 8. Sources

View file

@ -0,0 +1,480 @@
diff --git a/igneum-pow/src/accept.rs b/igneum-pow/src/accept.rs
index e02c92efe..b592235f3 100644
--- a/igneum-pow/src/accept.rs
+++ b/igneum-pow/src/accept.rs
@@ -365,7 +365,88 @@ pub struct SiteIndexStats {
/// The window of a load site in words at the rule's dataset: `2^28 >> min(win, 2)`.
pub fn site_window_words(ins: &Instr) -> u64 {
- (1u64 << ACCEPT_DATASET_LOG2) >> (ins.win as u64).min(2)
+ // a load of `width` words reads an aligned address, so its index space is the window over `width` (1 on every
+ // shipped class; the class v6 family-gate harness draws 4 and the expectation must follow, 8 October 2026)
+ ((1u64 << ACCEPT_DATASET_LOG2) >> (ins.win as u64).min(2)) / (ins.width.max(1) as u64)
+}
+
+/// Class v6 family-gate harness: what one load site's indices look like beyond [`SiteIndexStats`]: the largest
+/// 256-item bucket (4,096 words, the bucket of the F8 tail attribution of 8 October 2026) as a ratio to the window's
+/// expectation and in sigma of it, and the largest index-bit one-count excess in sigma over the window's free bits
+/// (adv-cache-2's value-level read: a product's low bits placed at address bit R and up).
+#[derive(Clone, Copy, Debug, PartialEq)]
+pub struct FamilySiteStats {
+ pub base: SiteIndexStats,
+ pub bucket_ratio: f64,
+ pub bucket_z: f64,
+ pub bucket_id: u32,
+ /// the site's window draw `k_off` (0 the dataset, 1 a half, 2 a quarter)
+ pub win: u8,
+ pub bit_z: f64,
+ pub bit: u8,
+}
+
+/// The same run as [`site_index_stats`] with the two extra statistics per site.
+pub fn family_site_stats(p: &Program, units: usize) -> Result<Vec<FamilySiteStats>, Reject> {
+ let mut indices = site_indices(p, units)?;
+ let load_instrs: Vec<&Instr> = p.instrs.iter().filter(|i| i.op.is_load()).collect();
+ let mut out = Vec::with_capacity(indices.len());
+ for (site, ix) in indices.iter_mut().enumerate() {
+ let ins = load_instrs[site];
+ let n = ix.len() as f64;
+ let k = (ins.win as u32).min(2);
+ let free_bits = ACCEPT_DATASET_LOG2 - k;
+ let mut ones = [0u64; 32];
+ let mut buckets = vec![0u32; 1usize << (ACCEPT_DATASET_LOG2 - 12)];
+ for &x in ix.iter() {
+ buckets[(x >> 12) as usize] += 1;
+ let mut v = x;
+ let mut b = 0;
+ while v != 0 {
+ ones[b] += (v & 1) as u64;
+ v >>= 1;
+ b += 1;
+ }
+ }
+ let width = ins.width.max(1) as u32;
+ let align_bits = width.trailing_zeros();
+ let (mut bit_z, mut bit) = (0.0f64, 0u8);
+ for b in align_bits..free_bits {
+ let z = (ones[b as usize] as f64 - n / 2.0) / (n / 4.0).sqrt();
+ if z.abs() > bit_z.abs() {
+ bit_z = z;
+ bit = b as u8;
+ }
+ }
+ let window_buckets = (site_window_words(ins) * width as u64) >> 12;
+ let expect = n / window_buckets as f64;
+ let (mut bmax, mut bid) = (0u32, 0u32);
+ for (i, &c) in buckets.iter().enumerate() {
+ if c > bmax {
+ bmax = c;
+ bid = i as u32;
+ }
+ }
+ ix.sort_unstable();
+ let mut st = SiteIndexStats { distinct: 0, pairs: 0, top_index: 0, top_count: 0 };
+ let mut i = 0;
+ while i < ix.len() {
+ let mut j = i + 1;
+ while j < ix.len() && ix[j] == ix[i] {
+ j += 1;
+ }
+ let run = (j - i) as u32;
+ st.distinct += 1;
+ st.pairs += (run as u64) * (run as u64 - 1) / 2;
+ if run > st.top_count {
+ st.top_count = run;
+ st.top_index = ix[i];
+ }
+ i = j;
+ }
+ out.push(FamilySiteStats { base: st, bucket_ratio: bmax as f64 / expect, bucket_z: (bmax as f64 - expect) / expect.sqrt(), bucket_id: bid, win: k as u8, bit_z, bit });
+ }
+ Ok(out)
}
/// One interpreter run over `units` units of the seed's acceptance stream with every load site's word indices kept,
@@ -485,6 +566,9 @@ pub fn check_indices_v5(p: &Program) -> Result<(), Reject> {
/// Whether `class` is the class v4 shape (the 256-instruction shadow block over the class v3 base, the pass count and
/// the era set aside): the shape the sub-version 2 rules (a') and (c') apply to, on every draw path.
pub fn is_class_v4_shape(class: &LoadClass) -> bool {
+ if family_gate_on() {
+ return is_family_shape(class);
+ }
matches!(class.shadow, Some(ShadowClass { instrs: V4_SHADOW_INSTRS, .. }))
// class v5 (docs/design/class-v5-stored-state.md) is judged under the same rules: its state flag is set aside;
// class v6 lane 1's index fold and re-weight table are set aside too (the address path and the op table are
@@ -492,6 +576,31 @@ pub fn is_class_v4_shape(class: &LoadClass) -> bool {
&& LoadClass { era: None, shadow: None, state: false, fold: false, rw: 0, ..*class } == LoadClass { shadow: None, ..V4_CLASS }
}
+/// Class v6 family-gate harness (8 October 2026, `docs/analysis/class-v6/family-gate.md`): the acceptance keyed on the
+/// FAMILY's shapes instead of the one class v4 shape, on when `IGNEUM_FAMILY_GATE` is set in the environment of a
+/// harness run and never on a chain path. The family: the shadow block in {64, 128, 256} instructions at the same
+/// 6,912 instructions per iteration, the mixer multiplier in {4, 8, 16}, the read width the era's draw over {1, 4}
+/// words (the mix one-hot on the drawn width), the rest the class v4 base with the state flag, the class v6 fold and
+/// the re-weight table set aside (as the class's own predicate sets them aside).
+pub fn is_family_shape(class: &LoadClass) -> bool {
+ let sh = match class.shadow {
+ Some(s) => s,
+ None => return false,
+ };
+ if !matches!(sh.instrs, 64 | 128 | 256) || sh.instrs as usize * sh.reps as usize != V4_SHADOW_INSTRS as usize * crate::generator::V4_SHADOW_REPS as usize {
+ return false;
+ }
+ if !matches!(class.mixer_mult, 4 | 8 | 16) {
+ return false;
+ }
+ LoadClass { era: None, shadow: None, state: false, fold: false, rw: 0, mixer_mult: 8, mix: V4_CLASS.mix, ..*class } == LoadClass { shadow: None, ..V4_CLASS }
+}
+
+fn family_gate_on() -> bool {
+ static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
+ *ON.get_or_init(|| std::env::var_os("IGNEUM_FAMILY_GATE").is_some())
+}
+
/// One pass of the dataflow freshness over the base program then the shadow block (the order of one iteration),
/// from `fresh`; `check` reports the first load that reads a register that is not fresh. The rule (AP-F8-1,
/// `docs/analysis/ca3-v4-uniform.md`): a load leaves its destination fresh only if its source was (a saturated
@@ -1398,4 +1507,236 @@ mod tests {
assert!(check(&generate(s)).is_ok());
}
}
+ /// Class v6 family-gate harness (8 October 2026, `docs/analysis/class-v6/family-gate.md`): one drawn era per seed of a
+ /// label space, every parameter of the family drawn from the era's own stream (the shadow block shape in {64, 128,
+ /// 256}, the mixer multiplier in {4, 8, 16}, the read width over {1, 4} words through the era draw, the ten non-load
+ /// weights perturbed within B points with shuffle and mulhi never raised), the chain draw of that era's epoch
+ /// through the real rule with the first failing part of every candidate, then on the accepted program the per-site
+ /// statistics at the rule's own 2^20 sample: the (c'') ratio, the largest 256-item bucket, the index-bit bias.
+ /// One TSV row per era to `IGNEUM_FG_OUT`. Pins for the strata: `IGNEUM_FG_SHAPE`, `IGNEUM_FG_MIXER`,
+ /// `IGNEUM_FG_WIDTH`, `IGNEUM_FG_B` (default 4), `IGNEUM_FG_LOSSY_CAP` (or, mul and mulhi at +B: the lossy corner),
+ /// `IGNEUM_FG_LABEL` (default `igneum-family-gate`). Requires `IGNEUM_FAMILY_GATE=1` in the environment.
+ #[test]
+ #[ignore]
+ fn family_gate_era_census() {
+ use crate::generator::{set_family_weights, LoadClass, ShadowClass, NONLOAD_WEIGHTS, V5_CLASS, MAX_ATTEMPTS_V4};
+ use crate::seed::seed_words_from_bytes;
+ use std::io::Write;
+ use std::sync::atomic::{AtomicU32, Ordering};
+ use std::sync::Mutex;
+ assert!(family_gate_on(), "IGNEUM_FAMILY_GATE=1 is required");
+ let env_u = |k: &str, d: u32| std::env::var(k).ok().and_then(|v| v.parse().ok()).unwrap_or(d);
+ let seeds = env_u("IGNEUM_FG_SEEDS", 1000);
+ let from = env_u("IGNEUM_FG_FROM", 0);
+ let threads = env_u("IGNEUM_FG_THREADS", 32) as usize;
+ let b_pts = env_u("IGNEUM_FG_B", 4) as i64;
+ let label = std::env::var("IGNEUM_FG_LABEL").unwrap_or_else(|_| "igneum-family-gate".to_string());
+ let pin_shape = std::env::var("IGNEUM_FG_SHAPE").ok().and_then(|v| v.parse::<u16>().ok());
+ let pin_mixer = std::env::var("IGNEUM_FG_MIXER").ok().and_then(|v| v.parse::<u8>().ok());
+ let pin_width = std::env::var("IGNEUM_FG_WIDTH").ok().and_then(|v| v.parse::<u8>().ok());
+ let lossy_cap = std::env::var_os("IGNEUM_FG_LOSSY_CAP").is_some();
+ // the proposed band after the 12:00 BST reading of 8 October: B on the injecting families only, the three lossy
+ // families (or, mul, mulhi) never raised above their base
+ let lossy_base = std::env::var_os("IGNEUM_FG_LOSSY_BASE").is_some();
+ // the lossy-share curve (the full report's item 3): or, mul and mulhi each at exactly +n points
+ let lossy_plus: Option<i64> = std::env::var("IGNEUM_FG_LOSSY_PLUS").ok().and_then(|v| v.parse().ok());
+ let out_path = std::env::var("IGNEUM_FG_OUT").unwrap_or_else(|_| "family-gate.tsv".to_string());
+ let out = Mutex::new(std::fs::File::create(&out_path).expect("IGNEUM_FG_OUT"));
+ let wnames: Vec<String> = NONLOAD_WEIGHTS.iter().map(|(o, _)| format!("w_{o:?}").to_lowercase()).collect();
+ writeln!(out.lock().unwrap(), "k\tshape\treps\tmixer\twidth\tR\tM\tpos\t{}\tattempt\tcandidates\ta_prime\ta\tb\tc_const\tc_lane\tc_sat\tc_bias\tc_distinct\tc1_sat_source\tc2_low_entropy\tc3_hot_item\tc_other\tmin_ratio\tmin_site\ttop_count_max\tbucket_ratio_max\tbucket_z_max\tbucket_site\tbucket_win\tbit_z_max\tbit_site\tbit_win\tbit\tlast_resort_k\tc3_ratios\tsecs", wnames.join("\t")).unwrap();
+ let part_ix = |r: &Reject| -> usize {
+ match r {
+ Reject::UnfreshLoadSource { .. } => 0,
+ Reject::StaleLoadSource { .. } => 1,
+ Reject::NoInjectingWrite { .. } => 2,
+ Reject::ConstantBit { .. } => 3,
+ Reject::LaneConstantSite { .. } => 4,
+ Reject::Saturated { .. } => 5,
+ Reject::OutputBias { .. } => 6,
+ Reject::DistinctAddresses { .. } => 7,
+ Reject::SaturatedSource { .. } => 8,
+ Reject::LowEntropySite { .. } | Reject::RepeatedSource { .. } => 9,
+ Reject::HotItemSite { .. } => 10,
+ // class v6's own parts (the fold's dead-window register and whatever the branch adds next): one column
+ _ => 11,
+ }
+ };
+ let next = AtomicU32::new(from);
+ let t0 = std::time::Instant::now();
+ let done = AtomicU32::new(0);
+ std::thread::scope(|sc| {
+ for _ in 0..threads {
+ sc.spawn(|| loop {
+ let k = next.fetch_add(1, Ordering::Relaxed);
+ if k >= from + seeds {
+ break;
+ }
+ let ts = std::time::Instant::now();
+ let w = |s: String| -> Vec<u8> { seed_words_from_bytes(s.as_bytes()).iter().flat_map(|x| x.to_le_bytes()).collect() };
+ let epoch = w(format!("{label}/program/{k}"));
+ let era = w(format!("{label}/era/{k}"));
+ // the family's draws from the era's own stream, in a fixed order, each consumed whether pinned or not
+ let dw = seed_words_from_bytes(&[b"igneum-family-gate/draw/".as_slice(), era.as_slice()].concat());
+ let mut rng = SplitMix64::new(dw[0] as u64 | ((dw[1] as u64) << 32));
+ let shapes = [64u16, 128, 256];
+ let shape = pin_shape.unwrap_or(shapes[rng.below(3) as usize]);
+ let mixers = [4u8, 8, 16];
+ let mixer = pin_mixer.unwrap_or(mixers[rng.below(3) as usize]);
+ // the base table: NONLOAD_WEIGHTS, or IGNEUM_FG_WEIGHTS when set (a fixed genesis table under test
+ // with B = 0, the research lane's close of 15:3x BST on 8 October)
+ let base_weights = crate::generator::family_weights_env_table();
+ let mut weights = base_weights;
+ let mut raw = [0i64; 10];
+ for (i, (op, wgt)) in base_weights.iter().enumerate() {
+ let mut d = rng.below((2 * b_pts + 1) as u64) as i64 - b_pts;
+ if matches!(op, Op::Shfl | Op::MulHi) && d > 0 {
+ d = 0;
+ }
+ if lossy_cap && matches!(op, Op::Or | Op::Mul | Op::MulHi) {
+ d = b_pts;
+ }
+ if lossy_base && matches!(op, Op::Or | Op::Mul | Op::MulHi) && d > 0 {
+ d = 0;
+ }
+ if let Some(n) = lossy_plus {
+ if matches!(op, Op::Or | Op::Mul | Op::MulHi) {
+ d = n;
+ }
+ }
+ // a drawn table keeps every family at one point or more; a fixed table under test (B = 0 with
+ // IGNEUM_FG_WEIGHTS) is taken as given, a zero weight included (the first run of the best-mix
+ // table floored `or` at 1 of 75 by this line; the record names both)
+ raw[i] = (*wgt as i64 + d).max(if b_pts == 0 { 0 } else { 1 });
+ }
+ // renormalise to 75 by largest remainder
+ let total: i64 = raw.iter().sum();
+ let mut floors = [0u64; 10];
+ let mut rems: Vec<(i64, usize)> = Vec::new();
+ let mut sum = 0u64;
+ for i in 0..10 {
+ floors[i] = ((raw[i] * 75) / total) as u64;
+ rems.push((((raw[i] * 75) % total), i));
+ sum += floors[i];
+ }
+ rems.sort_by(|x, y| y.0.cmp(&x.0).then(x.1.cmp(&y.1)));
+ let mut short = 75 - sum;
+ for &(_, i) in &rems {
+ if short == 0 {
+ break;
+ }
+ floors[i] += 1;
+ short -= 1;
+ }
+ for i in 0..10 {
+ weights[i].1 = floors[i];
+ }
+ set_family_weights(Some(weights));
+ let reps = (V4_SHADOW_INSTRS as u32 * crate::generator::V4_SHADOW_REPS as u32 / shape as u32) as u16;
+ let base = LoadClass { shadow: Some(ShadowClass { instrs: shape, reps }), mixer_mult: mixer, ..V5_CLASS };
+ // a one-entry set is the pinned-width path of LoadClass::era (the mix stays the base's and the draw is
+ // redrawn to the base's widest, 1 word) and the set must be strictly ascending, so a pinned width is
+ // built by hand: the era drawn over the one-entry set and the mix one-hot on that width, as the
+ // drawn-set path does
+ let class = match pin_width {
+ Some(w) if w == 1 || w == 4 => {
+ let mut c = base;
+ let i = crate::generator::WIDTH_WORDS.iter().position(|&x| x == w).unwrap();
+ c.mix = [0, 0, 0];
+ c.mix[i] = 100;
+ c.era = Some(crate::generator::era_draw(&era, &[w]));
+ c
+ }
+ _ => LoadClass::era(base, &era, &[1, 4]),
+ };
+ let e = class.era.unwrap();
+ let lbl = f8_label(&epoch);
+ let mut parts = [0u32; 12];
+ let mut accepted: Option<(u32, Program)> = None;
+ // the ratios of the candidates the two floors refused (the pre-floor spread per width: the full
+ // report's per-width calibration), as "c2:<milli>" or "c3:<milli>" per refusal
+ let mut c3_ratios: Vec<String> = Vec::new();
+ for attempt in 0..MAX_ATTEMPTS_V4 {
+ let c = candidate_class(&lbl, &epoch, attempt, class);
+ match check(&c) {
+ Ok(_) => {
+ accepted = Some((attempt, c));
+ break;
+ }
+ Err(r) => {
+ match &r {
+ Reject::HotItemSite { ratio_milli, .. } => c3_ratios.push(format!("c3:{ratio_milli}")),
+ Reject::LowEntropySite { ratio_milli, .. } => c3_ratios.push(format!("c2:{ratio_milli}")),
+ _ => {}
+ }
+ parts[part_ix(&r)] += 1
+ }
+ }
+ }
+ // an exhausted era takes class v5's last resort: the scan of 256 more candidates past the cap, each
+ // rewritten (or, mul, mulhi to xor) and its stale loads re-sourced, the first that passes the whole
+ // rule; the column records that k (cap + i), or "fallback" when none of the 256 passed
+ let last_resort_k: String = if accepted.is_none() {
+ use crate::generator::{last_resort_v4, repair_stale_loads, LAST_RESORT_SCAN};
+ let mut found = String::from("fallback");
+ for kk in MAX_ATTEMPTS_V4..MAX_ATTEMPTS_V4 + LAST_RESORT_SCAN {
+ let q = repair_stale_loads(last_resort_v4(candidate_class(&lbl, &epoch, kk, class)));
+ if check(&q).is_ok() {
+ found = kk.to_string();
+ break;
+ }
+ }
+ found
+ } else {
+ String::new()
+ };
+ set_family_weights(None);
+ let candidates: u32 = parts.iter().sum::<u32>() + accepted.is_some() as u32;
+ let (attempt, stats_row) = match &accepted {
+ Some((att, p)) => {
+ let st = family_site_stats(p, ACCEPT_UNITS_DISTINCT_V4).expect("the accepted program runs");
+ let n = (ACCEPT_UNITS_DISTINCT_V4 * LANES * ITERATIONS) as f64;
+ let loads: Vec<&Instr> = p.instrs.iter().filter(|i| i.op.is_load()).collect();
+ let (mut min_r, mut min_s) = (f64::MAX, 0usize);
+ let (mut bmax, mut bz, mut bsite, mut bwin) = (0.0f64, 0.0f64, 0usize, 0u8);
+ let (mut zmax, mut zsite, mut zbit, mut zwin) = (0.0f64, 0usize, 0u8, 0u8);
+ let mut top = 0u32;
+ for (s, fs) in st.iter().enumerate() {
+ let r = site_ratio(fs.base.distinct, n, site_window_words(loads[s]));
+ if r < min_r {
+ min_r = r;
+ min_s = s;
+ }
+ if fs.bucket_z > bz {
+ bmax = fs.bucket_ratio;
+ bz = fs.bucket_z;
+ bsite = s;
+ bwin = fs.win;
+ }
+ if fs.bit_z.abs() > zmax.abs() {
+ zmax = fs.bit_z;
+ zsite = s;
+ zbit = fs.bit;
+ zwin = fs.win;
+ }
+ top = top.max(fs.base.top_count);
+ }
+ (*att as i64, format!("{min_r:.5}\t{min_s}\t{top}\t{bmax:.3}\t{bz:.2}\t{bsite}\t{bwin}\t{zmax:.2}\t{zsite}\t{zwin}\t{zbit}"))
+ }
+ None => (-1, "\t\t\t\t\t\t\t\t\t\t".to_string()),
+ };
+ let wcols: Vec<String> = weights.iter().map(|(_, x)| x.to_string()).collect();
+ let pcols: Vec<String> = parts.iter().map(|x| x.to_string()).collect();
+ let row = format!("{k}\t{shape}\t{reps}\t{mixer}\t{}\t{}\t{:08x}\t{:?}\t{}\t{attempt}\t{candidates}\t{}\t{stats_row}\t{last_resort_k}\t{}\t{:.1}", e.width_words, e.stride_rot, e.stride_mul, e.pos, wcols.join("\t"), pcols.join("\t"), c3_ratios.join(","), ts.elapsed().as_secs_f64());
+ writeln!(out.lock().unwrap(), "{row}").unwrap();
+ let d = done.fetch_add(1, Ordering::Relaxed) + 1;
+ if d % 100 == 0 {
+ println!("family_gate_era_census: {d} eras in {:.0} s", t0.elapsed().as_secs_f64());
+ out.lock().unwrap().flush().unwrap();
+ }
+ });
+ }
+ });
+ out.lock().unwrap().flush().unwrap();
+ println!("family_gate_era_census: {} eras {from}..{} in {:.0} s on {threads} threads -> {out_path}", done.load(Ordering::Relaxed), from + seeds, t0.elapsed().as_secs_f64());
+ }
}
diff --git a/igneum-pow/src/generator.rs b/igneum-pow/src/generator.rs
index 61fe4874b..a0d7109ba 100644
--- a/igneum-pow/src/generator.rs
+++ b/igneum-pow/src/generator.rs
@@ -637,6 +637,9 @@ impl LoadClass {
/// The non-load op table this class draws from, in draw order, and its sum (the roll's range). The plain table
/// for every class without the re-weight flag, so their streams are byte for byte what they were.
pub fn nonload_weights(&self) -> (&'static [(Op, u64); 10], u64) {
+ if let Some(o) = family_weights_override() {
+ return o;
+ }
match self.rw {
1 => (&NONLOAD_WEIGHTS_RW, NONLOAD_WEIGHTS_RW_SUM),
2 => (&NONLOAD_WEIGHTS_RW2, NONLOAD_WEIGHTS_RW2_SUM),
@@ -1059,14 +1062,27 @@ pub fn generate_era_generator(seed_string: &str, seed_bytes: &[u8], base: LoadCl
p
}
+/// Class v6 family-gate harness: `base` with the shadow block shape from `IGNEUM_FG_SHAPE` (64, 128 or 256 at 6,912 per
+/// iteration) under `IGNEUM_FAMILY_GATE`, so an unmodified harness runs the family's shape; `base` otherwise.
+fn family_shape_of(base: LoadClass) -> LoadClass {
+ if std::env::var_os("IGNEUM_FAMILY_GATE").is_some() {
+ if let Some(n) = std::env::var("IGNEUM_FG_SHAPE").ok().and_then(|v| v.parse::<u16>().ok()) {
+ assert!(matches!(n, 64 | 128 | 256), "IGNEUM_FG_SHAPE is 64, 128 or 256");
+ let reps = (V4_SHADOW_INSTRS as u32 * V4_SHADOW_REPS as u32 / n as u32) as u16;
+ return LoadClass { shadow: Some(ShadowClass { instrs: n, reps }), ..base };
+ }
+ }
+ base
+}
+
impl ProgramClass {
/// The load class this program class draws from.
pub fn load_class(&self) -> LoadClass {
match self {
ProgramClass::V2 => LoadClass::V2,
ProgramClass::V3 => V3_CLASS,
- ProgramClass::V4 => V4_CLASS,
- ProgramClass::V5 => V5_CLASS,
+ ProgramClass::V4 => family_shape_of(V4_CLASS),
+ ProgramClass::V5 => family_shape_of(V5_CLASS),
}
}
@@ -1499,6 +1515,50 @@ pub const NONLOAD_WEIGHTS: [(Op, u64); 10] = [
/// Class v6 lane 1, the re-weight table behind the index fold (`+rw`): the k lane's optimiser split in draw order,
/// sum [`NONLOAD_WEIGHTS_RW_SUM`] (83). Shuffle stays at 4 (a shuffle-heavy draw is the worst thing the class can do
/// on the chip side, k 0.021 routed) and `or` is never drawn. The roll ranges over the table's own sum.
+thread_local! {
+ static FAMILY_WEIGHTS: std::cell::Cell<Option<&'static [(Op, u64); 10]>> = const { std::cell::Cell::new(None) };
+}
+/// Class v6 family-gate harness (8 October 2026): the ten non-load weights this thread's draws use when set (a leaked
+/// table per era, so [`LoadClass::nonload_weights`] keeps its static signature), else the process-wide table. Set only
+/// by the harness census (`accept::tests::family_gate_era_census`), never on a chain path.
+pub fn set_family_weights(w: Option<[(Op, u64); 10]>) {
+ FAMILY_WEIGHTS.with(|c| c.set(w.map(|t| &*Box::leak(Box::new(t)))));
+}
+pub fn family_weights() -> [(Op, u64); 10] {
+ FAMILY_WEIGHTS.with(|c| c.get()).map(|t| *t).unwrap_or_else(family_weights_env_table)
+}
+/// The family override for a draw, when the harness set one: the table and its sum.
+fn family_weights_override() -> Option<(&'static [(Op, u64); 10], u64)> {
+ if std::env::var_os("IGNEUM_FAMILY_GATE").is_none() {
+ return None;
+ }
+ let t: &'static [(Op, u64); 10] = match FAMILY_WEIGHTS.with(|c| c.get()) {
+ Some(t) => t,
+ None => FAMILY_WEIGHTS_ENV.get_or_init(family_weights_env),
+ };
+ Some((t, t.iter().map(|w| w.1).sum()))
+}
+static FAMILY_WEIGHTS_ENV: std::sync::OnceLock<[(Op, u64); 10]> = std::sync::OnceLock::new();
+/// The process-wide table (`IGNEUM_FG_WEIGHTS` under `IGNEUM_FAMILY_GATE`, else [`NONLOAD_WEIGHTS`]).
+pub fn family_weights_env_table() -> [(Op, u64); 10] {
+ *FAMILY_WEIGHTS_ENV.get_or_init(family_weights_env)
+}
+/// The harness's process-wide weight table: `IGNEUM_FG_WEIGHTS=w0,..,w9` (sum 75, the order of [`NONLOAD_WEIGHTS`]),
+/// read only with `IGNEUM_FAMILY_GATE` set (an unmodified harness such as attack-f8 then draws the family's weights).
+fn family_weights_env() -> [(Op, u64); 10] {
+ let mut w = NONLOAD_WEIGHTS;
+ if std::env::var_os("IGNEUM_FAMILY_GATE").is_some() {
+ if let Ok(s) = std::env::var("IGNEUM_FG_WEIGHTS") {
+ let v: Vec<u64> = s.split(',').filter_map(|x| x.trim().parse().ok()).collect();
+ assert!(v.len() == 10 && v.iter().sum::<u64>() == 75, "IGNEUM_FG_WEIGHTS: ten weights summing to 75");
+ for (i, x) in v.into_iter().enumerate() {
+ w[i].1 = x;
+ }
+ }
+ }
+ w
+}
+
pub const NONLOAD_WEIGHTS_RW: [(Op, u64); 10] = [
(Op::Add, 16),
(Op::Xor, 14),
@@ -1651,8 +1711,9 @@ pub fn candidate_from_words_class(
// class v5 (docs/design/class-v5-stored-state.md) draws under the same rule: its state flag is set aside here too
// class v6 lane 1: the index fold and the re-weight table are set aside too (the address path and the table are not
// the shape; a +fold or +rw program draws its sources under the same rule)
- let source_rule_v4 = matches!(class.shadow, Some(ShadowClass { instrs: V4_SHADOW_INSTRS, .. }))
- && LoadClass { era: None, shadow: None, state: false, fold: false, rw: 0, ..class } == LoadClass { shadow: None, ..V4_CLASS };
+ // class v6 family-gate harness (8 October 2026): the same predicate as the acceptance's, generalised to the
+ // family's shapes only when IGNEUM_FAMILY_GATE is set (never on a chain path; see accept::is_family_shape)
+ let source_rule_v4 = crate::accept::is_class_v4_shape(&class);
// the op table and the roll's range: the plain table at 75 for every class without the re-weight flag
let (weights, weights_sum) = class.nonload_weights();
let mut fresh = [false; 8];

View file

@ -0,0 +1,26 @@
binary 77835525ede65a55 commit v6 test family_gate_era_census from 0 seeds 1500 threads 16 start 2026-10-08T17:09:32Z
lease: holding 16 pool cores (40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55, waited 0 s, class measure, mem 8 GB): family gate: family_gate_era_census ktable0-w1 seeds 0+1500
running 1 test
test accept::tests::family_gate_era_census ... family_gate_era_census: 100 eras in 33 s
family_gate_era_census: 200 eras in 59 s
family_gate_era_census: 300 eras in 86 s
family_gate_era_census: 400 eras in 113 s
family_gate_era_census: 500 eras in 139 s
family_gate_era_census: 600 eras in 165 s
family_gate_era_census: 700 eras in 192 s
family_gate_era_census: 800 eras in 219 s
family_gate_era_census: 900 eras in 247 s
family_gate_era_census: 1000 eras in 276 s
family_gate_era_census: 1100 eras in 303 s
family_gate_era_census: 1200 eras in 331 s
family_gate_era_census: 1300 eras in 360 s
family_gate_era_census: 1400 eras in 388 s
family_gate_era_census: 1500 eras in 418 s
family_gate_era_census: 1500 eras 0..1500 in 418 s on 16 threads -> /srv/builds/_adv-family-gate/logs/ktable0-w1-family_gate_era_census-0-1500.tsv
ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 80 filtered out; finished in 418.43s
lease: released 16 pool cores after 418 s, exit 0
end 2026-10-08T17:16:30Z rc 0

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,26 @@
binary 77835525ede65a55 commit v6 test family_gate_era_census from 0 seeds 1500 threads 16 start 2026-10-08T17:09:32Z
lease: holding 16 pool cores (8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23, waited 0 s, class measure, mem 8 GB): family gate: family_gate_era_census ktable0-w4 seeds 0+1500
running 1 test
test accept::tests::family_gate_era_census ... family_gate_era_census: 100 eras in 50 s
family_gate_era_census: 200 eras in 93 s
family_gate_era_census: 300 eras in 135 s
family_gate_era_census: 400 eras in 176 s
family_gate_era_census: 500 eras in 213 s
family_gate_era_census: 600 eras in 249 s
family_gate_era_census: 700 eras in 284 s
family_gate_era_census: 800 eras in 316 s
family_gate_era_census: 900 eras in 347 s
family_gate_era_census: 1000 eras in 382 s
family_gate_era_census: 1100 eras in 418 s
family_gate_era_census: 1200 eras in 463 s
family_gate_era_census: 1300 eras in 505 s
family_gate_era_census: 1400 eras in 549 s
family_gate_era_census: 1500 eras in 592 s
family_gate_era_census: 1500 eras 0..1500 in 592 s on 16 threads -> /srv/builds/_adv-family-gate/logs/ktable0-w4-family_gate_era_census-0-1500.tsv
ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 80 filtered out; finished in 592.40s
lease: released 16 pool cores after 592 s, exit 0
end 2026-10-08T17:19:24Z rc 0

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -18,12 +18,12 @@ Built 8 October 2026, 18:3x BST (the founder's order: no feature left out, no st
| 12 | D1. A frozen, reproducible baseline (p. 8 to 9) | Mixed FP32 branch: KILL 8 Oct 16:3x. Deterministic FP32 costs the cards 15 to 26 percent energy per hash against a 10 percent budget (four fifths of it the integer masking that keeps the FP unit deterministic) and the chip's edge grows to 3.0x to 3.2x because that masking is ARX work it pays at the floor. Document: docs/analysis/class-v6/mixed-fp32.md. Regression control, never resurrected. | coordinator (ad8809ecfd00fdd51) | 23:30 tonight | PASSED (checked in the plan): in flight | |
| 13 | D1. A frozen, reproducible baseline (p. 8 to 9) | Mining and proving measured together on the final configuration, not combined on paper. | fleet lane (ac055d60427caab99) | tomorrow 12:00 on the 2.0 devnet | open: the coexist-rows (3060, 4060) landed 11653ece; the final configuration not yet | docs/analysis/class-v6/coexist-rows.md |
| 14 | D1. A frozen, reproducible baseline (p. 8 to 9) | Wall power alongside device telemetry; accepted work, rejected work, compile time, memory use, sustained thermals. | hash lane (a690540514aa453d7) | tomorrow 12:00 | open: compile time and host power added to the PC 1 row columns tonight; wall power and sustained thermals owed | |
| 15 | D1. A frozen, reproducible baseline (p. 8 to 9) | Reference GPU population published: several vendors, memory sizes, generations, used cards (5090, 5080, 4090, 3090, 9070 XT, RX 7600 8 GB, Arc, Apple). | research lane (ad6a2bd47d4a46105) | tomorrow 11:00 (the missed pin 1) | open: the tiers file holds 31 classes; the served table owed | app/igneum-app/tiers |
| 16 | D1. A frozen, reproducible baseline (p. 8 to 9) | Two tests per class: existing owner (power, wear, fees, alternative use) and new entrant (purchase, operating, resale). | research lane (ad6a2bd47d4a46105) | tomorrow 11:00 | open | |
| 17 | D1. A frozen, reproducible baseline (p. 8 to 9) | Central measure served: cost per accepted unit of work = (annualised hardware + power + hosting, failures, fees) / annual accepted work. | research lane (ad6a2bd47d4a46105) | tomorrow 11:00; the site lane serves by 12:00 | open | |
| 15 | D1. A frozen, reproducible baseline (p. 8 to 9) | Reference GPU population published: several vendors, memory sizes, generations, used cards (5090, 5080, 4090, 3090, 9070 XT, RX 7600 8 GB, Arc, Apple). | research lane (ad6a2bd47d4a46105) | the 21:00 landing | landed: reference-population.md (2db4fe34), every cell labelled; the 7600 grid, the Arc watts and the Ada and Ampere knees owed | docs/analysis/class-v6/reference-population.md |
| 16 | D1. A frozen, reproducible baseline (p. 8 to 9) | Two tests per class: existing owner (power, wear, fees, alternative use) and new entrant (purchase, operating, resale). | research lane (ad6a2bd47d4a46105) | the 21:00 landing | landed: in reference-population.md at 0.06 / 0.12 / 0.25 per kWh | docs/analysis/class-v6/reference-population.md |
| 17 | D1. A frozen, reproducible baseline (p. 8 to 9) | Central measure served: cost per accepted unit of work = (annualised hardware + power + hosting, failures, fees) / annual accepted work. | research lane (ad6a2bd47d4a46105) | 12:00 tomorrow | in flight: in reference-population.md; the site serves by 12:00 tomorrow | docs/analysis/class-v6/reference-population.md |
| 18 | D2. Two architectural experiments, not twenty knobs (p. 10) | (a) Reorganise existing work for unavoidable live state and resource coupling, counts held constant. RESULT 8 Oct 17:25: KILL as a class. Only the window width reaches the chip (+1.2 pJ per lane-op at N5); rearranging the dependency graph of the same ops moves neither side. Document: docs/analysis/class-v6/connected-state.md. The generator variant and liveness tool stay behind a flag. | adversary lane (a1a9876a88f5a72fc) | landed | PASSED (as a KILL): connected-state.md on master 3919d430; the class fails its own gate | docs/analysis/class-v6/connected-state.md |
| 19 | D2. Two architectural experiments, not twenty knobs (p. 10) | (b) Attack memory sharing, recomputation and data-local execution against v6 (ProgPoW review threat: dataset split across processors, compute moved to the data). Price the cheapest combination of moving state, moving data, recomputing and local resources, not the expected architecture. | adversary lane (a1a9876a88f5a72fc) | 21:30 tonight | in flight: the first D2(b) row at 17:3x (the stored-half hybrid), the data-local and dataset-comparison additions in the 21:30 delta | docs/analysis/class-v6/multi-family-adversary.md |
| 20 | D2. Two architectural experiments, not twenty knobs (p. 10) | Cumulative memory complexity and bandwidth hardness mapped onto the actual evaluation across many hashes (shared datasets, partial caches, recomputation, multiple engines amortising setup). Which trade-offs are bounded, which rest on physical-design experiments. | adversary lane (a1a9876a88f5a72fc) | tomorrow 12:00 (the missed pin 2, the formal memory model) | open | |
| 20 | D2. Two architectural experiments, not twenty knobs (p. 10) | Cumulative memory complexity and bandwidth hardness mapped onto the actual evaluation across many hashes (shared datasets, partial caches, recomputation, multiple engines amortising setup). Which trade-offs are bounded, which rest on physical-design experiments. | adversary lane (a1a9876a88f5a72fc) | 12:00 tomorrow | in flight: section 16 of multi-family-adversary.md (3a8874fef) | docs/analysis/class-v6/multi-family-adversary.md |
| 21 | D2. Two architectural experiments, not twenty knobs (p. 10) | Selective participation: distribution of the specialist's advantage across programs and epochs, not the mean; downtime, difficulty adjustment, re-entry included. | adversary lane (a1a9876a88f5a72fc) | landed | landed: 9 percent spread across 2,000 era draws, the best-half specialist 2 percent for half its revenue (the D2(b) row) | docs/analysis/class-v6/multi-family-adversary.md |
| 22 | D2. Two architectural experiments, not twenty knobs (p. 10) | Cryptographic review of the template, nonce, expensive work and result binding: no expensive intermediate reused across cheap winning attempts. | pool design seat (a3832b1c3b274b310) | tomorrow 15:00 (the missed pin 3, with the hash lane) | open | |
| 23 | D3. A programmable adversary allowed to survive (p. 12) | Whole-system cost minimised across every published family, free to change lane count, register implementation, instruction storage, memory technology, scheduling and support hardware. | adversary lane (a1a9876a88f5a72fc) | 21:30 tonight | in flight: the placed 18-family core 9.36 pJ, k 0.64 same-node; the 32-lane rows 18:30 and 21:00 | docs/analysis/class-v6/multi-family-adversary.md |
@ -88,26 +88,27 @@ Built 8 October 2026, 18:3x BST (the founder's order: no feature left out, no st
| 82 | Proving correctness (p. 16) (p. 16) | Boundary served: a proof of execution is not a proof of authenticated consensus inputs, canonical history or data availability. | site lane (a846fd66b5403e35a) | tonight 18:05 | in flight: the sentence in the spec and the ledger; on the litepaper with the landing | |
| 83 | Consensus and failure behaviour (p. 19) (p. 19) | D5 scenario rows: prolonged partition (no conflicting final histories inside the fault assumptions; liveness loss explicit); authority-set transition (verifiable continuity from the last certified history; a timeout alone is not evidence missing voters are gone); signing stops while mining continues (defined checkpoint, weight and recovery; no contradictory certificates); old voting keys compromised (its own analysis, distinct from new hashrate); finality unavailable at a seed boundary (a seed and mining path that does not depend on an unavailable certificate); partitions reconnect (deterministic recovery, no quiet reversal of an irreversible label). | finality lane (aca0f5ed924a2a99b) | landed (the table); the runs days two and three | landed: the six-scenario table mapped to the evidence with gaps and clocks in the D5 block | docs/plans/igneum-2.0.md D5 |
| 84 | Consensus and failure behaviour (p. 19) (p. 19) | The real integration tested together: ordering, finality, proof queues, voter tables, seed transitions. | fast-time lane (a8be71a0db962911c) | the morning's first cut | in flight: the 617cb441 run holds five switches in one network (the ladder, the v5 crossing, the v6 floor, the restart step, the cold restart); the proof queues and seed transitions not in the same network, owed | v5-fasttime 92bf6a7f |
| 85 | Consensus and failure behaviour (p. 19) (p. 19) | User-facing rule everywhere: included, executed, proven and finalised are four states; a safe pause is shown as a pause. | node lane (a283f5f0d364ceef0) | 23:00 tonight (the four-state RPC); the interfaces v2.0.1 and the site's 20:00 sweep | in flight: section 9 landed; igneum_getTransactionStatus gains state and paused by 23:00 | |
| 85 | Consensus and failure behaviour (p. 19) (p. 19) | User-facing rule everywhere: included, executed, proven and finalised are four states; a safe pause is shown as a pause. | node lane (a283f5f0d364ceef0) | v2.0.1 (the cut tomorrow) | landed on the node: igneum_getTransactionStatus carries state and paused on release-2.0.0-node 9fc9f42a (18:02); the interfaces the site's sweep and v2.0.1 | |
| 86 | User verification (p. 20) (p. 20) | Light wallet: starting point, voter weights and authority changes authenticated. Execution proof: the permitted proof verified and its inputs authenticated, never an aggregator statement in its place. Payment receipt: proves the transfer with asset, recipient and amount, or is labelled "transaction-inclusion receipt". Oracle: deployer-installed trust anchors removed or disclosed, unchecked signatures named. Data availability: how state is obtained and reconstructed, explained. | reference-apps lane (a2060899d2a27d31c) | landed | landed: the labels and the boundary on /light, /receipt and /oracle (8e947c3b); the oracle redeployed for chain id 4465 | site/lc |
| 87 | Operational independence (p. 21) (p. 21) | One machine-readable release manifest: network identity, source commits, mining class, dataset parameters, finality rule, program and verifier identities, activation state, fee schedule. Status pages generate from it; historical records are labelled. | shipper (ae892a8b0f78fe31c) | 19:30 tonight (with the site lane); the node side landed | in flight: igneum_getManifest on d5981514; /release.json served by the site lane | |
| 87 | Operational independence (p. 21) (p. 21) | One machine-readable release manifest: network identity, source commits, mining class, dataset parameters, finality rule, program and verifier identities, activation state, fee schedule. Status pages generate from it; historical records are labelled. | shipper (ae892a8b0f78fe31c) | 19:30 tonight (the edge) | landed on the node: igneum_getManifest on d5981514; /release.json served by the site lane (3511bbca at the edge, 70f8c871 landed) | site/release-manifest.json |
| 88 | Operational independence (p. 21) (p. 21) | Software release: reproducible builds, pinned source and binaries, explicit operator acceptance, a signing-key incident procedure. A fleet that auto-accepts a release key is operationally centralised. | shipper (ae892a8b0f78fe31c) | tomorrow 15:00 (the missed pin 9, with the build-server lane) | open: the signing-key incident procedure owed | |
| 89 | Operational independence (p. 21) (p. 21) | Public infrastructure, mining, proving and aggregation continue without founder services and without unpublished files or hidden configuration. | node lane (a283f5f0d364ceef0) | days two and three (D5) | open | |
| 90 | Programme order, mainnet prerequisites, funding (p. 24, 26) (p. 24, 26) | Spend order: proof enforcement and finality boundaries first while closing the v6 evidence packet; hardware research and one narrow paid pilot in parallel; broad ecosystem expansion waits until the core path is secure, reproducible and useful. | coordinator (ad8809ecfd00fdd51) | standing | landed: applied in the lease classes (proof enforcement and finality outrank research on build-2 and build-4) and the map | |
| 91 | Programme order, mainnet prerequisites, funding (p. 24, 26) (p. 24, 26) | Mainnet needs: complete proof enforcement, a resolved finality and recovery model, operator-control tests, the compatible-application test suite, a funded maintenance plan (engineering, audits, infrastructure, incident response), committed funding distinguished from adoption-dependent income; fair launch is a principle, not a funding strategy. | coordinator (ad8809ecfd00fdd51) | tomorrow 12:00 | open: the prerequisites checklist in the record; the maintenance plan the founder's (the missed pin 10) | |
| 92 | Programme order, mainnet prerequisites, funding (p. 24, 26) (p. 24, 26) | Acceptance scorecard served as a checklist with its "do not substitute" column (plan p. 25), never as a completion dashboard. | site lane (a846fd66b5403e35a) | tonight 21:00 (/scorecard) | in flight | |
| 93 | Programme order, mainnet prerequisites, funding (p. 24, 26) (p. 24, 26) | Brand kit stands: Unbounded, IBM Plex Sans, IBM Plex Mono; obsidian, ember, graphite, bone, molten. | site lane (a846fd66b5403e35a) | landed | landed: unchanged on every page | |
| 94 | Pins the finish line missed (checked against the 37 pages, 18:1x BST) (p. 8 to 24) | Served reference GPU population and the cost-per-accepted-unit table, existing owner and new entrant per class (p. 8). Owner: research lane with the site lane. | research lane (ad6a2bd47d4a46105) | tomorrow 11:00; the site lane serves by 12:00 | open | |
| 94 | Pins the finish line missed (checked against the 37 pages, 18:1x BST) (p. 8 to 24) | Served reference GPU population and the cost-per-accepted-unit table, existing owner and new entrant per class (p. 8). Owner: research lane with the site lane. | research lane (ad6a2bd47d4a46105) | the 21:00 landing; served 12:00 tomorrow | landed: reference-population.md at 2db4fe34 on counter-asic-4 (18:03), in the 21:00 landing; the site serves by 12:00 tomorrow | docs/analysis/class-v6/reference-population.md |
| 95 | Pins the finish line missed (checked against the 37 pages, 18:1x BST) (p. 8 to 24) | The formal memory model: the evaluation mapped into capacity, bandwidth, energy and amortisation terms, what is bounded and what rests on physical design (p. 10). Owner: adversary lane. | adversary lane (a1a9876a88f5a72fc) | tomorrow 12:00 | open | |
| 96 | Pins the finish line missed (checked against the 37 pages, 18:1x BST) (p. 8 to 24) | Cryptographic review of the template, nonce, work and result binding: no expensive intermediate reused across cheap winning attempts (p. 10). Owner: the attack seat, with the hash lane. | pool design seat (a3832b1c3b274b310) | tomorrow 15:00 (the missed pin 3, with the hash lane) | open | |
| 97 | Pins the finish line missed (checked against the 37 pages, 18:1x BST) (p. 8 to 24) | The disclosure prize live with terms rewarding a better adversary or a reproduced shortcut (p. 12). Owner: site lane, terms from the coordinator. | site lane (a846fd66b5403e35a) | tomorrow 12:00 (the terms from the coordinator by 10:00) | open | |
| 98 | Pins the finish line missed (checked against the 37 pages, 18:1x BST) (p. 8 to 24) | Home-connection against datacentre accepted-work penalty, measured on the 2.0 devnet (p. 14). Owner: fleet lane with a home box (PC 1 or PC 2). | fleet lane (ac055d60427caab99) | tomorrow 15:00 (PC 1 or PC 2 the home box) | open | |
| 99 | Pins the finish line missed (checked against the 37 pages, 18:1x BST) (p. 8 to 24) | Published optimisation work, compiler settings and safe tuning logic (p. 14). Owner: hash lane, served by the site lane. | hash lane (a690540514aa453d7) | tomorrow 15:00 (the missed pin 6; the site lane serves) | open: the tuning rows exist (the knee rule, the AMD knob); the published form owed | |
| 100 | Pins the finish line missed (checked against the 37 pages, 18:1x BST) (p. 8 to 24) | Negative test six, proof side: derivation inside the aggregator guest (P22 stage 3) (p. 16). Owner: enforced-proving lane. | enforced-proving lane (a6e8f84588b809d62) | tomorrow 18:00 (P22 stage 3) | open: the native veto team-tested; the derivation inside the aggregator guest owed | |
| 100 | Pins the finish line missed (checked against the 37 pages, 18:1x BST) (p. 8 to 24) | Negative test six, proof side: derivation inside the aggregator guest (P22 stage 3) (p. 16). Owner: enforced-proving lane. | enforced-proving lane (a6e8f84588b809d62) | 18:00 tomorrow | open: the row on enforced-proving 610f07c01 landing; the native-veto half team-tested (421bb852) | docs/spec/proving-enforcement.md |
| 101 | Pins the finish line missed (checked against the 37 pages, 18:1x BST) (p. 8 to 24) | The proving benchmark served with typical and tail latency per stage (p. 17). Owner: fleet lane's record, served by the site lane. | fleet lane (ac055d60427caab99) | tomorrow 15:00 (the site lane serves) | open: proving-pipeline-2026-10-08.md landed 8c5da92f as the record | docs/analysis/proving-pipeline-2026-10-08.md |
| 102 | Pins the finish line missed (checked against the 37 pages, 18:1x BST) (p. 8 to 24) | Reproducible builds with a served recipe, and a signing-key incident procedure (p. 21). Owner: shipper with the build-server lane. | shipper (ae892a8b0f78fe31c) | tomorrow 15:00 (with the build-server lane) | open | |
| 103 | Pins the finish line missed (checked against the 37 pages, 18:1x BST) (p. 8 to 24) | The funded maintenance plan and the committed-funding line (p. 24). Owner: the founder. | main / the founder | the founder's | open | |
| 104 | Architecture and product (review 2) (p. 15 to 17) | Key succession: a succession field in the object (the pinned program ids and verifier versions with an activation height and a window; below the height the old pair, across the window either pair, from its end the new pair; the payment rule on the same floor; the manifest carrying both pairs with the height; never on every network; the daemon refusing to start if it embeds neither pair the object names), designed and tested on the fast-time harness before any devnet carries it, so a re-pin is a scheduled transition and never a genesis (main's order, 18:1x BST) | enforced-proving lane (a6e8f84588b809d62) with the node lane (a283f5f0d364ceef0) | the design doc tonight; the node branch with its known-failed tests and the fast-time case by 18:00 tomorrow | in flight: docs/design/key-succession.md tonight; the re-pinned guest (afd952e89, shard 0x282dcfce, aggregator 0x3fd721e8) waits on it | docs/design/key-succession.md |
## Coverage at 18:3x BST: 103 pins, 103 owned, open 35, in flight 34, landed 29, passed 5.
## Coverage at 18:3x BST: 104 pins, 104 owned, open 30, in flight 35, landed 34, passed 5.
Pass conditions are the plan's own, per section: D1 an independent operator reproduces the baseline from the served kit alone; D2 the candidate improves against re-optimised adversaries within the preset limits, else published as a failure; D3 the best supported advantage inside the chosen envelope with uncertainty published; D4 the model names credible conditions for sustained commodity participation and where it fails; D5 specified behaviour with no emergency change and no privileged intervention; the launch requirements live; the claim ladder earned rung by rung.

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

View file

@ -4,9 +4,9 @@
// selected-chain headers from the previous locked checkpoint to this one. Read from what tools/observer wrote to
// Neon (table live_certificates, or fintest_live_certificates for the test network).
//
// ?source=live Devnet 3 (default: the dn3_ tables, or LIVE_TABLE_PREFIX; falls back to the test network while the chain has no lock yet)
// ?source=dn3 the same as live (the Devnet 3 name, for callers that want to say so)
// ?source=test the finality test network's tables (fintest_live_certificates)
// ?source=live the live chain (default: the dn4_ tables, or LIVE_TABLE_PREFIX); certificate:null with a reason before its first lock, never the fixture
// ?source=dn4 the same as live (the Igneum 2.0 devnet's name, for callers that want to say so)
// ?source=test the finality test network's fixture (fintest_live_certificates), only with the ops key in x-igneum-ops-key; 404 otherwise
// ?index=N one certified checkpoint by index instead of the newest (the explorer's per-block re-check, 8 October 2026)
//
// Zero dependencies: Neon's HTTP SQL endpoint over Node's built-in fetch, like live.mjs.
@ -31,7 +31,13 @@ function neon() {
const num = v => (v === null || v === undefined ? null : Number(v));
const tablePrefix = () => (process.env.LIVE_TABLE_PREFIX === undefined ? 'dn3_' : process.env.LIVE_TABLE_PREFIX).replace(/[^a-z0-9_]/gi, '');
const tablePrefix = () => (process.env.LIVE_TABLE_PREFIX === undefined ? 'dn4_' : process.env.LIVE_TABLE_PREFIX).replace(/[^a-z0-9_]/gi, '');
// the live chain's name from its table prefix (dn4_ is igneum-devnet-4); LIVE_NETWORK overrides
const liveNetwork = () => process.env.LIVE_NETWORK || (/^dn(\d+)_$/.test(tablePrefix()) ? `igneum-devnet-${tablePrefix().slice(2, -1)}` : 'igneum-devnet-4');
// the finality test network's fixture (fintest_live_certificates) is served only to a request carrying the ops key (main, 8 Oct 2026 18:4x:
// a fixture chain answered on the public 2.0 site as source test, network igneum-devnet-7); with no IGNEUM_OPS_KEY set it is never served
const opsOk = req => { const k = process.env.IGNEUM_OPS_KEY || ''; const h = String((req.headers && req.headers['x-igneum-ops-key']) || ''); return k.length > 0 && h === k; };
const noCertificate = source => ({ source, network: liveNetwork(), certificate: null, reason: 'no finality certificate yet; the first lock is expected about 23:00 BST' });
async function latest(sql, table, index = null) {
// A table the observer has not created yet (the live chain before the cut-over) reads as "no certificate"
const rows = await (index === null ? sql(`SELECT * FROM ${table} ORDER BY index DESC LIMIT 1`) : sql(`SELECT * FROM ${table} WHERE index = $1 LIMIT 1`, [index])).catch(() => []);
@ -95,11 +101,12 @@ export async function earliestCheckpointAbove(sql, number, chainNumberOf, table
/** The latest certified checkpoint of `want` ('live', 'test' or 'dn3') through `sql`, shaped for the verifier, or null
* (the reference apps' read service calls this off Vercel: 'dn3' names the dn3_ tables whatever LIVE_TABLE_PREFIX says). */
export async function readCheckpoint(sql, want = 'live') {
let row = null, source = null;
if (want === 'dn3' || want === 'dn4') { row = await latest(sql, `${want}_live_certificates`); if (row) source = want; }
if (!row && want !== 'test' && want !== 'dn3' && want !== 'dn4') { row = await latest(sql, `${tablePrefix()}live_certificates`); if (row) source = 'live'; }
if (!row && want !== 'dn3' && want !== 'dn4') { row = await latest(sql, 'fintest_live_certificates'); if (row) source = 'test'; }
return row ? shape(row, source) : null;
// 'live', 'dn4' (and the retired 'dn3') read that chain's tables and never fall back to the test fixture; 'test' is the fixture, for the ops
// caller only (the handler gates it; this function is called off Vercel by the reference apps' read service, which names its chain)
if (want === 'test') { const row = await latest(sql, 'fintest_live_certificates'); return row ? shape(row, 'test') : null; }
const table = (want === 'dn3' || want === 'dn4') ? `${want}_live_certificates` : `${tablePrefix()}live_certificates`;
const row = await latest(sql, table);
return row ? shape(row, want === 'dn3' || want === 'dn4' ? want : 'live') : null;
}
export { neon };
@ -114,12 +121,20 @@ export default async function handler(req, res) {
const sql = neon();
const want = String((req.query && req.query.source) || 'live');
const idx = req.query && req.query.index !== undefined && /^\d{1,12}$/.test(String(req.query.index)) ? Number(req.query.index) : null;
let row = null, source = null;
if (want !== 'test') { row = await latest(sql, `${tablePrefix()}live_certificates`, idx); if (row) source = 'live'; }
if (!row && idx === null) { row = await latest(sql, 'fintest_live_certificates'); if (row) source = 'test'; }
if (!row) {
if (want === 'test') {
res.setHeader('Cache-Control', 'no-store');
return res.status(404).json({ ok: false, error: 'no certified checkpoint stored yet' });
if (!opsOk(req)) return res.status(404).json({ ok: false, error: 'no such source' });
const row = await latest(sql, 'fintest_live_certificates', idx);
if (!row) return res.status(404).json({ ok: false, error: 'no certified checkpoint stored yet' });
return res.status(200).json({ ok: true, now: new Date().toISOString(), ...shape(row, 'test') });
}
// the public answer: the live chain's tables (source live; dn4 names them too), never the test fixture
const source = want === 'dn4' ? 'dn4' : 'live';
const row = await latest(sql, `${tablePrefix()}live_certificates`, idx);
if (!row) {
res.setHeader('Cache-Control', 'public, max-age=5');
if (idx !== null) return res.status(404).json({ ok: false, network: liveNetwork(), error: `no certified checkpoint at index ${idx} yet` });
return res.status(200).json({ ok: true, now: new Date().toISOString(), ...noCertificate(source) });
}
// the hand merge of 12:22 BST (a0b62cf7) returned `...cp` with no cp bound: every caller read "cp is not defined" (8 Oct 2026, build-server lane)
return res.status(200).json({ ok: true, now: new Date().toISOString(), ...shape(row, source) });

View file

@ -22,7 +22,18 @@ import { neon, num, tablePrefix, chainIdOf } from './_neon.mjs';
import { classify } from '../lib/explorer.mjs';
const STALE_AFTER_S = 30;
export const NETWORK_LABEL = 'Devnet 3';
export const NETWORK_LABEL = 'the Igneum devnet';
/** The served name of a network from the node's own name (the coordinator's ruling of 8 October 2026, 18:09 UK): igneum-devnet-4 reads
* "the Igneum 2.0 devnet" (the plan's wording, as the app, the site's badge and the manifest say it); any other suffix reads
* "the Igneum devnet N" or "the Igneum testnet N", so a future chain is never mislabelled. The API's network field stays the raw name. */
export const NETWORK_NAMES = { 'igneum-devnet-4': 'the Igneum 2.0 devnet' };
export function labelOf(network) {
const n = String(network || '');
if (NETWORK_NAMES[n]) return NETWORK_NAMES[n];
const m = n.match(/^igneum-(devnet|testnet|mainnet)(?:-(\d+))?$/);
if (!m) return NETWORK_LABEL;
return m[1] === 'mainnet' ? 'Igneum' : `the Igneum ${m[1]}${m[2] ? ' ' + m[2] : ''}`;
}
const ROW = `hash, number, blue_score, daa_score, timestamp_ms, parents, parent_hashes, is_chain_block, vote_key_hash, miner_address, evm_miner, engine,
tx_count, proof_records, subsidy_sompi, paid_sompi, selected_parent, color, received_at`;
// the EVM's own hash for the block's number (the indexer's explorer_blocks): a block the observer called a chain block that a tip
@ -101,7 +112,7 @@ export function createHandler({ env = process.env, sql, evm = evmCall } = {}) {
// the highest number the indexer has ever read, less three: a node re-syncing from a snapshot reports a low tip for a while
// (10:21 UTC on 8 October 2026: the observer node restarted and its tip read 1,981 against 27,145 indexed) and must not unsettle the table
settledBelow = head[0].indexed_max === null || head[0].indexed_max === undefined ? -1 : Number(head[0].indexed_max) - 3;
const base = { ok: true, now: new Date(now).toISOString(), network: s ? s.network : null, network_label: NETWORK_LABEL, chain_id: ix && ix.chain_id ? num(ix.chain_id) : (s ? chainIdOf(s.network) : null), stale: updated === null || (now - updated) / 1000 > STALE_AFTER_S, evm_rpc_configured: !!EVM, indexer };
const base = { ok: true, now: new Date(now).toISOString(), network: s ? s.network : null, network_label: labelOf(s ? s.network : null), chain_id: ix && ix.chain_id ? num(ix.chain_id) : (s ? chainIdOf(s.network) : null), stale: updated === null || (now - updated) / 1000 > STALE_AFTER_S, evm_rpc_configured: !!EVM, indexer };
const lockRow = await sql(`SELECT index, hash, blue_score, daa_score, fraction_total, voters, locked_at FROM ${T}live_checkpoints WHERE state = 'locked' ORDER BY index DESC LIMIT 1`).catch(() => []);
const lock = lockRow[0] ? { index: num(lockRow[0].index), hash: lockRow[0].hash, blue_score: num(lockRow[0].blue_score), daa: num(lockRow[0].daa_score), fraction_total: num(lockRow[0].fraction_total), voters: num(lockRow[0].voters), locked_at: lockRow[0].locked_at } : null;
base.lock = lock;

View file

@ -362,14 +362,14 @@
<div class="bar"><i></i><i></i><i></i><b>Prove</b></div>
<img src="/img/app-prove-dark.webp" width="2880" height="1800" class="img-dark" alt="The Prove page of the current build: the shard card, your card proved shard 3 of block 160,390 for 1.15 IGN; the Prove on this machine switch on, one sentence for the Apple M5 Max, which proves on the CPU slowly; one line of counts, and Details" loading="lazy" decoding="async">
<img src="/img/app-prove-light.webp" width="2880" height="1800" class="img-light" alt="The Prove page of the current build in light mode: the shard card and the Prove on this machine switch" loading="lazy" decoding="async">
<figcaption>The current build, rendered from the recorded state of the team&rsquo;s Apple M5 Max, 7 October 2026. Apple silicon proves on the CPU, slowly; a 16 GB NVIDIA card is the line for proving beside the miner at the proposed 5.5 GiB dataset (the costed alternative; the first step sits at 4 GiB, where the miner holds less and the rule is not yet measured), and an 8 GB or 12 GB card time-shares with the miner paused (measured 8 October 2026).</figcaption>
<figcaption>The current build, rendered from the recorded state of the team&rsquo;s Apple M5 Max, 7 October 2026. Apple silicon proves on the CPU, slowly; a 16 GB NVIDIA card is the line for proving beside the miner at the proposed 5.5 GiB dataset (the costed alternative; a first step at 4 GiB is proposed and pending decision, where the miner would hold less and the rule is not yet measured), and an 8 GB or 12 GB card time-shares with the miner paused (measured 8 October 2026).</figcaption>
</figure>
<div>
<div class="eyebrow">05 · Prove</div>
<h2>One switch. The same card proves.</h2>
<p>Every block is turned into a short proof, in pieces called shards. Your cards prove the shards the chain assigns to them and earn IGN for each one.</p>
<ul class="lines">
<li><strong>One sentence per card</strong>What it can prove and how. NVIDIA proves, AMD and Apple mine; a 16 GB card mines and proves together at the proposed 5.5 GiB dataset (the first step sits at 4 GiB, not yet measured), an 8 GB or 12 GB card time-shares with the miner paused; on the stock server a 24 GB card proves the full shard and a 32 GB card mines and proves at once.</li>
<li><strong>One sentence per card</strong>What it can prove and how. NVIDIA proves, AMD and Apple mine; a 16 GB card mines and proves together at the proposed 5.5 GiB dataset (a first step at 4 GiB is proposed, pending decision, not yet measured), an 8 GB or 12 GB card time-shares with the miner paused; on the stock server a 24 GB card proves the full shard and a 32 GB card mines and proves at once.</li>
<li><strong>One line of counts</strong>Assigned, proven, paid, IGN. Details holds the verifier, the program id and the segments.</li>
</ul>
</div>

File diff suppressed because one or more lines are too long

View file

@ -464,7 +464,7 @@ async function loadStats() {
$('st-rate').innerHTML = esc(s.blocks_per_s_10m === null ? 'n/a' : s.blocks_per_s_10m.toFixed(2)) + '<small>blocks/s</small>';
$('st-rate-s').textContent = `${s.chain_blocks_per_s_10m === null ? 'n/a' : s.chain_blocks_per_s_10m.toFixed(2)} chain blocks per second, ${int(s.miners_10m)} miners in 10 min`;
const f = s.finality;
if (f) { $('st-fin').innerHTML = f.latest_locked_index === null ? 'no lock' : `lock ${int(f.latest_locked_index)}`; $('st-fin-s').textContent = `${int(f.voters)} voters, ${int(f.total_weight)} blocks of weight${s.lock ? `, ${(s.lock.fraction_total * 100).toFixed(1)}% signed the newest lock` : ''}${f.active ? '' : ', finality paused'}`; }
if (f) { $('st-fin').innerHTML = f.latest_locked_index === null ? 'no lock' : `lock ${int(f.latest_locked_index)}`; $('st-fin-s').textContent = `${int(f.voters)} voters, ${int(f.total_weight)} blocks of weight${s.lock ? `, ${(s.lock.fraction_total * 100).toFixed(1)}% signed the newest lock` : ''}${f.active ? '' : (f.latest_locked_index === null ? `, finality not yet active: the first lock comes when the weight window fills at DAA 7,200${s.daa !== null && s.daa !== undefined ? ` (DAA ${int(s.daa)} now)` : ''}` : ', finality paused')}`; }
$('st-class').textContent = s.class ? s.class.class : 'v4';
const next = s.class && s.class.floors ? s.class.floors.find(x => x.daa > (s.daa || 0)) : null;
$('st-class-s').textContent = next ? `${next.class} from DAA ${int(next.daa)}` : (s.class && s.class.floors && s.class.floors.length ? `since DAA ${int(s.class.floors[s.class.floors.length - 1].daa)}` : 'the hash program family at the tip');

View file

@ -234,7 +234,8 @@
<div class="fold-body">
<div class="fold-coin"><span class="fold-glow"></span><svg viewBox="0 0 1024 1024" aria-hidden="true"><rect width="1024" height="1024" rx="230" fill="#0C0C0E"></rect><g transform="translate(166.95 166.95) scale(6.901)"><polygon points="50,4 74,34 67,58 80,54 61,96 39,96 20,54 33,58 26,34" fill="#F2541B"></polygon><polygon points="50,42 59,58 50,82 41,58" fill="#0C0C0E"></polygon></g></svg></div>
<h1>The card you own has <span class="accent">a new chain</span> to&nbsp;mine.</h1>
<p class="lead">A GPU-secured network for Ethereum-compatible applications and verifiable computation. One click installs the node, the miner and the prover, and the card starts.</p>
<p class="lead">A GPU-secured network for Ethereum-compatible applications and verifiable computation.</p>
<p class="fold-more">One click installs the node, the miner and the prover, and the card starts.</p>
<div class="fold-actions" id="fold-actions">
<a data-dl="miner-windows" href="https://dl.igneum.network/public/igneum-miner-windows.exe" data-os-button="windows" class="btn primary"><span class="osmark bare" data-os="windows" title="Windows"><svg viewBox="0 0 24 24" width="22" height="22" aria-hidden="true" focusable="false" fill="currentColor"><path d="M3 5.6l7.3-1v7.1H3zM11.4 4.4L21 3v8.7h-9.6zM3 12.3h7.3v7.1L3 18.4zM11.4 12.3H21V21l-9.6-1.4z"/></svg></span>Download for Windows<span class="meta" data-dl-meta="miner-windows">v0.3.26 · 63.8 MB</span></a>
<a data-dl="miner-mac" href="https://dl.igneum.network/public/igneum-miner-mac.dmg" data-os-button="mac" class="btn"><span class="osmark bare" data-os="mac" title="macOS"><svg viewBox="0 0 24 24" width="22" height="22" aria-hidden="true" focusable="false" fill="currentColor"><path d="M16.4 12.6c0-2.5 2-3.6 2.1-3.7-1.2-1.7-3-1.9-3.6-2-1.5-.2-3 .9-3.8.9-.8 0-2-.9-3.3-.8-1.7 0-3.2 1-4.1 2.5-1.8 3-.5 7.6 1.3 10.1.9 1.2 1.9 2.6 3.2 2.5 1.3 0 1.8-.8 3.3-.8 1.6 0 2 .8 3.3.8 1.4 0 2.3-1.2 3.1-2.5 1-1.4 1.4-2.8 1.4-2.9 0 0-2.7-1-2.9-4.1zM13.9 5.3c.7-.8 1.2-2 1-3.2-1 0-2.2.7-2.9 1.5-.6.7-1.2 1.9-1 3 1.1.1 2.2-.5 2.9-1.3z"/></svg></span>Download for macOS<span class="meta" data-dl-meta="miner-mac">v2.0.0 · 45.9 MB</span></a>
@ -257,11 +258,27 @@
<p class="home-quiet">The Igneum 2.0 devnet is the network today.</p>
</div>
</section>
<section class="section compact" id="mission">
<div class="container">
<div class="home-head"><h2>What Igneum 2.0 delivers, and why it matters.</h2><a href="/scorecard" class="text-link">The acceptance scorecard<svg class="icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M5 12h14M13 6l6 6-6 6"/></svg></a></div>
<div class="tbl"><table>
<thead><tr><th>What we deliver</th><th>Why it matters</th><th>Evidence today</th></tr></thead>
<tbody>
<tr><td>GPUs remain economically competitive against realistic specialised hardware</td><td>Miners can invest without depending on emergency algorithm changes to protect them.</td><td>MODELLED: the chip rows on <a href="/litepaper#chip-model">the litepaper</a> (<a href="https://git.igneum.network/igneum-network/igneum/src/branch/master/docs/analysis/class-v6/coexistence-model.md">coexistence-model.md</a>); no chip measured</td></tr>
<tr><td>A straightforward, efficient miner with reliable payouts and retained operator control</td><td>Ordinary owners can participate successfully, not just sophisticated mining businesses.</td><td>TEAM-REPORTED: the card rows on <a href="/miners">the bench table</a> and the facts page; pools as a pin, PENDING</td></tr>
<tr><td>Useful proofs that outside customers repeatedly purchase</td><td>You establish demand for a service, rather than relying on enthusiasm for the coin.</td><td>PENDING: no external job has been paid; the external market is designed, not built</td></tr>
<tr><td>Secure execution, finality and independently operated infrastructure</td><td>Developers and customers have a reason to trust the network with meaningful activity.</td><td>TEAM-REPORTED: proofs enforced in consensus from block zero (<a href="/evidence">the facts page</a>); the no-rescue exercise PENDING (<a href="https://git.igneum.network/igneum-network/igneum/src/branch/master/docs/spec/finality-guarantees.md">finality-guarantees.md</a>)</td></tr>
</tbody>
</table></div>
<p class="home-quiet">Proven execution is not finality. EVM compatibility is not Ethereum security. ZK is not privacy.</p>
<p class="home-quiet"><b>Where it stands today:</b> designed for GPU competitiveness, team-tested on the Igneum 2.0 devnet, nothing above that rung. Profitability and market position depend on demand, competition, liquidity and operating costs; protocol design alone cannot guarantee them.</p>
</div>
</section>
<section class="section compact" id="miner">
<div class="container">
<div class="home-head"><h2>What you get as a miner.</h2><a href="/miner" class="text-link">The miner, in full<svg class="icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M5 12h14M13 6l6 6-6 6"/></svg></a></div>
<div class="home-three rows">
<div class="home-row"><span class="n">01</span><div><b>The same card finds the block and proves it.</b><p>Both jobs pay. When you stop, the card still games.</p></div></div>
<div class="home-row"><span class="n">01</span><div><b>The same card finds the block and proves it.</b><p>Both jobs are paid by the chain today (TEAM-REPORTED on the facts page); what a card earns depends on the network and the price, which this site never states. When you stop, the card still games.</p></div></div>
<div class="home-row"><span class="n">02</span><div><b>A fair start.</b><p>Nobody holds a coin before block one. The protocol carries no fee. The one payment to the project is the Ember software’s optional 1% dev fee, like other GPU miners, off with one flag.</p></div></div>
<div class="home-row"><span class="n">03</span><div><b>Built for graphics cards.</b><p>Igneum remains competitive on accessible commodity GPUs even when specialised mining hardware is assumed to exist, remain compatible and seek profit; its security does not rely on identifying that hardware or retiring it through emergency changes. Class v6 adopts the 64-register window and retains it across every rotation. Current modelling estimates a 1.5x to 3.1x energy-efficiency advantage for the specialised designs assessed as complete machines against the GPU tier, from a board on commodity DRAM at 1.5x to an SRAM-store die at 3.1x (1.5x to 2.3x on the GPU's own node). The long-program and select-tree proposals were rejected. Economic resistance depends on development cost, deployment economics and productive hardware lifetime; family transitions receive an obsolescence benefit only where a loss of competitiveness is demonstrated; programmable multi-epoch designs are included in the assessment. MODELLED: the GPU side measured (the RTX 5090 at its lock, 8 October 2026), the chip’s core placed and routed, the rest of the machine modelled, no chip measured, hardware cost approximate within 2x; the coexistence model finds the DRAM board passes six of seven conditions, the hybrid coexists only in a growing chain at cheap GPU electricity, and the SRAM die fails four conditions at its reconciled machine cost; a 5090 locked at its knee pays 82 W for the class v4 shadow work (measured, 7 October 2026). <a href="/litepaper#chip-model">Every number with its label, the harness and the scoring rules.</a></p></div></div>
</div>

View file

@ -18,6 +18,30 @@ const short = h => { const s = String(h).replace(/^0x/, ''); return s.slice(0, 8
const clone = x => JSON.parse(JSON.stringify(x));
const flipHex = (s, at) => { const h = s.replace(/^0x/, ''); const i = Math.min(at, h.length - 1); const d = (parseInt(h[i], 16) ^ 1).toString(16); return (s.startsWith('0x') ? '0x' : '') + h.slice(0, i) + d + h.slice(i + 1); };
// The release manifest names the network every page verifies against (/release.json, network.id); a certificate or a proof
// naming any other network string is refused before any check runs (the coordinator's rule, 8 October 2026).
let manifestNetwork = null;
async function expectedNetwork() {
if (manifestNetwork) return manifestNetwork;
try { const m = await (await fetch('/release.json', { cache: 'no-store' })).json(); manifestNetwork = m && m.network && m.network.id; } catch { manifestNetwork = null; }
return manifestNetwork;
}
async function guardNetwork(cp, extra) {
const want = await expectedNetwork();
if (!want) throw new Error('the release manifest is unreadable, so the network to verify against is unknown; refused');
if (cp.chain_id !== want || (cp.network && cp.network !== want)) throw new Error(`the certificate names ${cp.chain_id}, the manifest names ${want}; refused`);
if (extra && extra.chain_id && extra.chain_id !== want) throw new Error(`the proof names ${extra.chain_id}, the manifest names ${want}; refused`);
return want;
}
async function lockLine() {
// "no certificate yet": the lock condition from the chain, live
try {
const r = await fetch(RPC, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'igneum_getProvingStatus', params: [] }) });
const st = (await r.json()).result;
return `no finality certificate yet on the Igneum 2.0 devnet; the first lock comes when the weight window fills at DAA ${Number(st.weightWindow).toLocaleString('en-GB')}, the chain reads DAA ${Number(st.tipDaa).toLocaleString('en-GB')} now`;
} catch { return 'no finality certificate yet on the Igneum 2.0 devnet; the first lock comes when the weight window fills at DAA 7,200'; }
}
const RPC = (q.get('rpc') || 'https://rpc.devnet.igneum.network');
async function getJson(url) {
const r = await fetch(url, { cache: 'no-store' });
let j = null; try { j = await r.json(); } catch { j = null; }
@ -39,11 +63,14 @@ const ago = ms => { const s = Math.max(0, Math.round((Date.now() - Number(ms)) /
export async function runLight(address) {
const out = $('[data-result]'); out.innerHTML = '';
setStatus('fetching the latest certified checkpoint…', 'busy');
const cp = await getJson(`${API}/checkpoint`);
let cp;
try { cp = await getJson(`${API}/checkpoint`); } catch (e) { if (/no finality certificate yet/.test(String(e.message))) throw new Error(await lockLine()); throw e; }
await guardNetwork(cp);
setStatus(`checkpoint ${cp.index} fetched; fetching the proof for ${short(address)}…`, 'busy');
const proof = await getJson(`${API}/balance?address=${address}&checkpoint=${cp.hash}&index=${cp.index}`);
// the service may answer with an earlier certificate (the first checkpoint above the carrier: the smallest proof); verified like any other
if (proof.checkpoint_certificate) { Object.assign(cp, proof.checkpoint_certificate); }
await guardNetwork(cp, proof);
setStatus('verifying in this tab…', 'busy');
await new Promise(r => setTimeout(r, 20));
// known-failed first: the same proof with one byte of the last account-proof node altered
@ -66,9 +93,12 @@ export async function runLight(address) {
export async function runReceipt(tx) {
const out = $('[data-result]'); out.innerHTML = '';
setStatus('fetching the latest certified checkpoint…', 'busy');
const cp = await getJson(`${API}/checkpoint`);
let cp;
try { cp = await getJson(`${API}/checkpoint`); } catch (e) { if (/no finality certificate yet/.test(String(e.message))) throw new Error(await lockLine()); throw e; }
await guardNetwork(cp);
setStatus(`checkpoint ${cp.index} fetched; fetching the inclusion proof…`, 'busy');
const r = await getJson(`${API}/receipt?tx=${tx}&checkpoint=${cp.hash}&index=${cp.index}`);
await guardNetwork(r.checkpoint.certificate || cp, r);
// the service may answer with an earlier certificate (the first checkpoint above the block: the smallest proof); it is verified like any other
const receipt = { format: 'igneum-receipt-v1', kind: 'transaction inclusion receipt', authenticates: 'inclusion of the signed transaction in a finalised block; the execution outcome is reported by the node, not authenticated', issued: new Date().toISOString(), ...r, checkpoint: { ...r.checkpoint, certificate: r.checkpoint.certificate || cp } };
delete receipt.ok; delete receipt.now;

View file

@ -270,6 +270,7 @@
<div class="row"><input id="address" name="address" inputmode="text" autocomplete="off" spellcheck="false" placeholder="0x…" pattern="^0x[0-9a-fA-F]{40}$"><button class="btn primary" type="submit">Verify in this tab</button></div>
<div class="vstatus" data-status role="status" aria-live="polite">The devnet, no value. Nothing here is a wallet that sends. It reads and proves.</div>
</form>
<p class="note" data-lock-line>Until the Igneum 2.0 devnet has its first finality certificate, this page answers: no finality certificate yet; the first lock comes when the weight window fills at DAA 7,200 (the live DAA is shown with the answer). A certificate naming any network other than the release manifest's (<a href="/release.json">/release.json</a>, network.id) is refused before any check runs.</p>
<div data-result></div>
</section>

View file

@ -468,10 +468,10 @@ body.all .pager{display:none}
<h3 id="chip-model">The chip model</h3>
<p><b>Igneum remains competitive on accessible commodity GPUs even when specialised mining hardware is assumed to exist, remain compatible and seek profit; its security does not rely on identifying that hardware or retiring it through emergency changes.</b> Class v6 adopts the 64-register window and retains it across every rotation. Current modelling estimates a 1.5x to 3.1x energy-efficiency advantage for the specialised designs assessed as complete machines against the GPU tier, from a board on commodity DRAM at 1.5x to an SRAM-store die at 3.1x (1.5x to 2.3x on the GPU's own node). The long-program and select-tree proposals were rejected. Economic resistance depends on development cost, deployment economics and productive hardware lifetime; family transitions receive an obsolescence benefit only where a loss of competitiveness is demonstrated; programmable multi-epoch designs are included in the assessment.</p>
<p><b>The labels.</b> MODELLED. The GPU side is measured (the RTX 5090 at its 1,300 MHz lock on class v4, 8 October 2026). The chip’s core is placed and routed on ASAP7 with SRAM macros and scaled on claimed node factors; the chip’s memory and the rest of the machine (controller, host share, PSU, VRM, cooling) are modelled; no chip is measured, and the chip’s hardware cost is approximate within 2x. Beside it the coexistence verdict at these energies: the DRAM board passes six of the model’s seven conditions, the hybrid coexists only in a growing chain at cheap GPU electricity, the SRAM die fails the cost, hardware, fleet and margin conditions at its reconciled machine cost, and only private supply in a growing network coexists. The k lane’s own placed row amends the figures if it differs. The GPU side is measured: an RTX 5080 at its 1,100 MHz core lock, 2.06 microjoules per hash, and an RTX 5090 at its 1,300 MHz lock, 2.33 microjoules per hash, both under class v4, on the project’s own rigs and rented pods, 8 October 2026; the card’s cost of the window is measured too (a rented RTX 5090 and RTX 4090 at stock, 8 October 2026: within 5 percent per load with the liveness chain, no register spill). The chip side is synthesised and claimed: the clock-gated sequencer core with the 64-register window on ASAP7, scaled to N3 on the foundry’s headline factors (k about 0.37 at N3 and 0.51 node for node for the base core, the gated window adding about 0.13 of k against an adversary with a flop register file; the window’s liveness measured at 61 of 64 values necessary, its cost to the card measured under 5 percent); the window’s k is synthesis-derived and not a lower bound, and the multi-family adversary lane’s first core (its state in a macro) reads the window’s defence as close to nothing, a disagreement between two models that the placed rows settle. The chip’s memory is modelled: the GDDR7 board of the chip model. The placed gated figure is expected near 2.6x to 3.1x a node ahead and 2.3x to 2.7x node for node (approximate) and is served when its row lands.</p>
<p><b>The dataset policy.</b> The dataset’s size is a one-off hardware ticket on specialised designs and a running energy tax on commodity cards: at the proposed 5.5 GiB a locked Blackwell card pays about 14 percent more energy per hash, over the 10 percent budget, while the board on commodity DRAM pays nothing and the SRAM designs pay a hardware cost that does not change their outcome; so the first step sits at 4 GiB, inside the budget, and later steps are taken only when the chain’s own state outgrows them, not on a calendar. (the card rows measured: an RTX 5090 at its 1,300 MHz lock on the project’s own rig and a rented 5090 at stock, 8 October 2026; the chip tickets modelled and approximate; later steps’ costs expected, not measured.)</p>
<p><b>The dataset policy (PROPOSED; decision pending).</b> First step proposed at 4 GiB inside the budget; the 5.5 GiB rows are the costed alternative; decision pending. The reasoning: The dataset’s size is a one-off hardware ticket on specialised designs and a running energy tax on commodity cards: at the proposed 5.5 GiB a locked Blackwell card pays about 14 percent more energy per hash, over the 10 percent budget, while the board on commodity DRAM pays nothing and the SRAM designs pay a hardware cost that does not change their outcome; so the first step sits at 4 GiB, inside the budget, and later steps are taken only when the chain’s own state outgrows them, not on a calendar. (the card rows measured: an RTX 5090 at its 1,300 MHz lock on the project’s own rig and a rented 5090 at stock, 8 October 2026; the chip tickets modelled and approximate; later steps’ costs expected, not measured.)</p>
<p>Three statements, kept separate. The baseline is the hash as it stands under the scoring rule; rotation is an optional improvement to that baseline, not the mechanism the claim rests on.</p>
<div class="tbl"><table><thead><tr><th>Statement</th><th>What it says</th><th>Label and date</th></tr></thead><tbody>
<tr><td>Energy resistance</td><td>Per machine against a locked RTX 5090, node for node and a node ahead: the DRAM board 1.5x (1.3x to 1.7x) and 1.8x; the board with SRAM holding the hottest half of the dataset 1.9x and 2.4x; the SRAM-store die 2.3x and 3.1x; per dollar of hardware at list price 3x to 6x (modelled on the placed full 18-family core, routed 8 October 2026). The honest tier moves to the next node with every GPU generation; a chip must tape out again. Whole machine per tier (synthesis with the SRAM band and node factors, claimed; placed rows to follow): the complete GDDR7 machine about 1.8x the RTX 5090 at its lock per joule node for node and 2.1x a node ahead; 1.6x and 1.9x the RTX 5080; 2.8x and 3.3x the Ada, Ampere and RX 9070 XT cohort; about 1.5x the Apple tier, reported, never headlined.</td><td>MODELLED: placed and routed core energies against the measured RTX 5090 lock row, 8 October 2026, no chip measured</td></tr>
<tr><td>Energy resistance</td><td>Per machine against a locked RTX 5090, node for node and a node ahead: the DRAM board 1.5x to 1.6x (1.3x to 1.7x) and 1.8x; the board with SRAM holding the hottest half of the dataset 1.9x to 2.1x and 2.4x to 2.6x; the SRAM-store die 2.3x and 3.1x; across the adversary’s lane-count choice the same-node bracket is 1.5x to 2.1x and a node ahead 1.8x to 2.6x, so about 2x on the same node stands at the bracket’s top; per dollar of hardware at list price 3x to 6x (modelled on the placed full 18-family core, routed 8 October 2026, and the 32-lane core’s synthesis). The honest tier moves to the next node with every GPU generation; a chip must tape out again. Whole machine per tier (synthesis with the SRAM band and node factors, claimed; placed rows to follow): the complete GDDR7 machine about 1.8x the RTX 5090 at its lock per joule node for node and 2.1x a node ahead; 1.6x and 1.9x the RTX 5080; 2.8x and 3.3x the Ada, Ampere and RX 9070 XT cohort; about 1.5x the Apple tier, reported, never headlined.</td><td>MODELLED: placed and routed core energies against the measured RTX 5090 lock row, 8 October 2026, no chip measured</td></tr>
<tr><td>Economic resistance</td><td>Whether a chip gets built depends on development cost, deployment economics and productive hardware lifetime. The first cut of the profitability surface: the price at which a project pays scales as the project cost over its share of the chain times its discounted life, and moves by under 5 percent with the per-joule edge; a fixed-lane chip under rotation needs 4x the price a programmable one needs. No threshold is the headline: the five-year coexistence model (Deliverable 4; its first run is <code>docs/analysis/class-v6/coexistence-model.md</code>, every row modelled) replaces any capex wall: the DRAM board passes six of its seven success conditions at a one to three year life; the SRAM die fails four conditions at its reconciled machine cost; the larger half of a chip's edge is capital cost per accepted hash, not joules. The result, as the model words it: A specialised supplier may earn a normal return; ordinary GPUs remain sufficiently close in total cost, widely obtainable and useful outside mining that new operators can still compete. On today's modelled rows that holds for the chip anyone can build, a stored-dataset board on commodity DRAM, at a productive life of one to three years: its cost per accepted unit of work sits within the range of the best GPU owner and entrant, it passes six of the seven conditions (a third of the network in boards now costs less than a year's revenue at the final hardware price), and a fleet of it holds a minority of the network with GPU entrants still setting the price. For the SRAM-store die the outcome turns on its hardware cost per unit of work, not its energy advantage: at the reconciled machine cost, set by the power train and the shadow core rather than the die, it fails the cost, hardware, fleet and margin conditions at every point of the band, a modest fleet holds about two fifths of a growing network and three fifths of a flat one on arrival and takes every flat or shrinking network within five years, and the only coexistence-shaped outcome is private supply in a growing network; what holds it is the investment decision, since its economics are project economics. A third design, a DRAM board with the hottest half of the dataset in on-board SRAM, sits between the two: it coexists only in a growing network at cheap GPU electricity and fails the cost conditions in a flat or shrinking one, and the dataset's size floor is a real lever on it where it was none on the SRAM die. What holds the die is the investment decision, not the hash; every chip figure here is modelled, not measured, and the chip's hardware cost per unit of work is approximate within 2x. The model holds under every market structure for the DRAM board (private supply, hardware sales, multiple suppliers, with no single supplier above a quarter of the network), and the SRAM die under none but private supply in a growing network; the thresholds live in the model's sensitivity workbook (<code>docs/analysis/class-v6/coexistence-workbook.md</code>), never on a page.</td><td>MODELLED, the coexistence model's first run, 8 October 2026</td></tr>
<tr><td>Response capability</td><td>Rotation is an optional improvement, not the mechanism. A passed rotation boundary proves the rotation works, not that hardware dies. The schedule: a new program every hour, a parameter era every week, a family epoch every 180 days, an emergency vote when miners call one. Rotation costs a chip versatility, not life: the family bank is firmware plus about 43 percent of core cells, and no transition carries an obsolescence credit (modelled).</td><td>measured per boundary, 8 October 2026; the family-bank cost modelled, 8 October 2026</td></tr>
</tbody></table></div>
@ -493,7 +493,7 @@ body.all .pager{display:none}
<tr><td>Light verification</td><td>256 MB cache on a CPU, milliseconds</td><td>256 MB cache on a CPU (512 MB from year 4), one warp under 10 ms, the gate. Measured 2.1 ms on one Apple M5 Max core for class v3 (3.4x class v2's 0.61 ms); a 2019-class core not yet</td></tr>
<tr><td>Changes over time</td><td>None. A fixed design, unchanged for seven years</td><td>A new program every hour, its memory pattern with it; era draws and reserved families on a schedule fixed at genesis. Nobody touches it</td></tr>
<tr><td>Seed grinding</td><td>Not applicable, the program comes from the hash input</td><td>Closed by a verifiable delay between seed and program</td></tr>
<tr><td>Useful work</td><td>None. Hashing only</td><td>NVIDIA cards prove: from 8 GB alone on the patched server, 16 GB and up beside the miner at the proposed 5.5 GiB dataset (the costed alternative; the first step sits at 4 GiB, where the miner holds less and the rule is not yet measured), 8 and 12 GB cards time-sharing with the miner paused, 24 GB on the stock server (measured on rented cards, 6 and 8 October 2026). Selling proofs to other chains is Designed, not built. AMD and Apple cards mine and do not prove; a prover for them lands when a zkVM ships one</td></tr>
<tr><td>Useful work</td><td>None. Hashing only</td><td>NVIDIA cards prove: from 8 GB alone on the patched server, 16 GB and up beside the miner at the proposed 5.5 GiB dataset (the costed alternative; a first step at 4 GiB is proposed and pending decision, where the miner would hold less and the rule is not yet measured), 8 and 12 GB cards time-sharing with the miner paused, 24 GB on the stock server (measured on rented cards, 6 and 8 October 2026). Selling proofs to other chains is Designed, not built. AMD and Apple cards mine and do not prove; a prover for them lands when a zkVM ships one</td></tr>
<tr><td>Track record</td><td>About seven years with one shipped chip, Bitmain’s Antminer X5 (September 2023), at 1.46x per joule over a desktop CPU; the one announced beyond it, Bitmain’s Antminer X9, was withdrawn in May 2026 with zero units; RandomX v2 released 25 March 2026, activation pending</td><td>Zero years. Every number above is measured and logged with the commands that produced it. The specification, reference hash, test vectors and simulators are public now (git.igneum.network/igneum-network/spec). The node, the miner and the wallet follow to the same host as the repository is published</td></tr>
</tbody>
</table></div>
@ -658,7 +658,7 @@ body.all .pager{display:none}
<p>The size of that third stream today, in numbers: all of Ethereum L1's proving is about USD 36 a day at the September 2026 tracker cost (USD 0.005 a block, 7,200 blocks a day; the tracker figure is a secondary source), against about USD 13,700 a day of Igneum's year-1 emission at USD 0.005 per IGN (31.688 IGN a block, 86,400 blocks a day; the price is an input, not a forecast). So external proving is a small second income at launch and the lottery pays the bills; for proving to become the main income the paid demand would have to grow about 1,000x in dollars (the chip and economy analysis of 6 October 2026, section 3.11; ledger E19).</p>
<p>The honest bear-market case rests on cost. A miner's card is already running and the power is often domestic, so Igneum miners' electricity cost in the proving market is close to power. The price they must charge is another matter: the price a prover must charge is the subsidy it forgoes while it proves, which falls as one over network hash, so the edge over data-centre provers appears only once the network's hash is large (near 100 GH/s for a card proving beside its miner) and is nothing more. Which of the two in-chain streams pays more per GPU-second depends on the size of the fleet: measured on 4 October 2026, three machines at 275 million hashes a second, a second of hashing paid about 4.9x a second of proving the pool share; at 10,000 cards the same arithmetic favours proving by about 930x. That is arithmetic on measured devnet rates, approximate, not a market measurement.</p>
<h3>Hardware</h3>
<p>The dataset starts at 2 GB and grows (the proposed schedule, fixed at genesis: 2 GB, doubling at years 4, 12 and 28, the average of half a gigabyte a year), so a 4 GB card mines for about four years and an 8 GB card for about twelve, approximate. NVIDIA cards prove: from 8 GB alone on the patched server, 16 GB and up beside the miner at the proposed 5.5 GiB dataset (the costed alternative; the first step sits at 4 GiB, where the miner holds less and the rule is not yet measured), 8 and 12 GB cards time-sharing with the miner paused, 24 GB on the stock server (measured on rented cards, 6 and 8 October 2026). NVIDIA and AMD cards both mine, because the mining program is generated for the architecture both share; only NVIDIA cards prove today. Apple's chips are GPUs with unified memory, so Macs mine too, at about a fifth of a flagship card: Measured, 26.7 against 123 million hashes a second, an Apple M5 Max beside an RTX 5090, mining side by side, 4 October 2026. A Mac is a poor miner per dollar. There is no CPU mining lane, on purpose, because CPU mining is what botnets farm. Nodes, wallets and exchanges need no GPU at all.</p>
<p>The dataset starts at 2 GB and grows (the proposed schedule, fixed at genesis: 2 GB, doubling at years 4, 12 and 28, the average of half a gigabyte a year), so a 4 GB card mines for about four years and an 8 GB card for about twelve, approximate. NVIDIA cards prove: from 8 GB alone on the patched server, 16 GB and up beside the miner at the proposed 5.5 GiB dataset (the costed alternative; a first step at 4 GiB is proposed and pending decision, where the miner would hold less and the rule is not yet measured), 8 and 12 GB cards time-sharing with the miner paused, 24 GB on the stock server (measured on rented cards, 6 and 8 October 2026). NVIDIA and AMD cards both mine, because the mining program is generated for the architecture both share; only NVIDIA cards prove today. Apple's chips are GPUs with unified memory, so Macs mine too, at about a fifth of a flagship card: Measured, 26.7 against 123 million hashes a second, an Apple M5 Max beside an RTX 5090, mining side by side, 4 October 2026. A Mac is a poor miner per dollar. There is no CPU mining lane, on purpose, because CPU mining is what botnets farm. Nodes, wallets and exchanges need no GPU at all.</p>
<h3>What a miner's hour looks like</h3>
<p>The card hashes the lottery continuously. On an NVIDIA card, when the client sees a shard it can win (or, once the job market is built, an external job), it switches the card to proving for a few seconds, posts the proof, and goes back to hashing. The client does the switching and the miner sees one balance.</p>
<p>The protocol carries no fee: no dev fund, no cut to any team. Ember, the miner software, takes an optional 1% dev fee, the way other GPU miners do. One block template in 100 is requested with the dev address instead of yours, by a counter, not a random draw, so it is exactly 1 in 100 and anyone can check it from the source or from the chain. One flag turns it off (<code>--dev-fee 0</code>, a switch in the app, a line in the HiveOS config). The miner prints the fee and the address when it starts. Any other client is welcome.</p>

View file

@ -619,7 +619,7 @@ details.tablebar summary{display:flex;align-items:center}
set('s-blue',fmtN(s.blue_score));set('s-hash',fmtHash(s.hashes_per_second_estimate));
set('s-keys',fmtN(s.miners_10m!==null&&s.miners_10m!==undefined?s.miners_10m:(d.miners||[]).length));set('s-peers',fmtN(s.peers));
var lock=null;if(fn&&fn.checkpoints)fn.checkpoints.forEach(function(c){if(c.state==='locked'&&(!lock||c.index>lock.index))lock=c;});
var le=$('s-lock');if(fn&&fn.supported&&!fn.active){set('s-lock',fn.latest_locked_index?'#'+fmtN(fn.latest_locked_index):'none');le.className='v tick';$('s-lock-d').textContent='finality paused: under two thirds of the weight is signing';}
var le=$('s-lock');if(fn&&fn.supported&&!fn.active){var everLocked=!!(fn.latest_locked_index||lock);set('s-lock',everLocked?'#'+fmtN(fn.latest_locked_index||lock.index):'none');le.className='v tick';$('s-lock-d').textContent=everLocked?'finality paused: under two thirds of the weight is signing':(function(){var best=null;(d.blocks||[]).forEach(function(b){if(b&&b.daa>best)best=b.daa;});return 'finality not yet active: the first lock comes when the weight window fills at DAA 7,200'+(best!==null?' (DAA '+fmtN(best)+' now)':'');})();}
else if(lock){set('s-lock','#'+fmtN(lock.index));le.className='v tick';var t=lock.locked_at?new Date(String(lock.locked_at).replace(' ','T')).getTime():NaN;$('s-lock-d').textContent=(isNaN(t)?'locked':'locked '+rel(now-t))+(lock.fraction_total?' with '+Math.round(lock.fraction_total*100)+'% of all 30-day weight':'');}
else{set('s-lock',fn&&fn.supported?'none yet':'no rule');le.className='v tick word';$('s-lock-d').textContent=fn&&fn.supported?'the 30-day weight window is filling':'';}
// the graph's count, the blue range, the chain-only view

View file

@ -439,7 +439,7 @@ pre b{color:var(--molten-text);font-weight:500}
</div>
<div class="faq">
<details><summary>Does this website use my GPU to mine?</summary><p>No. The pictures on the home page are drawn, not mined. Mining happens only in the app you install, and only when you press Start.</p></details>
<details><summary>Does my card mine and prove?</summary><p>Every card mines. NVIDIA proves; AMD and Apple mine. Mining and proving together needs a 16 GB NVIDIA card at the proposed 5.5 GiB dataset (the costed alternative; the first step sits at 4 GiB, where the miner holds less and the rule is not yet measured) (the miner about 6.1 GiB, a compressed proof about 7.5 GiB at its peak); an 8 GB or 12 GB card time-shares, the app pausing the miner for the proof; on the stock server the full shard needs 24 GB and a 32 GB card does both at once (measured, 6 and 8 October 2026). Apple silicon proves on the CPU, slowly. The Prove page of the app says in one sentence what your card can do.</p></details>
<details><summary>Does my card mine and prove?</summary><p>Every card mines. NVIDIA proves; AMD and Apple mine. Mining and proving together needs a 16 GB NVIDIA card at the proposed 5.5 GiB dataset (the costed alternative; a first step at 4 GiB is proposed and pending decision, where the miner would hold less and the rule is not yet measured) (the miner about 6.1 GiB, a compressed proof about 7.5 GiB at its peak); an 8 GB or 12 GB card time-shares, the app pausing the miner for the proof; on the stock server the full shard needs 24 GB and a 32 GB card does both at once (measured, 6 and 8 October 2026). Apple silicon proves on the CPU, slowly. The Prove page of the app says in one sentence what your card can do.</p></details>
<details><summary>Is the devnet paying real money?</summary><p>No. Devnet coins have no value and the chain may reset. The app’s pounds row reads 0.00 on devnet and says why. The Igneum 2.0 devnet is the network today. Mainnet has not started.</p></details>
<details id="fee-faq"><summary>Is there a fee?</summary><p>Not in the protocol: no dev fund, no fee to any team. The app takes an optional 1% software fee, the norm for GPU miners, and one flag turns it off. <a href="#fee">The fee, in full view.</a></p></details>
<details><summary>Can I run it on a rig or in a pool?</summary><p>The Linux and HiveOS tarball is above, with the flight sheet. Today every machine mines solo on its own keys, and a card runs several.</p></details>

View file

@ -260,8 +260,8 @@
<section class="card" aria-labelledby="deployed">
<h2 id="deployed">On Sepolia</h2>
<div class="kv" data-oracle-kv>
<div class="k">Oracle</div><div class="mono" data-oracle-address>0x3ad71d4660d8a8d2f92248b9c286392dd76a3ab6 <small>IgneumStateOracle, deployed 8 October 2026 on the shared verifier (tx 0x16312cf8…4e2a, block 11870855); its trust() names the two unchecked items below; accepts statements with chain id 4463 or 4464 (the earlier devnet it was deployed against; a redeploy against the 2.0 devnet, chain id 4465, is owed).</small></div>
<div class="k">Verifier</div><div class="mono" data-verifier-address>0xAf74f3F512081291D663Bb1d6b6d37E99e37D744 <small>the shared IgneumCertificateVerifier (the DEX lane's, a real BLS12-381 check on chain through the EIP-2537 precompiles, voter table installed at checkpoint 2127), set by setVerifier on 8 October 2026 (tx 0xd679bb65…db8e); the stand-in 0xa197ef31…a6f6 served until then</small></div>
<div class="k">Oracle</div><div class="mono" data-oracle-address>0xbb3450049926da3572e6b67cb94df312fe349e34 <small>IgneumStateOracle for the 2.0 devnet (igneum-devnet-4, statements with chain id 4465), deployed 8 October 2026 on the shared verifier (tx 0x5eb08f3a…a5a9, block 11871485). The Devnet 3 oracles stay as deployed with their records: 0x3ad71d46…3ab6 (chain ids 4463 and 4464, on the shared verifier) and 0xefe9879d…a1b2 (the stand-in verifier at first).</small></div>
<div class="k">Verifier</div><div class="mono" data-verifier-address>0x874D8Be5385474414aeb69EE4AB8374DA34b8c1F <small>the shared IgneumCertificateVerifier for igneum-devnet-4 (the DEX lane's, a real BLS12-381 check on chain through the EIP-2537 precompiles), set on this oracle by setVerifier on 8 October 2026 (tx 0x7ccffe23…3df1); its voter table installs at the first lock (the chain's first finality certificate, when the weight window fills at DAA 7,200), so no devnet-4 certificate is recorded before that: "table installs at the first lock". The Devnet 3 verifier 0xAf74f3F5…D744 holds Devnet 3's table at checkpoint 2127.</small></div>
<div class="k">Chain</div><div>Sepolia, chain id 11155111; the Igneum 2.0 devnet (igneum-devnet-4), no value</div>
<div class="k">Proven roots</div><div class="mono">Certificate 2232 (0xf056c26e…, 29 voters, signed weight 4,988 of 7,164) recorded on the shared verifier with a real BLS check, tx 0x1794b785…1e8a, 792,677 gas. Devnet chain block 28439, post_root 0x6e6d2fc8… stored on this oracle with a 6-header path, tx 0xcdb24dbc…ee5fc, 1,624,984 gas; provenBalance read 721.451608 IGN for 0xcaed79d8…c087 on Sepolia, equal to the devnet node's eth_getProof.</div>
</div>
@ -269,6 +269,7 @@
<p class="note">How to read a balance from another contract: <code>IIgneumStateOracle(oracle).provenBalance(number, account, accountProof)</code>, where <code>number</code> is a devnet chain block whose state root the oracle holds and <code>accountProof</code> is the <code>eth_getProof</code> account proof at that block. A storage slot: <code>provenStorage(number, account, slot, accountProof, storageProof)</code>. Both revert on any mismatch.</p>
</section>
<p class="note" data-lock-line>Until the Igneum 2.0 devnet has its first finality certificate, this page answers: no finality certificate yet; the first lock comes when the weight window fills at DAA 7,200 (the live DAA is shown with the answer). A certificate naming any network other than the release manifest's (<a href="/release.json">/release.json</a>, network.id) is refused before any check runs.</p>
<h2 id="checked">What the contract checks</h2>
<ol>
<li>The certificate, through the shared verifier: the aggregate BLS signature over the checkpoint under the installed voter table and the weight rule.</li>
@ -277,7 +278,7 @@
<li>Every read: a keccak-keyed Merkle Patricia proof against the stored root, verified on chain.</li>
</ol>
<div class="trust"><h3>Trust anchors and unchecked signatures, named (also returned by the contract's <code>trust()</code>)</h3><ul>
<li><b>Deployer-installed trust anchor, disclosed, not removed:</b> the shared verifier's voter table. It stays because no header field commits to the table yet; the day one does, the table is read from the chain and this anchor goes.</li>
<li><b>Deployer-installed trust anchor, disclosed, not removed:</b> the shared verifier's voter table. It stays because no header field commits to the table yet; the day one does, the table is read from the chain and this anchor goes. The devnet-4 verifier's table installs at the chain's first lock; the roots and certificate recorded below are Devnet 3's, under the Devnet 3 verifier's table (checkpoint 2127).</li>
<li>The voter table and weights the shared verifier checks certificates against were installed by the verifier's deployer (read from a node at checkpoint 2127), not read from the chain. A certificate is verified against that installed table; a later table that drifts past the rule's margin needs a new install by that deployer.</li>
<li>The aggregator's BLS signature over the segment record is not checked on chain, and the SP1 proof behind its statement is not verified on chain. A stored root rests on the aggregator's statement as the nodes check and pay it.</li>
<li>A balance read from a stored root is executed and finalised state under those assumptions, not a payment outcome.</li>

View file

@ -256,7 +256,7 @@
<tr><td>Light verification</td><td>256 MB cache on a CPU, milliseconds</td><td>256 MB cache on a CPU (512 MB from year 4), one warp under 10 ms, the gate. Measured 2.1 ms on one Apple M5 Max core for class v3 (3.4x class v2's 0.61 ms); a 2019-class core not yet</td></tr>
<tr><td>Changes over time</td><td>None. A fixed design, unchanged for seven years</td><td>A new program every hour, its memory pattern with it; era draws and reserved families on a schedule fixed at genesis. Nobody touches it</td></tr>
<tr><td>Seed grinding</td><td>Not applicable, the program comes from the hash input</td><td>Closed by a verifiable delay between seed and program</td></tr>
<tr><td>Useful work</td><td>None. Hashing only</td><td>NVIDIA cards prove: from 8 GB alone on the patched server, 16 GB and up beside the miner at the proposed 5.5 GiB dataset (the costed alternative; the first step sits at 4 GiB, where the miner holds less and the rule is not yet measured), 8 and 12 GB cards time-sharing with the miner paused, 24 GB on the stock server (measured on rented cards, 6 and 8 October 2026). Selling proofs to other chains is Designed, not built. AMD and Apple cards mine and do not prove; a prover for them lands when a zkVM ships one</td></tr>
<tr><td>Useful work</td><td>None. Hashing only</td><td>NVIDIA cards prove: from 8 GB alone on the patched server, 16 GB and up beside the miner at the proposed 5.5 GiB dataset (the costed alternative; a first step at 4 GiB is proposed and pending decision, where the miner would hold less and the rule is not yet measured), 8 and 12 GB cards time-sharing with the miner paused, 24 GB on the stock server (measured on rented cards, 6 and 8 October 2026). Selling proofs to other chains is Designed, not built. AMD and Apple cards mine and do not prove; a prover for them lands when a zkVM ships one</td></tr>
<tr><td>Track record</td><td>About seven years with one shipped chip, Bitmain’s Antminer X5 (September 2023), at 1.46x per joule over a desktop CPU; the one announced beyond it, Bitmain’s Antminer X9, was withdrawn in May 2026 with zero units; RandomX v2 released 25 March 2026, activation pending</td><td>Zero years. Every number above is measured and logged with the commands that produced it. The specification, reference hash, test vectors and simulators are public now (git.igneum.network/igneum-network/spec). The node, the miner and the wallet follow to the same host as the repository is published</td></tr>
</tbody>
</table></div>

View file

@ -268,6 +268,7 @@
<div class="row"><input id="tx" name="tx" inputmode="text" autocomplete="off" spellcheck="false" placeholder="0x…" pattern="^0x[0-9a-fA-F]{64}$"><button class="btn primary" type="submit">Prove it in this tab</button></div>
<div class="vstatus" data-status role="status" aria-live="polite">the devnet, no value. A transaction is final once a certified checkpoint has it in its past, about 30 to 90 s after it executes.</div>
</form>
<p class="note" data-lock-line>Until the Igneum 2.0 devnet has its first finality certificate, this page answers: no finality certificate yet; the first lock comes when the weight window fills at DAA 7,200 (the live DAA is shown with the answer). A certificate naming any network other than the release manifest's (<a href="/release.json">/release.json</a>, network.id) is refused before any check runs.</p>
<div data-result></div>
</section>

View file

@ -90,7 +90,7 @@
"size": "1 GiB on the devnets (2^24 items at the genesis size)",
"keyed_by": "the execution state after the epoch's reference block (class v5)",
"cache": "256 MiB day cache (class v3 lineage)",
"growth": "the first step sits at 4 GiB, inside the 10 percent GPU-cost budget, and later steps are taken only when the chain's own state outgrows them, not on a calendar (the design's dataset policy, 8 October 2026); the 5.5 / 8.5 / 11.5 GiB figures are the costed alternative in the sensitivity workbook; no growth step is set on the Igneum 2.0 devnet (2^24 items at 1 GiB)"
"growth": "PROPOSED, decision pending: a first step at 4 GiB inside the 10 percent GPU-cost budget, later steps only when the chain's own state outgrows them, not on a calendar (the design's dataset policy, 8 October 2026); the 5.5 / 8.5 / 11.5 GiB figures are the costed alternative in the sensitivity workbook; no growth step is set on the Igneum 2.0 devnet (2^24 items at 1 GiB)"
},
"ladder": {
"rung": 0,

View file

@ -109,6 +109,9 @@ async function setupSchema() {
`ALTER TABLE ${TB} ADD COLUMN IF NOT EXISTS proof_records int`,
`ALTER TABLE ${TB} ADD COLUMN IF NOT EXISTS subsidy_sompi bigint`,
`ALTER TABLE ${TB} ADD COLUMN IF NOT EXISTS paid_sompi bigint`,
// wei-scale amounts on the Igneum 2.0 chain (8 Oct 2026): widen both in place, a no-op once numeric
`ALTER TABLE ${TB} ALTER COLUMN subsidy_sompi TYPE numeric(40,0)`,
`ALTER TABLE ${TB} ALTER COLUMN paid_sompi TYPE numeric(40,0)`,
`ALTER TABLE ${TB} ADD COLUMN IF NOT EXISTS selected_parent text`,
`ALTER TABLE ${TB} ADD COLUMN IF NOT EXISTS number bigint`,
`ALTER TABLE ${TB} ADD COLUMN IF NOT EXISTS detail jsonb`,
@ -503,7 +506,8 @@ async function flushBlocks() {
const rows = pendingBlocks.splice(0, 200);
const cols = ['hash', 'blue_score', 'daa_score', 'timestamp_ms', 'parents', 'parent_hashes', 'is_chain_block', 'vote_key_hash', 'miner_address', 'engine',
'tx_count', 'evm_miner', 'proof_records', 'subsidy_sompi', 'paid_sompi', 'selected_parent', 'detail'];
const cast = { parent_hashes: '::text[]', detail: '::jsonb', subsidy_sompi: '::bigint', paid_sompi: '::bigint' };
// numeric, not bigint: the Igneum 2.0 chain reports the subsidy and the paid amount in wei (9.52 IGN = 9522644482386090276, past int64; 8 Oct 2026)
const cast = { parent_hashes: '::text[]', detail: '::jsonb', subsidy_sompi: '::numeric', paid_sompi: '::numeric' };
const params = []; const values = [];
for (const r of rows) {
const ph = [];

View file

@ -136,7 +136,12 @@ async function checkpoint() {
if (process.env.DATABASE_URL) {
// the Devnet 3 observer's rows, read the way the site's /api/checkpoint reads them (DATABASE_URL from the box's observer env)
const cp = await readCheckpoint(neon(), SOURCE);
if (!cp) throw httpError(404, 'no certified Devnet 3 checkpoint stored yet');
if (!cp) {
// the lock condition, live: the first certificate comes when the weight window fills (Params.min_daa = the window)
let daa = null, window = null; try { const st = await exec('igneum_getProvingStatus', []); daa = Number(st.tipDaa); window = Number(st.weightWindow); } catch {}
throw httpError(404, `no finality certificate yet on the Igneum 2.0 devnet (${CHAIN_ID_NAME}); the first lock comes when the weight window fills at DAA ${window ?? 7200}${daa !== null ? `, the chain reads DAA ${daa.toLocaleString('en-GB')} now` : ''}`);
}
if (cp.chain_id !== CHAIN_ID_NAME) throw httpError(409, `the stored certificate is for ${cp.chain_id}, not ${CHAIN_ID_NAME}; refused`);
return { ok: true, now: new Date().toISOString(), ...cp };
}
const r = await fetch(CHECKPOINT_URL, { signal: AbortSignal.timeout(15000), cache: 'no-store' });

View file

@ -29,13 +29,15 @@ async function deploy(name, args) {
const oracleOnly = process.argv.includes('--oracle-only');
// --verifier 0x..: construct the oracle on that verifier (the shared IgneumCertificateVerifier) instead of the stub
const vi = process.argv.indexOf('--verifier'); const verifierArg = vi > 0 ? process.argv[vi + 1] : null;
// --chain-ids A,B: the statement chain ids the oracle accepts (Devnet 3: 4463,4464; the 2.0 devnet igneum-devnet-4: 4465,4465)
const ci = process.argv.indexOf('--chain-ids'); const chainIds = ci > 0 ? process.argv[ci + 1].split(',').map(Number) : [EVM_CHAIN_ID, EVM_CHAIN_ID_AFTER_FLOOR];
const previous = existsSync(out) ? deployment() : null;
const stub = oracleOnly && previous ? previous.stub : await deploy('StubCertificateVerifier', []);
const oracle = await deploy('IgneumStateOracle', [verifierArg || stub.address, EVM_CHAIN_ID, EVM_CHAIN_ID_AFTER_FLOOR]);
const oracle = await deploy('IgneumStateOracle', [verifierArg || stub.address, chainIds[0], chainIds[1] ?? chainIds[0]]);
const after = await pub.getBalance({ address: account.address });
const d = {
network: 'sepolia', chainId: 11155111, rpc: 'https://ethereum-sepolia-rpc.publicnode.com', deployer: account.address,
evmChainId: EVM_CHAIN_ID, evmChainIdAfterFloor: EVM_CHAIN_ID_AFTER_FLOOR, verifier: verifierArg || 'stub', verifierAddress: verifierArg || stub.address, stub, oracle, deployedAt: new Date().toISOString(), spentWei: (bal - after).toString(),
evmChainId: chainIds[0], evmChainIdAfterFloor: chainIds[1] ?? chainIds[0], verifier: verifierArg || 'stub', verifierAddress: verifierArg || stub.address, stub, oracle, deployedAt: new Date().toISOString(), spentWei: (bal - after).toString(),
previous: previous ? [...(previous.previous || []), { stub: previous.stub, oracle: previous.oracle, deployedAt: previous.deployedAt, writes: previous.writes || [] }] : [],
};
writeFileSync(out, JSON.stringify(d, null, 1) + '\n');

View file

@ -3,8 +3,8 @@
"chainId": 11155111,
"rpc": "https://ethereum-sepolia-rpc.publicnode.com",
"deployer": "0xe1D08384ef4c4c1511c8f05F94C914C4DeDc2787",
"evmChainId": 4463,
"evmChainIdAfterFloor": 4464,
"evmChainId": 4465,
"evmChainIdAfterFloor": 4465,
"verifier": "0xAf74f3F512081291D663Bb1d6b6d37E99e37D744",
"verifierAddress": "0xAf74f3F512081291D663Bb1d6b6d37E99e37D744",
"stub": {
@ -14,13 +14,13 @@
"gasUsed": 8434842
},
"oracle": {
"address": "0x3ad71d4660d8a8d2f92248b9c286392dd76a3ab6",
"tx": "0x16312cf8177eb79c28858d79509ea56376cefb94e09fb1872bfea018df734e2a",
"block": 11870855,
"address": "0xbb3450049926da3572e6b67cb94df312fe349e34",
"tx": "0x5eb08f3aadfbcec8e6eeccf5349278e1c5eeba27c08d6150aa2fdbd0395ca5a9",
"block": 11871485,
"gasUsed": 20561543
},
"deployedAt": "2026-10-08T14:47:15.139Z",
"spentWei": "20561810300059",
"deployedAt": "2026-10-08T16:53:26.556Z",
"spentWei": "20561871984688",
"previous": [
{
"stub": {
@ -165,21 +165,54 @@
"headers": 6
}
]
},
{
"stub": {
"address": "0xa197ef31d5d5613125779179668e2482ac69a6f6",
"tx": "0xa01091b62dfb4a89b25afb57a3d384677a9ad62370748504d2b3f288c0d3ad38",
"block": 11869583,
"gasUsed": 8434842
},
"oracle": {
"address": "0x3ad71d4660d8a8d2f92248b9c286392dd76a3ab6",
"tx": "0x16312cf8177eb79c28858d79509ea56376cefb94e09fb1872bfea018df734e2a",
"block": 11870855,
"gasUsed": 20561543
},
"deployedAt": "2026-10-08T14:47:15.139Z",
"writes": [
{
"at": "2026-10-08T14:47:39.502Z",
"what": "submitStateRoot",
"vector": "devnet-3",
"number": "28439",
"postRoot": "0x6e6d2fc8993db13220d84f39ca9cbc08a8c6975ce2932a4289a1d6ef4cef60c8",
"certIndex": "2232",
"tx": "0xcdb24dbceb5b1c5f4605842809228428c59bd73291a0114e555df74c57dee5fc",
"block": 11870857,
"gasUsed": 1624984,
"calldataBytes": 24391,
"headers": 6
}
]
}
],
"writes": [
{
"at": "2026-10-08T14:47:39.502Z",
"what": "submitStateRoot",
"vector": "devnet-3",
"number": "28439",
"postRoot": "0x6e6d2fc8993db13220d84f39ca9cbc08a8c6975ce2932a4289a1d6ef4cef60c8",
"certIndex": "2232",
"tx": "0xcdb24dbceb5b1c5f4605842809228428c59bd73291a0114e555df74c57dee5fc",
"block": 11870857,
"gasUsed": 1624984,
"calldataBytes": 24391,
"headers": 6
"at": "2026-10-08T16:58:16.749Z",
"what": "sepolia transfer 0.01 ETH to the DEX lane build-4 key (no value)",
"to": "0x70e692A9f1fa1067a8c2d64fDAf1636b4EAEC7e2",
"tx": "0xc938895c2bf96d3237b48aa01f32be81932c73b47000384e164108a0fd62dde4",
"block": 11871509,
"gasUsed": 204600
},
{
"at": "2026-10-08T17:00:39.551Z",
"what": "setVerifier (igneum-devnet-4 verifier)",
"verifier": "0x874D8Be5385474414aeb69EE4AB8374DA34b8c1F",
"tx": "0x7ccffe23aed7ef4876c37f8f595853362d8989f79e6d47a8c943ec378c163df1",
"block": 11871521,
"gasUsed": 31170
}
]
}

View file

@ -10,11 +10,11 @@
# tools/site-deploy-from-mirror.sh --self-test-checks the post-deploy checks against the live site as it stands (known-failed first)
#
# POST-DEPLOY CHECKS (main, 7 October 2026 22:1x UK), before the edge time is printed: /api/live's network must equal LIVE_NETWORK
# (igneum-devnet-3), the index must carry INDEX_STRINGS (the launch-first chip line, the git.igneum.network link), LEGAL_PAGE must
# (igneum-devnet-4 since the 8 Oct 2026 re-point), the index must carry INDEX_STRINGS (the launch-first chip line, the git.igneum.network link), LEGAL_PAGE must
# carry "Not legal advice" (the litepaper: no commit on master puts it on the index) and /miners must carry at least MINERS_MIN_ROWS table rows; any mismatch prints "DEPLOY RED <field>: ..." and exits 1. The
# edge serves the previous deployment for some seconds after the CLI returns, so the checks retry for up to 90 s before the verdict.
set -euo pipefail
LIVE_NETWORK="${LIVE_NETWORK:-igneum-devnet-3}"
LIVE_NETWORK="${LIVE_NETWORK:-igneum-devnet-4}"
INDEX_STRINGS=("At launch the strongest chip in our public model" "git.igneum.network/igneum-network/")
LEGAL_PAGE="${LEGAL_PAGE:-/litepaper}"; LEGAL_STRING="Not legal advice" # the legal line lives on the litepaper in master's tree (22:16 UK: no commit put it on the index)
MINERS_MIN_ROWS="${MINERS_MIN_ROWS:-20}" # the current-class table alone (the datacentre rows sit in their own table since 8 Oct 2026)
@ -53,8 +53,10 @@ post_checks() { # one pass: prints the first mismatch as "field: detail" and ret
[ -z "$want" ] || printf '%s' "$body" | /usr/bin/grep -cF -- "$want" >/dev/null || { echo "page /$path: string missing: \"$want\""; return 1; }
done
# the certified-checkpoint API (the receipt, the light wallet and the explorer's per-block re-check read it): ok:true on Devnet 3
local ckp; ckp=$(curl -fsS "${nc[@]}" --max-time 25 "$SITE/api/checkpoint?source=dn3&x=$RANDOM" 2>/dev/null) || { echo "api/checkpoint: not reachable"; return 1; }
printf '%s' "$ckp" | python3 -c 'import sys,json; d=json.load(sys.stdin); sys.exit(0 if d.get("ok") is True and d.get("network")=="igneum-devnet-3" else 1)' 2>/dev/null || { echo "api/checkpoint?source=dn3: $(printf '%s' "$ckp" | head -c 120)"; return 1; }
local ckp; ckp=$(curl -fsS "${nc[@]}" --max-time 25 "$SITE/api/checkpoint?x=$RANDOM" 2>/dev/null) || { echo "api/checkpoint: not reachable"; return 1; }
# rule 25's class (main, 8 Oct 2026 18:4x): the public checkpoint answers the live network only (source dn4 or live, never test), ok:true,
# and either a certificate or certificate:null with a reason before the chain's first lock; its network must equal /api/live's
printf '%s' "$ckp" | python3 -c 'import sys,json; d=json.load(sys.stdin); ok=d.get("ok") is True and d.get("network")==sys.argv[1] and d.get("source") in ("live","dn4") and ("certificate" in d); sys.exit(0 if ok else 1)' "$LIVE_NETWORK" 2>/dev/null || { echo "api/checkpoint: $(printf '%s' "$ckp" | head -c 160) (want network $LIVE_NETWORK, source live or dn4)"; return 1; }
# the explorer's stats API (8 Oct 2026): Devnet 3 named, the indexer not stale, the any-age 10-minute shard count a number
local st; st=$(curl -fsS "${nc[@]}" --max-time 25 "$SITE/api/explorer?stats=1&x=$RANDOM" 2>/dev/null) || { echo "api/explorer?stats=1: not reachable"; return 1; }
printf '%s' "$st" | python3 -c 'import sys,json; d=json.load(sys.stdin); ok=d.get("network_label")=="Devnet 3" and (d.get("indexer") or {}).get("stale") is False and isinstance(((d.get("paid_10m_any_age") or {}).get("shards")), (int,float)); sys.exit(0 if ok else 1)' 2>/dev/null || { echo "api/explorer?stats=1: $(printf '%s' "$st" | head -c 160)"; return 1; }