# The devnet fee switch: calibrated v1 behind `fees_v1_activation_daa`, runbook (5 October 2026) Owner's decision: "2 execute". The adopted fee table (spec 05 section 5.11: `B_p` 120,000 pgas, `S_p` 30,000, intrinsic 300, modexp 10 + 1 per 10 bytes, floors 100 gwei per gas and 10,000 gwei per pgas) goes live on the devnet at a DAA score H, by the height switch the 0.3.6 node fork carries (`docs/plans/release-0.3.6.md` section 5). Prepared by the consensus and execution engineer on branch `fee-switch` (worktree `igneum-wt-feeswitch`). Steps 1 to 3 below are done; step 4 (the publish) waits for Igneum Miner 0.3.9, which must carry the new prover tools on the Mac. Nothing was published, no node was restarted, no override file was changed. ## 1. What changed, and what did not | Side | Change | Where | |---|---|---| | Prover (changed) | `igneum-prove-core` mirrors the node's `fees.rs`: `PgasTable`, `FeeParams` (`PROTOTYPE`, `CALIBRATED_V1`), `FeeSchedule { base, v1_activation_daa }` with `at(daa)`. The shard input carries the schedule (`ShardInput.fees`) and the block's DAA score (`FixtureEnv.daa_score`); `execute_range` reads the set at that score, raises the carried base fees to its floors (as the node's `execute_segment` does), and meters with its intrinsic, `B_p` and modexp entry. One pinned guest serves before and after H | `proving/igneum-prove/core/src/{config,pgas,executor,shard,fixture}.rs` | | Prover (changed) | The exporter reads the schedule from the dump (`feesV1ActivationDaa`, `fees`) and each segment's `daaScore`, replays the whole chain with the switch applied per segment (every state root must equal the node's on both sides), and cuts at the set's `S_p` at the block | `proving/igneum-prove/export/src/main.rs`, `tools/prove-fixtures/{gen.mjs,net.sh}` | | Prover (changed) | The host prints and records the set, refuses a fixture whose consensus plan was cut at the wrong `S_p`, and tests fixtures on both sides of the switch: `fees-switch-prototype` (block 51, DAA 187, one 7.5 M shard) and `fees-v1-shards2`, `fees-v1-shards3` (blocks 351 and 355, DAA 1,105 and 1,117, 30,000-pgas shards), all from ONE private simnet chain with the switch at DAA 800, replayed from genesis across the switch with every state root equal to the node's (`docs/bench-log.md`, 5 October 2026, "the prover carries both fee tables") | `proving/igneum-prove/host/src/main.rs`, `proving/fixtures/` | | Prover (unchanged) | The 328-byte public values (`ShardOutput`). The switch is NOT a field of the statement. Reason: the node recomputes the statement natively (`igneum/exec/src/proving.rs::statement_bytes`) and a vetoed record pays nothing, so the layout is consensus (payouts land in state). A new layout would need its own digest-bearing switch and would fork any 0.3.8 node after it. What pins the schedule instead: a shard metered under another table has another `pgas_used`, another post-root (the floors change what is burned) and so another statement, and the node vetoes it (spec 7.2 item 5). A light verifier that needs the schedule in the statement is a follow-up (design 2.1 removes the duplicate executor) | | | Node (unchanged) | Fork 2b6d23ef already reads the switch everywhere (`fee_params_at(daa)`, the digest rule, the daemon's print). Confirmed on this Mac: `cargo test --release -p igneum-exec` 11 passed, 0 failed, among them `the_fee_switch_meters_by_the_block_daa_score` (15:32:27Z to 15:36:30Z, 4 min 03 s wall, target `vendor/igneum-node/target-036`) | | | Guest | Re-pinned: new program ids (section 3) | `proving/igneum-prove/elf/` | ## 2. The new pin (shard program id) `proving/igneum-prove/pin-guests.sh` on this Mac, pinned 2026-10-05T16:20:38Z (SP1 crate 6.8.1, circuit v6.1.0): | Guest | Program id | ELF | |---|---|---| | shard (`igneum-prove-program`) | `0x2b1a81cb413236cf063077b46ed3111628f6c41036bcf6e23ee4cbbf5679ef7a` | 2,832,504 bytes, sha256 `0x150f4c05a2951fc5...` | | aggregator (`igneum-prove-aggregator`, embeds the shard key) | `0x474678f35f7545db28055d5e5bbc308231d84a5a072202087a2a8d5b09123896` | 319,744 bytes | The 0.3.8 ids, running on the Mac and PC 2 today: shard `0x0dfade071ffc05a50be5f7e6640fb12638bac0ea63697ec252863f55658be16a`, aggregator `0x135e67e742fbbcc8303676765266f25a6520ab36299820280125541051fb6c62`. A verifier on one id rejects proofs made under the other (`program id 0x... IS NOT OURS`), so every prover and verifier moves together (section 5, steps 2 to 5). `tools/ci/pinned-guests-check.sh` passes on the new `elf/`. ## 3. H and the digest Measured block rate: DAA 111,230 at 15:23Z and 112,227 at 15:40:13Z (`igneum_getProvingStatus.tipDaa` of the Mac app node), 997 blocks in 1,033 s = 0.965 blocks/s, about 3,470 an hour, 83,300 a day. H = 210,000. From 15:40Z that is 97,773 blocks, 28.1 h at the measured rate, so the devnet reaches H around 19:50Z on 6 October 2026. The 24-hour rule at the moment of the publish: `H - tipDaa >= 86,400`, which holds for a publish before about 19:50Z on 5 October. If the publish is later than that, H moves to the next round thousand above `tipDaa + 86,400` and the digest is re-read (one 20-second scratch node, the command below; nothing else changes). The digest with the override `{"difficulty_v2_activation_daa":33000,"proving_v0_activation_daa":84100,"fees_v1_activation_daa":210000}`, read on the 2b6d23ef node (`vendor/igneum-node/target-036/release/igneumd --devnet --override-params-file= --appdir= --rpclisten=127.0.0.1:60985 --listen=127.0.0.1:60986 --nodnsseed --nologfiles --yes`, 20 s, 15:41:27Z to 15:41:50Z, then killed): ``` Calibrated v1 fees from the override file: chain blocks metered with the adopted table, budgets and floors from DAA score 210000 Proving v0 from the override file: provers paid from DAA score 84100 Difficulty rule v2 from the override file: active from DAA score 33000 Fees on igneum-devnet: pgas table v0, B_p 30000000 pgas, S_p 7500000 pgas, floors 1000000000 wei per gas and 1000000000 wei per pgas; calibrated v1 from DAA score 210000 Consensus params digest: ab8847da538dead1dc10e046dfaadab3c1c35928e3748810c4e050d4a886087a (exchanged in the p2p handshake; a peer with another digest is refused) igneumd/2.1.0-2b6d23ef ``` The live digest today (no fee switch) is `f10a4eab4b1f4f341d54b0b0221161d1122fac302bca90d6b61d14939b69fbd6` (`release-0.3.6.md` 8g). A node with the switch and a node without it never handshake, which is why section 5 moves every node in one sweep. ## 4. What every machine runs today, and what it must run before H | Node | Binary today | Reads the switch | Before the override is published | |---|---|---|---| | App nodes (Mac d937c69d, PC 1 ae432dc7, PC 2 1ccfe586, PC 37ba0461, Sam's Mac 3a9bf309) | 0.3.8 app, node 2b6d23ef (PC 37ba0461 and Sam's Mac were on 0.3.7 and 0.3.5 at the 0.3.8 cut, `release-0.3.8.md` 9) | yes on 2b6d23ef | every app on 0.3.9 (section 5 step 3); a 0.3.5 node refuses an override file with `fees_v1_activation_daa` (`OverrideParams` is `deny_unknown_fields`) and dies at start | | Node 1 and the observer (hand nodes on this Mac) | `vendor/igneum-node/target-release/release/igneumd` = fork 20139145 (their logs: `igneumd/2.1.0-20139145`, no `Fees on` line) | NO | move to `vendor/igneum-node/target-036/release/igneumd` (2b6d23ef, sha256 64138a17..., `release-0.3.6.md` 8e) with the restart script of section 5 | | The seed (188.245.5.161, unit igneumd-v4) | `/opt/igneum/v4/bin/igneumd` sha c98a23da (the 0.3.5 build, journal: `igneumd/2.1.0-20139145`) | NO | move to the Linux cross-build of 2b6d23ef, `igneum-wt-ship036/infra/cross/out/igneumd` (sha256 c24fd2c5e8c4f976e2b3abc55873bca29ae6b97b47046c946f779d3a04947025, 48,733,480 bytes, `release-0.3.6.md` 8e) with the restart script of section 5 | | Provers (the Mac app's host, PC 2's `/opt/igneum` host) | shard program id `0x0dfade07...` (0.3.8 pin) | | the new pin (section 2) on both, by the 0.3.8 order: provers off, pool empty, install, `--mode id` equal, provers on | The two restart scripts now live in the repository, `infra/devnet/restart-hand-nodes.sh` and `infra/devnet/restart-seed.sh`, and take the override object as their one argument (the scratchpad forms of 5 October took only the proving height and hard-coded the rest; both pointed at the 20139145 binaries). Each refuses a binary that is not 2b6d23ef and ends by printing the new pids with their start times and the fee and digest lines. ## 5. The rollout, in order Everything before step 6 can run as soon as 0.3.9 is published; step 6 is the point of no return; H is at least 24 h after step 6. | Step | What | Check | |---|---|---| | 1 | Ship 0.3.9 with the new prover tools in the DMG (`igneum-prove-host` built from this branch's `elf/`) and `igneum-prove-wsl2.zip` from this branch (`SKIP_GATE=1 proving/windows-wsl2/make-package.sh`), node 2b6d23ef unchanged. The manifest's `consensus.override` is CARRIED OVER unchanged at this step (no `fees_v1_activation_daa` yet): `--activation-height 84100 --deadline-note "proving v0"` as 0.3.8 did | the live manifest's consensus object identical to 0.3.8's; `--mode id` on the DMG's host prints the section 2 shard id | | 2 | Provers off: Mac (the app's prove setting) and PC 2 (job `prove-off-pc2-039`: `POST /api/prove {"on":false}`) | `igneum_getProvingStatus` on the Mac app node: pool `pending 0`, no new records for 10 min (the 0.3.8 watch treated a connection error as "not empty" and said nothing: this watch prints every error line) | | 3 | Every app to 0.3.9: `update-now` job to all; wait for every app to log `update to 0.3.9 complete` and its node to report a DAA score (`node tools/console.mjs machines`) | Machines card: all on 0.3.9, node 2b6d23ef; PC 37ba0461 and Sam's Mac may lag, see section 7 | | 4 | PC 2's WSL tools: `fetch-prove-039` then `rebuild-prover-pc2-039` as 0.3.8 7a did (rsync of the package, every source re-stamped, `cargo build --release -p igneum-prove-export -p igneum-prove-host --features igneum-prove-host/cuda`, install into `/opt/igneum`) | `/opt/igneum/igneum-prove-host --mode id` prints the section 2 shard id, equal to the Mac's installed `/Applications/Igneum Miner.app/Contents/Resources/bin/igneum-prove-host --mode id` | | 5 | Provers on: Mac setting on, PC 2 job `prove-on-pc2-039` | the Mac node logs `VERIFIED in` for a PC 2 record under the new id; the live page shows a paid shard | | 6 | Publish the switch, every node at once, in this order (`release-0.3.6.md` section 7, "Operational lessons"): (a) `packaging/ota/publish-manifest.sh --version 0.3.9 --override '{"difficulty_v2_activation_daa":33000,"proving_v0_activation_daa":84100,"fees_v1_activation_daa":210000}' --activation-height 210000 --deadline-note "fees v1" --notes "" --deploy` (the Mac and Windows entries are carried over from the folder's 0.3.9 manifest; the override object is the whole set of switches, not only the new one); (b) `update-now` job to every app: the apps re-read the manifest and restart their node at a safe moment with the new override; wait until every app node logs `Calibrated v1 fees from the override file: ... from DAA score 210000`; (c) `infra/devnet/restart-hand-nodes.sh ''`; (d) `infra/devnet/restart-seed.sh ''` | every node prints digest `ab8847da538dead1dc10e046dfaadab3c1c35928e3748810c4e050d4a886087a`; the peer lists stay full (a node with the old digest is refused by the new ones and shows as a dropped peer) | | 7 | Before H: the digest sweep. On every app node's log and the two hand nodes' `.out` files and the seed's journal: one digest. The console's Machines card: every node's DAA score within a few blocks of the others (an isolated node falls behind at once, as the early-restarted hand node did on 5 October) | one digest, one height | | 8 | At H (about 19:50Z, 6 October): the first chain block at or above 210,000 meters with v1 and its base fees jump to the floors | `igneum_getBudgets` returns `provingGasLimit` 120000 and the two base fees 100 gwei and 10,000 gwei; `eth_getBlockByNumber` of the switch block shows `provingBaseFeePerGas` 0x9184e72a000; the first shard proven after H verifies and pays (its plan is 30,000-pgas shards) | Why the order: a 0.3.5 node refuses the override file (dies), a node restarted early with the switch is refused by every peer (isolated), and a prover on the old pin is rejected by a verifier on the new one (and the reverse). So: tools first with provers off, then every node in one sweep, then H a day later. ## 6. Commands for the release engineer ``` # 0.3.9 ship (release engineer): this branch merged, then as 0.3.8 did, override carried over unchanged node tools/ship-app.mjs 0.3.9 --node vendor/igneum-node-036 --branch release-0.3.9 --dl-both \ --activation-height 84100 --deadline-note "proving v0" --notes "The prover mirrors the fee switch (new pinned guest); node 2b6d23ef unchanged" --from ci # step 6a, the switch (ONLY after steps 2 to 5 are checked) packaging/ota/publish-manifest.sh --version 0.3.9 \ --override '{"difficulty_v2_activation_daa":33000,"proving_v0_activation_daa":84100,"fees_v1_activation_daa":210000}' \ --activation-height 210000 --deadline-note "fees v1" --notes "Calibrated v1 fees from DAA score 210000" --deploy # step 6b: the update-now job to every app, then wait for the "Calibrated v1 fees" line on every app node # step 6c and 6d infra/devnet/restart-hand-nodes.sh '{"difficulty_v2_activation_daa":33000,"proving_v0_activation_daa":84100,"fees_v1_activation_daa":210000}' infra/devnet/restart-seed.sh '{"difficulty_v2_activation_daa":33000,"proving_v0_activation_daa":84100,"fees_v1_activation_daa":210000}' # re-reading the digest for another H (20 s) printf '{"difficulty_v2_activation_daa":33000,"proving_v0_activation_daa":84100,"fees_v1_activation_daa":H}\n' > /tmp/ov-H.json vendor/igneum-node/target-036/release/igneumd --devnet --override-params-file=/tmp/ov-H.json --appdir=/tmp/digest-H \ --rpclisten=127.0.0.1:60985 --listen=127.0.0.1:60986 --nodnsseed --nologfiles --yes # 20 s, then Ctrl-C; read "Consensus params digest" ``` ## 7. Open | Item | State | |---|---| | PC 37ba0461 and Sam's Mac | silent at the 0.3.8 cut; they take 0.3.9 on their next check. A node that has not restarted with the switch by H is refused by every peer from its next restart; it is not a fork, it is an isolated node until its override file is updated (the app writes the manifest's object on its next update cycle) | | The statement does not name the schedule | section 1; the native veto pins it. Follow-up: a statement v2 with the switch and the DAA score, behind its own digest-bearing switch, when a light verifier needs it | | The export RPC carries no `daaScore` and no switch | `igneum_exportSegments` writes neither; gen.mjs adds them from `eth_getBlockByNumber` and the environment. Follow-up on the fork: write `daaScore` per segment and `feesV1ActivationDaa` at the top level, so a dump is self-describing | | The prototype-side fixtures at `S_p` 7.5 M | unchanged and still valid: a fixture without `fees` is the devnet schedule (prototype, never) |