igneum-prove-core carries the node's fees.rs (PgasTable, FeeParams PROTOTYPE and CALIBRATED_V1, FeeSchedule::at); the shard input and every fixture carry the schedule and the block's DAA score; the executor reads the set at that score, raises the base fees to its floors and meters with its intrinsic, B_p and modexp entry, as the node's execute_segment does. The 328-byte statement is unchanged: the node's native veto pins the schedule (a new layout would be a consensus change for every node). Exporter: schedule and daaScore from the dump (gen.mjs writes them), per-segment switch on replay, S_p from the set. Fixtures from one simnet chain across the switch at DAA 800: fees-switch-prototype (block 51), fees-v1-shards2 (351), fees-v1-shards3 (355); 358 segments replayed, every state root the node's. Host tests on both sides. Guest re-pinned: shard 0x2b1a81cb..., aggregator 0x474678f3...; pinned-guests-check passes. Node fork 2b6d23ef unchanged (igneum-exec tests 11 passed). Digest for the override with fees_v1_activation_daa 210000: ab8847da538dead1dc10e046dfaadab3c1c35928e3748810c4e050d4a886087a. Runbook docs/plans/fee-switch-devnet.md; infra/devnet/restart-hand-nodes.sh and restart-seed.sh take the override object (the hand nodes and the seed run 20139145 today and must move to 2b6d23ef first). One line on the live page, spec 5.11, the testnet README, the floor analysis, the bench log and the journey. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
127 lines
15 KiB
Markdown
127 lines
15 KiB
Markdown
# 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=<file>
|
|
--appdir=<scratch> --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 <app.url>/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 "<the 0.3.9 note>" --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 '<the same object>'`; (d) `infra/devnet/restart-seed.sh '<the same object>'` | 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) |
|