igneum/docs/plans/finality-leave.md

8.6 KiB

The departure announcement (spec 03 W7): plan for 0.3.16

Written 6 October 2026, 21:2xZ, by the finality owner, during the live devnet's finality pause (18:40Z to about 20:41Z). Cause and candidates: docs/analysis/horizon/finality-and-weight.md (Horizon lane 3). Incident: docs/bench-log.md "6 October 2026, 18:40Z to 20:41Z, the live devnet's finality pause"; ledger F27 and the F19 correction. Code: fork branch finality-pause-node on 4c6b129d (release 0.3.14's node), worktree /Users/joshm/Projects/igneum-wt-finality-pause/vendor/igneum-node-finpause.

1. What changes

Piece Where What
The leave item consensus/core/src/finality.rs Leave { daa, pubkey, signature } (152 bytes) signed under DST_LEAVE over `"igneum-leave-v1/"
The switch consensus/core/src/config/params.rs finality_leave_activation_daa on Params and OverrideParams (never on mainnet, devnet, simnet; 0 on the testnet, which is born on every switch), in the digest with leave_delay ONLY ONCE SET (the 0.3.15 rule for new fields, applied at the 0.3.17 rebase: the binary rollout leaves the devnet digest at c562d70e...; the digest moves when the override file sets the switch)
The rule consensus/src/processes/finality.rs leaves_at (like bans_at: a key has left at C when a block in C's past carries its leave and daa(e) + leave_delay <= daa(C) < daa(e) + window); voters_at applies leaves with the bans (the sliding table); frozen_floor subtracts the keys that left at C from the frozen table's denominator and their signatures from its numerator (Q5); ingest_leave (one record per key, carriers dated), submit_leave, carriage in template_section LAST and only once the rule is active; persisted state layout 3 (leaves), layouts 2 and 1 read and upgraded
p2p protocol/flows/src/v10/finality.rs, flow_context.rs, v17/, service.rs KIND_LEAVE = 3; protocol version 17; broadcast_finality_leave to peers at 17 or later only (an older relay flow returns a protocol error on an unknown kind and drops the peer); every peer still reads leaves from blocks
RPC rpc/* submitFinalityLeave (hex of the wire bytes; accepted, reason), op 157, proto ids 1129 and 1130
The miner igneum/miner/src/main.rs mine sends the leave for every identity when secs run out unless --no-leave; igneum-miner leave <grpc url> <label> by hand (a node back from an unplanned stop, an operator retiring a key)
The node's first lines kaspad/src/daemon.rs Finality leave rule from the override file: ... from checkpoint DAA score N and the Finality v2 (...) line ends with leave rule (W7, delay 3600 DAA) from checkpoint DAA N

Nothing in block validity changes. A block carrying a leave is as valid as before on every version; a 0.3.14 or 0.3.15 node's section decoder stops at the leave's tag and keeps the votes, certificates and evidence before it, which is why the template puts leaves last. Before the activation no node carries a leave in a block, so an old node never meets the tag; after it every node is new by the 95 percent signal rule (or the floor height), so the voter tables agree.

2. Why this and not the others

Section 4.2 of the Horizon analysis: a rule that removes weight on what a view has NOT seen (a vote missing for T hours, a decaying denominator, a hysteresis floor) gives each side of a partition a different denominator and reopens the double lock (467 to 473 conflicting locks in the 50/50 split under fast decay; the 13.3 percent equivocator bound of 3 October back under hysteresis). A leave is seen, not inferred: a partition side never sees the other side leave. In the simulator (3 seeds, mainnet scale) the leave rule gives a first lock 1 hour after a 34, 45 or 50 percent departure where v3 waits 30 days, 0 conflicting locks in every partition, eclipse and equivocator row, and an attacker who buys keys to make them leave is worse off than one who signs with them (w + L must still reach 2/3).

3. Gate (Devnet 2, the 0.3.16 crossing)

Step Pass line
Unit tests on igneum-build-1 (cargo test -p kaspa-consensus --lib finality, -p kaspa-consensus-core --lib) a_signed_leave_ends_the_pause_after_the_delay_and_a_silent_departure_holds_it_for_the_window passes: the known-failed case (a silent 40 percent pauses until the frozen table expires) and the fixed case (lock within leave_delay plus a few checkpoints), and a leave before activation removes nothing; leave_item_round_trips_and_an_older_decoder_keeps_the_items_before_it passes
Digest test ("finality leave height", ...) and ("finality leave delay", ...) move the digest; the fast-time profile keeps the devnet switch
Devnet 2, the node cut's standing checks a MINING 0.3.16 node beside a 0.3.15 node on the live file for ten minutes: the old node accepts the new node's blocks, the hub's reject count unchanged (no leave is carried before activation); a 0.3.16 node restarted mid-window re-syncs from an old peer
Devnet 2, the W7 case (the fast-time harness, tools/finality-attacks, a new leave.mjs on the v3 runner's shape: N nodes, the switch at 0 in the override, leave_delay 10 in the 60x profile) 45 percent of weight stops with leaves (their mine runs end): first lock within leave_delay + 1 checkpoint of the first carrier; the same 45 percent stopped with --no-leave: no lock until the frozen table expires (the known-failed case); 0 conflicting certificates in the 50/50 and 60/40 splits and the 34 percent eclipse (the s4 shape) with leaves in flight
Activation on the live devnet by the 95 percent signal rule with a floor height, miners first, hands last; the override object gains "finality_leave_activation_daa": N7

The harness case is designed here and not yet run: it needs 0.3.16 binaries on the harness host (the box builds Linux binaries; the Mac's harness runs macOS ones), so it is the first thing the Devnet 2 crossing does.

3a. The testnet genesis (the project lead's decision, 6 October 2026 night: "leave on from genesis")

Field igneum-testnet-1 genesis value Why
finality_leave_activation_daa 0 the testnet is born on every switch (TESTNET_PARAMS, consensus/core/src/config/params.rs); a chain with no pre-switch history has nothing to protect, and W7 is a launch requirement (a 30-day pause on a silent departure is the alternative)
finality.leave_delay 3600 one hour of DAA, the merge-depth bound; the same value on every network

Both go into the testnet's genesis table in docs/plans/testnet-go.md beside finality_v3_activation_daa: 0, in the 0.3.17 object. The testnet digest moves with them (the field enters the digest once set, and on the testnet it is set from genesis), which is fine before go: nothing mines until the go checklist passes. On the live devnet the switch stays at never until the 95 percent signal; the fast-time profile keeps never.

4. What it does not do, stated

A crash, a power cut or a region going dark still age out over the window under Q5 (two hours on the devnet, 30 days on mainnet); the app's Stop and the fleet library send the leave, and a node back from an unplanned stop sends it on return (igneum-miner leave), which shortens the pause from that point. Staging a departure under 10 percent an hour keeps finality on with no protocol change (every lock re-freezes the table); the fleet rule of 20:0x UK stands beside this. Its interaction with W5 succession (O-3.11) and with a key that leaves and keeps mining (its blocks count again once the leave is a window old; before that it is out of every table) is written into spec 03 W7 and needs the cryptographer's read.

5. Per tier

Tier What changes
Home miner, rig (Windows, Linux, macOS; any card) the app sends the leave on Stop and on a clean update restart, so a clean exit never holds the network; a crash still ages out over 30 days unless the operator sends the leave on return, which the app will offer
Pool one leave per server on maintenance; a pool that moves servers without one holds the network's finality for a window
Holder fewer and shorter pauses: an orchestrated departure of any size ends its pause in an hour
Rollup customer, bridge the same; anything that waits for a certified checkpoint waits an hour at most for a clean departure instead of a month
Node operator one more item type, one more RPC, protocol version 17; the persisted finality state upgrades itself on first start