27 KiB
Counter ASIC 2.0: the node side (program class v3 as a height switch)
5 October 2026, night, worker "ca2-node". Branches: ca2-v3 (main repository: the igneum-pow seam, the workers, the fast-time gate) and ca2-v3-node (the fork, from the 0.3.10 tip 21d4c73c plus pack-loop 05ef0fa3). Plan: docs/plans/counter-asic-2-rollout.md; status: docs/plans/counter-asic-2-status.md. The shape follows finality v3 (docs/plans/finality-v3-rollout-devnet.md section 6): one height switch read from the override file, every node carries the same object before the height.
Everything below is the SEAM. The class itself (igneum_pow::V3_CLASS) is a placeholder, w16 (LoadClass::fixed(4, 16)), that the integration branch replaces with the decided width, mix and scratch share; the ca2-era draw and the ca2-mixer item construction fill what Epoch::from_chain_seeds calls. Nothing here changes a v2 program, a pinned pack or any live node.
1. What changed, where
| Piece | What |
|---|---|
igneum-pow generator.rs |
ProgramClass { V2, V3 }, V3_CLASS (placeholder), GENERATOR_VERSION_V3 = 3, generate_from_seed_bytes_program_class(label, seed, class, era); Program::era_bytes; a v3 program's id is program_id(3, seed, attempt) (spec 01 section 1.4.6) |
igneum-pow verify.rs |
Epoch::from_chain_seeds(epoch, day, era, class, label), Epoch::chain_program (no cache fill), Epoch::chain_dataset(day, class) (the one entry the node's day cache goes through; today both classes build the same cache) |
igneum-pow emit.rs |
program.h: IGNEUM_PROGRAM_CLASS "v3" and IGNEUM_ERA_SEED_HEX beside IGNEUM_GENERATOR 3; program.json: program_class, era_seed_bytes; nothing on a v2 pack (tests/packs.rs diffs the pinned packs igneum-genesis-mh and igneum-devnet-v4-epoch0: identical) |
igneum-pow packcheck.rs |
verify_pack_texts_chain / verify_pack_dir_chain(dir, epoch, day, want_class, want_era): PackFault::WrongClass for the wrong class, the wrong era, or a generator other than 2 or 3; PackIdentity carries generator, class, era_hex |
proto-cuda/nvrtc/packfile.h |
pf_load refuses a generator other than 2 or 3 (spec 1.4.5), reads the class (must match the generator) and the era; pf_pack_class_ok, pf_class_token (the one rule for the class= / era= tokens) |
proto-cuda/nvrtc/worker.cpp, proto-opencl/host.c |
a pair's identity includes its class and era when the line names them; right seeds and the wrong class answer need <e> <d> and error <id> pack <dir>: program class mismatch ..., so the miner prepares the pair from a pack of the right class; a prepare on a wrong-class pack fails in plain words |
proto-metal/main.swift |
refuses every class=v3 line (the Swift generator is version 2; the integration adds 3) |
fork consensus/core/src/igneum.rs |
ProgramClass, program_class_for_epoch_at(e, N4, L), program_class_v3_first_epoch_at, the process-wide activation (install_program_class_v3_activation, program_class_for_epoch, program_class_at), POW_ERA_BLOCKS, POW_ERA_LEAD, pow_era_index, pow_era_seed_score; PowEpochInfo + program_class, next_program_class, program_class_v3_activation_daa, era_index, era_seed (serde defaults: v2, never, none) |
fork consensus/core/src/config/params.rs |
program_class_v3_activation_daa in Params and OverrideParams (default never on devnet, simnet, mainnet; 0 on the testnet like every other switch), override_params, From<Params>, the digest (unconditionally, right after finality_v3_activation_daa), Params::install_program_class_v3_activation, Params::program_class_v3_first_epoch; tests override_params_carry_the_program_class_v3_activation, the digest test's 11th edit, fast_time_60x_file_is_the_devnet_at_60x (every field present) |
fork kaspad/src/daemon.rs |
Program class v3 from the override file: active from epoch E (DAA score N4 rounded up to the epoch boundary at 3600*E, epochs of 3600 DAA); the activation installed next to the PoW schedule |
fork consensus/pow/src/igneum.rs |
EpochSeeds { epoch, day, class, era } (+ EpochSeeds::v2), day caches keyed on (day, class), the program through Epoch::chain_program, the cache through Epoch::chain_dataset, standalone_epoch through Epoch::from_chain_seeds; test program_class_v3_seeds_hash_their_own_program_over_their_own_cache |
fork consensus/src/pipeline/header_processor/{processor,pre_ghostdag_validation}.rs |
the class of the header's epoch (program_class_at(daa)), HeaderProcessor::era_seed (the stand-in, memoised per era), RuleError::EraSeedUnavailable |
fork consensus/src/processes/pruning_proof/igneum_pow.rs |
seeds_for: the class of the epoch; era 0 = genesis; a later era is PruningImportError::MissingEraSeed (no era witness in the proof format yet) |
fork consensus/src/consensus/mod.rs |
get_pow_epoch_info: the class of this and the next epoch, the activation, the era index and seed (the era walk memoised once per era per process) |
fork rpc/core/src/model/message.rs, rpc/grpc/core/proto/rpc.proto (fields 12 to 16), rpc/grpc/core/src/convert/message.rs |
RpcPowEpochInfo + program_class (generator number), next_program_class, program_class_v3_activation_daa, era_index, era_seed; an old node's absent fields read as v2, never, none |
fork igneum/miner/src/main.rs |
seeds from the template (seeds_from_info), the activation installed from the template, the legacy seed walk keys the class on it; next_pair takes the next epoch's class; the job and prepare lines end with class=v3 era=<hex> for a v3 epoch; seeds.txt carries program_class and era_seed_hex; write_pack_checked checks class and era (verify_pack_dir_chain); the "program and 256 MiB cache ready" lines and export-pack print the class and the program id |
infra/fast-time/override-60x.json, tools/finality-attacks/redteam/override-60x-v3.json, infra/fast-time/README.md |
the field at never, the README row |
infra/fast-time/class-v3.mjs |
the G4 gate (section 5) |
2. The epoch-boundary rule
One epoch has one program (spec 01 section 1.12), so the switch keys on the EPOCH: epoch e is class v3 when L * e >= N4 with L the live epoch length (pow_epoch_blocks(), 3,600 on the devnet, 60 on the fast-time profile). The first v3 epoch is ceil(N4 / L); a height inside an epoch rounds UP to the next boundary and never splits an epoch between two programs. A block's class is a function of its DAA score alone (program_class_at(daa)), as its epoch seed is.
| N4 | L | first v3 epoch | first v3 DAA | the epoch before |
|---|---|---|---|---|
| 150 | 60 | 3 | 180 | epoch 2 (DAA 120 to 179) is v2 to its last block |
| 180 | 60 | 3 | 180 | |
| 181 | 60 | 4 | 240 | |
| 136,000 | 3,600 | 38 | 136,800 | epoch 37 (133,200 to 136,799) is v2 |
| 136,800 | 3,600 | 38 | 136,800 | |
| 0 | any | 0 | 0 | (the testnet) |
| never | any | none |
The unit tests program_class_switch_rounds_up_to_the_epoch_boundary (consensus-core) and override_params_carry_the_program_class_v3_activation (params) pin these rows and sweep every activation 0..399 at L = 60.
The digest: the field (and pow_genesis_dataset_log2) enters consensus_digest unconditionally, so the digest flips the moment a binary carrying the field (at never) runs, exactly as the finality v3 field did. This is intended: every node must carry the object before any node reaches the height, and a node without the field is refused at the handshake (rollout section 1, order step 1). Measured: the devnet digest with no override file moves from 9409dedac4bf9f0f... (0.3.10) to c562d70e1428c9789823cc40067623b4767f7c555ce7ff4ea11c1498f013ef6c (0.3.11; the pinned value of consensus_digest_keeps_the_0_3_5_value_until_the_fee_switch_is_set), which is the expected digest of a scratch node at the publish.
3. The era stand-in
E_n of spec 04 section 4.4, until the 1-hour VDF is in the node (docs/plans/era-layout.md section 2):
| Era | E_n |
|---|---|
| 0 | the genesis block hash |
| n >= 1 | the hash of the last selected-chain block whose DAA score is below 15,552,000 n - 7,200 |
One function per path: HeaderProcessor::era_seed (the header's era from its DAA score, the walk down the selected parents from the header's selected parent, memoised per era in era_seed_memo), Consensus::get_pow_epoch_info (the same walk from the sink for the template, memoised once per era per process), ProofSeeds::seeds_for (era 0 only). The seed block is at least one era lead (7,200 DAA) below any header that uses it, past the merge depth (3,600), so one walk per era per process is sound; the walk itself is up to an era long on the first header of era n >= 1 (about 15.5 million selected parents), which is why it is memoised and why the VDF should land before era 1 (180 days after genesis). A v2 program never reads the era; the placeholder v3 class does not either (the ca2-era draw will); the era is carried and recorded in the pack so a worker of the wrong era is refused from the first v3 build.
4. The job line, the prepare line, the pack
| Surface | Class v2 (today) | Class v3 |
|---|---|---|
| job line | job <id> <prehash> <target> <start> <count> <epoch> <day> |
the same with class=v3 era=<64 hex> at the end |
| prepare line | prepare <epoch> <day> [<dir>] |
the same with class=v3 era=<64 hex> at the end |
program.h |
IGNEUM_GENERATOR 2 |
IGNEUM_GENERATOR 3, IGNEUM_PROGRAM_CLASS "v3", IGNEUM_ERA_SEED_HEX "<64 hex>" |
program.json |
"generator": 2 |
"generator": 3, "program_class": "v3", "era_seed_bytes": "<hex>" |
seeds.txt |
epoch_seed_hex, day_seed_hex, day_index |
plus program_class v3, era_seed_hex <hex> |
template pow_epoch |
programClass 2 |
programClass 3, nextProgramClass, programClassV3ActivationDaa, eraIndex, eraSeed |
A v3 program's identity is the pair (program id, era seed): the id covers the generator, the seed words and the attempt (spec 01 section 1.4.6, unchanged), so every era of one epoch seed shares one id, and the era seed, carried by the pack (IGNEUM_ERA_SEED_HEX) and the job line (era=), tells them apart. The workers and packcheck compare both.
A v2 line and a v2 pack are byte for byte what the workers read before this branch (the tokens are sent only for a v3 epoch), so a 0.3.10 worker on a 0.3.11 miner mines v2 epochs unchanged and refuses nothing until the switch; by the switch every worker is 0.3.11 (rollout order).
Refusals: pf_load refuses a generator that is not 2 or 3 (error 0 pack <dir>: program pack generator N is not a generator version this worker runs (2 or 3), the exit-44 path of 05ef0fa3: the miner rebuilds the pack before the restart). A job of class v3 against a resident v2 pack of the same seeds answers need <e> <d> and error <id> pack <dir>: program class mismatch: this pack is class v2, the job names class v3 (export the pack again); the miner's need handling prepares the pair again, write_pack_checked writes a v3 pack (checked with verify_pack_dir_chain before the worker hears of it), and the prepared v3 pair wins over the resident v2 pair because the pair identity now carries the class. The Metal worker answers error <id> program class v3 is not implemented by this worker until the Swift generator carries version 3.
5. The fast-time gate (rollout G4)
node infra/fast-time/class-v3.mjs [--secs 420] [--activation 150] [--epochs-after 2] under tools/lock/with-lock.sh run: three nodes on override-60x.json merged with genesis_bits 0x1f010000 (2^16 hashes per block, the CPU difficulty of sim/difficulty/testnet_v2.py) and program_class_v3_activation_daa 150 (inside epoch 2, so the rounding rule is exercised: the first v3 epoch is 3 at DAA 180); one real CPU miner per node (--engine igneum-pow, 1 thread, real lottery-hash solutions, every node verifying the other two); the run ends two epochs after the boundary.
Result, run 1 (5 October 2026, 21:33:04Z to 21:38:02Z, Apple M5 Max shared with other agents' builds)
Binaries: fork ca2-v3-node 79bd8e10 and igneum-pow at ca2-v3 66eeba3 (the mixer-x4 class, V3_CLASS = MX4, before the era and hot-table fields), target-ca2/release, built on the Mac under the build lock. Summary: docs/plans/counter-asic-2-gate/class-v3-20261005-2133Z-mx4.json. PASS: every check true.
| Check | Measured |
|---|---|
| Switch line on every node | 3 of 3: Program class v3 from the override file: active from epoch 3 (DAA score 150 rounded up to the epoch boundary at 180, epochs of 60 DAA) |
| Digest, all three nodes | 0186df7d0834d054... (the 60x profile with the CPU bits and the switch) |
| Template class per epoch | epochs 0 to 2 class 2 (nextProgramClass 3 from epoch 2), epochs 3 to 5 class 3; the switch seen at DAA 180, 168.0 s wall |
| Blocks before / after the boundary (node 0's DAG) | 181 / 124 (selected chain 176 / 123), 305 in all |
| Program id per epoch, all three miners agreeing | e0 v2 8f8806638d59850f, e1 v2 fd9562df32a68313, e2 v2 1ae6d90ab299154c, e3 v3 5d0dedd9fd9e29a1, e4 v3 e81808dcdb02ce05, e5 v3 06aff9c1d33e7a13; no v2 id reappears under v3 |
| Rejected blocks | miners 0 / 0 / 0 (97, 103, 104 accepted); nodes 0 / 0 / 0 PoW rejected lines |
| Forks | sinks 082fd39ba65df2ff on all three nodes, block counts 304 / 304 / 304, one tip each |
| Program and cache ready, one CPU core | a new (day, class) cache: v2 epoch 0 219 to 235 ms, the first v3 epoch 177 to 185 ms (two per miner: the 24-minute day rolled at DAA 190); a program swap inside a day 2 ms |
The mixer x4 build-time number the rollout asks for is not visible here: the CPU miner derives dataset words on demand from the cache (no dataset build), so the x4 cost lands on the GPU workers' dataset build, measured by the ca2-mixer playbooks on the PCs.
Result, run 2 (5 October 2026, 21:46:36Z to 21:51:31Z): the composed class
Binaries rebuilt on ca2-v3 b105a55 (era layout merged on the mixer: V3_CLASS = MX4 with the era drawn inside generate_from_seed_bytes_program_class, hot None), fork 79bd8e10 unchanged. Summary: docs/plans/counter-asic-2-gate/class-v3-20261005-2146Z-era-mx4.json. PASS: every check true.
| Check | Measured |
|---|---|
| Switch lines, digest | 3 of 3, the same line as run 1; digest 0186df7d0834d054... |
| Template class per epoch | epochs 0 to 2 class 2, 3 to 5 class 3; the switch at DAA 180, 171.0 s wall |
| Blocks before / after the boundary | 181 / 124 (selected chain 180 / 122), 305 in all; 102, 102, 100 accepted per miner |
| Program id per epoch, all three miners agreeing | e0 v2 8f8806638d59850f, e1 v2 bb8dd9ddbf9eb63f, e2 v2 8ee7a9f33d418e48, e3 v3 2d278041ba482dba, e4 v3 2ae786d294a8a59d, e5 v3 bc36813df2f41b5f |
| Rejected blocks | miners 0 / 0 / 0, nodes 0 / 0 / 0 |
| Forks | sinks 712c1b212091dcdc on all three nodes, block counts 303 / 303 / 303, one tip each |
| Program and cache ready, one CPU core | v2 epoch 0 178 ms, the first v3 epoch 181 ms, an in-day swap 2 ms |
CPU hash rate across the switch, run 2, miner cpu0 (one thread, the Mac shared with other agents' builds, so approximate): the cumulative rate read 0.024 MH/s through the v2 epochs (30 to 151 s), then fell to 0.021 MH/s cumulative by 271 s (100 s under v3), which puts the v3 interval rate near 0.017 MH/s, about 30 percent under v2 on the CPU interpreter (the era's strided windowed loads and the mixer path). The node has no per-block verify timing line; the CPU verifier is measured in igneum-pow (the mixer agent, one M5 Max core, ms per 32-lane unit, same minute, cited from ca2-mixer 1ab8b21's message of 5 October 2026 22:10Z):
| Path | readwidth | ca2-v3 88dafbc (before the fix) | ca2-v3 d233fa1 (after) |
|---|---|---|---|
| v2 (the live devnet) | 0.607 / 0.610 | 1.332 | 0.609 / 0.611 |
| v3 at x4 | 1.238 | ||
| v3 at x8 (the class) | 2.077 (worst cold 2.15) |
The regression was memhard::derive_items at m = 1 (2.2x); the fix dispatches to an out-of-line derive_items_mask, one instance per cache size with the line mask a constant. A v3 block costs the node about 3.4x a v2 block to verify (2.08 against 0.61 ms per unit).
Epoch 0's v2 id is the same in both runs (8f8806638d59850f: a v2 program is untouched by the era code, on the chain as in the packs); the v3 ids differ from run 1 because the era draw is now inside the class.
Result, run 3 (5 October 2026, 22:15:04Z to 22:19:44Z): the final class, x8 with the era
Binaries rebuilt on ca2-v3 d233fa1 (mixer x8, the era drawn inside the class, the verifier fix), fork 89dfcb95. Summary: docs/plans/counter-asic-2-gate/class-v3-20261005-2215Z-era-mx8.json. PASS: every check true.
| Check | Measured |
|---|---|
| Switch lines, digest | 3 of 3, the same line; digest 0186df7d0834d054... |
| Template class per epoch | epochs 0 to 2 class 2, 3 to 5 class 3; the switch seen at DAA 181, 127.9 s wall |
| Blocks before / after the boundary | 182 / 122 (selected chain 180 / 120), 304 in all; 101, 89, 113 accepted per miner |
| Program id per epoch, all three miners agreeing | e0 v2 8f8806638d59850f, e1 v2 e145305446a6b4ce, e2 v2 743ad2a3cab0518a, e3 v3 a6523b90cff501e3, e4 v3 bc811b3c4b8b1ced, e5 v3 e784541f19cdebe5 |
| Rejected blocks | miners 0 / 0 / 0, nodes 0 / 0 / 0 |
| Forks | sinks a9ce45df8beeaf13 on all three nodes, block counts 303 / 303 / 303, one tip each |
| Program and cache ready, one CPU core | v2 epoch 0 179 ms, the first v3 epoch 191 ms (x8 construction: the cache fill is unchanged, the items are derived on demand), an in-day swap 2 ms |
Epoch 0's v2 id 8f8806638d59850f is the same in all three runs. Three runs, three v3 classes (mixer x4; x4 with the era; x8 with the era), the same chain behaviour each time: the switch rounds up to epoch 3, no block rejected, one chain.
Result, run 4 (5 October 2026, 22:25:22Z to 22:31:28Z): a real Metal miner across the boundary (gate G4b)
The fleet-outage case: the three runs above used CPU miners, so the Metal worker's class v3 path had not mined. Run 4 puts node 0's miner on the Metal worker the way the app does (igneum-miner --worker igneum-bench --prepare-packs <dir> --exit-on-seed-change, igneum-bench built from ca2-v3 00c55aa: a class v3 program comes from the pack's program_bound.metal, its day from the pack's memhard.metal (the x8 construction, the era layout), keyed by (day, class, era)), genesis bits 0x1e010000 (2^24 hashes per block), CPU miners on nodes 1 and 2. Summary: docs/plans/counter-asic-2-gate/class-v3-20261005-2225Z-metal-mx8.json.
| Check | Measured |
|---|---|
| The chain | 182 / 123 blocks across DAA 180, 0 rejected (miners and nodes), sinks 66a33eca15d33ba2 on all three, 304 / 304 / 304, 3 of 3 switch lines, the switch at DAA 180, 264.5 s wall |
| PREPARE lines | 6 (three class v2, three class v3), each 6 to 7 DAA before its boundary, the v3 ones with the pack directory and class=v3 era=<hex> |
The worker's prepared for the v3 packs |
three: prepared <seed> <day> 252.5 program 55.5 dataset 196.9 race 0.0 variant base class v3 loads/hash 128 cache-fill 0.9 build 41.1 (the first, with the day built from the pack: 0.9 ms cache fill, 41.1 ms build on the GPU), then 51.1 and 46.0 ms (the day resident) |
| The swap at the boundary | SEED CHANGE at daa 180 ... (epoch 3): swapped with no pause (prepared 4 s ago, prepare took 252 ms); the same at 240 and 300 |
| Blocks on v3 | 124 accepted after the switch (301 in the run), every one re-checked on the CPU: mismatched 0; need 0; no class or era mismatch line; no refusal, no exit 42 or 44 |
One line before the switch, on the v2 path: PACK OUT OF DATE: the prepared pair b7eda892... is not the node's epoch bc056261...; rebuilding the program pack at DAA 60: the epoch-1 seed reported at boundary - lead flipped (the quarter-lead confirm is 3 DAA on the 60x profile, 150 on the devnet) and the miner's rebuild path of 05ef0fa3 wrote the right pack. The v2 prepares answered 33 to 35 s after they were sent (the Metal variant race runs inside the prepare, longer than the 10-DAA lead of the profile; 600 s on the devnet), so epochs 1 and 2 compiled inline; a pack program races nothing and answered in 252 ms. The v3 path is clean; the script now judges the Metal checks from the first v3 prepare on (the run's own report flagged the v2 line and read FAIL on that one check).
The PC 2 suite jobs for the same fork (79bd8e10), the G6 evidence (the coordinator's status file carries the SUMMARY lines):
| Job | Tree | What | Result |
|---|---|---|---|
build-20261005-215219 |
main b105a55 | the Linux node, the six node suites, the app tests | Linux build and app tests passed; kaspa-consensus failed on the known flake (ban_is_decided_by_the_carrying_block, UnexpectedDifficulty in mine_on_all, the 0.3.10 cut's section 11 case) |
build-20261005-215712 |
main b105a55 | kaspa-consensus alone |
97 passed, 0 failed, 3 ignored, ban_is_decided ... ok, 21:59:45Z |
build-20261005-220351 |
main 8ea6740 | the other five crates (kaspa-consensus-core igneum-exec kaspa-pow igneum-miner kaspa-p2p-flows) and the app tests |
22:04 to 22:06:55Z: igneum-exec 17, igneum-miner 18, consensus-core and p2p-flows green, the app 78 + 26 + 8; kaspa-pow FAILED on program_class_v3_seeds_hash_their_own_program_over_their_own_cache (the fork test compared a chain v3 program's class to V3_CLASS with era: None; on the era-merged crate the class carries the drawn era inside). Fixed on the fork at 89dfcb95 (the class minus its era is V3_CLASS, the era is drawn, another era seed keeps the program id and hashes another program over the same day cache). Re-run: job 4 |
build-20261005-221237 |
main d233fa1, fork 89dfcb95 | the five crates and the app | 22:12:37 to 22:15:44Z (185 s): every stage ok; kaspa-consensus-core 108 (2 ignored), igneum-exec 17, kaspa-pow 33 + 14 (the v3 engine test, with the igneum-pow feature), igneum-miner 18, kaspa-p2p-flows 7, igneum-app 78 + 26 + 8; 0 failed. With job 2, G6 is green |
The PC's test stage builds the fork's test binaries with the plan's feature set, so the v3 engine test DID run on PC 2 (the earlier reading that it was Mac-only is withdrawn): the PC job is the G6 evidence.
6. Tests
| Where | What | State |
|---|---|---|
igneum-pow cargo test --release |
42 unit + 11 pack tests, including program_classes, program_class_and_era_are_checked, the pinned-pack diffs |
pass (Mac, 5 Oct 2026) |
proto-cuda/nvrtc/emu/packfile-test.sh |
13 checks: the attempt rule, the generator rule, a v3 pack with its era, the token matcher | pass (Mac) |
host.c, worker.cpp (emulation), main.swift |
syntax / compile | pass (Mac) |
fork cargo test -p kaspa-pow --features igneum-pow |
14 engine tests including program_class_v3_seeds_hash_their_own_program_over_their_own_cache (rewritten at 89dfcb95 for the era-in-class rule) |
pass (Mac, 89dfcb95 against d233fa1; PC 2 job 4: 33 + 14) |
fork cargo test -p kaspa-consensus-core |
108 + 7: the switch rounding, the era clock, the params, digest and fast-time file tests | pass (Mac, fork 79bd8e10) |
PC 2 build-job.mjs suites |
kaspa-consensus-core igneum-exec kaspa-pow kaspa-consensus igneum-miner + igneum-app |
the coordinator publishes on its go |
6a. The integration merges for the ship (5 October 2026, 22:35Z to 22:45Z)
| Merge | Commit | Conflicts, and how they were kept |
|---|---|---|
readwidth 30ff674 (the OpenCL __local declaration rule, the read-width decision record, the per-watt rows) |
3566afd | emit.rs (2 hunks: the hot-table kernel arguments, HEAD's superset), packbench.swift (readwidth's resident-footprint lines added to HEAD's hot-aware RESULT line), bench-log.md (both entries), read-width.md add/add (readwidth's version, a pure superset of HEAD's) |
| origin/master 1f0d62c (the 0.3.10 merge) | 49c7e78 | host.c (one struct hunk: both fields, dupOf and pci[32]; master's topology, duplicate-platform and read-back code and ca2-v3's class/era tokens, mh_word, hot and mixer fields all auto-merged; cc -fsyntax-only clean), bench-log.md (both entries) |
Checks after the merges (49c7e78, 22:50Z): the igneum-pow suite 53 + 4 + 19 + 7 pass; the packfile test 0 failures; the CUDA emulation emu/test.sh PASS (ready + prepare 1, 64 + 64 + 32 found on pack A, pack B prepared with its self-test, the swap, the self-heal rebuild of pack A, 17 sampled hashes equal to igneum-pow hash-bound, the NVRTC source check PASS for 6 files); proto-opencl/test-generic.sh on Apple OpenCL PASS (the same protocol, job 5 refused after the swap, 15 sampled hashes equal); the fork's kaspa-pow, igneum-miner, kaspad check clean. The emulation needs IGNEUM_CUDA_INC pointed at a checkout's proto-cuda/nvrtc/redist/include (the worktree has none).
7. Unverified, and what is owed
- The era walk for era >= 1 has never run (the devnet is 180 days from era 1); a pruning-proof sync past era 0 fails with
MissingEraSeeduntil an era witness exists in the proof format. - The Metal worker takes class v3 from the pack (program and day) and mined across the boundary in run 4; the app must pass
--prepare-packsto its Metal worker (the app branch fixesengine.rs, which passed it to the non-Metal workers only). - The GPU workers' class refusal was checked by the C test of
pf_pack_class_okand the syntax of both hosts, not by a live worker on a v3 pack: the integration's bit-exact gate (G1) is where a real worker first builds a v3 pack. next_pairkeeps the current era seed for the next epoch; an epoch boundary that is also an era boundary (once per 180 days) would prepare the wrong era, and the job line then names the right one, so the worker refuses the prepared pair and the miner prepares again (one wasted compile, no wrong block). The VDF era seed replaces the stand-in before this matters.- Done 22:05Z: ca2-cache rebased as 1950661 fast-forwarded (the first attempt, 2de19e5 on 464d6e1, conflicted in 8 files and was aborted); igneum-pow 53 + 19 tests and the packfile test pass; the fork's kaspa-pow, miner and kaspad check clean against the merged crate (seam unchanged).
V3_CLASS = { era: None, hot: None, ..LoadClass::MX4 }. - Done 22:12Z: ca2-mixer 16dfd1e (MX8 candidate, tests) merged at 4e733bb with two one-line fixes the merge needed (3a7fba7
MX8getsera: None, hot: None; 795472e tests/scratch.rs'sInstrliterals getwin: 0, off: 0); then ca2-mixer 1ab8b21 (the verifier fix,V3_CLASS = { era: None, hot: None, ..LoadClass::MX8 }: class v3 is x8 by the coordinator's decision of 22:05Z) merged at d233fa1. Checks on d233fa1: igneum-pow 53 + 4 + 19 + 7, packfile 0 failures, the fork'scargo test -p kaspa-pow --features igneum-pow14 pass, the release rebuild 1 min 57 s. - Owed (0.3.12, coordinator's ask of 5 October 2026 22:20Z): per-day dataset reuse in the CUDA and OpenCL workers (a
Dayobject shared by consecutive pairs, the cache freed after the build); the Metal worker already keys datasets by day. Until then the iGPU tier mines v3 with a dataset rebuild per epoch on those two workers. - Wire compatibility:
RpcPowEpochInfogained five fields in its Borsh form (wRPC) and five proto fields (gRPC); the gRPC side reads an old node's zeros as v2 / never / none; the Borsh form is versioned byGetBlockTemplateResponse(version 2 carries the whole struct), so a 0.3.11 wRPC client against a 0.3.10 node reads short: the miner uses gRPC, the console reads JSON (serde defaults).