proto-metal --serve compiles igneum_hash_bound at runtime and mines jobs from stdin (init words in buffer 3, program and dataset cached per seed); proto-cuda and proto-opencl host serve modes from the pack's kernel_bound.cu / .cl with a seed guard; --vendor device filter for OpenCL. windows-miner/: START-MINING.bat + start-mining.ps1 (GPU and tool detection, pack export, cached builds, MINERS identities per vendor, status every 30 s, uploads every 60 s, rebuild on seed change, Ctrl+C summary), README.txt, make-package.sh (igneum-mine-test.zip with a cross-compiled miner). docs: fork-divergence devnet v1, bench-log entry with the CPU, Metal and overnight numbers. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
54 lines
15 KiB
Markdown
54 lines
15 KiB
Markdown
# Igneum fork divergence from rusty-kaspa
|
|
|
|
Fork: `vendor/igneum-node`, a git clone of `vendor/rusty-kaspa` at v2.1.0 commit `01b532e8` (22 Sep 2026). Every Igneum change is a commit on top of that base, one per subject, so `git log 01b532e8..HEAD` in the fork is the full list. This file is the reading guide: what each change touched, why, how risky it is, and what to do when upstream moves. `docs/fork-map.md` is the plan this implements; row ids there (a1 to f2) are cited below.
|
|
|
|
Status: devnet v1, mining layer on the real header-bound lottery hash (3 Oct 2026, later the same day as v0): CPU and GPU miners, three workers (Metal, CUDA, OpenCL). Finality, VDF seeds, the zkEVM and the proving layer are not in the fork yet.
|
|
|
|
## The table
|
|
|
|
| File (vendor/igneum-node/) | What changed | Why | Risk | Upstream-merge note |
|
|
|---|---|---|---|---|
|
|
| `consensus/core/src/header.rs`, `consensus/core/src/hashing/header.rs` | `Header` gains `vote_key_hash: Hash` (32 bytes) as the last field; `new_finalized` takes it; `hash_override_nonce_time` writes it after `pruning_point` so the header hash and the PoW commit to it | Finality rule v2: vote weight is blue blocks per BLS vote key. The hash of the key rides in every header now so the weight table can be built from headers alone later (fork-map d) | Low by depth, high by spread: every header hash and every genesis hash moved | Any upstream change to `Header` or to the hashing order conflicts here. Re-derive the four genesis hashes after merging (`consensus/core/src/config/genesis.rs` tests print them). |
|
|
| `consensus/core/src/config/genesis.rs` | All four genesis hashes recomputed; devnet genesis has payload `igneum-devnet`, timestamp 2026-10-03T00:00Z, nonce 0; bits `0x1d100000` (2^28 expected hashes per block, hash `edc4fa84...fb07`) for the GPU devnet (RTX 5090 229 MH/s + M5 Max 45 MH/s + gfx1036 4 MH/s measured, about 1.04 blocks/s); earlier the same day `0x1e400000` (2^18) for three 6-thread CPU miners on the real hash (0.294 MH/s: 825 blocks in 641 s) and `0x1e020000` (2^23) for the kHeavyHash stub | A devnet the miners at hand hold near 1 block per second until the DAA takes over at 600 blocks | Low | Mainnet, testnet and simnet genesis blocks are still Kaspa's content with new hashes. Replace them with Igneum genesis blocks before any public network. Every bits change moves the devnet genesis hash (print it with the ignored test). |
|
|
| `consensus/core/src/errors/block.rs`, `consensus/src/pipeline/header_processor/pre_ghostdag_validation.rs` | `RuleError::MissingVoteKeyHash`; `check_vote_key_hash_present` rejects an all-zero `vote_key_hash` | Nodes only check presence until the finality layer exists | Low | Keep. The presence check becomes a key-registry check later. |
|
|
| `protocol/p2p/proto/p2p.proto`, `protocol/p2p/src/convert/{header,block,messages}.rs`, `protocol/flows/src/v10/request_headers.rs` | `BlockHeader` wire message gains `Hash voteKeyHash = 15`; converters copy it both ways | p2p round trip of the field | Low | Field number 15 must stay unique if upstream adds header fields. Protocol version is still Kaspa's 11; bump it when the fork gets its own peers. |
|
|
| `rpc/grpc/core/proto/rpc.proto`, `rpc/core/src/model/header.rs`, `rpc/core/src/model/optional/header.rs`, `rpc/core/src/model/verbosity.rs`, `rpc/core/src/convert/verbosity.rs`, `rpc/grpc/core/src/convert/{header,optional/header}.rs`, `rpc/service/src/converter/consensus.rs`, `consensus/client/src/header.rs` | `RpcHeader` and `RpcOptionalHeader` carry `vote_key_hash`; gRPC proto fields (`string voteKeyHash = 16` on the header, `optional string voteKeyHash = 15` on the optional header) and converters; wasm client header | RPC round trip, template to miner and block back | Low | Mechanical. Borsh wire for wRPC changed shape: old wRPC clients cannot decode headers. |
|
|
| `consensus/src/model/stores/headers.rs`, `consensus/src/test_helpers.rs`, `mining/src/testutils/consensus_mock.rs`, `mining/src/template_limits_tests.rs`, `consensus/src/pipeline/virtual_processor/processor.rs`, `consensus/src/pipeline/body_processor/body_validation_in_isolation.rs` | Header store serde includes the field; template builder passes the field through; tests construct it | Storage and mining paths | Low | Database format changed: a node from before this commit must resync from scratch. |
|
|
| `consensus/core/src/network.rs` | Network name prefix `igneum-` (was `kaspa-`); devnet ports gRPC 26610, wRPC borsh 27610, wRPC json 28610, P2P 26611 (Kaspa devnet: 166xx to 186xx); error text | The prefixed name is the p2p handshake magic (`FlowContext::handshake` rejects a `Version.network` mismatch), so `igneum-devnet` never completes a handshake with `kaspa-devnet`. Distinct ports stop a Kaspa node on the same host from being dialled by mistake | Low | Mainnet, testnet and simnet ports are still Kaspa's. Change them before any public network. |
|
|
| `crypto/addresses/src/lib.rs`, `bridge/src/default_client.rs`, `bridge/src/tests.rs` | Devnet address prefix `igneumdev` (was `kaspadev`) | A devnet address can never parse as a Kaspa devnet address | Low | The bech32 prefix is only the devnet one so far. |
|
|
| `consensus/core/src/config/bps.rs`, `consensus/core/src/config/params.rs` | `OneBps = Bps<1>`; `DEVNET_PARAMS` uses `BlockrateParams::new::<1>()`: 1,000 ms blocks, GHOSTDAG k 18 (`calculate_ghostdag_k(2 x 5 x 1, 0.01)`), 10 max parents, mergeset limit 180, merge depth 3,600 blocks, finality depth 43,200 blocks, pruning depth 108,000 blocks, coinbase maturity 100; `crescendo_activation: always()`; doc table and a test pinning every value | 1 block per second at launch (fork-map f1, f2, e1). Finality and pruning depths are upper bounds only: live finality will be the certified checkpoint | Low; this is Kaspa mainnet's pre-Crescendo path | The `ForkedParam` and `bps_history` plumbing is still present for the other networks. Strip it in one pass when mainnet params are written. |
|
|
| `consensus/core/src/igneum.rs` (new), `consensus/core/src/lib.rs`, `consensus/core/src/constants.rs` | Emission constants and functions: 1,000,000,000 coins in year one, per-second rate halving every two years (`SUBSIDY_PER_SECOND_BY_PERIOD[i] = 3,168,808,781 >> i`, 33 periods), `block_subsidy(daa_score, bps)`, 30-day linear launch ramp from 10%, `proving_pool_share` 20% and `producer_share` 80%, `proving_pool_script_public_key` (OP_RETURN tagged `igneum-proving-pool-v0`), `POW_EPOCH_BLOCKS = 3,600` | The design's schedule, hard cap 4,000,000,000 (the geometric series sums to it; rounding leaves under 100 coins unminted), no emission treasury (fork-map b1, b2) | Medium: consensus money | Pure addition. Keep as the one source of the schedule. |
|
|
| `consensus/src/processes/coinbase.rs` | Kaspa's pre-deflationary phase, 426-month table and Crescendo rescaling removed; `CoinbaseManager::new(max_spk_len, max_payload_len, bps)`; `calc_block_subsidy` reads the period table; `expected_coinbase_transaction` pays 80% plus fees per rewarded block to its declared script and pools 20% of every subsidy (blues and reds) into one output to the proving pool script, placed after the blue outputs and before any red reward; tests rewritten | 80/20 split in consensus; the 20% is burned on devnet v0 and becomes the prover payout when the proving layer records prover sets (fork-map b3) | High: changes what every node accepts as a valid coinbase | Upstream edits to `coinbase.rs` will conflict. The payload format is unchanged (full subsidy in the payload, split derived from it), so Kaspa's payload parsing merges cleanly. |
|
|
| `consensus/src/consensus/services.rs`, `consensus/src/consensus/test_consensus.rs`, `consensus/src/pipeline/body_processor/body_validation_in_context.rs`, `consensus/src/processes/parents_builder.rs`, `consensus/src/processes/transaction_validator/tx_validation_in_isolation.rs` | Call sites of the new `CoinbaseManager` constructor; subsidy expectations in tests use `igneum::block_subsidy` | Wiring | Low | Mechanical. |
|
|
| `consensus/pow/src/igneum.rs` (new), `consensus/pow/src/lib.rs`, `consensus/pow/Cargo.toml`, `Cargo.lock` | `PowEngine` trait (`check_header(header, &EpochSeeds) -> (passed, pow)`), `HeavyHashEngine` stub (default), `IgneumEngine` behind feature `igneum-pow` calling the `igneum-pow` crate in its header-bound form (`Epoch::from_seed_bytes`, `Epoch::pow_bound`): `H` = header hash with the nonce zeroed (timestamp kept), init words `seed_words_from_bytes("igneum-block/" \|\| H \|\| nonce_hi_le32)`, lane nonce = low 32 bits, pow value = lane hash in the top 64 bits with zero low bits; epoch seed bytes = the epoch block hash, day seed bytes = `"igneum-day/" \|\| day_le64`; caches three `(epoch seed, day seed)` entries of program plus 256 MiB cache and exposes them to the miner (`epoch_for`, `header_prehash`, `target64`); `igneum-pow` as an optional path dependency `../../../../igneum-pow`; doc note on the existing `calc_block_level` (pruning proofs still use the stub for block levels) | The hash swap behind a trait (fork-map a1 to a3); the binding closes the "lane hash does not absorb the header" gap of v0 (spec 01 O-1.9) | Medium | Pure addition in the pow crate. `State` (kHeavyHash) is untouched, so upstream pow changes merge. The path dependency must become a workspace or git dependency when the fork gets its own repository. |
|
|
| `consensus/src/pipeline/header_processor/processor.rs`, `consensus/src/pipeline/header_processor/pre_ghostdag_validation.rs` | PoW check moved from `validate_header_in_isolation` to after GHOSTDAG (`check_pow_and_calc_block_level(header, selected_parent)`); `epoch_seed` walks the selected-parent chain to the last block below the epoch's start DAA score (genesis for epoch 0) with a memo; `pow_engine: Arc<dyn PowEngine>` on the processor; one `info` line per accepted or rejected PoW naming the engine (`PoW accepted <hash> by igneum-lottery-v1-bound ...`) | The epoch seed is chain state, so PoW cannot be checked in isolation any more (fork-map a4). Temporary seed rule until the 10-minute VDF over a certified checkpoint exists | High: a header now reaches GHOSTDAG before its PoW is checked, so an attacker can make a node run GHOSTDAG on headers with bad nonces (bounded by the per-peer header rate; the stub engine ignores the seeds so devnet v0 is not exposed) | Upstream rarely touches this ordering, but any refactor of `process_header` conflicts. Pruning-proof validation (`processes/pruning_proof/validate.rs`) still uses the stub for block levels; thread seeds through it before enabling the real engine on a pruning network. |
|
|
| `consensus/Cargo.toml`, `kaspad/Cargo.toml` | Feature `igneum-pow` forwarded (`kaspad -> kaspa-consensus -> kaspa-pow`) | `cargo build -p kaspad --features igneum-pow` selects the real engine | Low | Keep. |
|
|
| `consensus/core/src/config/constants.rs` | Comment block only: the DAA constants kept at 1 BPS (sample every 4 blocks, 661 samples, 2,644-block window, min window 150 samples) and the two timestamp rules kept (132 s future tolerance in isolation, strictly above the sampled past median time of 27 samples in context) | Difficulty step verified rather than changed (fork-map c1, c2); the known gap (per-epoch hash-speed step vs a 44-minute window) is recorded there | None | Comment only. |
|
|
| `igneum/miner/` (new crate `igneum-miner`), `Cargo.toml` (workspace member), `Cargo.lock` | Devnet miner: `--engine stub` (kHeavyHash, v0) or `--engine igneum-pow` (CPU warps through the node's own `IgneumEngine` cache, 64-bit nonces, random start per thread), `--worker <exe>` (GPU serve protocol: `job`/`found`/`done` lines, CPU re-check of every found nonce, one submit per template, `--exit-on-seed-change` exits 42 for ahead-of-time workers, `--worker-args`, `--status-secs`), `export-pack` (the pack for the node's current epoch and day plus `seeds.txt`), `bad-nonce` (submits an unmined nonce, expects a rejection); the epoch seed is derived as the node does it, walking from the sink over gRPC; `watch` and `inspect` as in v0 | Kaspa ships no miner; the devnet needs one that follows the fork's own PoW crate, and the GPU workers need a driver | None to consensus | Internal tool. Depends on `igneum-pow` by path and on `kaspa-pow` with the `igneum-pow` feature. Cross-compiles for `x86_64-pc-windows-gnu` with mingw-w64 (no rocksdb in its closure). |
|
|
|
|
## Decisions recorded as open
|
|
|
|
| Decision | v0 choice | Why it is open |
|
|
|---|---|---|
|
|
| 8 or 18 decimals | Kaspa's 8 (`SOMPI_PER_KASPA`), so one coin is 100,000,000 units and the cap is 4e17 units, inside u64 | The zkEVM side expects 18 decimals (wei). 18 decimals put the cap at 4e27, which does not fit u64, so the UTXO amount type, mass rules and every RPC amount would change. Decide with the execution engineer before the EVM bridge; a fixed 1e10 scaling at the bridge is the alternative. |
|
|
| Epoch seed | Hash of the last selected-chain block of the previous 3,600-block epoch; genesis for epoch 0 | The design uses a 10-minute class-group VDF over a certified checkpoint (bench-log, proto-vdf). The v0 rule is grindable in principle (a miner choosing which block ends an epoch) and needs the VDF and checkpoints to close. |
|
|
| Day seed for the 256 MiB cache | `"igneum-day/" \|\| day_le64` with `day = header.timestamp / 86,400,000` | Timestamps are miner-chosen inside the two timestamp rules, so a day boundary can be straddled by a few blocks; harmless for a cache seed. Spec 01 O-1.10 proposes the first epoch seed of the day instead, which waits for the VDF schedule. |
|
|
| Lane hash to 256-bit target | Lane hash (64 bits) in the top 64 bits, low 192 bits zero; `pow <= target` is exactly `lane <= target >> 192` | Closed for the binding (3 Oct 2026, `igneum-pow/src/bind.rs`: the init words commit to the nonce-zeroed header hash and the high nonce word). Still open: the block level for pruning proofs reads `calc_level_from_pow` on a value whose low 192 bits are zero (`leading_zeros(lane)` shifted), and pruning-proof validation itself still uses the stub. |
|
|
| GPU workers and the hourly program | Metal recompiles at runtime; CUDA and OpenCL are built ahead of time per pack and are rebuilt by the launcher at each epoch or day change (miner exit 42) | NVRTC and a runtime OpenCL rebuild inside the worker would remove the rebuild gap (about 30 s for CUDA). |
|
|
| Proving pool payee | OP_RETURN burn tagged `igneum-proving-pool-v0` | Becomes a payout to the prover set of the proven block once the proving layer records prover sets (20 to 60 s behind the tip). |
|
|
| Finality and pruning depths | Kaspa's 12 h and 30 h at 1 BPS | Upper bounds. Live finality is the 30-s certified checkpoint; pruning depth must stay above the longest checkpoint gap. |
|
|
| PoW before or after GHOSTDAG | After | Needed for the chain-derived seed; costs GHOSTDAG work on invalid headers. A header-only seed (for example the VDF output carried in the header and verified against the checkpoint) would move it back. |
|
|
|
|
## Per-second subsidy numbers (8 decimals, 1 BPS)
|
|
|
|
| Period | Years | Per second (units) | Per second (coins) | Per block at 1 BPS |
|
|
|---|---|---|---|---|
|
|
| 0 | 0 to 2 | 3,168,808,781 | 31.68808781 | same |
|
|
| 0, day 0 of the ramp (10%) | | 316,880,878 | 3.16880878 | same |
|
|
| 0, day 15 of the ramp (55%) | | 1,742,844,829 | 17.42844829 | same |
|
|
| 1 | 2 to 4 | 1,584,404,390 | 15.84404390 | same |
|
|
| 2 | 4 to 6 | 792,202,195 | 7.92202195 | same |
|
|
| 31 | 62 to 64 | 1 | 0.00000001 | same |
|
|
| 32 and after | 64 on | 0 | 0 | 0 |
|
|
|
|
Split of 3,168,808,781: producer 2,535,047,025 (80%, plus the rounding remainder), proving pool 633,761,756 (20%). Total over the schedule: under the 4,000,000,000-coin cap by less than 100 coins (test `total_emission_stays_under_the_cap`).
|