30 KiB
Igneum protocol specification, section 2: the ordering layer
Spec version 0.1, 3 October 2026. Status of this section: Designed. The base (rusty-kaspa at commit 01b532e8b553523216471682649693af92f0fd16, v2.1.0) is built and has run a 3-node devnet at Kaspa's own parameters (docs/bench-log.md, entry "rusty-kaspa base build and 3-node devnet"). No fork point is implemented. docs/fork-divergence.md does not exist yet; when it does, it records what was changed against this section.
This section is written as a delta on rusty-kaspa. Everything not named here is Kaspa's rule at the forked commit. File and line references are those of docs/fork-map.md, which must be re-checked after any vendor update.
2.1 Block rate and GHOSTDAG parameters
| Parameter | Launch value | Kaspa source | Label |
|---|---|---|---|
| Target block rate | 1 block per DAA second | consensus/core/src/config/bps.rs, Bps::<1> (fork map f1) |
Designed |
| GHOSTDAG k | 18 | Kaspa's k table at 1 BPS (fork map f1) | Designed, taken from Kaspa's table (delta 0.01, network delay bound 5 s, constants.rs:13 to 16) |
| Max parents | 10 | same | Designed |
| Mergeset size limit | 180 | same | Designed |
| Merge depth | 3,600 DAA s (3,600 blocks at 1 BPS) | MERGE_DEPTH_DURATION (fork map e1); check_bounded_merge_depth, post_pow_validation.rs:79 |
Designed, unchanged from Kaspa. Bounds which red blocks a block may merge; it is not a reorg bound (ledger F15, round 3) |
| Finality depth | 43,200 DAA s (12 hours at 1 BPS) | FINALITY_DURATION; virtual_processor/processor.rs:1626 |
Designed. The reorg bound a user relies on whenever no certified checkpoint is newer: the virtual switches to any heavier selected chain forked above this depth (ledger F15). Live finality is section 3 |
| Pruning depth | 108,000 DAA s | PRUNING_DURATION |
Designed; MUST stay above the longest checkpoint gap and MUST NOT pass the latest certified checkpoint (section 3, F3) |
| Coinbase maturity | 100 blocks | bps.rs:119 to 121 |
Designed |
| Block-rate steps | 1, then 4, then 10 per second | design document, decisions table | Designed. Each step is a planned fork with its own test campaign (as Kaspa's Crescendo), taken only once proving lag holds under 60 s. Each step re-derives k, max parents and mergeset limit from Kaspa's table; the DAA-second schedules of sections 1, 3, 4 and 5 do not move |
Every Kaspa fork activation (ForkActivation) is always() on Igneum: there is no history to replay (fork map f2). Own genesis, network prefix, ports and seeders (network.rs:42 to 60, 238 to 252).
The reorg bound (ledger F15, round 3). Merge depth limits what an old block can merge; it does not stop the virtual switching to a heavier selected chain. The depth that stops a switch is the finality point, FINALITY_DURATION, 43,200 DAA s, and that is the bound users and exchanges rely on whenever no certified checkpoint is newer (section 3.9). It is stated to users in past-median time, 12 hours, because DAA seconds run fast under a retarget lag (ledger M14; docs/review/round-3-2026-10-03.md, "Tonight's devnet", where DAA time ran 2.9x the wall clock for 17 minutes). Whether to lower FINALITY_DURATION and accept Kaspa's finality-conflict handling at the lower depth is decided at gate 2 with a simnet test: a heavier private chain forked 2, 6 and 13 hours back is released and the node must refuse it at the depth this section names.
2.2 Proof of work (fork points a1 to a5)
| Fork point | Kaspa today | Igneum |
|---|---|---|
| a1, a2 | PowHash cSHAKE256 then KHeavyHash matrix |
Both deleted. The lottery hash of section 1 replaces them. kaspa_pow::State carries the igneum_pow::Epoch for the header's epoch (program by epoch of the header's DAA score, dataset by day of the header's DAA score, section 1.12) |
| a3 | pow <= target on a Uint256 |
Section 1.10: hash64 <= target64. The mapping from the 256-bit target and the block level for pruning proofs are Open (O-2.4) |
| a4 | Header validated in isolation | Header validation gains a dependency on chain state: the epoch seed (section 4). The seed source is named in the header (section 2.4) so the dependency is checkable from the header plus its past. During IBD and pruning-proof validation (pruning_proof/validate.rs:192) the node MUST be able to derive the program for any header from headers and certificates alone; this is the hardest fork point (fork map, risk High) and is Open until the devnet proves it (O-2.5) |
| a5 | Pre-PoW hash over 12 fields | Adds vote_key_hash, seed_source and proof_ref (section 2.4) so all three are committed by the nonce. Every header-hash test vector changes, genesis included |
What the nonce commits to: the pre-PoW header hash H enters the register initialisation (section 1.6, Open O-1.9). The VDF requirement that the seed checkpoint commits to the full block hash including the nonce (proto-vdf/README.md) is satisfied by Kaspa's Header.hash covering the nonce.
2.3 Difficulty adjustment (fork points c1, c2)
Status: Implemented on the difficulty branch of vendor/igneum-node (SampledDifficultyManager::igneum_difficulty_bits, pure core igneum_target, constants kaspa_consensus_core::igneum::difficulty), measured in sim/difficulty/ and on a 3-node CPU test network (docs/analysis/difficulty-2026-10-03.md). Rule v2 (4 October 2026, docs/analysis/difficulty-2026-10-04-oscillation.md, on devnet-v4): the reference lane is capped at the newest 600 DAA score of the epoch and the sampled long lane is not consulted, behind the height switch difficulty_v2_activation_daa (a consensus parameter, default never, set by the override file; rule v1 below it, v2 from it, no new genesis). Measured on the live devnet: a second RTX 5090 joining 7 minutes into an epoch left the whole-epoch reference lane polluted for the rest of the hour, the short lane disagreed with it by 11 to 25%, and the trigger flipped between the two for 40 minutes (difficulty 102M to 164M against a true 139M, 54 to 81 blocks a minute); the DAG replay reproduces it (std of log difficulty 0.115 against the record's 0.134) and v2 settles it within 10 minutes (0.026, 0 flips, 142.6M). Network parameter difficulty_rule: igneum-dual on every Igneum network, kaspa-sampled selectable through the override file ({"difficulty_rule": "kaspa-sampled"}) so the two rules can be compared on the same genesis. This closes O-2.1: the window is not shortened and the cost-equalising rule of section 1.4.2 is no longer load-bearing for difficulty (it remains desirable for the verifier bound). Revised 4 October 2026 after the attack run of sim/difficulty/attacks/README.md (ledger M23): the timestamp rules are Igneum's own (10 s both ways), every chain step is measured on a sanitised clock stored per header (stores::clock), and the output has a floor; the addendum of docs/analysis/difficulty-2026-10-03.md has the before and after.
Lineage, credited: the long lane is Kaspa's KIP-4 sampled window (difficulty.rs, samples at every 4th block, 661 samples, estimateNetworkHashesPerSecond's blue-work-over-time estimator); the short lane is Zawy's LWMA weighting (linear weights rising to the newest block, solvetimes clamped symmetrically, 20 target times here against Zawy's 6) applied to the same work-over-time estimator instead of LWMA's average target, which is biased while targets ramp (measured: the fast lane stalled at 15x of a 50x step before the change, docs/analysis/difficulty-2026-10-03.md).
The rule
- Every lane estimates the hash rate as work over time (blue-work increment over the step of a sanitised clock), never as average target times average solvetime. The clock: c(genesis) = t(genesis); c(b) = max(c(p) + clamp(t(b) - c(p), -20 T, +20 T), t(b) - 60 T) for selected parent p (
sanitised_clock); the step of b is min(c(b) - c(p), 20 T) (clock_step). The steps of a lane sum to the clock's span over it, so a forged stamp is paid back by the honest blocks after it; the 60 T bound keeps an honest idle gap from being paid back as cap-sized steps for many blocks (measured: without it the polluted case peaks at 275x, with it 11.4x against 8.0x before). The clock is computed at header validation and stored beside blue work; a header whose selected parent carries no clock (a pruning-proof or trusted header) starts from the parent's raw stamp. The long lane keeps the raw sampled span. - Short lane S: the newest 120 selected-chain blocks of the block's own epoch, linear weights. Reference lane R, rule v1 (below
difficulty_v2_activation_daa): for the first 600 blocks of an epoch the epoch window (all its blocks, blended with the parent's implied rate as k : 16); from block 600 of the epoch Kaspa's sampled window restricted to the epoch. Reference lane R, rule v2 (fromdifficulty_v2_activation_daa): the epoch window capped at the newest 600 DAA score of the epoch (chain blocks with DAA score at least max(epoch start, DAA - 600)), blended with the parent's implied rate as k_ref : 16 where k_ref is the DAA span of that window; the sampled window is not consulted. Why: a hash-rate step inside an epoch must leave R within 10 minutes, not at the next epoch boundary; measured 4 October 2026, the whole-epoch R polluted by a 1.85x step 7 minutes into the hour held the trigger flipping for 40 minutes (docs/analysis/difficulty-2026-10-04-oscillation.md). - S takes over when S and R differ by more than 25%; otherwise R rules. The trigger compares the two lanes, not the short rate to target (the literal form chatters once the short window is back on target while the long window is still polluted: measured 1,910 s against 72 s to settle on the polluted-window case).
- The output may make the target fall (difficulty rise) by at most 3% per block and rise (difficulty fall) by at most 10% per block, relative to the selected parent's target, then is bounded to [
MIN_DIFFICULTY_TARGET,MAX_DIFFICULTY_TARGET] (bound_target, both rule paths).MIN_DIFFICULTY_TARGET= 2^128: 2^128 expected hashes per block is unreachable by any network (the whole Bitcoin network is near 2^69 hashes per second, approximate) and keeps every block's work under 2^128, so the 192-bit blue work cannot overflow (measured: without the floor, 85 blocks per second of PoW-less input underflowed the work type after 4,142 blocks and the node panicked,sim/difficulty/attacks/README.mdscenario 7; with it the flood stops at 2^128 after about 2,630 blocks, unit testigneum_flood_at_85_blocks_per_second_stops_at_the_minimum_target). - The parent's target is held for the first 8 blocks of every epoch, including epoch 0: the warm-up starts at block 8, not 600.
| Parameter | Value | Constant | Label |
|---|---|---|---|
| Epoch | 3,600 DAA s, the block's own DAA score (section 1.12) | POW_EPOCH_BLOCKS |
Designed |
| Short window | 120 chain blocks | SHORT_WINDOW |
Measured: 60 is noisier (steady std 0.069 vs 0.039), 180 slower on hops (356 s vs 276 s) |
| Hold at epoch start | 8 blocks | EPOCH_HOLD |
Designed |
| Epoch-lane prior weight | 16 blocks | PRIOR_BLOCKS |
Measured: 8, 16, 32 indistinguishable |
| Long lane from (v1 only) | 600 blocks of the epoch (150 samples) | LONG_MIN |
Kaspa's minimum window, kept |
| Long window (v1 only) | 661 samples at 4-block intervals (2,644 blocks), restricted to the epoch | Kaspa's constants, kept | Designed |
| Reference window (v2) | newest 600 DAA score of the epoch, epoch lane only | REF_WINDOW_V2 |
Measured on the live replay (3 seeds, join window): whole epoch std of log difficulty 0.115 with 22 lane flips; 1,200 blocks 0.108; 900 0.081; 720 0.055; 600 0.026 with 0 flips; 480 0.021. The brief's candidates (short lane 240 or 360, ease clamp 3%, clamp once per DAA second, hysteresis, median of three) each leave 0.09 to 0.13 |
| Rule v2 activation | DAA score, default never (u64::MAX), set by the override file |
difficulty_v2_activation_daa |
Designed: a height switch so upgraded nodes rule v1 until the height and v2 from it on the same chain |
| Trigger | 25% disagreement between S and R | TRIGGER_PERCENT |
Measured: 15% engages on noise (steady std 0.077), 35% slower on hops |
| Harden clamp | 3% per block | HARDEN_PERCENT |
Measured: 2% slows the 50x step-up to 84 s from 62 s, 5% buys nothing on the up step |
| Ease clamp | 10% per block | EASE_PERCENT |
Measured: 3% leaves the 50x step-down at 1,786 s and a 211 s worst gap, 10% gives 1,164 s and 65 s |
| Solvetime cap | 20 target times, both signs, on the sanitised clock | CAP_BLOCKS |
Measured: Zawy's 6 T leaves the 50x step-down at 1,164 s, 20 T gives 657 s and a 35 s worst gap; 132 s gains nothing more and lets an idle gap over-ease (16x peak against 8.5x on the devnet case) |
| Clock lag bound | 60 T (3 caps) behind the header's own stamp | CLOCK_LAG_CAPS |
Measured: no bound peaks the polluted case at 275x, 120 T at 17.8x, 60 T at 11.4x; the forger results are the same at 60 and 120 T |
| Timestamp rules | 10 s future tolerance in isolation (FUTURE_TOLERANCE_MS); in context strictly above the sampled past median (Kaspa's 27-sample window, unchanged) and at least the selected parent's timestamp minus 10 s (BACK_TOLERANCE_MS); the template floor is the same |
FUTURE_TOLERANCE_MS, BACK_TOLERANCE_MS |
Measured: a 50% forger drifts the rate +0.7% to +1.1% with both bounds at 10 s and the clock, -36% to -83% with the bounds alone, collapse with the clock alone under Kaspa's 132 s (sim/difficulty/attacks/README.md). 10 s is half the cap: a forged step and its pay-back both fit inside one capped step |
| Minimum target | 2^128 | MIN_TARGET_BITS |
Designed: unreachable by any network, keeps block work under 2^128 |
What the clamps and caps bound. A forged timestamp moves the sanitised clock by at most 20 s and the honest blocks after it move the clock back to real time, so the sum of steps over any lane telescopes to the real span up to one step at each end; the timestamp rules keep every forgery inside 10 s, half the cap, so the pay-back fits in one step and a 50% forger cannot hold the clock away from real time (measured: +0.7% rate drift at 50% past-stamping, +0.9% future, +1.1% alternating, worst seed +2.7%; the rule of 3 October, which cancelled a forged step against the next honest one, measured 1 - 2a(1 - a) of real time at forger share a and lost 42% to 88% of the block rate, sim/difficulty/attacks/README.md). The per-block clamps bound how fast any estimator error, honest or not, reaches the target. The 3% harden clamp means a 50x step up is corrected in about 130 blocks (a few seconds of wall time at 50x), the 10% ease clamp means a 50x step down costs about 40 blocks of shrinking gaps (11 minutes measured, against 3.4 hours for Kaspa's rule).
Measured comparison
Simulator (sim/difficulty/sim.py, seed 7, honest timestamps; the full tables with overshoot, gap and noise columns are in the analysis document). "Settled" is the first time the 121-block mean rate stays within 10% of target for 100 blocks.
| Case | Kaspa sampled DAA | Monero 720 | LWMA 60 | LWMA 120 | Igneum |
|---|---|---|---|---|---|
| Hash rate x50, settled s | 1,542 | 94 | 105 | 231 | 62 |
| Hash rate /50, settled s | 12,296 | 6,433 | 578 | 1,074 | 657 |
| Hash rate /50, worst gap s | 179 | 187 | 119 | 65 | 35 |
| Epoch step +-30%, settled s (mean of 6) | 1,583 | 456 | 157 | 153 | 144 |
| Epoch step +-30%, first within 10% s | 1,583 | 456 | 91 | 137 | 87 |
| 3x pool hopping every 15 min, settled s | never | 337 (6 of 12 never) | 167 | 184 | 190 |
| 10x pool hopping every 15 min, settled s | never | 284 (6 of 12 never) | 264 | 326 | 212 |
| Polluted window (devnet case), settled s | 2,748 (peak 7.9x) | 124 (peak 15x) | 66 | 110 | 70 |
| Genesis 10x too hard, settled s | never | 287 | 155 | 188 | 322 |
| Genesis 10x too easy, settled s | 1,555 | 95 | 58 | 88 | 59 |
| Steady std of block rate, constant hash rate | 0.012 | 0.037 | 0.131 | 0.092 | 0.038 |
| Steady std of block rate, 10%/h random walk | 0.049 | 0.049 | 0.132 | 0.093 | 0.062 |
| Devnet record (75x step on arrival of the PC), settled s | never (peak 7.6x, 2,340 blocks above 2x) | 136 | 75 | 157 | 79 |
Under attack (sim/difficulty/attacks/README.md, simulator, seeds 7 to 9, the 4 October rule): a forger at 30% or 50% of the hash rate stamping every block at the earliest or latest allowed time, or alternating, drifts the block rate by +0.4% to +1.1% after an hour (worst seed +2.7%, mean difficulty ratio 1.00, worst gap 10 s); the 3 October rule lost 34% to 88% in the same cells. Pool hopping, pulsed rental, the short-lane oscillation, the epoch games and the polluted window are unchanged by the clock (honest stamps make the sanitised clock the raw clock). Test network (3 igneumd nodes from the fix, 50% forger for the last 10 of 15 minutes, docs/analysis/difficulty-2026-10-03.md section 11): earliest allowed stamps, chain rate 0.82 blocks/s honest to 0.88 forging at difficulty 102k to 100k; latest allowed, 0.78 to 0.89 at 102k to 97k; 0 rejected of 986 and 1,028 blocks; the 3 October rule on the same schedule fell to 0.24 at 275k and 0.59 at 170k.
Test network (3 igneumd nodes, CPU miners, 20 minutes, docs/analysis/difficulty-2026-10-03.md): genesis 4x too hard, CPU miners A then A+B+C (x1.51) then A (/1.45) on a loaded machine; measured first-within-10% 132 s, 214 s, 85 s against simulator medians of 263 s, 61 s and 231 s on the same profile; 1,133 blocks, 0 rejected; Kaspa's rule on the same genesis would not have retargeted within the run (600-block dead zone at 0.25 blocks/s). Kaspa's rule live on the same genesis, 10 minutes: 70 blocks, bits unchanged on all 70, 0.08 blocks/s, worst gap 63 s, never within 10% of target.
What this section does not do
Blocks off the selected chain count only through blue work (red blocks carry work the estimator ignores, as Kaspa's estimator does). The chain walk costs up to 600 compact-header reads per block for the first 600 blocks of an epoch and 120 afterwards; re-measure before the 4 BPS step, where the sampled window should replace the walk for the epoch lane too. Real-time targeting (a target that depends on the header's own timestamp) was considered for step-downs and left out: it changes template building and header validation, and the 10% ease clamp with the 20 T cap already brings the 50x step-down to 11 minutes. The 30-day vote-weight window of section 3 is denominated in past-median time since 3 October 2026 (section 3.1 W2, ledger F14), so a retarget lag cannot age it; it is not a retarget change.
2.4 Header (fork point d)
Kaspa's Header (version, parents, hash_merkle_root, accepted_id_merkle_root, utxo_commitment, timestamp, bits, nonce, daa_score, blue_score, blue_work, pruning_point) plus:
| Field | Size | Meaning | Label |
|---|---|---|---|
vote_key_hash |
32 bytes | Hash (the chain's BLAKE2b-based Hash) of the producer's BLS12-381 G1 compressed public key (48 bytes). The first block that uses a key reveals the key itself in the coinbase payload. Vote weight accrues to this key (section 3, W1) |
Designed. The design document says a 32-byte hash; docs/fork-map.md row d says a 48-byte key. This specification takes the hash: 32 bytes x 86,400 blocks/day = 2.8 MB/day of header growth against 4.1 MB with the key |
seed_source |
32 bytes | Hash of the checkpoint block from which the header's epoch program seed was derived (section 4.3) | Designed, proposed here, Open (O-4.3) |
proof_ref |
32 bytes | Hash of the highest aggregated block proof the producer knows | Forward reference. The proof format, what "highest" means and the validation rule belong to the chunked proving protocol, which is out of scope for 0.1. Until that protocol is specified the field is all zeros and unvalidated |
Also edited (fork map d): p2p p2p.proto:76 to 89, convert/header.rs:13, 45; RPC rpc.proto:25, model/header.rs:85, 105, 248; genesis headers; the headers store (serde, re-sync needed); header mass.
Certificates (section 3, C3) and votes (section 3, Q2) travel in the block body, not the header: every block carries the highest certificate its producer knows and every valid vote it has received for the presence window that is not already in its past, and a block whose selected chain does not pass through every certified checkpoint in its past is invalid (post-PoW validation, fork map e2).
2.5 Emission (fork points b1, b2, b3)
Designed (design document, "The token" and "Difficulty, block timing and proving cadence"). No pre-deflationary phase, no month table, no treasury.
| Parameter | Value | Label |
|---|---|---|
| Hard cap | 4,000,000,000 IGN, approached and never reached | Designed |
| Year | 31,557,600 DAA s (365.25 days) | Decided 3 October 2026 (ledger E9, round 3): the specification follows the code, consensus/core/src/igneum.rs SECONDS_PER_YEAR; the earlier 365-day prototype value is withdrawn. The design document says "1 billion a year" |
| Emission in the first two years | 1,000,000,000 IGN per year | Designed |
| Halving interval | 63,115,200 DAA s (2 years of 365.25 days), for ever | Decided (ledger E9), HALVING_INTERVAL_SECONDS |
| Launch ramp | linear from 10% at genesis to 100% at DAA second 2,592,000 (30 days) | Designed |
| Split | 80% block producer, 20% proving pool. A red block inside the DAA window pays its 80% to the miner of the block that merges it and its 20% to the pool | Designed; the red rule Decided 3 October 2026 (ledger E10, round 3), coinbase.rs:102 to 109 |
| Base unit | 10^18 base units per IGN on the testnet and mainnet (the EVM's wei, so a consensus amount and an EVM balance are one number and the exec bridge is the identity); the devnet keeps 10^8 (Kaspa's sompi, what it mints). A genesis parameter, Params::base_unit_decimals, per network, in the consensus digest once it leaves 8. Every amount is a u128 behind unit::Amount, so nothing depends on the cap fitting any type. Decided 6 October 2026 (O-2.6; the project lead's word "18", docs/plans/ledger-decisions.md row 5; the coordinator's approval of the design, 23:2x UK; design docs/design/base-unit.md). Implemented end to end in the fork on 7 October 2026 (branch decimals, phase B: the UTXO value and entry, the coinbase and its payload, the fees, the mass rule, the stores, the p2p and gRPC wires, the mempool, the exec bridge and the script numbers all carry unit::Amount at the network's width: 8 bytes at 8 decimals so the devnet is byte-identical, 16 bytes at 18; unit::IMPLEMENTED_DECIMALS = [8, 18]; the mainnet and testnet genesis coinbase payloads carry a 16-byte subsidy, so igneum-testnet-1's genesis is re-cut on that layout). The two-node gate at 18 decimals passed on 7 October 2026, 03:13 UK (tools/fleet/base-unit-gate.sh, design section 8). Ships as 0.3.18 (0.3.17 went without it). The emission schedule (Params::emission, the economy lane) is written at 8 decimals and rescaled to the network's unit, so one schedule reads the same at every unit (the 18-decimal rate is exactly 10^10 times the 8-decimal floor. Was: Open, 8 decimals in the code, docs/fork-map.md b2 cap_sompi = 4e9 x 1e8 |
Emission per DAA second at DAA score t:
E(t) = ramp(t) * floor(10^9 * UNIT / 31,557,600) >> floor(t / 63,115,200)
ramp(t) = min(1, 1/10 + 9/10 * t / 2,592,000) evaluated in integers as a rational with denominator 25,920,000
The pre-ramp rate is 31.68808781 IGN per DAA second at 8 decimals (BASE_SUBSIDY_PER_SECOND_SOMPI = 3,168,808,781, asserted by the code's own test) and 31,688,087,814,028,950,237 base units at 18 (igneum::base_subsidy_per_second_units(18), asserted; the floor is taken at the finer unit, so 18 decimals mints 4,028,950,237 wei a second more than 10^10 times the 8-decimal rate, and the display agrees to 8 digits); about 31.688 IGN at any unit (Decided, ledger E9). The geometric series sums to 4 x 10^9 IGN; the ramp withholds 0.45 x 2,592,000 / 31,557,600 x 10^9 = about 37 million IGN that are never minted, and integer floors withhold a negligible further amount, so the cap is a strict bound.
Emission is keyed to DAA score, not to timestamps: E is a function of DAA score and miner-chosen timestamps cannot mint (hostile review table, "Emission per wall-clock second invites timestamp games"). E is paid per block, so coins are blocks times E: when the controller lets the block rate run above target, short-run emission runs above schedule by the same factor, as on every proof-of-work chain, and the cap is unaffected because the halving schedule and the ramp are in DAA seconds (Decided 3 October 2026, ledger E10, round 3; the earlier sentence "more blocks never means more coins" is withdrawn, and the devnet of 3 October 2026 minted 4.7x the schedule for eight minutes during a retarget lag, docs/review/round-3-2026-10-03.md, "Tonight's devnet"). Payment is through the merging block's coinbase as in Kaspa (coinbase.rs:97 to 142): the coinbase of block B pays, for each blue block M in B's mergeset, E(daa_score(B)) split 80% to M's miner and 20% to the proving pool; for each red block in B's mergeset that is inside the DAA window, the same E split 80% to B's own miner and 20% to the pool (coinbase.rs:102 to 109, Kaspa's rule, Decided, ledger E10); a red outside the DAA window earns only its coinbase-level fees, of which Igneum has none (fees are in section 5). How the DAA-score increment is apportioned among the blues of one mergeset at higher block rates is Open (O-2.7); at 1 BPS the mergeset is usually one block.
The 20% proving share is paid to the prover set recorded for the proven block, which is known 20 to 60 s after the block (design document, "Proving lag"). The coinbase payload format gains prover outputs (fork map b3, risk High). The rule that the pool is paid as a fixed amount per block divided among shards by consensus proving cost is in section 5.3.
Fees are not in the coinbase; they are in the execution layer (section 5).
2.6 Duplicate inclusion for the EVM layer
Designed (hostile review table, "Parallel blocks include the same transaction"). Blocks carry transactions only and make no claim about state. The execution layer orders transactions by the GHOSTDAG ordered sequence (the selected chain's mergeset order, Kaspa's), and:
- The first copy of a transaction (by transaction hash) in the ordered sequence executes and pays its fees.
- Every later copy is deduplicated before execution and before proving. It executes nothing and pays nothing.
- A later copy still occupies block space (mass) in the block that includes it, so the including miner bears the cost.
- Transactions from one account are subject to the EVM nonce rule over the same ordered sequence: a transaction whose nonce is not the account's next nonce at its position is skipped, not failed, and pays nothing. This is Kaspa's "skip conflicting spends" pattern applied to the EVM.
Block number, timestamp, blockhash, coinbase and prevrandao over the ordered sequence are fixed in section 7.1 (ledger P5, closed 3 October 2026).
2.7 What the devnet showed and did not show
Measured (docs/bench-log.md, devnet entry): three kaspad nodes at Kaspa's devnet parameters (10 BPS, k 124) held identical block counts, DAA scores and sink hashes at 18 of 19 ten-second samples under a 27 MH/s CPU miner; the DAA raised difficulty from genesis bits at block 6,018 and the block rate fell from 56 to 62 blocks/s toward the 10 BPS target. GHOSTDAG k was not exercised (a single serial miner never produced parallel blocks); a second miner is the next step. Nothing in that run used an Igneum parameter.
2.8 One parameter set per network (ledger G12 and X18, 4 October 2026 night)
Status: Implemented on the node's fud-consensus branch, pending rollout.
- A node's consensus parameters come from its network's constants (
Params) and, on devnet and simnet only, from an override file. The override file is refused on mainnet (since devnet v4) and the environment never sets a consensus parameter outside devnet and simnet:IGNEUM_POW_EPOCH_BLOCKS,IGNEUM_POW_EPOCH_LEADandIGNEUM_POW_DAY_MSare applied on devnet and simnet after the file, and on mainnet and testnet are ignored with one line at start ("Ignoring ... the environment never sets a consensus parameter outside devnet and simnet"). The PoW schedule the node runs is installed fromParamson every network at start; nothing in the node reads the environment for it later. A miner takes all three schedule values from the block template (pow_epoch.day_msjoinedepoch_blocksandepoch_lead), never from its environment. - The params digest. Every node computes
Params::consensus_digest(): BLAKE2b-256 under the domainIgneumParamsDigestover, in a fixed tagged order, the network name, the genesis hash, bits, timestamp and DAA score, the difficulty windows and rule, the coinbase and mass limits, the lane limits, the storage mass parameter, the deflationary schedule,skip_proof_of_work, the block level and proof parameters, every blockrate field (target time, k, sample rates, parents, mergeset, merge depth, finality depth, pruning depth, maturity), the crescendo activation, every finality field (3.10), the PoW schedule and the three activation heights (difficulty_v2_activation_daa,proving_v0_activation_daa,finality_v3_activation_daa). Not covered: seeders, ports, the maximum difficulty target (a constant of the code) andtimestamp_deviation_tolerance(dead, read by nothing). The node prints the digest at start. - The handshake. The p2p version message carries the digest (
paramsDigest, field 11). A peer whose digest differs is refused at the handshake with one WARN naming both digests ("Refusing peer ...: consensus params digest mismatch, local X remote Y (the peer's override file, environment or build differs)") and the errorParamsDigestMismatch; no flow is registered and no block is exchanged. A peer that sends no digest (a build older than this rule) is refused on mainnet and testnet; on devnet and simnet it is let in with a WARN per connection while the devnet rolls, an allowance to remove once every devnet node carries the digest. - Why: round 4 found that two nodes with two override files connected and diverged per field, a finality mismatch only as a WARN after the fact (X18), and that the environment set the epoch length on every network including mainnet (G12). Unit tests
consensus_digest_covers_every_consensus_field_and_nothing_else(every consensus field moves the digest, the dead field does not, the networks differ, the same file gives the same digest) andenv_pow_schedule_is_devnet_and_simnet_only; the two-node run indocs/bench-log.md, "round-4 consensus items" (digest run).