# Devnet v4 cut-over: runbook for the morning of 4 October 2026 Staged overnight by the release engineer (packages built, seed build installed, nothing switched). The cut-over is a ten-minute operation in this order. Background: `docs/fork-divergence.md`, section "Integration 4 Oct 2026" (what v4 is, why it is a new chain, the test-network numbers). All Mac commands run from `/Users/joshm/Projects/igneum` unless a `cd` says otherwise. Times are BST. ## What is staged | Item | Where | What it carries | |---|---|---| | Windows combined package 0.2.0 | `~/Desktop/igneum-windows-v4.zip` (26,238,253 bytes, rebuilt 08:56 BST) | v4 `igneumd.exe` and `igneum-miner.exe` (cross-build of `6457ca95`: generator v2 and the 2/3 floor), the three mingw DLLs, the launchers: peers = seed `188.245.5.161:26611` then Mac `192.168.68.64:26611`, appdir `%LOCALAPPDATA%\igneum\devnet-v4`, `MINERS=8` as `--identities 8` on ONE worker per card, `--evm-address` (`PAYOUT_EVM`, else derived from the PC name per vendor), voting on, `--prepare-packs` for the hot swap, `--exit-on-seed-change` kept as the fallback, `PLAIN=1` kept, version in the dashboard header | | Windows node-only package | `/tmp/igneum-integration/igneum-node-windows-v4.zip` and `~/Desktop/igneum-node-windows-v4.zip` (32,973,919 bytes, rebuilt 08:57 BST from `6457ca95`) | same exes, `src.zip`, `START-NODE.bat` with appdir `devnet-v4` and `EXTRA_ARGS=--addpeer=188.245.5.161:26611 --yes` | | Mac app 0.2.0 | `packaging/mac/dist/Igneum-Miner-0.2.0.dmg` (19,924,804 bytes, rebuilt 08:56 BST from `6457ca95`; the Metal worker carries the generator v2 port) | v4 `igneumd` and `igneum-miner` from `target-integration/release`, the Metal worker rebuilt from the hot-swap source with `-target arm64-apple-macos11` (`prepare 1`), `SEED_PEERS` = seed then Mac, data `~/Library/Application Support/Igneum/devnet-v4`, `--evm-address` and `--identities` | | Seed node v4 | igneum-seed-1 (188.245.5.161): `/opt/igneum/v4/bin/{igneumd,igneum-miner,run-seed-v4.sh}`, `/var/lib/igneum-v4` (empty), `/etc/igneum/seed-v4.env`, unit `igneumd-v4` installed and DISABLED | first staged from `dc749905` overnight; RE-STAGED from `6457ca95` (generator v2, 2/3 floor) starting 08:55 BST on 4 October (`stage-v4.sh igneum-seed-1 start`, log `infra/seed-nodes/build/stage-v4-gen2-run.out`); originally built from plus the working-tree `igneum-pow` (`infra/seed-nodes/stage-v4.sh`; log `infra/seed-nodes/build/stage-v4-igneum-seed-1.log`). The v3 unit `igneumd` keeps running on `/var/lib/igneum` until step (c) | | Mac binaries | `vendor/igneum-node/target-integration/release/igneumd` (40,463,680 bytes), `igneum-miner` (7,916,096) | built from `dc749905` | Not staged, by design: the live devnet (node 1 on 26610/26611, the observer peer on 26640/26641/28640, `observer.mjs`, the Mac seed relay on 26680/26681) and the seed's v3 unit all still run the v3 chain. ## The order ### (a) the founder stops the PC In both windows on the PC: Ctrl+C (the mining window first, then the node window; or one window if START-IGNEUM.bat was running: it stops the miners first, then the node). Wait for "Igneum has stopped" / "The node has stopped". Expected: the PC's node leaves the Mac's peer list within a few seconds (step (e) checks `peers`). The old folder `C:\igneum` (or wherever v3 was extracted) and `%LOCALAPPDATA%\igneum\devnet` are left as they are. ### (b) Main session cuts the Mac over ``` # 1. stop node 1 (caffeinate exits with it) and move the v3 database aside kill -INT 72039; while kill -0 72039 2>/dev/null; do sleep 1; done mv /tmp/igneum-devnet/node1 /tmp/igneum-devnet/node1-v3-2026-10-04 # 2. start igneumd v4: same appdir path, rpclisten and listen, plus the two peers (seed, PC) cd /Users/joshm/Projects/igneum/vendor/igneum-node nohup caffeinate -dims target-integration/release/igneumd --devnet --nodnsseed --disable-upnp --enable-unsynced-mining \ --appdir=/tmp/igneum-devnet/node1 --rpclisten=0.0.0.0:26610 --listen=0.0.0.0:26611 \ --addpeer=188.245.5.161:26611 --addpeer=192.168.68.67:26611 --nologfiles --yes > /tmp/igneum-devnet/node1-v4.out 2>&1 & # the execution layer starts with it (eth_ JSON-RPC on 127.0.0.1:26790); expected in node1-v4.out within 5 s: # "GRPC Server starting on: 0.0.0.0:26610", "P2P Server starting on: 0.0.0.0:26611", the genesis as the sink, 0 blocks # 3. restart the observer peer from the same binary (fresh appdir) and observer.mjs kill -INT 73692; kill -INT 74029 nohup target-integration/release/igneumd --devnet --nodnsseed --disable-upnp --appdir=/tmp/igneum-devnet/observer-v4 \ --rpclisten=127.0.0.1:26640 --rpclisten-json=127.0.0.1:28640 --listen=127.0.0.1:26641 --connect=127.0.0.1:26611 \ --nologfiles --yes > /tmp/igneum-devnet/observer-v4.out 2>&1 & cd /Users/joshm/Projects/igneum && IGNEUM_RPC=ws://127.0.0.1:28640 nohup node tools/observer/observer.mjs > /tmp/igneum-devnet/observer-mjs-v4.out 2>&1 & # block numbers restart at 0 on the new chain; LIVE_TABLE_PREFIX=v4_ keeps the v3 rows apart if wanted # 4. the Mac seed relay (pid 72139, v3 binary): stop it; node 1 now dials the seed itself (--addpeer above), so the # relay is not needed. If the seed should also be reached through a second path, start it again from v4: kill -INT 72139 # RELAY_BIN=vendor/igneum-node/target-integration/release/igneumd infra/seed-nodes/addpeer-from-mac.sh relay start (optional; it needs # its own fresh appdir: rm -rf /tmp/igneum-seed-relay first, or the v3 database refuses to open) ``` Where the Mac miner runs (the Metal worker, `/tmp/igneum-devnet/metal-worker.log`): restart it from `target-integration/release/igneum-miner` with `--evm-address 0x...` (any address the founder holds) once node 1 is up; an old miner's blocks are rejected from the first epoch boundary (DAA 3,600). Expected after (b): `igneum-miner watch 1 grpc://127.0.0.1:26610` prints `blocks=1 headers=1 ... peers=0 synced=true` (the genesis only, `--enable-unsynced-mining` reports synced); the live page shows the new chain from block 0. ### (c) Seed: switch to v4 ``` cd /Users/joshm/Projects/igneum/infra/seed-nodes ./stage-v4.sh igneum-seed-1 status # expect: build=done | ... | v4: installed igneumd 2.1.0, unit igneumd-v4 disabled/inactive | v3: unit igneumd enabled/active ./switch-v4.sh igneum-seed-1 # stop+disable igneumd, enable+start igneumd-v4, then one sync line every 10 s ``` Expected output of the switch: `igneumd: disabled/inactive igneumd-v4: enabled/active`, six journal lines ending in `P2P Server starting on: 0.0.0.0:26611` and `WRPC Server starting on: 127.0.0.1:28610`, then lines like `synced=False version=2.1.0 blocks=1 headers=1 daa=0 sink=... peers=0 active` turning into `synced=True ... peers=1` within a minute once node 1 (which dials the seed) has connected and the few v4 blocks have crossed. `./health.sh` then prints `unit=active-v4`. The seed never mines; it relays. Rollback of this step alone: `./switch-v4.sh igneum-seed-1 --back`. ### (d) the founder starts the PC on v4 1. Extract `igneum-windows-v4.zip` to a FRESH folder, for example `C:\igneum-v4` (not over the old one: the old package's built workers and packs must not be mixed in). 2. Double-click `START-IGNEUM.bat`. Settings at the top are already right (peers, `devnet-v4`, `MINERS=8`). If the founder wants a specific payout address, set `PAYOUT_EVM=0x...` first; otherwise the launcher derives one per card and prints it. 3. Firewall prompt for the new `igneumd.exe` path: tick Private networks, Allow access (new exe path = new prompt). Expected on the dashboard (header "Igneum: node and miners devnet v4, package 0.2.0"): NODE card "starting", then "syncing" with blocks climbing (the v4 chain is minutes old, so sync is seconds), "peer joined (1 peers)" then "(2 peers)" in the events, "synced in N s", then "exporting the program pack", "building the RTX 5090 worker (about 30 s for CUDA)", "started 8 RTX 5090 identities (1 miner)", "RTX 5090 worker ready, hot swap at the hour boundary" and the first "RTX 5090 found a block" within a minute. The launcher log (`igneum-.log` next to the bat) carries the EVM address line and the full miner command line. If the worker's ready event says "no hot swap (nvcc missing)", nvcc is not on the PATH of the launcher: the old exit-42 path rebuilds the worker at the boundary (about 30 s of no mining per hour), nothing else changes. ### (e) Checks | Check | Command (Mac) | Expected | |---|---|---| | node 1 peers | `vendor/igneum-node/target-integration/release/igneum-miner watch 1 grpc://127.0.0.1:26610` | `peers=2` (seed and PC), `synced=true`, `blocks` climbing about 1 per second once the PC mines | | PC synced, blocks flowing | the PC dashboard NODE card: `synced`, `blocks` equal to the Mac's within a few blocks; `chain N blocks/s` near 1.00 | the Mac's `watch` line shows the same `sink` as the PC's launcher log status block | | seed synced | `infra/seed-nodes/health.sh` | `OK ... unit=active-v4 rpc=yes synced=True blocks= peers=1..2` | | live page | https://igneum.network/live | LIVE dot, the strip (blocks, miners, difficulty, peers) counting from the new genesis, the finality lock markers appear only after the first lock (next row) | | first checkpoint lock | `IGNEUM_RPC=ws://127.0.0.1:28640 python3 infra/cloud-devnet/node/wrpc.py call getFinalityCheckpoints` | "window filling, N of 7,200" until the sink's DAA score reaches 7,200: the devnet weight window and `min_daa` are 7,200 DAA seconds, which is TWO HOURS of chain time after the v4 genesis (finality.rs `DEVNET`). The first lock lands at the first checkpoint whose DAA score is at least 7,200: expect it about 2 h after the chain starts, and `observer.mjs` then emits "checkpoint N locked (xx% of weight)" and the live page draws the lock marker. Locks every 30 s after that | | hourly program swap | the PC events at the first boundary (DAA 3,600, about one hour after the v4 genesis; the pack for the next program is written 600 DAA s, 10 minutes, before it) | "the next hourly program is compiled and resident (hot swap ready)" about 10 minutes before the boundary, no miner restart at the boundary, hash rate unbroken. On the Mac miner's log: `prepared ...` then jobs on the new seed. Test-network numbers: first block of the new epoch accepted 0.5 to 2.2 s after the last of the old (fork-divergence) | | EVM smoke | `cd tools/evm-smoke && IGNEUM_RPCS=http://127.0.0.1:26790 IGNEUM_MINER_KEY= node smoke.mjs` | `SMOKE SUMMARY: 84 checks passed, 1 failed` is the known result (the one failure wants duplicate sends in parallel blocks and a PoW network with one tip rarely offers any); fewer passes means the execution layer is not running. The quick check without a key: `curl -s -X POST http://127.0.0.1:26790 -H 'content-type: application/json' -d '{"jsonrpc":"2.0","id":1,"method":"eth_chainId","params":[]}'` returns `"0x116f"` (4463) and `eth_blockNumber` climbs | Shorter finality window for the first day (optional). The two-hour wait is the window filling; to see locks sooner, every node on the network must carry the same override file (it is a consensus parameter, so node 1, the observer peer, the seed and the PC all need it, and a node without it rejects nothing but certifies on a different schedule, which the lock rule treats as disagreement). The override file, with every finality field present (the parser takes the whole object): ``` { "finality": { "checkpoint_interval": 30, "checkpoint_depth": 20, "weight_window": 1800, "dust": 5, "presence_window": 20, "aggregators": 8, "equivocation_ban": 1800, "min_daa": 1800, "aggregator_fallback": 15 } } ``` Flag on every node: `--override-params-file=/path/override.json` (Mac: in the two `nohup` lines above; PC: `EXTRA_ARGS` at the top of START-IGNEUM.bat, for example `--override-params-file=C:\igneum-v4\override.json`; seed: `EXTRA_ARGS=` in `/etc/igneum/seed-v4.env`, then `systemctl restart igneumd-v4`). 1,800 DAA s = 30 minutes to the first lock. The recommendation is to leave the default (7,200) and let the first lock arrive two hours in: that is the rule the network will run and the window is the test. The 300-DAA window of the integration test was the same mechanism. ### (f) Rollback to v3 (two commands per machine) Mac (node 1; the observer peer and `observer.mjs` the same way with their v3 commands from `docs/fork-divergence.md`): ``` pkill -INT -f 'target-integration/release/igneumd --devnet --nodnsseed --disable-upnp --enable-unsynced-mining --appdir=/tmp/igneum-devnet/node1'; sleep 3; mv /tmp/igneum-devnet/node1 /tmp/igneum-devnet/node1-v4-abandoned && mv /tmp/igneum-devnet/node1-v3-2026-10-04 /tmp/igneum-devnet/node1 cd vendor/igneum-node && nohup caffeinate -dims ./target/release/kaspad --devnet --nodnsseed --disable-upnp --enable-unsynced-mining --appdir=/tmp/igneum-devnet/node1 --rpclisten=0.0.0.0:26610 --listen=0.0.0.0:26611 --addpeer=192.168.68.67:26611 --nologfiles > /tmp/igneum-devnet/node1-v3-again.out 2>&1 & ``` (`pkill -f` with the full v4 command line only, never a bare name: the observer peer and the relay must not match.) Seed: `infra/seed-nodes/switch-v4.sh igneum-seed-1 --back` (stops `igneumd-v4`, starts `igneumd` on the untouched `/var/lib/igneum`). PC: Ctrl+C in the v4 window, then `START-IGNEUM.bat` in the OLD folder (its `igneumd.exe`, `igneum-miner.exe` and `%LOCALAPPDATA%\igneum\devnet` are untouched). Mac app users: the 0.1.0 app and its `devnet` data folder are untouched by 0.2.0. ## What the old chain's data means Nothing is lost. The v3 chain (3 October, 12,000+ blocks, the finality v2 locks, the hash-rate measurements) stays in `/tmp/igneum-devnet/node1-v3-2026-10-04` on the Mac, `/var/lib/igneum` on the seed, `%LOCALAPPDATA%\igneum\devnet` on the PC and `~/Library/Application Support/Igneum/devnet` on any Mac that ran 0.1.0. The v3 binaries (`target/release/kaspad`, `target-finality/release/igneumd`, the old zips and `Igneum-Miner-0.1.0.dmg`) can read it. The live page's v3 rows stay in the `live_*` tables unless `LIVE_TABLE_PREFIX=v4_` is set (then both chains keep their rows apart). The v3 chain is kept aside for reference and rollback; it is not deleted by any step above. The v4 chain starts again from the same genesis with block numbers from 0, so comparisons across the cut (block counts, DAA scores, difficulty) restart as well. ## Untested before the morning | Item | Why | |---|---| | Windows package on the PC | no Windows machine on the Mac side; the PowerShell edits are checked by hand and by a bracket and quote balance pass (no `pwsh` on this Mac: the Homebrew cask is unavailable), not parsed by PowerShell. The CUDA worker's hot swap (`host.cu` with `prepare`, uncommitted working tree) has not run on the PC either; the exit-42 rebuild path stays underneath it | | Mac app sync and mining | tested on this Mac with `MINERS=0` on ports 28900/28901 against a dead peer (launch, version line, EVM address line, node up, Terminal title escape, `--stop` in 4 s, ports free, live devnet untouched); no v4 node was running, so sync and the Metal miner were not exercised by the 0.2.0 bundle (the same binaries mined on the integration test network) | | Seed v4 unit | built and installed, never started; `switch-v4.sh` is new code, its rollback too | | First lock and the hourly swap on the real devnet values | 7,200 and 3,600 DAA; only the 300/60 test values ran |