igneum/docs/plans/fee-switch-devnet.md
igneum-labs 24aaa0e254 Prover mirrors the fee switch: both tables and fees_v1_activation_daa in the shard input, guest re-pinned, devnet runbook for H = 210,000
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>
2026-10-05 16:24:35 +00:00

15 KiB

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)