The sweep (main's item 1): 199 tracked text files, 783 lines. The founder's full name, first name and possessive become "the founder" (sentence starts capitalised); the lowercase operating-system user name in WSL paths and commands becomes <user>; the second owner login becomes "the second owner login"; the three earlier businesses and the two other brands become "the other business", "the earlier entity", "the earlier business" and "another brand"; the Chrome profile rule names the igneum.network profile, not the profile's label. The standing commit login igneum-labs is not a founder term here: the fresh-repository step renames it in the history (docs/plans/history-rewrite.md, tools/repo/fresh-repo.sh). The patterns never appear in plain text in the tree (a plaintext list would be the hit): tools/ci/founder-strings.b64 (perl regex, tab, a sample per row) is read by tools/ci/founder-strings-check.sh (every tracked text file, perl, known-failed first: the self-test plants each row's sample in a fixture and the hit must name the file), by tools/community/discord-hooks.mjs (the guard's founder and business rows; the test takes its fixtures from the samples) and by tools/repo/fresh-repo.sh (the business names of the rewrite rules). site/forbidden-strings.txt carries the same patterns as b64: lines, decoded case-insensitive by site/scrub.mjs and tools/ci/launch-gates-check.mjs (whose fixture now plants an encoded made-up name). The check runs in the gate's tree checks on every merge. Not in this commit, by main's word: the 105 commit messages and 40 personal-identity commits that need the history rewrite (listed, not run), and the secrets found by gitleaks over the history (reported with owners). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
162 lines
14 KiB
Markdown
162 lines
14 KiB
Markdown
# Finality rule v3 on the live devnet: rollout plan, 4 October 2026 (evening)
|
|
|
|
Prepared by the finality engineer after the founder's "we need to fix these serious issues before making things public"
|
|
(ledger F21 and F22). Nothing in this file has been run on the devnet, and the 12-node Hetzner network of the morning
|
|
was destroyed at 15:30 UTC, so there was no cloud rehearsal; the measurements are the simulator (`sim/results_v2.md`,
|
|
"Rule v3"), the node's unit tests and the fast-time 3-node network with emulated 300-ms links (`docs/bench-log.md`,
|
|
"finality rule v3"). The main session runs section 4 in order. Background: spec 03 (Q4, Q5, 3.3.1, 3.7 items 2 and 9,
|
|
3.10, 3.11), `docs/fud-ledger.md` F21 and F22, `infra/cloud-devnet/results/2026-10-04/f22-vote-timing.md`. Shape and
|
|
rules follow `docs/plans/difficulty-v2-rollout-devnet.md`: the PCs get igneumd only through an OTA app version, and the
|
|
activation height leaves at least three hours from the manifest publish.
|
|
|
|
## 1. What changes and what does not
|
|
|
|
Only `igneumd` changes. Rule v3 is one height switch, `finality_v3_activation_daa`, read from the override file like
|
|
`difficulty_v2_activation_daa`; the default on every network is `u64::MAX` (never). A v3 node applies rule v2 to every
|
|
checkpoint whose block's DAA score is below the height and rule v3 from the first checkpoint at or above it:
|
|
|
|
- F21, the frozen weight table (spec Q5): a certificate locks only if its signers also hold two thirds of the weight
|
|
table at the last locked checkpoint on the chain, at that table's weights, while that checkpoint is less than one
|
|
weight window old (7,200 DAA on the devnet, 30 days on mainnet). A side of a partition cannot fill that table, so
|
|
no side under two thirds locks until a full window has passed without a lock; the heal resumes on one chain.
|
|
- F22, the certificate fold (spec Q4): the first certificate still forms at quorum; once every voter has signed, or
|
|
`certificate_fold` DAA seconds (3) after the determination, the node rebuilds it from every vote seen and gossips
|
|
the heavier one, and every node replaces a held certificate with a verified heavier one over the same block.
|
|
|
|
Nothing in block validity changes: a certificate verifies against the table at its own checkpoint as before, a block
|
|
carrying a lighter certificate is as valid as before, and the chain, the genesis and the databases stay. The persisted
|
|
finality state is unchanged (the fold clock is in memory). The miners follow templates and need nothing. A node that
|
|
reaches the height WITHOUT the field keeps rule v2: in a connected network it locks the same checkpoints (both rules
|
|
pass there) and ignores heavier certificates, so there is no fork; under a partition longer than `W / (3R)` such a
|
|
node could lock alone where the v3 nodes pause, which is the fork the switch exists to prevent, so every node must
|
|
carry the same height before it arrives: Mac node 1, the observer node, the seed, PC 1 and PC 2.
|
|
|
|
The new `FinalityParams` field `certificate_fold` has a serde default (3), so every existing override file still
|
|
parses; `infra/fast-time/override-60x.json` carries both new fields explicitly.
|
|
|
|
## 2. The binaries (built 4 October 2026, evening, on the loaded Mac at nice 19, 4 cargo jobs)
|
|
|
|
Source: `vendor/igneum-node-finality` (branch `finality-fixes`, from the proving head `8c0cff15`, which is difficulty v2
|
|
plus proving v0), commit `6aa69a45`. Each binary has its own target directory seeded by APFS clone from the
|
|
proving, v2-linux and v2 directories; the live binaries (`target-integration`, `target-linux`, `target-v2`, the seed's
|
|
`/opt/igneum/v4/bin`) were not touched.
|
|
|
|
| Platform | Path | sha256 (igneumd) | Build | Verified |
|
|
|---|---|---|---|---|
|
|
| Mac arm64 | `vendor/igneum-node/target-finality/release/igneumd` | `fe982a1d5be125ce89eafec87c5d0495373a4dc996614203ecf9a1555f8b2842` | release build, kaspad and igneum-miner, incremental on a target directory cloned from the proving worktree (about 10 min behind other agents' builds) | `--version` = igneumd 2.1.0; started on a private suffix with `{"finality_v3_activation_daa": 123456}` and printed `Finality rule v3 from the override file: active from checkpoint DAA score 123456`; drove the fast-time 3-node runs of the bench-log entry |
|
|
| Linux x86-64 (glibc 2.36) | `infra/cross/out-finality-v3/igneumd` (from `vendor/igneum-node/target-finality-linux`) | `7c100fc2b372139a52ed7ec47a579aec59ce5e3a5502b5f39065480d985a5c13` | 2,055 s (34 min) with cargo-zigbuild, zig 0.17.0, target directory cloned from `target-v2-linux`; 47,156,904 bytes | ELF x86-64 PIE, glibc 2.36; `strings` carries the field name, the switch line and the frozen-table LOCKED text; `version.txt` names 6aa69a45. Not run on a Linux host (the Hetzner network is gone) |
|
|
| Windows x86-64 | `vendor/igneum-node/target-finality-win/x86_64-pc-windows-gnu/release/igneumd.exe` (50484224 bytes) | `cc1d1001b5b39bba2f4890e947f049292dc7cd7fda472e6c7f65f5a3d018f4db` | 12 min 28 s (cross-build.sh, mingw, 4 jobs, target directory cloned from `target-v2`) | PE32+ x86-64; `strings` carries the field name, the switch line and the frozen-table text; the same DLL import set as the shipped v2 exe; it cannot run here |
|
|
|
|
`igneum-miner` was built alongside on each platform (the proving-branch miner: vmine, sign-record, the 3bfe346f
|
|
guards); it carries no v3 change and the devnet miners can stay on what they run.
|
|
|
|
## 3. The activation height N3, and how every node learns it
|
|
|
|
Same rule as difficulty v2 (the founder, 4 October 2026): `N3 = DAA score at the manifest publish + 10,800`, chosen as
|
|
`DAA now + 14,400` when the line is edited and checked (`N3 - DAA >= 10,800`) at publish. The switch keys on the
|
|
DAA score of the checkpoint block, which trails the sink by 20 to 50 blocks, so the first v3 checkpoint comes about
|
|
a minute after the sink crosses N3.
|
|
|
|
The packaged line carries both switches, the difficulty one at whatever value is live by then (it is empty in this
|
|
worktree; the difficulty v2 rollout sets it first):
|
|
|
|
```
|
|
NODE_OVERRIDE_PARAMS='{"difficulty_v2_activation_daa": N, "finality_v3_activation_daa": N3}'
|
|
```
|
|
|
|
The same object goes verbatim into the override files of Mac node 1, the observer node and the seed. The DAA score:
|
|
`IGNEUM_RPC=ws://127.0.0.1:28640 python3 infra/cloud-devnet/node/wrpc.py call getBlockDagInfo | python3 -c 'import json,sys; print(json.load(sys.stdin)["virtualDaaScore"])'`.
|
|
|
|
## 4. The order, with the exact commands
|
|
|
|
All Mac commands from `/Users/joshm/Projects/igneum`. Steps 1 to 3 are the package; 4 to 6 the hand-run nodes;
|
|
7 the watch. The difficulty v2 plan's steps apply with the binary paths below and the two-field override object.
|
|
|
|
### Step 1: fix N3 and cut the app version
|
|
|
|
```
|
|
sed -i '' "s/^NODE_OVERRIDE_PARAMS=.*/NODE_OVERRIDE_PARAMS='{\"difficulty_v2_activation_daa\": N, \"finality_v3_activation_daa\": N3}'/" packaging/mac/packaged-config.sh
|
|
# bump app/igneum-app/Cargo.toml, app/windows/version.h, packaging/windows/resources/igneum-app.rc; commit as igneum-labs, push
|
|
```
|
|
|
|
### Step 2: the Windows payload inputs
|
|
|
|
The inputs were staged on 4 October 2026 into a scratch downloads folder (section 7a) and NOT deployed. To publish
|
|
them to the real downloads host, when the founder says so:
|
|
|
|
```
|
|
IGNEUM_WIN_RELEASE=vendor/igneum-node/target-finality-win/x86_64-pc-windows-gnu/release IGNEUM_NODE_SRC=vendor/igneum-node-finality packaging/windows/push-inputs.sh
|
|
```
|
|
|
|
### Step 3: the Mac DMG and the manifest
|
|
|
|
```
|
|
NODE=vendor/igneum-node/target-finality/release/igneumd packaging/mac/build-dmg.sh
|
|
python3 -c 'import json; print(json.load(open("packaging/mac/build/dmg/Igneum Miner.app/Contents/Resources/igneum-app.json"))["node_override_params"])'
|
|
packaging/ota/publish-manifest.sh --version <v> --mac packaging/mac/dist/Igneum-Miner-<v>.dmg \
|
|
--notes "finality rule v3 from checkpoint DAA score N3" --activation-height N3 --deadline-note "finality v3" --deploy
|
|
packaging/windows/fetch-ci-artifacts.sh --deploy
|
|
```
|
|
|
|
### Steps 4 to 6: the observer node, the seed, Mac node 1
|
|
|
|
Exactly the difficulty v2 plan's steps 4 to 6 with `target-finality/release/igneumd` (Mac) and
|
|
`infra/cross/out-finality-v3/igneumd` (seed, sha256 of section 2) and the override file
|
|
`{"difficulty_v2_activation_daa": N, "finality_v3_activation_daa": N3}`. Each node's first lines must show BOTH
|
|
`Difficulty rule v2 from the override file: active from DAA score N` and
|
|
`Finality rule v3 from the override file: active from checkpoint DAA score N3`, and the `Finality v2 (...)` line ends
|
|
with `rule v3 (frozen table, certificate fold) from checkpoint DAA N3`.
|
|
|
|
### Step 7: the watch
|
|
|
|
| When | Where | Expected |
|
|
|---|---|---|
|
|
| after each restart | the node's first lines | both switch lines, N3 the same on every node |
|
|
| before N3 - 1,800 | log intake STATUS lines, the live page | both PCs on the new app version |
|
|
| N3 to N3 + 600 | node 1 and the observer logs, `getFinalityCheckpoints` | locks continue at the 30-s cadence; every LOCKED line from the first v3 checkpoint ends with `..% of the table frozen at lock <j>`; "certificate ... folded" lines appear within 3 s of determinations; the lock margin (`% of total`) rises from the 66.8 to 89% band of the morning toward the vote count of every connected key |
|
|
| N3 to N3 + 3,600 | the same, and the observer's finality events | 0 "CONFLICTING certificate", 0 "held by the frozen table" at info level (that line is debug), no node stuck; `finality_active` stays true |
|
|
| the next PC restart or miner outage | the same | a checkpoint with a key missing still locks when the rest hold two thirds of the frozen table; a 10-minute outage of a third of the keys, as the morning's hop.sh restarts caused, now pauses finality until they return or the window passes (3.7 item 2) |
|
|
|
|
If a node forks at N3 (its locks stop while others' continue, or it logs CONFLICTING): its override file lacks N3 or
|
|
carries another value. Fix the file, restart; the database re-syncs (same chain).
|
|
|
|
## 5. Rollback
|
|
|
|
Before N3: remove the field on every node and restart; nothing has happened. After N3: a node restarted without the
|
|
field evaluates rule v2 on every checkpoint from then on; in a connected network it agrees with the v3 nodes on every
|
|
lock, so a rollback is a plain restart of each node with the field removed, in any order. What a rollback gives up is
|
|
the pause under a partition (the morning's fork at 205 s of a 3/3 split comes back) and the folded certificates.
|
|
|
|
## 6. Pieces this plan adds to the repository
|
|
|
|
| Piece | What |
|
|
|---|---|
|
|
| node `consensus/core/src/config/params.rs` | `finality_v3_activation_daa` in `Params` and `OverrideParams` (default never on every network), test `override_params_carry_the_finality_v3_activation`, the fast-time test reads `IGNEUM_FAST_TIME_FILE` |
|
|
| node `consensus/core/src/finality.rs` | `FinalityParams::certificate_fold` (devnet 3, mainnet 6, serde default 3) |
|
|
| node `consensus/src/processes/finality.rs` | `frozen_table`, the Q5 test in `evaluate`, the fold round, certificate replacement in `ingest_certificate`, the two unit tests |
|
|
| node `kaspad/src/daemon.rs`, `consensus/src/consensus/services.rs`, `test_consensus.rs` | the switch's log line, the manager's activation argument, the test accessor |
|
|
| `infra/fast-time/override-60x.json`, `README.md` | both new fields, the rows explaining why the fold is not divided by 60 |
|
|
| `sim/finality_v2.py`, `sim/results_v2.md` | `P.frozen`, `rule_v3`, scenario M and its tables |
|
|
| `tools/finality-attacks/v3.mjs`, `lib/net.mjs`, `vote-timing.py`, `README.md` | the v3 runner, the env-driven library with a proxy delay, the cloud-log analysis |
|
|
| `infra/cross/out-finality-v3/` | the Linux v3 binaries and `version.txt` (not committed: binaries) |
|
|
|
|
## 7. What was rehearsed and what was not
|
|
|
|
No cloud rehearsal: the Hetzner network was destroyed before this work started. Before the devnet roll, the cheapest
|
|
real-network check is to recreate it and repeat the morning's partition with v3 on every node:
|
|
|
|
```
|
|
infra/cloud-devnet/create.sh && infra/cloud-devnet/provision.sh # 12 nodes, own chain (docs/plans/cloud-devnet.md)
|
|
# stage infra/cross/out-finality-v3/igneumd, write {"genesis_bits": ..., "difficulty_v2_activation_daa": 0, "finality_v3_activation_daa": 0}
|
|
# (rollout-v2.sh's override_json writes only the difficulty field; add the finality one before using it for v3)
|
|
infra/cloud-devnet/experiments/partition.sh sin 10 # expect: 0 locks on the minority, every interval on the majority, certificates with 12 of 12 votes
|
|
```
|
|
|
|
What stands in for it: the fast-time 3-node network of the bench-log entry (300-ms links, the 3/3 split past the old
|
|
bound with the heal, the 70/30 split, the fold before and after), the simulator's scenario M, and the unit tests.
|
|
|
|
### 7a. The Windows payload inputs (staged, not deployed)
|
|
|
|
`packaging/windows/push-inputs.sh --no-deploy` was run with `IGNEUM_WIN_RELEASE` at the v3 Windows release directory,
|
|
`IGNEUM_NODE_SRC` at the finality worktree and `IGNEUM_DLSITE` at a scratch copy of the downloads folder, so the live
|
|
downloads folder and host are untouched: `payload-inputs.zip` 64,294,811 bytes, sha256 `7c9d21e3df26fabf1761fa50fc627e628c46af80255d86429ef7d9d6ba5653a9`, manifest `node_source_commit` 6aa69a45, `igneumd.exe` cc1d1001b5b39bba2f4890e947f049292dc7cd7fda472e6c7f65f5a3d018f4db (50,484,224 bytes), `igneum-miner.exe` f1f9a7d96460e4d32e23aa3562c64058c5a2d4d87bb5058d287a697dc360fc1d (10,307,072 bytes), with the same CUDA and OpenCL workers, NVRTC and mingw DLLs as the morning's payload. The zip sits in the scratch folder only; the deploy command the script printed is `cd <dlsite> && npx vercel@latest --global-config ~/.config/igneum/vercel deploy --prod --yes`, to be run against the real folder after the step 2 command above, when the founder says so.
|