soft3/audit/chain/chain mine — reference miner backends measured, ASERT step simulated.md

Receipt for soft3 mine (crate/src/chain/mine/, branch feat/chain-mine) against specs/chain.md §1.1–1.3, §1.5, §8 and chain-wire §7. Measured 2026-10-15 on an Apple M4 Max (16 cores, macOS 26), release build, stand ~/cyber/.stands/N3 (soft3 83e9012 + this branch, lens release/0.2 + feat/mine-kernel = lens#28, hemera origin/main, honeycrisp origin/main for aruminium contents_ptr).

1. hashes per second, soft3 mine bench --seconds 10

backend hashes seconds H/s conformance §5 nonce
cpu (16 threads) 30,146,560 10.1 3.00 MH/s found
cpu (1 thread) 1,392,640 5.1 275.4 kH/s found
metal (Apple M4 Max) 163,577,856 10.1 16.13 MH/s found
webgpu (Apple M4 Max (Metal)) 71,303,168 10.2 7.02 MH/s found

One hash = four hemera permutations (168 bytes = 3 rate blocks + pad). "found" = the backend returns index 933 for base 40,165,000 on the chain-conformance §5 mined header (nonce 40,165,933) and the header verifies with hemera::hash.

Readings:

  • the spec's 64.4 kH/s per core (chain.md §1.2) is the scalar hemera::hash path; hemera's 16-lane interleaved batch permutation (permute_batch, one shared inversion per partial round) gives 275 kH/s on one core, 4.3×, and 3.0 MH/s on the machine — the "0.6 MH/s per machine" estimate of §1.2 is 5× low for a CPU miner written against the batch path;
  • the first competent GPU kernel on the same chip is 16.1 MH/s: 5.4× the CPU of the same machine, 58× one core. A discrete GPU with a comparable kernel is expected an order above this (the M4 Max's 13 MH/s WebGPU permute2 path is bandwidth-free: everything stays in registers);
  • WebGPU through wgpu on Metal reaches 44 % of native Metal (32-bit limb arithmetic, two states a thread vs four with a shared inversion); it is the Vulkan/Android/Linux path, good enough that a GTX 1080 (deimos, chain.md §9.2) will mine on day one without an MSL port.

2. what the genesis target implies

GENESIS_BITS = 2^232 → 2^24 = 16,777,216 expected hashes a block; T = 30 s → the network rate the target is sized for is 559 kH/s. One M4 Max CPU miner is 5.4× that (a block every 5.6 s at genesis), one M4 Max GPU 29× (0.6 s). ASERT takes over within minutes (§3). The three cybernode machines are x86 and were not measured; at the x86 batch path's expected ~150–250 kH/s a core they will not out-hash one laptop GPU.

Consequence for §8's threat table: at launch the network's hash rate is whatever the owner's machines plus a few volunteers bring, order 10–50 MH/s; a single rented GPU box with a competent kernel (100+ MH/s) is the majority miner, as the spec says — the reorg limit and the checkpoints remain the only defence, and the reference miner shipping its GPU kernels removes the secret kernel advantage, not the majority one.

3. ASERT (H = 600 s, T = 30 s) under a 100× step — integer simulation

crate/src/chain/mine/asert_sim.rs asert_behaviour_and_hundredfold_step over the kernel's integer daa::asert (conformance §4 vectors green there, including the floor-division and clamp rows), a deterministic miner model: a block at target t and rate r takes round(2^256 / (t+1) / r) seconds, no randomness, no floats.

scenario result
T-spacing, 200 blocks bits constant 0x1d100000
T/2 spacing for 600 s of block time (40 blocks) target halved exactly (0x1d080000), spec's [1.9, 2.1]
100× step up interval back to ≥ 27 s from block 192, 31 min of wall time; 30 s after 1,000 blocks
100× step up at the real anchor 2^232 first block 0.3 s (rounds to 1), settled from block 212, same 31 min
100× collapse (farm leaves after the step is absorbed) first block 49 min; blocks ≤ 60 s again after 62 min

Readings:

  • chain.md §1.5's "a 10× step within about two hours" is conservative for 100×: the lead the chain needs is H · log2(100) = 3,986 s, it grows at most 30 s a block, so absorption needs ≥ 133 blocks — the blocks are fast while it happens, so it is 192 blocks and 31 minutes of wall time;
  • the collapse side matches the spec's "k = 100 → 66 min": the target doubles every 600 s of wall time whether or not blocks arrive, so the first post-collapse block takes 49 min and the chain is back under a minute per block at 62 min. During that hour a farm that joined, mined for an hour at 100× and left has taken ~190 blocks' subsidy and leaves the chain nearly stalled for 50 minutes — the §8 residual, measured;
  • H = 600 s therefore handles a 100× step in both directions within the hour the spec budgets; the price is the 49-minute gap after a collapse. A shorter half-life would shorten the gap and widen the timestamp-play window (§1.5); the trade is the owner's and this receipt only measures it.

4. conformance pinned by the miner's own tests

  • target.rs: the miner asks the kernel (chain::header::decode_bits, chain::pow::id_meets_target, chain::daa::asert) and implements no consensus of its own; on feat/chain-mine before the kernel merged the same questions were answered by a local copy (local.rs) pinned to the §2/§4/§5 vectors (all 13 bits rows, both headers, six ASERT rows), which the branch feat/chain-mine-on-kernel deletes.
  • job.rs: the lane-wise sponge equals hemera::hash for nonces in both words of the 128-bit nonce.
  • mod.rs every_backend_agrees_and_verifies: CPU, Metal and WebGPU return the same index for the same dispatch — the conformance nonce, a base in the high word, a base 20,000 below a 2^64 boundary, and a target nothing passes.
  • tests/mine_e2e.rs: two blocks mined against a mock node (a dropped first request → retry, a stale template → refresh, PoW verified natively by the mock, nonce salt in the high word).
  • lens#28 tests/differential.rs: both kernels against lens's CPU reference and cyber_hemera::hash.

5. API added to the spec

chain-wire §7 gains the JSON of GET /template?to=, POST /submit (200/409/422/400) and GET /tip, as the reference miner speaks them; target repeats bits decoded so an external worker needs no compact codec. The node side of these routes belongs to the kernel/api work.

Graph