diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 86ba5409..2897d6bc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -97,6 +97,8 @@ jobs: run: bash tools/ci/commit-string-check.sh --self-test - name: build server remote checkout self-test (the stale-overlay class of 6 October 2026) run: bash infra/build-server/remote-run.sh --self-test + - name: no shell assignment hides behind a trailing comment (the swallowed-defaults class of 6 October 2026) + run: bash tools/ci/defaults-line-check.sh --self-test && bash tools/ci/defaults-line-check.sh - name: no secret file names and no 64-hex secrets in the tree (self-test first, then the tree) run: bash tools/ci/no-secrets-check.sh --self-test && bash tools/ci/no-secrets-check.sh - name: faucet unit tests (validation, the daily limits, the signed transaction; keccak, RLP and secp256k1 vectors) diff --git a/docs/analysis/horizon-2026-10.md b/docs/analysis/horizon-2026-10.md index 7c7ff3ca..342309df 100644 --- a/docs/analysis/horizon-2026-10.md +++ b/docs/analysis/horizon-2026-10.md @@ -2,36 +2,160 @@ Written 6 to 7 October 2026 by the Horizon coordinator (branch `horizon`, worktree `igneum-wt-horizon`) on the project lead's ask of 6 October 2026, 20:4x UK: "deep backward and forward predictive research and modelling for our algo and all of our systems: is there room for improvement, room for more coin utility, algo improvements, security improvements, anything we can do to make a 51% attack impossible, basically creating a level of polish that has not been seen before." Later the same evening: "research anything else that we can research too, predictions, forward thinking, what we can actually do that has not been done or applied, think outside the box", "be revolutionary", and "if we create a new way of hashing or a new way of proof of work to revolutionise the space then that's absolutely fine, I want you to deploy everything to create something that has not ever been done before." -The bar main set, and the bar this document holds every claim to: "impossible" is not available to any proof-of-work chain. The bar is that a majority of hash buys nothing: it cannot reverse what finality locked, cannot forge a proof the nodes re-execute, cannot change a rule without 95 percent signalling, and loses more than it earns. Every claim here is a number with a model or a simulation behind it, priced in rented hash at the measured 6 October rate (`docs/bench-log.md`, "Rental cost of hash, 6 October 2026": USD 0.0117 per MH/s-hour, the live devnet at 1.16 GH/s), with its consequence per tier and what to build. +The bar main set, and the bar this document holds every claim to: "impossible" is not available to any proof-of-work chain. The bar is that a majority of hash buys nothing: it cannot reverse what finality locked, cannot forge a proof the nodes re-execute, cannot change a rule without 95 percent signalling, and loses more than it earns. Every claim here is a number with a model or a simulation behind it, priced in rented hash at the measured 6 October rate (`docs/bench-log.md`, "Rental cost of hash, 6 October 2026": USD 0.0117 per MH/s-hour, so USD 11.7 per GH/s-hour and about USD 281 per GH/s-day; the live devnet at 1.16 GH/s), with its consequence per tier and what to build. Hours are agent hours (the project lead's rule: Claude-side work takes hours, never weeks). ## The lanes | Lane | File | State | |---|---|---| -| 1 consensus-security | `docs/analysis/horizon/consensus-security.md`, and the paper `docs/analysis/51-percent.md` | running | -| 2 algorithm | `docs/analysis/horizon/algorithm.md` | running | -| 3 finality-and-weight | `docs/analysis/horizon/finality-and-weight.md` | running | -| 4 economy-and-utility | `docs/analysis/horizon/economy-and-utility.md` | running | -| 5 network | `docs/analysis/horizon/network.md` | queued (waits for the 10 bps rows of `block-rate-devnet2.md`) | -| 6 polish | `docs/analysis/horizon/polish.md` | queued | -| 7 frontier | `docs/analysis/horizon/frontier.md`, model `sim/horizon/frontier/frontier_model.py` | landed 6 Oct 2026, 21:1x UK | -| 8 new-proof-of-work | `docs/analysis/horizon/new-pow.md`, prototypes in `proto-newpow/` | running (designs tonight, measured rows tomorrow afternoon UK) | +| 1 consensus-security | `docs/analysis/horizon/consensus-security.md`, and the paper `docs/analysis/51-percent.md`; models `sim/horizon/consensus-security/` | landed, commit eec2cd7 | +| 2 algorithm | `docs/analysis/horizon/algorithm.md`; model `sim/horizon/algorithm/model.py` | landed, commit 5ff7393 | +| 3 finality-and-weight | `docs/analysis/horizon/finality-and-weight.md`; models `sim/horizon/finality-and-weight/` | landed, commit c3aa502 | +| 4 economy-and-utility | `docs/analysis/horizon/economy-and-utility.md`; models `sim/horizon/economy-and-utility/` | landed, commit 4617a01 | +| 5 network | `docs/analysis/horizon/network.md` | running (takes the 10 bps runs A, A2 and the 1 bps control B) | +| 6 polish | `docs/analysis/horizon/polish.md` | running | +| 7 frontier | `docs/analysis/horizon/frontier.md`; model `sim/horizon/frontier/frontier_model.py` | landed, commit 5ff7393 | +| 8 new-proof-of-work | `docs/analysis/horizon/new-pow.md`; prototypes `proto-newpow/` | designs landed (5ff7393); measured rows by the afternoon of 7 October UK | ## 1. One page for the project lead -(Written last, from the lanes.) +What the night found, in the order it matters. + +1. **The one line where a majority earns more than it spends is the proving pool, not the chain.** Consensus checks a carried proof record's signature and its statement, not the proof (spec 07, 7.7 items 3 and 4; ledger P21, decided as v0 through the public testnet). Any block producer can therefore carry its own fake-proof records and be paid its block share of the 20 percent pool: 11,636 IGN an hour at 51 percent of blocks (lane 1, `cost_model.py`). Every other attack line costs more than it earns. Fix: verify the aggregated segment proof in consensus, 8 to 12 hours, no liveness cost. Until it lands, the public text must not say the 20 percent pool is "paid to provers" without the caveat. +2. **Tonight's two-hour finality pause was the rule working, then the frozen table holding it, and the fix is a signed exit.** Twenty keys holding 42.7 percent of the frozen voter table left in three minutes (the class v4 rehearsal job); checkpoint 6843 saw 53.1 percent of total and the 2/3 rule paused as designed; locks had formed without the hub, so topology is refuted (lane 3, from the observer rows). Rule v2 would have re-locked after 35 minutes; rule v3's frozen table holds it for a window: 2 hours on the devnet, 30 days on mainnet for the same event. Of five candidate rules simulated, only the departure announcement (a `leave` item in blocks, the key out of every denominator one hour later) keeps zero conflicting locks in every partition and eclipse AND ends the pause in under an hour (6 hours). The operational rule costs nothing: a standing box never leaves the live chain for an experiment, and any orchestrated departure over 10 percent of weight goes in slices under 10 percent an hour. +3. **A 51 percent attacker on Igneum buys a 90-second reorder window and nothing past a certificate.** A 45 to 51 percent withholder wins the selected-chain race over a 90-s hold 70 to 85 percent of the time (32 to 46 blocks at 1 bps); the lock lands 63 to 93 s after the checkpoint block and bounds what a majority can reorder; sustained withholding lifts weight share to about 56 percent, never two thirds (lane 1, GHOSTDAG simulator mirroring `protocol.rs`, 20 seeds). The veto (one third of weight) costs 20 days at 51 percent of hash: USD 6k at 1 GH/s, USD 5.8 M at 1 TH/s, half earned back as subsidy; once held, a pause is free and a pause-time 12-hour double spend costs USD 146 to 146k. Weight-gated deep fork choice (a tip forked more than 10 minutes back is a candidate only if its builders hold a third of the weight table at the fork) and vote-or-burn (a silent key's blocks burn 20 percent of their producer share) close those two lines, 16 to 24 hours together, no liveness cost. The paper is `docs/analysis/51-percent.md`. +4. **The chip question is settled in kind and open in degree.** The chip that matters is the stored-dataset memory-controller chip: 5.7x per joule against the 5090 at class v3, 2.1x at class v4 with a chip core as efficient as the GPU's (k = 1), 0.9x against the M5 Max (lane 2, `chip-model-v3` method). The reserve and the era draw buy about nothing against a chip (every drawn parameter is firmware; a reserve block is about USD 4 of silicon) and the public text should say what they do buy. The lever is the latency-shadow size N, and lane 2 and lane 7 agree it belongs in the era draw at genesis as a verifier-bounded ladder {100k, 130k, 200k, 330k, 650k, 1.0M}, each step by 90 percent miner signal, never unconditional (an unconditional doubling retires the M5 Max at era 1). First measured verifier proxies tonight: class v4 5.06 ms cold on a box core, 8.23 with the SMT sibling loaded (passes); dr736 10.51 and 15.49 (out); R0 is dr368. +5. **Fees are not a security budget for a decade, and the proving price is a function of network hash.** All utility curves together pay miners and provers USD 450 a day at launch and USD 4,200 in year 5 against USD 54,800 and 13,700 of daily emission (lane 4). The price a prover must charge is the subsidy it forgoes, 1 / network hash: 100 to 300x Boundless's rate at 1.16 GH/s, 0.2 to 0.4x at 100 GH/s beside its miner. The adopted job floor overprices the market above about USD 0.014 per IGN. A ten-day prover refusal strands 547,570 IGN a day of pool credit in an escrow with no rule to return it. Fixes: decouple the job price from `f_p` (16 hours), roll unproven credit forward (8 hours), publish the price as a formula, never a number (3 hours). +6. **The signalling window can be bought for a day.** The P2 one-day 95 percent window costs about 19 N of hash for 24 hours (USD 534k at 100 GH/s) and would force a class flip onto a fleet not yet on the object; a 6 percent holdout buys delay to the floor for nothing. Seven consecutive daily windows with the floor a week past publish fixes it, 3 hours. The three signalling thresholds (60 parameter, 90 upgrade, 95 class with floor) are stated inconsistently across spec 5.7, CLAUDE.md and the litepaper and must become one sentence. +7. **New proof of work.** Scheme A (mining is proving) is ruled out twice over, by lane 8's design pass (2.9 MB of openings per block, a 32 to 40 ms proof verify against the 10 ms gate, sampleability) and by lane 7's prior-art pass (Ball et al. 2017, Ofelimos 2022, Aleo). Schemes B (a tensor-shaped integer shadow that forces a chip to carry a GPU-class datapath) and C (the dataset derived from the execution state, so every hash proves the miner holds the chain) are in prototype on two rented 4090s; measured rows land by the afternoon. (Section 4, lane 8, when it lands.) +8. **The frontier list, honestly cut.** Do now: the finality weight table carried inside the recursive segment proof (a consensus proof at mergeset cost, a browser that trusts no node for the voter set; 60 hours, measure the in-guest BLS and colouring cycles first), reproducible-build attestations with Ember refusing a release under N of M (12 hours), a WASM verifier of the wrapped block proof in the tab (16 hours), Ember as node, wallet and light client for everyone (20 hours). Never: proving others' chains as the main income (all of Ethereum L1's proving is about USD 36 a day at the Sep 2026 tracker cost against USD 13,700 a day of year-1 emission at USD 0.005), the hash partly a proof, proof verify on a hardware wallet's secure element, a general unverifiable compute market, burn-redirect audit bounties (a dev fund with a veto). + +Rows from lanes 5 (network: block rate, the controller's red-block feedback seen in run A, node and bandwidth tiers), 6 (polish: the ten things the project lead would notice) and 8 (the two prototypes' measured rows) join this page and the table below when they land. ## 2. The ranked list: top 25 across every lane +Rank is payoff over cost across lanes, with safety first, then liveness, then money, then text. "L1 r3" means lane 1's own rank 3; the lane file holds the full evidence row. + | Rank | Item | Lane | Evidence | Model | Hours | Consequence per tier | What to build | Gate | |---|---|---|---|---|---|---|---|---| +| 1 | Verify the aggregated segment proof in consensus; a record whose proof fails against the pinned aggregator key is invalid | L1 r1 | spec 07 7.7 items 3 and 4; P21; a producer captures its block share of the 20 percent pool with fake records | capture = pool x (H inside the window + most outside): 11,636 IGN/h at 51 percent; SP1 light-verifier cost per record 1.8 to 2.4 s measured (bench-log) | 8 to 12 | prover: paid only for proofs; holder: the pool is real; node operator: one verify per record on the proof pool thread; miners: nothing | the verify call in the record check of `consensus/core` proving, the v0 per-shard record payout-only and capped at the exclusive window | fast-time: a fake-proof record is rejected by every node and the producer loses the block; a true record pays | +| 2 | The departure announcement: a `leave` item (key, DAA score, signature) in blocks; D = 1 h later the key is in no denominator, sliding or frozen, and its votes are invalid; app Stop and the fleet library send it | L3 r1, L1 r8 | tonight's pause (L3 3.1): 42.7 percent left in 3 min, the frozen table held a window; sim T: first lock 1 h after a 34, 45 or 50 percent departure, 0 conflicts in every partition, eclipse and equivocator row | `finality_horizon.py` seeds 7, 11, 13 | 6 (+4 for F5's trusted certificate) | holder: a 30-day mainnet pause becomes 1 h when leavers are honest; a silent leaver still costs a window; pool and rig: one message on a clean stop; attacker: buying keys to leave them gains nothing (w + L must still reach 2/3) | the item in the coinbase finality section, the denominator rule in `finality.rs`, the send in Ember and `tools/fleet/lib` | harness: 45 percent leaves with leaves, lock within D + 1 checkpoint; 0 conflicts in 50/50, 60/40 and the 34 percent eclipse | +| 3 | Signalling over 7 consecutive daily windows at 95 percent, the floor no nearer than 7 days past the publish, the stale-box list empty before the floor | L1 r6, L4 4.5 | the P2 one-day window is buyable: 19 N of hash for 24 h (USD 534k at 100 GH/s) forces a flip onto an unready fleet; a 6 percent holdout buys delay for nothing | `signalling.py`, `signal_game.py` | 3 | pool and rig: a week more before a class change and a week of visible share; home miner: a week to update; attacker: the bill x7 in public | the window count in the P2 rule, spec 5.7 text, one sentence carrying 60 / 90 / 95 in spec, CLAUDE.md and the litepaper | fast-time: 6 of 7 days at 95 percent does not flip; 7 does; the harness's failed case | +| 4 | Weight-gated deep fork choice: a tip whose fork point is older than D (10 min of past-median time) is a candidate only if the blocks on it since the fork were produced by keys holding at least 1/3 of the weight table at the fork point | L1 r2 | during a pause or the first 20 days a renter with fresh keys double-spends at the 12-h depth for USD 146 at 1 GH/s | `cost_model.py`; deterministic over the block's past | 10 to 16 | holder and exchange: rented hash cannot reorg past 10 min even while nothing locks; honest miners: nothing (their keys hold the weight); new chain: the first 20 days gain a bound they lack today | the candidate filter in GHOSTDAG tip selection over the certified weight table | fast-time: a 51 percent fresh-key fork from 15 min back is refused by every honest node; a 1/3-weight fork is accepted; partition heal unchanged | +| 5 | Vote-or-burn: a block whose producer key has participation under 0.5 over the presence window in its own past burns 20 percent of its producer share | L1 r3 | the pause as a liveness attack has zero marginal cost once the veto is held | 0.2 x A x 0.8 x subsidy: 7,757 IGN/h at 34 percent (USD 155/h at 0.02) | 6 to 8 | attacker: a pause costs; honest home miner: nothing while signing (the official client signs every checkpoint); pool user: the pool's participation; partition-safe because the test is the block's own past | the participation read in coinbase validation, the burn in the subsidy split | fast-time: a 34 percent silent set's blocks pay 80 percent; a 50/50 partition burns nothing on either side | +| 6 | Roll unproven shard credit into the next proven segment's pool instead of the escrow for ever | L4 r2 | a ten-day prover refusal strands 547,570 IGN a day (5.5 M IGN) with no rule; spec 5.3, 7.7 item 3, 7.8 item 7 are silent | `stress.py` refuse scenario, `stranded_share` | 8 | prover: credit is never lost to the market's slow days; holder: no undecided burn; rollup customer: nothing | the roll-forward in the pool credit split, spec 5.3 text | fast-time: 100 unproven then 10 proven segments return the escrow to zero | +| 7 | Decouple the job price from `f_p`: reserve = measured proving electricity per pgas at the published settlement rate (spec 5.10.3), the 1.5 premium becomes a bid | L4 r1 | at the adopted floor a billion-cycle job is 15 IGN = USD 0.075 / 0.30 / 1.50 against Boundless's 0.21 (approximate); above USD 0.014 per IGN the chain overprices by rule | `utility.py` sections 1 to 2 | 16 | rollup customer: a price that can clear; prover: still paid above electricity; holder: more jobs, more burn | the reserve rule in spec 5.4 and 5.11, the bid field on the job | a simulated job book clears within 20 percent of Boundless's median at all three prices | +| 8 | Operational rule, no protocol change: a standing box never leaves the live chain for an experiment; any orchestrated departure over 10 percent of weight is staged in slices under 10 percent an hour; the fleet library refuses to swap a standing box's chain | L3 r2 | the rehearsal took 36.5 percent at once; sim L2 and M4: a gradual departure costs nothing because every lock re-freezes the table | spec 3.7 item 2 | 1 | fleet operator: finality stays on; every tier: no pause from our own experiments | a refusal in `tools/fleet/lib` plus a `--staged` departure | the next rehearsal keeps `finality_active` true | +| 9 | Peer floor and mesh, and no `unwrap` on a peer-driven path with a sync-request fuzz gate (tonight's pruned-node crash: `consensus/src/processes/sync/mod.rs:87`, plus 27 sibling sites listed in L1) | L1 r4 | run A's star (321 tips, 77 percent red); the hub crash at 19:57Z: any peer can crash any pruned node at zero hash cost | a star with its hub down is n islands; the sibling list by file:line | 9 to 11 | node operator: no crash from a peer's request; home miner and rig: the app alarms under 3 outbound peers or 2 checkpoints without a vote; fleet: no star | the fleet lib dials 3 boxes beside the hands; the node alarm and app line; the unwrap sweep; the fuzz in `tools/ci` | the fuzz runs the sync request space against a pruned node with no panic; the alarm fires on a known-cut case and stays quiet on a healthy one | +| 10 | N (the latency-shadow size) as a genesis ladder per era {100k, 130k, 200k, 330k, 650k, 1.0M}, floor and ceiling fixed at genesis (10x verifier headroom on the 2.5x rule; O-1.14 sets the ceiling), one era draw consumed as for `epoch_len`, each step by 90 percent miner signal over 7 days, never unconditional | L2 r5, L7 finding 1 | HBM4 raises the stored-dataset chip's bare edge (4.9x to 12x depending on tFAW, L2 and L7 disagree and both are labelled); N = 100k holds the chip at 2.1x on GDDR7 at k = 1, N = 330k at 1.3x; an unconditional doubling retires the M5 Max at era 1 (-10.5 percent at 200k) | `model.py --section ladder` (L2 5.3a, the reconciled table) | 6 to 8 | 100k to 130k: the M5 Max -3.3 points, nobody else; 130k to 200k: M5 Max -6 more, the 5090 -2.7 at its cap, the 4070 +21 W; 200k to 330k: compute-bound on every capped NVIDIA card; pool users nothing at any step; verifier +0.17 to 0.56 ms per warp | the ladder in the era-draw table of spec 1.13, the step rule on P2's mechanism | per step: every public-benchmark card within 5 percent of its previous-step rate, bit-exact on three vendors, verifier under 10 ms cold on the O-1.14 core | +| 11 | Aggregate-first vote verification and aggregated in-block carriage as the mainnet default (spec 3.4.2 item 2, decided) | L3 r3 | per-vote verification is 1.6 s per checkpoint per node at 1,000 voters and about 13 s at 8,192 (approximate); measured lock delay p50 0.86 to 1.26 s at 4 to 93 voters on node 1 | L3 4.3 table; blst 0.3.17 `fast_aggregate_verify` already in the fork | 8 | node operator on a laptop: a checkpoint costs one pairing, not a thousand; every tier: lock delay stays under 3 s at 8,192 voters | batch votes per (index, hash), verify once, bisect on failure; the in-block aggregate | fast-time with 1,000 and 8,000 synthetic voters: p50 lock delay under 3 s, CPU under 25 percent of a core, 0 conflicts | +| 12 | The finality weight table carried inside the recursive segment proof, updated one mergeset per segment: a consensus proof at mergeset cost | L7 r1 | phase two's hardest item (P4) becomes incremental on code that exists; about one BLS verify per 30 s (one shard's budget, approximate, unmeasured) | `frontier_model.py` | 60 (first 10: measure) | phone wallet and bridge: finality from one proof, no node trusted for the voter set; rollup customer: one object; prover: one more public-value block per segment | the W2 transition in the aggregator guest, the GHOSTDAG colouring check in-guest | first: BLS verify and colouring cycle counts inside the SP1 guest on a 24 GB card within 2x of the estimate; then a proof per checkpoint under 30 s on a proving-only 5090 | +| 13 | Reproducible-build attestations in a registry contract; Ember refuses a release under N of M attestations | L7 r3 | ledger G7, G13: the update key is an operator channel; Bitcoin's guix.sigs is the precedent with the chain as the sigs repo | text and contract | 12 | every tier: an update needs N independent builders, not one key; fleet: the box's reproducible Windows and Linux builds (CLAUDE.md 6 Oct) are the inputs | the contract, the builder tool, the Ember check | a release with 1 of 3 attestations is refused by Ember; 3 of 3 installs; the known-failed case | +| 14 | Close O-1.14 with a real 2019 laptop run; adopt the half-core proxy as the standing stand-in; dr736 out, dr368 in as R0 | L2 r1 | box proxy: dr736 10.51 ms cold and 15.49 half-core; class v4 5.06 and 8.23 | L2 5.5 | 2 | miners 0; pool verifier cores x1.3 at dr368 instead of x2.4; a 2019 node keeps 1.8 ms under the gate on class v4 | Windows cross build on the box, relay to the laptop, bench, log | ms per warp under 10 cold on the real core for class v4 and dr368 | +| 15 | The vote signs the execution root too (chain id, index, block hash, post_root); a certificate pins the state; snapshots check against the last certificate | L1 r5 | snapshot poisoning: a wrong state above the pin is undetectable today | every voter executes natively (spec 07) | 8 to 12 | holder: a lock is a state lock; node joining from a snapshot: cannot be poisoned; cost: exec lag enters lock latency | the vote message, the pin rule, the snapshot check | fast-time: a poisoned snapshot node cannot join the quorum; honest lock delay rises by the measured exec lag only | +| 16 | `finality_provisional` reported beside `finality_active`, never as a lock, plus a detector-driven `security_alert` (correlated group over 1/3 of a day's blue blocks; pause; conflict) shown by wallets and the explorer as "confirm at 12 h" | L3 r4, L1 r7 | sim P: provisional conflicts in every partition (516 to 1,062 per 360 min), final 0; the exchange guidance exists only as text today | L3 4.1; the detector of counter-asic-3 | 3 + 4 to 6 | exchanges: a third row in the guidance; holder: told when to wait; home miner: a line in Ember | the RPC fields, the explorer and wallet copy, spec 3.9 row | forced pause: explorer and wallet show provisional and the alert; a healthy day shows neither | +| 17 | Every miner-signalled execution parameter enters the consensus digest the same release, with a `tools/ci` check that fails a `Params` field marked signalled and absent from the digest | L4 r5 | signal-then-defect on an execution parameter is a silent state fork unless the handshake refuses the defector; `Params.fees` is in the digest, nothing guarantees the next | `signal_game.py` section 3 | 6 | node operator: no quiet fork; every tier: a defector is refused at the handshake | the check, the digest rule in spec 5.8 | the check fails on a planted field and passes on the live set | +| 18 | The 80/20 split as a 60 percent parameter inside a hard band [10, 30] percent | L4 r4 | not load-bearing at launch traffic; 30 percent buys backlog relief at 100 shards a block (economy-2026-10-04 5.3) | `stress.py` | 10 | prover and miner: a split the market can move inside a band nobody can break; holder: the cap and the halving untouched | the band in spec 5.3 and 5.7 | harness: a 60 percent vote reaches 30 percent; a 100 percent vote cannot pass the band | +| 19 | A WebAssembly verifier of the wrapped block proof in the tab, with the millisecond count shown | L7 r4 | three working precedents (Helios WASM, ziren-wasm-verifier, wasm-groth16-verifier); the certificate half already runs at 58 to 68 ms warm, 139 to 155 ms cold (bench-log round 6, P3) | text | 16 | holder and rollup customer: verify in the browser; the P3 wrapper measurement comes forward | the Groth16 or Plonk wrapper, the WASM verifier on the site | a block proof verifies in the tab under 500 ms on a laptop; the measurement labelled on the page | +| 20 | Ember as node, wallet and light client for everyone | L7 r5 | 1,000 testnet miners become 1,000 verifying nodes; Monero shows about 5,000 peers (monero.fail), Ethereum 8,136 execution nodes (ethernodes), approximate | text | 20 | home miner: one app; node count = miner count; Sybil counts of nodes irrelevant by design; node cost per lane 5's tiers | the node and wallet surfaces inside Ember | a fresh Windows install mines, verifies and shows a balance in the first 60 s with no second download | +| 21 | Work-stake: vote weight as the external-job bond, with the spec sentence "no coin stake; the only thing at stake is 30 days of public work" written first | L7 r2 | a 0.1 percent key has about 16,427 IGN of 30-day pool income plus its vote at risk against a designed 0.0015 IGN coin bond per job | `frontier_model.py` section 3 | 24 (prototype) | prover: a bond nobody can buy; rollup customer: a griefing cost that scales with the prover's standing; holder: "no stake" stays true as "no coin stake" | the bond rule in the job claim | a griefed job costs the griefer its weight for 30 days in the fast-time harness; the spec sentence lands before the prototype | +| 22 | Client-shipped certified checkpoint (index, hash, voter-table digest) refused if missing from the DAG; exec generations spaced geometrically to the finality depth (about 16, 1.8 GB today) | L1 r9, r10 | long-range and seed attacks; a pause-time deep reorg needs a peer's snapshot today | assumevalid's shape; 114.8 MB per snapshot measured | 3 + 3 | every tier: a fresh install cannot be bootstrapped onto a private DAG; node operator: disk for generations, no blocked executor after a deep reorg | the checkpoint in the release, the generation schedule | a cold node refuses a DAG missing the checkpoint; a 5,000-block reorg re-executes from a generation with no snapshot request | +| 23 | Public-text corrections, one bundle: the prover's price as a formula with network hash as the input, never a number; the era draw and the reserve described as schedule changes against fixed datapaths, not unpredictability against a chip, with the chip's USD per MH/s-hour beside the honest cards'; F19's "day 19 to 20" is a v2 number (v3: a full window); funding.md section 4's dev-fee ceiling is 1 percent of the producer share (38,520 / 154,080 / 770,400); the dev fee is "default-on, switchable", not "optional"; "proofs at the cost of power" conditioned on hash; the three signalling thresholds in one sentence | L4 r3, r7; L2 r3; L3; L1 | each row cites its lane section | text | 1 to 3 each | holders and critics read claims that survive review | the litepaper, the customer brief, spec 5.7, funding.md, the ledger F19 | the site's forbidden-strings check and a reviewer's read | +| 24 | Measure the FPGA lane on AWS F2 (one VU47P, HBM2, about USD 1.98 an hour) and replace the ceiling row | L2 r2 | the measured 2.4 G reads/s equals the JEDEC tFAW ceiling; the old 1.9x row rests on a 12 ns tFAW the JEDEC HBM2 table does not give (28 ns); the soft overlay reads 0.30x to 0.47x of the 5090 per watt | L2 5.1 | 8 to 10 plus USD 2 to 8 | none today; the public FPGA claim becomes a measured number | the overlay bench on F2 | reads per second per watt at 1 GiB; the row replaced | +| 25 | Ember tune as the shipped default per card model, and the two fleet measurements every price rests on: the miner's hash loss while each tier proves, and a full 30 M-cycle shard beside the miner on 12 and 16 GB cards | L2 r7, L4 r8 | the 4070 at 3.65 uJ untuned and 2.57 tuned (-30 percent); the hybrid row is the only one that undercuts the market and rests on one 5090 measurement; the 4.7 M fixture is 16 percent of a full shard (linear scaling says 240 s on a 3060, outside the 120-s claim timeout) | L2 5.1; `utility.py hybrid_hash_loss` | 2 + 6 (fleet) | every NVIDIA tier gains 10 to 30 percent per joule; the 12 GB tier learns whether it can claim a full shard in time | the defaults table in the app from the fleet priors; eleven rows with both numbers in `prover-tiers-real-cards.md` | the rows land; the claim timeout is set from them | + +Rows from lanes 5, 6 and 8 are inserted and the table re-ranked when they land (expected: the controller's red-aware correction and the block-rate recommendation from lane 5; the ten the project lead-visible polish rows from lane 6; the class v5 verdicts from lane 8). + +### Not recommended, with the reason (as valuable as the list above) + +| Item | Lanes | Why not | +|---|---|---| +| Prover attestations as a second finality leg | L1 r12, L3 r8 | provers are the miners (same vote keys), so no new party; proof coverage is 2.4 to 4.7 percent of blocks tonight with lag p99 62 s, so every lock would wait on proofs; revisit only after 99 percent of blocks are proven within 60 s for 7 days with 3 provers per block, and even then it adds about a minute of lock delay | +| Time-locked (vesting) weight | L1 r13 | a bought key transfers vested weight, so the acquired-keys bound is unchanged; honest new cohorts wait longer | +| Any automatic re-lock after an abrupt departure | L1 r14, L3 r7 | departure and partition are the same observation in one view; the decaying denominator produces 467 to 473 conflicting locks in a 360-minute 50/50 honest split, the hysteresis floor brings the 13.3 percent equivocator bound back (565 to 597 conflicts at 20 percent); the two-tier rule is a report, never a lock | +| Mining is proving (the lottery's work as a proving step) | L8 scheme A, L7 3.10 | dead on bytes (2.9 MB of openings per block), on the verifier (32 to 40 ms against 10), and on sampleability (Ball et al. 2017, Ofelimos 2022); re-opens Aleo's fastest-prover-wins | +| Proving others' chains as the main income | L7 3.11 | all of Ethereum L1's proving is about USD 36 a day at the Sep 2026 tracker cost against USD 13,700 a day of year-1 emission at USD 0.005; demand must grow 1,000x against a falling cost curve | +| Burn-redirect audit bounties, a review escrow by 60 percent signal | L7 3.6, L4 task 3 | a dev fund with a veto, the switch spec 5.5 removed; the honest payer for a second audit is the entity's own provers | +| Proof verification on a hardware wallet's secure element | L7 3.9 | a bn254 pairing on a Cortex-M-class element is seconds to minutes (approximate); do the companion verify | +| A general compute market for unverifiable work (rendering, inference) | L7 3.12 | an escrow without a verifier is a trust-me payment with lower fees; the verifiable subsets are named and kept | ## 3. What a 51 percent attacker can and cannot do -(Summary of `docs/analysis/51-percent.md`.) +The paper is `docs/analysis/51-percent.md` (lane 1). In one table, at the measured USD 11.7 per GH/s-hour: + +| Hash share | What it buys | For how long | Cost at 1 GH/s / 1 TH/s of network | What it earns | Net | +|---|---|---|---|---|---| +| 20 percent | reorders only the last k = 18 blocks; nothing past a certificate | per attempt | subsidy forgone while withholding | its block share minus reds | loss | +| 34 percent | wins the selected-chain race only inside 60 s (40 percent of the time); holds the veto (blocks every lock) after 20 days of mining at 51 percent, or 30 days at 52 percent of the network's hash | 20 to 30 days to acquire, then free to hold while silent | about USD 6k / 5.8 M to acquire (half earned back as subsidy); a pause then costs nothing | subsidy; nothing from the pause itself unless it double-spends at the 12-h depth during the pause (USD 146 to 146k) | the one line vote-or-burn (rank 5) and weight-gated fork choice (rank 4) close | +| 45 to 51 percent | wins the selected-chain race over a 90-s hold 70 to 85 percent of the time (32 to 46 blocks at 1 bps); turns 26 to 28 percent of honest blocks red; lifts its weight share to about 56 percent, never 2/3 | the 63 to 93 s between a checkpoint block and its lock | USD 11.7 / 11,700 per hour of rented hash; the market could not supply a TH/s on 6 October (RunPod: 0 pods for 20 asks) | a quarter of honest subsidy while withholding; the pool capture of rank 1 (11,636 IGN/h) until the in-consensus verifier lands | loss on the chain; gain only through the pool, which rank 1 closes | +| 67 percent of weight | locks alone | 2.03 x N of hash for 30 days: USD 17,100 per GH/s of network | | subsidy | at rental equilibrium about 33 percent of 30 days of subsidy net | +| any share | a rule change | 95 percent of blue-block weight over the window, or the floor | 19 N for 24 h today (USD 534k at 100 GH/s); x7 under rank 3 | | | + +What no share buys: a block the nodes do not re-execute (the native-execution veto), a state the aggregated proof chain does not commit to, a lock past a certificate under two thirds of weight, a parameter change without the signal. + +The residual risks stated plainly: the first 20 days (no weight table yet); a pause after a sudden departure of a third of weight (30 days under v3 until rank 2 lands); the Sybil count of keys (X5's definition adopted, the measurement scheduled); the proving pool until rank 1; the 2/3-of-total rule under long churn (the window bound: a 50/50 honest partition locks on both sides from day 10). ## 4. Per lane: the three biggest findings +### Lane 1, consensus-security +1. The lock bounds a majority; the k-cluster does not (45 to 51 percent wins a 90-s race 70 to 85 percent of the time; the lock at 63 to 93 s is the bound). +2. The veto is cheap (20 days of 51 percent: USD 6k at 1 GH/s, 5.8 M at 1 TH/s, half earned back) and the pause is then free. +3. The proving pool is capturable today (11,636 IGN/h at 51 percent) with a correct-statement fake-proof record; the only attack that earns more than it costs. + +### Lane 2, algorithm +1. Class v4's verifier measured on a 2022 server core: 4.90 / 5.06 / 8.23 ms (steady / cold / half-core); dr736 9.76 / 10.51 / 15.49, out; R0 is dr368. +2. The f = 1 GDDR7 chip: 5.7x per joule against the 5090 at v3, 2.1x at v4 with k = 1; USD 0.00021 per MH/s-hour against USD 0.0117 rented; the FPGA soft overlay 0.30x to 0.47x per watt. +3. The reserve and the era draw buy about nothing against a chip; the N ladder in the era draw at genesis is the lever, and lane 7's HBM4 column is reconciled with three named disagreements. + +### Lane 3, finality-and-weight +1. Tonight's pause: the 2/3 rule at checkpoint 6843 (53.1 percent of total after a 42.7 percent departure), then the frozen table (Q5) holding it for a window; locks formed without the hub; topology refuted. +2. Only the departure announcement passes the line (0 conflicting locks everywhere, the pause under an hour); the decaying denominator, the hysteresis floor and the two-tier rule fail or are reports. +3. Weight capture costs USD 8,424 x N x W/(1 - W) to rent for the window: the veto 0.52 N for 30 days (USD 4,300 per GH/s of network), a lock alone 2.03 N (USD 17,100); lock delay measured p50 0.86 to 1.26 s at 4 to 93 voters; the path breaks near 1,000 voters on per-vote verification. + +### Lane 4, economy-and-utility +1. The proving price is h/N: 100 to 300x Boundless at 1.16 GH/s, 0.2 to 0.4x at 100 GH/s beside the miner; the adopted floor overprices above USD 0.014 per IGN. +2. Fees are not a security budget for a decade (USD 450 a day at launch, 4,200 in year 5, against 54,800 and 13,700 of emission); sustained honest hash costs USD 24.8 per GH/s-day against USD 281 rented, so the 20-day veto costs 11.8x the honest fleet at every price and year. +3. The 80/20 survives every stress but a ten-day prover refusal, which strands 547,570 IGN a day of pool credit with no rule to return it. + +### Lane 7, frontier +1. HBM4 raises the stored-dataset chip's edge (lane 2 bounds the figure at 4.9x to 12x bare by tFAW); the N schedule belongs in the era draw at genesis. +2. Vote weight is already a slashable, non-purchasable bond: work-stake for external jobs, with "no coin stake" written into the spec first. +3. The consensus proof can be incremental: the weight table inside the recursive segment proof, one mergeset per segment; the first measurement is the in-guest BLS verify and colouring cycle counts. + +### Lanes 5, 6, 8 +(Filled when they land.) + ## 5. What was not run, and why +| Item | Lane | Why | +|---|---|---| +| The in-guest BLS verify and GHOSTDAG colouring cycle counts (rank 12's first gate) | 7, 3 | no 24 GB card free tonight: PC 2 and the fleet were on class v4 and Devnet 2 | +| The real 2019-class core (O-1.14) | 2 | the US laptop was not on the relay; the box's half-core proxy stands in | +| The FPGA soft overlay on real HBM2 | 2 | no FPGA in the fleet; AWS F2 plan written | +| The AMD watts at every N | 2 | the ADLX sampler row is owed on the runner's `--cards-off` mechanism (counter-asic-3-status 6a) | +| A `cargo bench` of `fast_aggregate_verify` at 93, 1,000 and 8,192 keys | 3 | the BLS figures are arithmetic on blst's published timings, approximate | +| The 10 bps runs A2 (mesh) and B (control) | 5 | landing during the night; lane 5 reads them before it closes | +| The full 30 M-cycle shard beside the miner on any tier; the hash loss while proving on Ampere and Ada | 4 | the fleet measured the 4.7 M fixture only | +| Market prices for Taiko-class batch proving and Bonsai | 4 | not published; marked approximate | +| The observed end of tonight's pause | 3 | expected at DAA 216,402, about 20:40Z; the timeline closes when the observer rows show the lock | + ## 6. Rules and corrections for main + +| # | Rule or correction | Lane | State | +|---|---|---|---| +| 1 | P21 is a priced economic hole until the in-consensus verifier lands; the 20 percent pool is not "paid to provers" without the caveat | 1 | relayed | +| 2 | The one-day P2 signalling window is buyable for a day; seven consecutive windows | 1, 4 | relayed | +| 3 | The DAG controller reads blue work only, so a withholder or a star eases difficulty 16 to 32 percent (run A's loop) | 1, 5 | relayed; lane 5 owns the fix | +| 4 | No `unwrap` on a peer-driven path; the sibling list; a sync-request fuzz in `tools/ci` | 1 | relayed | +| 5 | A standing box never leaves the live chain for an experiment; orchestrated departures over 10 percent staged under 10 percent an hour | 3 | relayed; the fleet library refusal is rank 8 | +| 6 | Under rule v3 any sudden departure of a third of weight is a 30-day mainnet pause; the leave item is the one safe way to shorten it | 3 | relayed | +| 7 | F19's "stalls until day 19 to 20" is a v2 number; under v3 a full window | 3 | relayed, ledger text owed | +| 8 | "No coin stake; the only thing at stake is 30 days of public work" into spec 03/05 and the litepaper before any work-stake prototype | 7 | relayed; routed to the site-miner agent | +| 9 | The litepaper's "proving: a second income" carries the USD 36 a day arithmetic | 7 | relayed; routed | +| 10 | Ember's updater installs nothing while finality is paused | 7 | relayed; sent to the Ember agent for 0.3.16 | +| 11 | The customer brief's "priced in dollars per proof" and the litepaper's "proofs at the cost of power" are conditioned on network hash | 4 | relayed | +| 12 | The three signalling thresholds (60 / 90 / 95) in one sentence across spec 5.7, CLAUDE.md and the litepaper | 4 | relayed | +| 13 | The spec is silent on pool credit nobody claims; today it is a burn nobody decided | 4 | relayed | +| 14 | funding.md section 4 overstates the dev-fee ceiling by a quarter | 4 | relayed | +| 15 | Any future miner-signalled execution parameter outside the consensus digest makes signal-then-defect a quiet state fork | 4 | relayed | +| 16 | The era draw and the reserve are not unpredictability against a chip; the public text should say what they buy | 2 | relayed | diff --git a/docs/analysis/horizon/network.md b/docs/analysis/horizon/network.md new file mode 100644 index 00000000..5ecbfe85 --- /dev/null +++ b/docs/analysis/horizon/network.md @@ -0,0 +1,244 @@ +# Horizon lane 5: network. Block rate, propagation, node cost, bandwidth, pruning and the snapshot path + +6 October 2026, evening UK. Lane 5 of the Horizon programme. Worktree `/Users/joshm/Projects/igneum-wt-horizon`, branch `horizon`. Scripts in `sim/horizon/network/` (README there). Nothing live was touched: the Devnet 2 seed log on igneum-build-1 and `~/Desktop/fleet/bps/A.jsonl` were read, never written. + +What was read: `docs/spec/02-consensus.md` (2.1 parameters, 2.3 the difficulty rule, 2.4 header, 2.5 emission), `08-client-security.md`, `10-light-client.md`; the fud-close worktree's `docs/spec/03-finality.md` C1 and 3.4.2 (vote item 281 bytes, the bitmap and per-block bounds); `docs/fud-ledger.md` M20, M21 (block sizes, k re-derived, the 490 KB body run), X20, M30; `docs/bench-log.md` entries "4 October 2026, cloud devnet" (inter-region RTT and propagation), "ledger M30" (RSS, the 256 MiB cache, the s8 steady slope), "6 October 2026, 12:25 to 13:20Z, the finality route" (the seed as the only peer, the route overflow), "Rental cost of hash, 6 October 2026"; `docs/plans/hands-on-build-1.md` (node 1 and the observer: data dirs 856 and 822 MB, the 127.6 MB exec snapshot), `seed-nodes.md`, `cloud-devnet.md`, `release-0.3.14.md` (the snapshot path and the restart pin), `release-0.3.13.md` 4a; the fleet worktree's `docs/analysis/block-rate-devnet2.md` (read at 19:5xZ and again at 20:1xZ: RUN_A, RUN_B, TIER_TABLE and RECOMMENDATION are still placeholders), `tools/fleet/bps-collect.py`, `box-dn2.sh` (one `--addpeer=`: the star), `devnet2-override.json`, `docs/bench-log.md` of that branch; the live record of run A: `~/Desktop/fleet/bps/A.jsonl` (43 rows, 19:18 to 19:51Z), `collect-A.log`, `runA-start2.log`, `runB.sh` (run B starts at 20:25Z) and the seed's log `/home/build/dn2seed-A.log` on igneum-build-1 (20,529 lines, 19:17 to 19:59Z, read over ssh); `vendor/rusty-kaspa` `consensus/core/src/config/bps.rs` (the k table, `calculate_ghostdag_k`, parents, mergeset, pruning depth), `constants.rs` (delay bound 5 s, delta 0.01, DAA window), `params.rs` (mainnet `BlockrateParams::new::<10>()`, Crescendo activation 110,165,000), `consensus/src/processes/difficulty.rs` and `window.rs` (the window holds every mergeset block, blue and red), `docs/crescendo-guide.md` (10-bps node requirements); the fork `vendor/igneum-node` (read-only) at `release-0.3.14-node`: `consensus/core/src/igneum.rs` `difficulty` (CAP_BLOCKS 20, the sanitised clock, the clamps), `consensus/src/processes/difficulty.rs` `igneum_difficulty_bits` (blue-work steps on the selected chain), `consensus/src/processes/finality.rs` (MAX_VOTES_PER_BLOCK 48, KEEP_CHECKPOINTS 2,000), `igneum/exec/src/proving.rs` `p2p_snapshot_gate` and `on_exec_snapshot`, `igneum/exec/src/snapshot.rs`; the fork branch `devnet2-bps` 1279a1d6 (`IGNEUMD_DEVNET_BPS`); lane 3's `finality-and-weight.md` sections 5.5 and 8 (the aggregation path tonight), lane 4's `sim/horizon/consensus-security/ghostdag_results_{1,10}bps.md`. + +## 1. The question and the answer in one paragraph + +the project lead asked for Kaspa's answer to solo-miner variance: a higher block rate. Run A ran Devnet 2 at 10 blocks per second through one seed and produced 77 percent red blocks, 321 tips and a 55-block reorg. The propagation model says the links and the star did not do that: with the measured latencies it predicts under 0.1 percent red at 10 bps in a star and in a mesh. What did it is the seed's CPU per block, measured at 61 ms (narrow DAG) to 345 ms (mergeset 150 to 200), against a budget of 100 ms per block at 10 bps; with that cost in the model the star gives 47 to 88 percent red and queueing waits of 26 to 1,769 s, which are the "Accepted 100 blocks via relay" batches in the log. The difficulty rule then read blue work over a chain step capped at 2 s and hardened until the DAG ran at 2 s / (chain-step spacing) of target (model 4 blocks/s at a 5-s spacing, record 3.3 to 3.6), while a narrower-but-still-wide DAG would have made it ease (the direction main reported); counting every mergeset block over the real span, as Kaspa's window does, is unbiased in both regimes. The block rate for the public testnet is 1 bps; 10 bps is a gated step that needs the per-block node cost under 50 ms on a laptop core at a mergeset of 248, the checkpoint interval and the clock cap re-denominated in DAA seconds, and vote aggregation, because with C1 in blue blocks 8,192 voters at 10 bps are 66 GB per node per day of votes. + +## 2. Method + +| Step | What | Where | Machine, lock | +|---|---|---|---| +| Record | run A's per-minute rows (blocks, blue score, tips, peers, exec tip, CPU, RSS, bytes), the seed log's `PoW accepted` per minute, `Processed` lines (parents, mergeset per 10 s), `Finality: checkpoint N determined` (blue score against DAA), reorg lines, route drops | `~/Desktop/fleet/bps/A.jsonl`; `/home/build/dn2seed-A.log` on igneum-build-1 | read only | +| Propagation | event simulation: Poisson production, star or mesh, lognormal links, inv/request/block hops, a hub (or every node) as a single server with a per-block cost, GHOSTDAG colouring with the first k-cluster condition, parent and mergeset caps, reorg depth at miner 0 | `sim/horizon/network/propagation.py`, `results.md`, `results-2.md` | Mac, `with-lock.sh run nice -n 19`, seed 7 | +| Controller | the fork's estimator against a wide DAG in closed loop with the 3% / 10% clamps, against the whole-DAG estimator and a red-corrected blue estimator | `controller.py`, `controller-10bps.md`, `controller-1bps.md` | Mac, seed-free (deterministic) | +| Arithmetic | k and parameters per rate, votes and bounds, bytes per node per day, CPU budget, RSS and disk, pruned and archival growth, subsidy and payout intervals, finality timing, light-client bytes | `cost.py`, `cost-tables.md` | Mac | + +Nothing was built. No node ran. The fleet's run B (1 bps control, starts 20:25Z) and the mesh variant A2 (not scripted in the fleet worktree at 20:1xZ: no `mesh` or `A2` in `tools/fleet/` or its plans) had not landed when this file closed; section 7 names what they owe. + +## 3. Evidence + +### 3.1 Run A, measured (igneum-devnet-2, 10 bps, 41 miners through the seed on igneum-build-1, genesis bits 505413632) + +Phases from the seed log's checkpoint series (blue score against DAA score; red share = 1 - blue / DAA over the interval) and `Processed` lines; CPU per block from `A.jsonl` `cpu_rss` (ps lifetime %CPU times elapsed, differenced) over the accepted-block deltas. + +| Window (Z) | Production at the seed, blocks/s | Blue rate, blocks/s | Red share over the window | Mergeset per block (Processed) | Parents | Tips at the seed | Hub CPU per accepted block | Hub RSS | What the log shows | +|---|---|---|---|---|---|---|---|---|---| +| 19:23 to 19:29 | pods join (peers=1 each, blocks=0 synced=false on some), 26 blocks/s peak at 19:30 | | 33% (cp 1 to 29) | 1 to 35 | 1 to 16 | 6 to 30 (pods) | 61 ms | 0.45 to 1.9 GB | the 55-block reorg at 19:27:41Z "unwinding to height 1" (a late-joining pod's chain from genesis) | +| 19:29 to 19:31 | 19 to 26 | 1.3 | 77% (cp 29 to 37) | 29 to 36 | 15 | | 115 ms | 1.9 to 2.5 GB | | +| 19:31 to 19:37 | 13 to 19 | 0.7 | 94% (cp 37 to 45) | 53 to 197 | 12 to 15 | 295 | | 2.5 to 4.0 GB | `Accepted 97 / 100 / 31 blocks ... via relay` batches; headers ahead of blocks | +| 19:37 to 19:41 | 8 to 14 | 0.5 | 95% (cp 45 to 49) | 150 to 190 | 12 | 218 to 295 | 345 ms (19:37 to 19:51 mean) | 4.0 to 4.5 GB | window filled at 19:37Z; first lock cp 47 at 19:40:08Z | +| 19:41 to 19:47 | 3 to 7 | 1.0 | 50 to 88% (cp 49 to 61) | 80 to 125 | 13 to 15 | 230 to 350 | | 4.6 to 5.2 GB | `incoming route for IgneumFinality is full, message dropped ... the peer stays`: 109 to 2,547 drops per peer by 19:56Z | +| 19:47 to 19:59 | 3.3 to 3.7 | 1.4 to 1.8 | 41 to 56% (cp 61 to 92) | 47 to 90 | 15 to 16 | 348 to 396 | | 5.4 GB at 19:51 | 16 of 47 checkpoints after the window filled LOCKED (cp 47 to 79); cp 61 determined 19:47:00, locked 19:47:18 | +| Cumulative to 19:59:32Z | 6.1 (DAA 12,233 in 2,012 s) | 1.38 (blue 2,786) | 77.2% | | | | | | main's 12.4 blocks/s, 77 percent, 321 tips, max reorg 55 are the 19:3xZ to 19:45Z readings of the same record | + +Other measured rows of the run: 421 selected-chain reorgs at the seed (132 of depth 1, 62 of 2, 42 of 3, 25 of 4, 19 of 5; 2 of 43, 1 of 44, 1 of 55); the seed's `rx_tx` counter (netns-wide, so an upper bound) 325 MB in and 2,133 MB out between 19:37:05 and 19:51:15Z (850 s, 4,284 accepted blocks): 2.5 MB/s out, 61 KB/s per peer, about 12 KB per block per peer (a block with its finality section of up to 48 votes at 281 bytes is about 14 KB); exec tip 154 chain blocks at 19:51Z against 11,239 blocks (the executor follows the selected chain, which advanced about one chain block per 10 s at the widest). The run's difficulty values are not in the record: `bps-collect.py` strips `difficulty=` from the watch line before storing it and the node log carries no bits; main's statement that the rule "lowered difficulty" is therefore unverified here, and the production curve (26 to 3.5 blocks/s at a hash the pods' peers=41 say stayed connected) is the measured fact section 5.4 reads. + +### 3.2 Measured propagation and block sizes (the inputs the model takes) + +| Quantity | Value | Source | Label | +|---|---|---|---| +| Inter-region RTT | 35 ms (hel1-fsn1) to 289 ms (sin-ash); 0.4 to 0.7 inside a location | bench-log, cloud devnet 4 Oct | measured | +| Block propagation to 80% of 12 nodes over about 3 hops, 723-byte bodies | p50 343, p90 497, p99 666, max 2,313 ms | same | measured | +| One relay hop on 100-ms proxied links (inv, request, block) | p50 318 to 329 ms; own-node processing 6 to 16 ms | fud-ledger M21 run, 5 Oct | measured | +| Two hops with 490 KB bodies | p50 641, p99 812, max 857 ms (59-byte bodies: 626 / 1,093 / 2,099) | same | measured | +| Live devnet block, 1 coinbase, 22 keys' votes partly | p50 723 B, p90 1,022, max 6,908; coinbase payload p50 395, max 6,580 | fud-ledger M21 sweep | measured | +| Vote item, certificate, proof record | 281 B; 273 B + V/8; 274 B | spec 03 3.4.2 (fud-close), fork | cited | +| k from the measured delays at 1 bps | p99 0.67 s gives k 5, max 2.3 s gives k 10; k 18 is the 5-s bound | fud-ledger M21, `calculate_ghostdag_k` | cited | +| Kaspa 10-bps node requirements | 8 cores, 16 GB RAM, 256 GB SSD, 40 Mbit/s minimum; 12 to 16 cores, 32 GB preferred | `vendor/rusty-kaspa/docs/crescendo-guide.md` | cited | + +### 3.3 Measured node figures + +| Quantity | Value | Source | Label | +|---|---|---|---| +| PoW cache | 256 MiB per day key, KEEP_DAYS 3, 768 MiB worst, 512 MiB around midnight UTC | bench-log M30 entry | measured | +| RSS slope, narrow DAG, 60x fast time, 1 block/s | 30.2 MB per 1,000 blocks (s8 steady, 1,500 blocks) | same | measured | +| The live app node on the devnet profile | 1,081 MB at 27 min, 2,258 MB at 4 h 14 min (about 80 KB per block, derived) | same | measured, derived | +| Run A's seed | 153 MB before the chain, 5.42 GB at 12,000 blocks (about 440 KB per block; 41 peers, mergeset up to 197) | A.jsonl | measured | +| Exec snapshot | 127,564,588 B at tip 141,700 chain blocks (0.9 KB per chain block); `ExecState.records` 1 to 2 KB per chain block | hands-on-build-1.md; M30 note | measured; approximate | +| Node 1 and observer data dirs after 3 days | 856 MB and 822 MB | hands-on-build-1.md | measured | +| Lottery verify per header | class v3 2.79 ms on a loaded M5 Max core; class v4 4.90 steady, 5.06 cold, 8.23 ms on a half core (box proxy); gate 10 ms | consequences C29; lane 2 section 5.5; spec 01 | measured | +| Hub CPU per accepted block at 10 bps | 61 ms (mergeset about 8), 115 ms (about 30), 345 ms (150 to 200) | A.jsonl, section 3.1 | measured | + +### 3.4 What the fleet still owes (read `block-rate-devnet2.md` at 20:1xZ) + +RUN_A, RUN_B, TIER_TABLE and RECOMMENDATION are placeholders. Run B (1 bps, same boxes, fresh genesis, 30 min) starts at 20:25Z by `runB.sh` and its rows land about 21:00Z; the mesh variant A2 is not scripted (`box-dn2.sh` takes one `--addpeer`). The collector's `red` field is 0 in every row (its grep pattern matches no log line), so the fleet's red share must come from blue score against DAA as section 3.1 does; its `--report` payout arithmetic uses 28 MH/s for a 4070 where the brief uses 25. Section 5.2 states this lane's prediction for A2 before its rows land. + +## 4. Model + +Inputs are labelled measured (M), cited (C), simulated (S) or approximate (A). + +1. **k and the parameters per rate** (C, `bps.rs`): k = min k with P(Poisson(2 D lambda) > k) < 0.01 at D = 5 s: 18, 124, 362 at 1, 10, 32 bps; 1,074 at 100 bps (the table stops at 32; `calculate_ghostdag_k` in f64 underflows at x = 1,000, the lane's `cost.py` does it in log space). Parents = clamp(k/2, 10, 16); mergeset limit = clamp(2k, 180, 512); merge depth 3,600 bps; finality 43,200 bps; pruning max(108,000 bps, the Prunality lower bound); maturity 100 bps. +2. **Red share from topology and delay** (S, `propagation.py`): blocks arrive as Poisson(bps) split over miners; a block reaches a peer after one hop = 3 lognormal link latencies (median `--link`, sigma 0.29 from the cloud devnet's p50/p90) plus 10 ms processing; a star hub (or every node, `--node-s0`) is a single server with service s0 + s1 x mergeset ms and a FIFO queue; each block's colour is GHOSTDAG's first k-cluster condition over its own past (deterministic given parents); red share = reds in the mergesets of the final selected chain over merged blocks. The abstract rule behind it: reds appear when blocks in flight 2 d lambda exceed k, where d is the effective delay including queueing. +3. **Hub queue** (A, M/M/1 reading): utilisation rho = lambda x s; the knee is rho = 1: s = 100 ms at 10 bps, 1 s at 1 bps, 31 ms at 32 bps, 10 ms at 100 bps. Above the knee the wait grows without bound and d becomes the wait, not the link. +4. **The controller against a wide DAG** (C for the rule, `controller.py` for the loop): the fork walks the selected chain; a step carries work = blue_work(b) - blue_work(selected parent) (the mergeset blues only) and solvetime = min(clock step, 20 T) (`igneum.rs` CAP_BLOCKS, `difficulty.rs` `igneum_difficulty_bits`). With chain-step spacing sigma = max(1/lambda, d), mergeset m = lambda sigma and blues = min(m, k + 1): estimate / true hash = (blues / m) x (sigma / min(sigma, cap)). Two biases: when sigma > cap the step is capped and the rule over-reads by sigma / cap and hardens to a DAG rate of target x cap / sigma; when the DAG is wide (m > k + 1) and sigma <= cap it under-reads by (k + 1) / m and eases. Kaspa's window (`window.rs` `push_mergeset`: every mergeset block above the blue-score floor, blue and red; `difficulty.rs` `calculate_difficulty_bits`: average target x measured span / expected span) reads the whole-DAG rate over the real span: estimate / true = 1 in both regimes. A blue estimator divided by (1 - observed red share) is the same quantity. +5. **Bytes per node per day** (C+S+A, `cost.py` section 3): blocks per day x (header 286 + 32 x parents + body 300 + 274 / 8) + relay overhead (degree x 40 B inv + 40 B request per block, Kaspa's blockrelay flow, A) + votes V x checkpoints per day x 281 + one certificate (273 + V / 8) per block. Checkpoints per day = 2,880 x bps if C1 stays "every 30 blue blocks", 2,880 if it is re-denominated in DAA seconds. +6. **CPU budget** (A): the header pipeline validates in order, so per-block cost x bps must stay under about 0.8 core: 800 ms at 1 bps, 80 at 10, 25 at 32, 8 at 100. Cost = lottery verify (M) + BLS per carried vote (A 1.5 ms, unmeasured on this stack, O-10.3) + GHOSTDAG and reachability (O(k x mergeset) store reads; M at the hub only as a total) + exec and record checks. +7. **RSS and disk** (M slopes, A steady state): caches 768 MiB worst plus the DAG store's growth per block (30, 80 or 440 KB measured in three settings) over the pruning window; disk per day = blocks x bytes + votes + exec records; archival = the same per year without pruning. +8. **Payout and subsidy** (C): subsidy per block = 31.688 IGN / bps at full ramp, 80% to the producer; interval = 1 / (bps x miner hash / network hash). +9. **Finality timing** (C, spec 03 C1): checkpoint every 30 blue blocks, determination d = 60 blue (placeholder, 20 on the devnet), lock about 3 s after determination: cadence 30 / bps s, lock (60 / bps + 3) s if C1 and d stay in blue blocks. + +## 5. Results + +### 5.1 Propagation alone: red share and reorg depth per rate and delay (mesh of degree 8, 42 nodes, ideal nodes; `results.md` section D) + +| bps | k | hop delay median ms | red share | mergeset mean / max | tips mean | reorg max / p99 at miner 0 | delay to 90% of nodes, s | +|---|---|---|---|---|---|---|---| +| 1 | 18 | 50 / 100 / 300 / 1,000 | 0.0% in every cell | 1.1 / 2 to 2.3 / 7 | 1.1 to 2.4 | 1 / 1 to 2 / 2 | 0.09 to 1.82 | +| 10 | 124 | 50 / 100 / 300 / 1,000 | 0.0% in every cell | 1.7 / 6 to 9.2 / 22 | 1.7 to 8.1 | 2 / 1 to 4 / 3 | 0.09 to 1.82 | +| 32 | 362 | 50 / 100 / 300 / 1,000 | 0.0% in every cell | 2.9 / 9 to 18.8 / 42 | 2.9 to 14.4 | 3 / 2 to 6 / 5 | 0.09 to 1.82 | +| 100 | 1,074 | 50 / 100 / 300 / 1,000 | 0.0% in every cell | 6.0 / 17 to 31.1 / 104 | 5.6 to 25.3 | 4 / 3 to 8 / 8 | 0.09 to 1.82 | + +Reading. Kaspa's k is derived for a 5-s delay bound; at the measured delays (under 1 s per hop, under 2 s to 90 percent of nodes) blocks in flight stay far under k at every rate and no block turns red. The natural reorg depth is the DAG width: 2 to 4 chain blocks at 10 bps, 8 at 100 bps with 1-s hops. Lane 4's simulator (uniform one-way delay to every node, 8 miners) reads p99 11 at 10 bps with d = 0.35 s and 99 with d = 2 s (`ghostdag_results_10bps.md` section 1); the two agree in shape (depth grows with bps x d) and differ in the delay model, so the fleet's measured reorg distribution at 10 bps (section 3.1: 421 reorgs, p50 1, 4 over 40) is the arbiter: outside the start artefact and the saturated phase it sits at 1 to 8, inside this lane's model. Kaspa's published 10-bps red rates are not in the clone (`docs/crescendo-guide.md` carries requirements only); from the k derivation the design red rate is under 1 percent of blocks (delta 0.01 on anticones, approximate), which both simulators reproduce. + +### 5.2 Run A predicted from topology, then from the hub's cost (`results.md` A to C, `results-2.md` E, F, I) + +| Model input | Red share | Hub wait | Hub utilisation | Mergeset mean / max | Reorg max | What it says | +|---|---|---|---|---|---|---| +| Star, 41 miners, link 40 ms, ideal hub, run A's measured production schedule, join spread 360 s | 0.0% | 0 | 0 | 3.5 / 14 | 4 | topology and latency alone predict no reds at 10 bps | +| Star, hub cost 6 + 2 x mergeset ms (a linear GHOSTDAG cost), same schedule | 0.0% | 0.00 s | 0.14 | 3.7 / 15 | 3 | a hub under 20 ms per block keeps up | +| Star, hub cost 61 ms per block (M, mergeset 8), same schedule, 1,200 s | 46.8% | 26 s | 0.65 mean (1.6 during the 26 blocks/s burst) | 8.8 / 194 | 43 | the genesis burst alone saturates a 61-ms hub for 5 minutes | +| Star, hub cost 115 ms (M, mergeset 30) | 88.1% | 321 s | 1.23 | 20.7 / 199 | 2 | the measured mid-run regime | +| Star, hub cost 345 ms (M, wide DAG) | 82.6% | 1,769 s | 3.66 | 9.8 / 45 | 3 | relay in batches, most blocks unmerged at the end | +| Measured run A, cumulative | 77.2% | relay batches of 100 blocks | 180% of one core sustained (section 3.1) | 8 to 197 | 55 (start artefact) | | +| Star at a constant 10 bps, hub cost 20 / 40 / 60 / 80 ms | 0.0% | 0.00 / 0.01 / 0.05 / 0.18 s | 0.20 / 0.39 / 0.61 / 0.81 | 3.5 to 4.9 | 2 to 4 | under the knee | +| the same, 100 / 150 ms | 15.2% / 75.2% | 8.5 / 157 s | 1.01 / 1.51 | 20 / 116 and 27 / 66 | 8 / 3 | the knee is 100 ms per block at 10 bps | +| Star at 10 bps, ideal hub, link 100 / 200 / 300 ms | 0.0% | 0 | 0 | 5.8 / 15, 10 / 20, 12.4 / 23 | 3 to 4 | pure latency up to a 2.2-s delay to 90% of nodes gives no reds | +| Star with the 26 blocks/s burst for 5 min then 10 bps, hub 6 + 2 m | 0.0% | 0.01 s | 0.33 | 5.4 / 17 | 3 | the burst alone is harmless to a cheap hub | + +So the model predicts run A's red share from the hub's measured per-block CPU and not from its topology: 47 to 88 percent against 77 percent measured, with the waits that the log's relay batches show. The prediction for the mesh variant A2 (`results-2.md` G): a mesh of degree 8 whose nodes each pay 40 or 80 ms per block runs 10 bps at 0 percent red (node wait 0.01 and 0.12 s); at 115 ms per block every node is its own hub and the mesh goes to 52 percent red with 26-s waits and reorgs of 16. The pods run the same binary as the seed on smaller CPUs, so A2 with tonight's genesis bits (a 26 blocks/s burst) is predicted red again; A2 with genesis bits set for 10 bps at 2 GH/s and every node started synced is predicted under 5 percent red if the per-block cost on a pod is under 80 ms, and 50 percent or more if it is 115 ms. The control at 1 bps (`results-2.md` H): 115 or 345 ms per block gives 0 percent red and waits of 0.01 to 0.07 s, which is the live devnet's experience. + +### 5.3 What the hub's 61 to 345 ms per block is made of (the breakdown is not measured; the candidates and their bounds) + +| Component | Per block at 10 bps | Label | Note | +|---|---|---|---| +| Lottery verify, class v3 (the Devnet 2 override activates v3 at epoch 1) | 2.8 ms | M | one warp per header | +| BLS verification of carried votes, up to 48 per block | up to 72 ms at 1.5 ms per verify | A | run A's blocks carried up to 48 of 42 voters' votes; the route drops say the finality path was the hot one | +| GHOSTDAG and reachability at mergeset m, k 124 | O(k x m) store reads: 1,000 at m 8, 25,000 at m 200 | C (protocol.rs shape) | the 61 to 345 ms rise tracks m | +| Relay to 41 peers (serialise 14 KB x 41, inv handling) | 2.5 MB/s out measured | M | a mesh node of degree 8 does one fifth of it | +| Exec of chain blocks, record checks | small: one chain block per 10 s at the widest | M | | + +The gate that settles it is a profile (proposal 5). Whatever the split, the serial budget rule of section 4.6 is the design constraint for every block-rate step: at 10 bps the whole per-block path must stay under 80 ms on the slowest node the network wants to keep, at the mergeset limit 248, not at the narrow-DAG average. + +### 5.4 The controller: what the record shows and what the model says (`controller-10bps.md`, `controller-1bps.md`) + +| Regime (10 bps, k 124, cap 2 s) | Fork's estimator (blue work over the capped chain step) | Whole-DAG estimator (Kaspa's window shape) | Blue estimator corrected by (1 - r) | +|---|---|---|---| +| d = 0.3 / 1 / 2 s | DAG rate 10.00, red 0%, estimate 1.00x | 10.00, 1.00x | 10.00, 1.00x | +| d = 3 s | settles at 6.67 blocks/s, estimate 1.50x, difficulty 1.50x the correct value | 10.00 | 10.00 | +| d = 5 s | 4.00 blocks/s, 2.50x | 10.00 | 10.00 | +| d = 10 s | 2.00 blocks/s, 5.00x | 10.00 (red 0%, mergeset 100) | 10.00 | +| d = 30 s (a saturated hub) | 5.21 blocks/s, red 19.5%, estimate 12.1x, difficulty 1.93x | 10.00, red 58%, mergeset 300 | 10.00 | +| 1 bps, k 18, cap 20 s: d = 20 / 30 / 60 s | runs away upward: 179 / 43 / 10 blocks/s, red 97 to 99%, estimate 0.01 to 0.09x (the under-read, main's direction) | 1.00 / 1.00 / 1.17 blocks/s | 1.00 / 1.00 / 1.17 | + +Reading against the record. Run A's production fell from 26 blocks/s to 3.3 to 3.6 while blue stayed 1.4: the DAG hardened. The fork's rule at a chain-step spacing of 5 to 6 s (the hub's queue made chain blocks 10 to 60 s apart at the widest, 3 to 6 s late in the run) settles at target x 2 s / spacing = 3.3 to 4 blocks/s, which is the record's late plateau. The ease direction main reported is the other bias (blue work only) and the model shows it where chain steps stay under the cap while the DAG is wider than k + 1: at 1 bps with d of 20 s or more, or at 10 bps when production exceeds k / (2 d). Either way the rule is reading the wrong quantity: a controller that counts every mergeset block's work over the real span (what Kaspa's `calculate_difficulty_bits` does over its sampled window of blue and red mergeset blocks) holds 10.00 in every cell. A second inconsistency at 10 bps: CAP_BLOCKS 20 is 2 s while FUTURE_TOLERANCE_MS and BACK_TOLERANCE_MS stay 10 s, so a forged stamp (10 s) no longer fits inside half a cap; spec 2.3 derived the 10 s as half the cap at 1 bps. The cap, the tolerances and the 60 T lag bound should be denominated in DAA seconds (20 s, 10 s, 60 s) at every rate, and the step of a chain block that merges m blocks should be allowed m x 20 T before clamping. + +Cost of the bug tonight per tier: a home miner's blocks were 77 percent red (a red inside the DAA window pays its 80% to the merging miner, spec 2.5, so the solo miner lost the subsidy of 3 blocks in 4); a rig the same; a pool user nothing (Devnet 2 is a staging chain); the node operator saw 5.4 GB RSS and 180% CPU on a 96-thread box; a prover saw the exec tip at 154 chain blocks against 11,239 blocks; a holder nothing (no value on Devnet 2). + +### 5.5 Node tiers per block rate (`cost-tables.md` 4 and 5) + +| Rate | Per-block CPU budget (0.8 core) | Class v4 verify share of it (box proxy 4.9 ms; a 2019 laptop core about 2.5x, lane 2's rule: 12 ms) | Votes per block (V/30 if C1 stays in blue blocks) and their BLS cost at V = 1,000 | RSS: caches + DAG store over the 30-h window at 30 / 80 KB per block | Disk per day (blocks, votes at V = 1,000, exec records) | Verdict: laptop 2019-class 8 GB SATA | Raspberry-class 8 GB USB SSD | +|---|---|---|---|---|---|---|---| +| 1 bps | 800 ms | 0.6% (laptop 1.5%) | 33 votes, 50 ms | 0.77 + 3.3 to 8.6 GB | 0.92 GB | runs if the DAG store plateaus under about 4 GB (owed: the 30-h measurement); marginal at 80 KB per block | the same question; CPU fine (class v4 verify under 15 ms, GHOSTDAG at mergeset 1 to 2) | +| 10 bps | 80 ms | 6% (laptop 15%) | 33 votes, 50 ms: 62% of the budget on its own | 0.77 + 33 to 86 GB | 9.0 GB | no: the hub's measured 61 ms at a narrow DAG already uses 76% of the budget on a server core; RSS over 8 GB within hours unless the per-block footprint falls 10x | no | +| 32 bps | 25 ms | 20% (laptop 48%) | 33 votes, 50 ms: over budget | 0.77 + 104 to 276 GB | 29 GB | no | no | +| 100 bps | 8 ms | 61% (laptop 150%) | over budget | 0.77 + 326 to 864 GB | 91 GB | no: the verifier gate alone forbids it | no | + +Pruning changes the disk, not the RSS and CPU: the pruned node keeps the 108,000 DAA-s window (136 MB of headers and bodies at 1 bps, 1.55 GB at 10 bps, `cost-tables.md` 6) plus the UTXO set, the exec state (128 MB measured plus 1.5 KB per chain block) and the finality store's 2,000 indices (KEEP_CHECKPOINTS). What must change for 10 bps is the per-block footprint in RAM (30 to 440 KB measured; Kaspa's 16 GB minimum at 10 bps says their footprint is near 10 KB per block, derived from 16 GB over 1,080,000 blocks, approximate) and the per-block CPU (under 50 ms at mergeset 248 on a laptop core). + +### 5.6 Bandwidth per node per day (`cost-tables.md` 2 and 3) + +| Rate | Blocks (header + body + records) | Relay overhead (degree 8) | Votes at V = 12 / 100 / 1,000 / 8,192, C1 in blue blocks | Certificates at V = 12 / 8,192 | Total at V = 100 | Total at V = 8,192 | Mean Mbit/s at 8,192 | +|---|---|---|---|---|---|---|---| +| 1 | 57 MB | 31 MB | 10 MB / 81 MB / 809 MB / 6.6 GB | 24 MB / 112 MB | 194 MB | 6.8 GB | 0.63 | +| 10 | 649 MB | 311 MB | 97 MB / 809 MB / 8.1 GB / 66 GB | 237 MB / 1.1 GB | 2.0 GB | 68 GB | 6.3 | +| 32 | 2.4 GB | 1.0 GB | 311 MB / 2.6 GB / 26 GB / 212 GB | 759 MB / 3.6 GB | 6.8 GB | 219 GB | 20 | +| 100 | 9.3 GB | 3.1 GB | 971 MB / 8.1 GB / 81 GB / 663 GB | 2.4 GB / 11 GB | 23 GB | 687 GB | 64 | + +With C1 in DAA seconds the vote column is 81 MB / 809 MB / 6.6 GB per day at every rate; with lane 3's aggregated certificate (1.2 KB per checkpoint) in place of carried votes it is 3.5 MB per day. What carries it: a home connection (10 Mbit/s up, 50 down, approximate) carries 1 bps at any voter count and 10 bps at under 1,000 voters or with C1 in seconds; a Raspberry-class box on ethernet the same, bounded by its CPU not its link; a mobile node is a light client: 3.4 MB per day in checkpoint mode at 1 bps and 1,000 voters, 34 MB at 10 bps if C1 stays in blue blocks (`cost-tables.md` 10). A star hub pays its peer count times the block bytes in upload: run A's seed sent 2.5 MB/s (20 Mbit/s) to 41 peers; the three testnet seeds (cx23, 20 TB per month included, `seed-nodes.md`) would spend 6.5 TB per month each at that rate, inside the allowance and outside good sense; the peer floor of proposal 4 spreads it. + +### 5.7 Votes and the 3.4.2 arithmetic per rate (`cost-tables.md` 2) + +| Rate | Checkpoints per day (C1 in blue blocks) | Votes per block at V = 8,192 (V / 30) | Drain capacity per checkpoint at the cap 48 / 384 | Vote bytes per day at 8,192, single votes | Archival votes per year at 1,000 / 8,192 | +|---|---|---|---|---|---| +| 1 | 2,880 | 273 | 1,440 / 11,520 | 6.6 GB | 296 GB / 2.4 TB | +| 10 | 28,800 | 273 | 1,440 / 11,520 | 66 GB | 3.0 TB / 24 TB | +| 32 | 92,160 | 273 | 1,440 / 11,520 | 212 GB | 9.5 TB / 77 TB | +| 100 | 288,000 | 273 | 1,440 / 11,520 | 663 GB | 30 TB / 242 TB | + +Because C1 counts blue blocks, the votes per block and the drain capacity per checkpoint are the same at every rate (the 3.4.2 arithmetic holds: 384 per block drains 8,192 in 21.3 blocks), while the bytes per day and the checkpoint cadence scale with bps: 3-s checkpoints at 10 bps, 0.3 s at 100. Tonight at 42 voters and 10 bps the seed's inbound IgneumFinality route (4,096 deep since the fin-route fix) overflowed at 109 to 2,547 drops per peer, and 16 of 47 determinable checkpoints locked; at 1 bps the same voters cost one tenth. Re-denominating C1 and d in DAA seconds (300 blue blocks and 600 at 10 bps) keeps the finality cost, the lock delay (63 s) and the light-client bytes at their 1-bps values across every rate step; it is a one-line spec change and a parameter in the fork. + +### 5.8 Pruning, archival and the snapshot path + +What a pruned node keeps (spec 02 2.1, `cost-tables.md` 6): headers and bodies back to the pruning point (108,000 DAA s, never past the latest certified checkpoint, F3), with their GHOSTDAG and reachability data (the 2x index factor is approximate); the pruning proof (levels of headers, `pruning_proof`); the UTXO set; the finality store's last 2,000 indices; the exec state (`exec-snapshot.bin` 128 MB measured at chain block 141,700, growing 0.9 KB per chain block, plus `ExecState.records` 1.5 KB per chain block until a window bounds it, M30 note); the proof-record window (`RECORD_WINDOW_CHAIN_BLOCKS`). An archival node keeps every block and its finality section: 40 GB per year of blocks at 1 bps (453 GB at 10 bps) plus votes as table 5.7, so the archival cost is the votes, not the blocks, until certificates replace carried votes (1.3 GB per year at 1 bps). + +The p2p snapshot path as shipped in 0.3.14 (`proving.rs` `on_exec_snapshot`, `p2p_snapshot_gate`, read on `exec-sync-0313`): a peer's `ExecSnapshot` (version, chain id, genesis, tip number and hash, state root, records, the account and storage dump, fees, epoch, paid shards) is accepted only when its tip is a chain block known to this node's consensus, its tip is not 0, not below this node's exec restart, above this node's own executed tip, and only while the executor is blocked or has no state; and, when the restart pin is configured, the snapshot's record at the restart block must carry the pinned root (`exec_restart_state_root`, the fourteenth override field). The file is then written as the node's own resume point and served onward. Three things it does not check: the snapshot's state root at its tip against anything the chain commits to; the records between the restart block and the tip; agreement among peers. The poisoning attack: a peer of a blocked node (every node is blocked after a reorg deeper than its ring or after a restart below its retention root, the 6 October incident class) serves a snapshot whose tip is a real chain block above the victim's tip, whose record at the restart block is correct, and whose state at the tip is wrong. The victim loads it, executes forward from a false state, persists it, serves it to its own peers, and from then on refuses every honest proof record (`check_record`: the statement over its own records disagrees) while its own prover's records are refused by the network. Bound: it is a liveness attack on the victim and its downstream peers, not a consensus or a proof break (full nodes execute natively and a proof over the false state is a proof of the wrong statement; a light client verifies proofs against the chain's records, not against a node's state), and it needs the attacker among the victim's peers at the moment it is blocked, which the peer floor makes a 1-in-(peer count) race unless the attacker runs most of the victim's peers (an eclipse). The hardening: accept a snapshot only when its state root at the newest proven segment at or below its tip equals the `post_root` of the proof record the chain carries for that segment (the chain already carries 274-byte records in coinbase payloads, so this is a lookup, not a protocol change), and take the (tip, root) pair from N of M peers (the light client's 3 of 5, spec 10.6) before loading; a snapshot that fails either is refused and the peer is dropped for the session. Then poisoning needs a false proof record in the chain, which needs the proving key and a block that carries it, and the bound is the proof system's. Sizes per tier: the snapshot is the state (128 MB today, growing with accounts), so a home node's recovery is a 128 MB download and a sha256; an archival node's history is the table above; a light client never holds one. + +### 5.9 Finality through this lane's eyes (cross-reference to lane 3) + +Lane 3 (`finality-and-weight.md` 1 and 5.5) refutes the topology hypothesis for tonight's pause on the live devnet: certificates 6824 to 6842 formed while node 1 and the observer were down, the zero-aggregator fallback carried a quarter of the certificates, and the pause began at the checkpoint where signing weight fell to 53.1 percent of the frozen table, which is the two-thirds rule; the fleet's star is around the seed (the finality route entry: "on a Vast box the seed is the only peer"), not the Mac. This lane adds the measured shape of that star under load: 41 pods each with peers=1, every block and every vote through one process at 180 percent of one core, 2.5 MB/s of upload, the IgneumFinality route dropping thousands of messages per peer, 16 of 47 checkpoints locked. Lane 3's hub-cut harness case (its proposal 6) and this lane's peer floor (proposal 4) are one piece of work: the floor is what makes the harness case pass (a node with 4 outbound peers keeps blocks and votes when any one peer dies), and the fleet library is where both land. + +### 5.10 Block rate: payout intervals, subsidy, lock delay and the solo-miner answer (`cost-tables.md` 7 to 9) + +| Rate | Subsidy per block (full ramp), producer's 80% | 4070 (25 MH/s) at 1.16 GH/s / 100 GH/s / 1 TH/s / 10 TH/s | 5090 (128 MH/s) at the same | 8x 4090 rig (459 MH/s) at the same | Lock after a checkpoint if d stays 60 blue | +|---|---|---|---|---|---| +| 1 | 31.69 IGN, 25.35 | 46 s / 1.1 h / 11.1 h / 4.6 d | 9.1 s / 13 min / 2.2 h / 21.7 h | 2.5 s / 3.6 min / 36 min / 6.1 h | 63 s | +| 10 | 3.17, 2.54 | 4.6 s / 6.7 min / 1.1 h / 11.1 h | 0.9 s / 1.3 min / 13 min / 2.2 h | 0.3 s / 22 s / 3.6 min / 36 min | 9 s | +| 32 | 0.99, 0.79 | 1.4 s / 2.1 min / 21 min / 3.5 h | 0.3 s / 24 s / 4.1 min / 41 min | 0.1 s / 6.8 s / 1.1 min / 11 min | 4.9 s | +| 100 | 0.32, 0.25 | 0.5 s / 40 s / 6.7 min / 1.1 h | 0.1 s / 7.8 s / 1.3 min / 13 min | 0.0 s / 2.2 s / 22 s / 3.6 min | 3.6 s | + +The solo-miner question: a 4070 at 25 MH/s sees 21.6 blocks a day at 1 bps on a 100 GH/s network (548 IGN a day at full ramp) and 2.16 a day at 1 TH/s (55 IGN); one block a day needs 0.046 bps at 100 GH/s, 0.46 bps at 1 TH/s and 4.6 bps at 10 TH/s. Kaspa's 10 bps buys the solo miner a daily block up to about 20 TH/s; Igneum at 1 bps gives it up to about 2 TH/s, which is 1,700x tonight's hash and USD 562,000 a day of rented hash at the bench entry's USD 281 per GH/s-day. What the rate costs the node, from 5.5 and 5.6: 10x the bytes (2 GB a day at 100 voters), a per-block CPU budget of 80 ms that the measured 61 to 345 ms does not meet, an RSS that leaves 8 GB within hours at the measured footprints, 3-s checkpoints and 66 GB a day of votes at 8,192 voters unless C1 moves to seconds, and the controller's cap inconsistency. Per tier: a home miner on one card gains variance relief it does not yet need (a 4070 at 1 bps sees a block an hour at 100 GH/s); a rig gains nothing (36 min per block at 1 TH/s already); a pool user nothing (pools remove variance); a prover gains nothing and pays the exec follower's 10x chain blocks; a holder gains faster locks (9 s) only if d is left in blue blocks, which costs the finality bytes of 5.7; a rollup customer the same lock question; the node operator pays all of it. + +## 6. Ranked proposals + +| Rank | Proposal | Evidence | Model | Hours | Consequence per tier | Gate | +|---|---|---|---|---|---|---| +| 1 | Block rate: 1 bps for the public testnet and for mainnet launch; 10 bps stays a planned step (spec 2.1) behind three gates: per-block node CPU under 50 ms at mergeset 248 on a 2019 laptop core, C1 / d / the clock cap in DAA seconds, vote aggregation; no 32 or 100 bps step is proposed | 5.2 (reds are node throughput), 5.5 (budget 80 ms against 61 to 345 measured), 5.7 (66 GB a day of votes), 5.10 (1 bps already gives a 4070 a block an hour at 100 GH/s) | `propagation.py` F and G, `cost.py` 4 and 7 | 1 (the spec line) plus the gates below | home miner: no node change, a block an hour at 100 GH/s; rig and pool: unchanged; prover: the exec follower stays at 1 chain block per second; holder and rollup: 63-s locks; node operator: today's node | the three gates pass on Devnet 2 at 10 bps with zero red over the knee, before any rate step | +| 2 | Controller: every lane counts every mergeset block's work (blue and red) over the real span; the clock cap, tolerances and lag bound in DAA seconds (20, 10, 60 s) at every rate; a chain step that merges m blocks may span m x 20 T before clamping | 3.1 (26 to 3.5 blocks/s with blue at 1.4), 5.4 (the fork over-reads by spacing / cap and under-reads by (k + 1) / m; Kaspa's window reads the whole DAG) | `controller.py`: 10.00 in every cell for the whole-DAG estimator | 8 (rule and unit tests in `difficulty.rs`, `igneum.rs`) + 3 (the harness case: fast-time 3 nodes, a 3-s proxy delay at 10 bps; pass = DAG rate within 10% of target and difficulty within 10% of the hash-implied value after 10 min, the same at 1 bps with a 30-s delay) | home miner: the subsidy stops tracking the controller's error (tonight 77% of blocks unpaid to their miner); node operator: no runaway DAG from a slow hub; holder: emission on schedule (spec 2.5's "when the controller lets the rate run" clause stops firing) | the harness case passes; `sim/difficulty/sim.py --dag-delay` replay of run A's production curve settles at 10 bps | +| 3 | Finality interval and determination in DAA seconds (C1: checkpoint every 30 DAA s; d in DAA s), one carriage per vote (a block carries a vote only if no block in its past does, which is the rule as written; the relay dedups by (key, index)), and lane 3's aggregated certificate as the archival object | 5.7 (votes and route load scale with bps under the blue-block rule), 3.1 (2,547 route drops per peer, 16 of 47 locks) | `cost.py` 2 and 10 | 4 (spec 03 C1, 3.10 table) + 8 (fork: `finality.rs` interval in DAA s, relay dedup) | home miner and rig: finality bytes stay at 81 MB a day at 100 voters whatever the rate; node operator: the route stays inside 4,096; light client: 3.4 MB a day at every rate; holder: 63-s locks at every rate | a Devnet 2 run at 10 bps with 42 voters shows 0 route drops and every determinable checkpoint locked | +| 4 | Peer floor and topology: a node reports synced and mines only with at least 4 outbound peers (8 on seeds); the fleet library gives every box the seed plus 2 random boxes; the seed list carries 3 seeds; the hub-cut harness case of lane 3's proposal 6 with the floor as its pass condition | 3.1 (peers=1 on every pod, 2.5 MB/s out of one process), lane 3 section 5.5 ("on a Vast box the seed is the only peer") | `propagation.py` B (mesh of degree 4 and 8 against the star: the same red share at an ideal hub, one fifth of the upload per node, no single queue) | 4 (fleet library and the synced rule in `tools/fleet/lib/`) + 3 (the harness case) | node operator and seed: upload spread 41 ways to 8 ways; home miner: a dead seed no longer stops blocks and votes; finality: votes reach aggregators by two paths | the hub-cut case passes: a lock within 2 checkpoints of the cut, 0 conflicting certificates at the hub's return, every box at 4 or more peers throughout | +| 5 | Measure the per-block node cost and its split (lottery verify, BLS per vote, GHOSTDAG and reachability, relay, exec) with `perf` on a Devnet 2 box and on a 2019-class laptop at 1 and 10 bps, at a narrow DAG and at mergeset 248; set the budget rule (cost x bps under 0.8 core) as a CI check on the fast-time network | 3.1 and 5.3 (61 to 345 ms measured as a total only) | section 4.6 | 3 (profile) + 4 (the CI check in `tools/ci`) | every node tier: a number per machine class instead of the hub's; the 8 GB laptop and the Raspberry-class verdicts of 5.5 become measurements | the profile lands in the bench log with per-component ms; the check fails a build whose per-block cost exceeds the budget at the network's rate | +| 6 | Snapshot path hardening: a p2p snapshot is accepted only when its root at the newest proven segment at or below its tip equals the chain's proof record for that segment, and (tip, root) agree on 3 of 5 peers; a failing peer is dropped for the session; the gate's unit test gains the two cases | 5.8 (the gate checks tips and the restart pin, never the state at the tip; the 6 October seed loaded a tip-0 snapshot from a fleet box before the gate) | section 5.8 | 6 (fork `proving.rs`, `snapshot.rs`; the record lookup exists in `check_record`) | node operator: recovery from a peer cannot be poisoned below an eclipse; prover: no false-state records from a poisoned node; home miner: the app's node recovers by itself after a deep reorg | `tools/exec-sync/reorg.mjs` gains a poisoned-snapshot case: refused, peer dropped, honest snapshot loaded after it | +| 7 | Devnet 2 genesis bits set for the fleet's hash at the run's rate (no 26 blocks/s burst), and the fleet rule: a box mines only after synced with the peer floor (run A's pods mined from genesis for up to 6 minutes before they saw the chain) | 3.1 (the 55-block reorg "unwinding to height 1", the 33% red first phase), 5.2 (the burst alone saturates a 61-ms hub for 5 minutes) | `propagation.py` C and E | 2 (`box-dn2.sh`, `start-seed.sh`, `devnet2-override.json` per rate) | fleet operator: run A2 and run B measure the rate, not the start; every other tier: none | the next Devnet 2 run shows no reorg over 5 in its first 10 minutes and a first-minute production within 2x of target | +| 8 | RSS steady state: run a node through a full 30-h pruning window at 1 bps (fast time) and at 10 bps on Devnet 2, read RSS every 10 minutes, bound the DAG store (reachability and GHOSTDAG per block) and `ExecState.records`; decide the 8 GB tier from the plateau | 3.3 (slopes 30, 80, 440 KB per block over short runs; none is a steady state), 5.5 (8 GB is marginal at 1 bps at 80 KB per block) | `cost.py` 5 | 3 (the run) + 8 (the bounds, if the plateau is over 4 GB) | home miner on an 8 GB laptop: a yes or no at 1 bps; Raspberry-class: the same; node operator: a RAM line on the requirements page | RSS plateau under 4 GB at 1 bps over 30 h; the requirements page carries the measured line | +| 9 | Node and client tiers published with sizes: full pruned node (1 bps: 0.9 GB a day, 136 MB window plus state, 4 GB RAM target), archival node (40 GB a year of blocks plus 1.3 GB of certificates once aggregated; 296 GB a year of votes until then at 1,000 voters), light client checkpoint mode (3.4 MB a day), phase two (2.3 MB a day) | 5.6, 5.8, `cost-tables.md` 6 and 10 | `cost.py` | 2 (the requirements page and spec 10.5's table at 1 bps, with the rate rows) | every tier knows its cost before the testnet | the page's numbers match `cost-tables.md` and the first testnet week's measured bytes within 30% | +| 10 | Bandwidth and route bounds per peer: at most 2 x V / 30 votes per peer per block interval accepted (a second belt behind the dedup), block relay to at most 16 peers per node, the IgneumFinality route sized from V and the rate | 3.1 (2,547 drops per peer), 5.6 | `cost.py` 3 | 4 (fork p2p flows) | seed and node operator: a bound on what one peer can make a node do; home miner: none | the s7 flood scenario gains a vote flood: 0 disconnects, bounded CPU | + +**1. Block rate.** Everything measured tonight says 1 bps is the rate the current node can run and 10 bps is not: the hub spent 61 ms per block at a narrow DAG against a budget of 100, and the model turns that cost into tonight's red share (5.2). Nothing in propagation forbids 10 bps (5.1: zero reds at every measured delay with k 124), so the step stays on the plan (spec 2.1) as Kaspa took it, after a test campaign, with three gates: the per-block cost (proposal 5), the time denomination of finality and the controller (proposals 2 and 3), and vote aggregation (lane 3). The variance argument does not need the step yet: a 4070 sees a block an hour at 100 GH/s and two a day at 1 TH/s at 1 bps (5.10); a pool user never sees variance. Consequences: no node tier changes for the testnet; the testnet gives the measured bytes and RSS that fill proposals 8 and 9. + +**2. Controller.** The rule walks the selected chain and sums blue-work increments over capped clock steps (`igneum_difficulty_bits`, section 4.4). In a wide DAG both parts mislead it: the cap turns a 5-s chain step into 2 s (over-read, harden, the record's fall to 3.5 blocks/s) and the blue-only work hides the reds (under-read, ease, the direction main reported). Kaspa's window pushes every mergeset block, red included, and divides by the real span (`window.rs`, `difficulty.rs`), which the model holds at target in every cell. The change is inside `igneum_difficulty_bits` and `igneum_target`: work = the mergeset's total work per chain step, the step allowed m x 20 T before the cap, the cap and tolerances in DAA seconds. The harness case is the proxy-delayed fast-time network of `sim/difficulty/attacks/README.md` at 10 bps with a 3-s hold, and `sim.py --dag-delay` replaying run A's production curve (the schedule file is in `sim/horizon/network/`). + +**3. Finality interval in seconds.** C1 says 30 blue blocks, d says 60 blue blocks; at 10 bps that is a checkpoint every 3 s and ten times the votes, certificates, route load and light-client bytes per day (5.7), and tonight's seed showed the route overflowing at 42 voters. Written in DAA seconds the whole finality cost is rate-invariant and 3.4.2's arithmetic (384 votes per block drain 8,192 in 21 blocks) gets 10x more headroom at 10 bps. The one-carriage rule is already the spec's text ("not already in its past"); the relay dedup by (key, index) makes the wire match it. + +**4. Peer floor.** Run A's pods had one peer each; the live fleet's boxes have the seed as their only peer; the hub paid 41x the upload and ran one queue for every block and vote. Four outbound peers before a node calls itself synced, the seed plus two random boxes in the fleet library, three seeds in the list: the mesh runs of 5.2 show the same zero red at an ideal hub with one fifth of the per-node upload and no single queue, and lane 3's hub-cut case becomes passable. This is the proposal that serves both lanes. + +**5. The per-block profile.** The 61 to 345 ms is a total; the split decides which fix buys 10 bps: if BLS of carried votes dominates, aggregation and the dedup buy it; if GHOSTDAG at k 124 dominates, the mergeset limit and a cheaper reachability do; if relay dominates, the peer floor does. Three hours with `perf` on a Devnet 2 box, then a CI check that fails a build whose cost exceeds the budget at the network's rate, so the class is closed the way CLAUDE.md asks. + +**6. Snapshot hardening.** The gate refuses tips, not states (5.8). The chain carries the proof records' roots, so a snapshot's root at its newest proven segment can be checked against them without a protocol change, and 3-of-5 peer agreement makes poisoning an eclipse. The attack is a liveness attack on the victim, never a consensus break, but a poisoned seed serving its state onward is the fleet-wide class the 6 October gate was written for. + +**7 to 10.** Devnet 2's genesis bits and the start-synced rule make the next runs measure the rate instead of the start (the 55-block reorg and the first-phase reds were the start); the 30-h RSS run decides the 8 GB tier with a measurement instead of three slopes; the tier page and the per-peer bounds are two hours each and close open rows in spec 10.5 and the finality route entry. + +## 7. Open questions and what could not be run + +| Question | Why not tonight | What closes it | +|---|---|---| +| Run B (1 bps control) and A2 (mesh) rows | B starts at 20:25Z, rows about 21:00Z; A2 is not scripted | the fleet's RUN_B row; A2 built on `box-dn2.sh` with 3 `--addpeer` entries (seed plus two boxes) and genesis bits for 10 bps; this lane's prediction for A2 is in 5.2 | +| The difficulty trajectory of run A | the collector strips `difficulty=`; the node log has no bits | `bps-collect.py` keeps the field; or `getBlockDagInfo` difficulty per minute on the next run | +| The per-block CPU split | no profile; the hub's figure is a total that includes relay to 41 peers | proposal 5 | +| RSS steady state at 1 and 10 bps | three slopes over 1,500 to 15,000 blocks, none over a pruning window | proposal 8 | +| Kaspa's measured 10-bps red rate | not in the clone (`crescendo-guide.md` has requirements only) | the kaspanet KIP-14 text and the Crescendo testnet-10 reports, not cloned; approximate under 1 percent by the k derivation | +| BLS verify per vote on this stack | O-10.3 open; 1.5 ms is approximate | `fast_aggregate_verify` timing with the forked `blst` build | +| The pods' per-block cost (the A2 prediction's fork) | the pods' CPUs and their node logs were not read (fleet boxes are out of this lane's reach) | the fleet's collector reads `cpu_rss` on every box, not only the seed | +| GHOSTDAG's second k-cluster condition | `propagation.py` applies the candidate's own anticone bound only; lane 4's `ghostdag_sim.py` carries both | re-run the grid with lane 4's colouring if a cell is contested (every cell here is 0 percent, so the undercount cannot change the reading) | +| The 55-block reorg's cause | read from the log as a late-joining pod's chain from genesis ("unwinding to height 1") | the start-synced rule of proposal 7 removes the class; the next run's reorg distribution confirms | + +## 8. Summary for the coordinator + +Run A at 10 bps did not measure propagation; it measured a node that needs 61 to 345 ms per block through one hub whose budget at that rate is 100 ms. The propagation model with the measured links predicts under 0.1 percent red in the star and in the mesh; with the hub's measured per-block CPU it predicts 47 to 88 percent against the 77.2 percent measured, with the queueing waits the log's 100-block relay batches show, and it predicts that the mesh variant A2 goes red too unless every pod's per-block cost is under 80 ms and the genesis burst is removed. The controller then hardened (production 26 to 3.5 blocks/s with blue at 1.4) because its estimator reads blue work over a chain step capped at 2 s; the whole-DAG estimator Kaspa uses is unbiased in every regime the model covers. The block rate for the public testnet and mainnet launch is 1 bps; 10 bps stays a gated step, and the gates are a per-block cost under 50 ms on a laptop core at mergeset 248, the finality interval and the clock cap in DAA seconds, and vote aggregation. Lane 3's finding stands (the pause was the rule, the fleet's star is around the seed); the peer floor is the piece of work both lanes need. + +1. Reds from node cost, not topology: red share 0.0 percent in every cell of the bps x delay grid (1 to 100 bps, 50 to 1,000 ms hops, k from Kaspa's table) and in the run A replay with an ideal hub; 47 / 88 / 83 percent with the hub at 61 / 115 / 345 ms per block (measured); the knee at 10 bps is 100 ms per block (`sim/horizon/network/propagation.py`, `results.md`, `results-2.md`). +2. The controller's two biases: estimate / true hash = (blues / m) x (spacing / cap); at a 5-s chain-step spacing the fork settles the DAG at 4.0 blocks/s against 10 (record 3.3 to 3.6), at a 20-s spacing at 1 bps it runs away to 179 blocks/s; the whole-DAG estimator holds 10.00 and 1.00 in every cell (`controller.py`). +3. Finality cost scales with the rate under C1 as written: 8,192 voters cost 6.6 GB a day per node at 1 bps and 66 GB at 10 bps (24 TB a year archival); in DAA seconds 6.6 GB at every rate, 3.5 MB a day with aggregated certificates; tonight's seed dropped up to 2,547 finality messages per peer and locked 16 of 47 checkpoints at 42 voters (`cost.py`, section 3.1). diff --git a/docs/analysis/horizon/new-pow.md b/docs/analysis/horizon/new-pow.md index 827db988..78a30fae 100644 --- a/docs/analysis/horizon/new-pow.md +++ b/docs/analysis/horizon/new-pow.md @@ -10,9 +10,10 @@ the project lead's mandate, verbatim: "if we create a new way of hashing or a ne |---|---| | 19:05 | Lane started. Read: preamble, CLAUDE.md, the two personas, spec 01 (whole), 04, 07, chip-model-v3 (whole), asic-resistance-history (sections 0 to 3 and 4.3, 5), latency-shadow-2026-10-06 (whole), counter-asic-3-status sections 1 to 5, counter-asic-3-node section 6 (the P2 signalling rule), int8-matrix-family sections 1 to 3, scratch-soundness verdict, proving-methods (whole), fud-ledger M1, M7, M16, M22, M28, P2, F13, proto-cuda host.cu and the mx8-genesis pack (kernel.cu, memhard.h, program.h, vectors.h), proto-cuda/emu, family-probe.cu, the fleet's prover-tiers-real-cards.md, bench-log line 2582 (rental cost) | | 19:25 | Fleet agent asked for two boxes; answered at 19:29: two quiet RTX 4090s (RunPod, nvcc 12.8 at /usr/local/cuda/bin, directory /root/horizon-newpow, until 22:30Z). No quiet Ampere card exists tonight; a loaded 3090 is offered. Main's note: cost rows use bench-log 2582 (USD 0.0117 per MH/s-hour) | -| 19:35 | File skeleton written. Two prototype sub-agents launched (budget two at once): `mma-shadow` on box 1 (47.47.180.77), `state-dataset` on box 2 (213.173.98.36) plus CPU rows on igneum-build-1. Designs being written in this file meanwhile | +| 19:35 | File skeleton written. Two prototype sub-agents launched (budget two at once): `mma-shadow` on box 1 (), `state-dataset` on box 2 () plus CPU rows on igneum-build-1. Designs being written in this file meanwhile | | 19:41 | Section 3 complete: the three designs, the one-table comparison, the migration path. Scheme A's verdict is already visible in its own numbers (A1 dead on 2.9 MB of openings per block, A2 dead on sampleability and a 32 to 40 ms proof verify; A0 is scheme C with the trace as state). Prototypes running: `mma-shadow` (box 1) and `state-dataset` (box 2 and igneum-build-1). mm8 two-output correction sent to the prototype | -| (next) | Section 4 reviews (cryptographer, consensus engineer, two per scheme); the pick; section 5 measured rows as they land; section 6 verdicts; section 7 ranked next steps | +| 19:50 | Section 4 (reviews, the pick: B and C), section 7 (ranked next steps) and section 8 (open questions) written. Scheme C prototype complete and measured on box 2 and igneum-build-1: hash rate and watts unchanged (63.08 vs 63.09 MH/s, 207 W), build +1.4 ms, verifier +0.11 to 0.21 ms per unit, bit-exact 1,024 of 1,024 items and 128 of 128 lanes; rows in 5.2. Scheme B ladder running on box 1 (R = 0, 8, 32, 128 measured, 512 in progress) | +| 20:20 | Scheme B ladder complete on box 1 (R = 0 to 512, rate flat at 63.08 MH/s, 201 to 216 W, every fingerprint PTX = reference, 1,024 of 1,024 lanes at every R, verifier delta 0.05 to 4.39 ms). Sections 5.1, 5.3, 6 and 9 written. Box 2 released 19:53Z; box 1 released on the mma agent's report. File complete | ## 1. What was read and the facts this lane stands on @@ -133,8 +134,8 @@ So the fleet splits by generation: Turing-and-later NVIDIA and RDNA 3-and-later | Scheme | What it is | Chip edge per joule vs the 5090 (f = 1 chip, chip-model-v3 method) | Verifier ms per unit (model, then measured in section 5) | Vendor bit-exactness | Ships as class v5? | |---|---|---|---|---|---| | A, mining is proving | A1 committed codeword, A2 proving steps: dead on bytes and on sampleability; A0 trace-as-dataset survives and is C with the trace as state | A0 unchanged (5.1x GDDR7); A1, A2 not priced | A0: 2.06 + the leaf-read row; A1: 2.9 MB of openings; A2: 32 to 40 ms | A0 adds the zkVM executor to consensus | NEVER as mining = proving; A0 folds into C | -| B, tensor-shaped shadow | class v3 plus `8 R` int8 8x8x16 tile steps per hash in the PTX fragment layout, two outputs per lane | memory + `8 R x 1,024 x e_mac x k_mma`; `k_mma` near 1 with a floor near 0.5 (approximate); rows from the measured `e_mac` in 5.3 | 2.06 + about 0.9 microseconds per step per unit scalar (R = 128: +0.9 ms; R = 512: +3.7 ms); VNNI 4x to 16x less | native on sm_75+ and RDNA 3+ (AMD layout unverified); emulated on Pascal, RDNA 2, Apple | prototype further; a class v5 candidate after the AMD gate | -| C, stored state | the daily dataset derives from the execution state snapshot; kernel unchanged; a state sample per block | unchanged (5.1x GDDR7, 7.5x to 9.2x HBM3); the f = 0 chip disappears | 2.06 + 4,096 leaf reads (section 5.2) | nothing vendor-specific; the serialisation is the consensus risk | prototype further; a class v5 candidate on its own or beside B | +| B, tensor-shaped shadow | class v3 plus `8 R` int8 8x8x16 tile steps per hash in the PTX fragment layout, two outputs per lane | memory + `8 R x 1,024 x e_mac x k_mma`; `k_mma` near 1 with a floor near 0.5 (approximate); rows from the measured `e_mac` in 5.3 | 2.06 + about 0.9 microseconds per step per unit scalar (R = 128: +0.9 ms; R = 512: +3.7 ms); VNNI 4x to 16x less | native on sm_75+ and RDNA 3+ (AMD layout unverified); emulated on Pascal, RDNA 2, Apple | before measurement: prototype further. After section 5: NEVER as class content (the block costs the honest card 0.056 to 0.70 pJ per multiply-add, so it forces no joules on a chip); kept as reserve R8 evidence | +| C, stored state | the daily dataset derives from the execution state snapshot; kernel unchanged; a state sample per block | unchanged (5.1x GDDR7, 7.5x to 9.2x HBM3); the f = 0 chip disappears | 2.06 + 4,096 leaf reads (section 5.2) | nothing vendor-specific; the serialisation is the consensus risk | before measurement: prototype further. After section 5: SHIP as the class v5 candidate | ### 3.5 Migration through the class system (common to B and C) @@ -165,7 +166,7 @@ Written by this lane in the persona files' voices (`.claude/agents/cryptographer ### 4.4 The pick -B and C are the two to prototype: both are class objects on the shipped hash, both leave the dataset's memory bound untouched, and they compose (B is kernel text, C is the daily build). A is not prototyped: A1 and A2 fail on bytes and on sampleability before any kernel, and A0 is C with a worse data source. The order of merit at this point, before measurement: C first (no vendor risk, unchanged hash rate by construction, a real new property per block, the pool caveat stated), B second (a real lever against the f = 1 chip with a bounded `k`, a vendor split and an unverified AMD layout). Section 6 revisits the order on the measured rows. +B and C are the two to prototype: both are class objects on the shipped hash, both leave the dataset's memory bound untouched, and they compose (B is kernel text, C is the daily build). A is not prototyped: A1 and A2 fail on bytes and on sampleability before any kernel, and A0 is C with a worse data source. The order of merit at this point, before measurement: C first (no vendor risk, unchanged hash rate by construction, a real new property per block, the pool caveat stated), B second (a real lever against the f = 1 chip with a bounded `k`, a vendor split and an unverified AMD layout). Section 6 revisits the order on the measured rows: C holds, B retires into the reserve. ## 5. The prototypes and the measured rows @@ -173,31 +174,119 @@ Both prototypes live under `proto-newpow/` with a README carrying the exact comm ### 5.1 `mma-shadow` (scheme B), box 1 -ROWS_B +Prototype: `proto-newpow/mma-shadow/` (README with every command, `kernel_mm8.cu` with the PTX path and the `IGNEUM_MM8_REF` reference path, `bench.cu`, `verify_ref.c`, `gen_block.py`, `gen_ref_program.py`, `run.sh`, `out/` with every log and csv). Box 1: RTX 4090 24 GB (128 SMs), driver 570.172 (the box reports 570, not the 595 of box 2), nvcc 12.8.93, `-arch=sm_89`, 1 warp per block, 10 timed batches of 2^24 after a warm-up, power from `nvidia-smi` at 1 Hz over a 25-s sustained phase (mean after its first 10 s), idle 15.0 to 15.3 W. The two-output tile form of section 3.2 was built (the single-output form never was). Run 19:36 to 19:52 UTC. + +| R (steps per iteration) | mm8 per hash (8 R) | MH/s (GPU time) | Ratio to R = 0 | Watts, mean | Block watts over R = 0 | SM MHz | Microjoules per hash | Marginal pJ per multiply-add | Fingerprint (2^24 at base 0) | PTX = reference path | CPU = GPU (1,024 lanes) | Verifier block delta, ms per unit (box core, scalar C) | Registers per thread | +|---|---|---|---|---|---|---|---|---|---|---|---|---|---| +| 0 (control) | 0 | 63.08 | 1.000 | 201.2 | 0 | 2,670 | 3.19 | | 7c28cfb06c5c65a9 (the pack's; the 3 pack vectors PASS standalone and in batch) | yes | 1,024 of 1,024 | 0 (the R = 0 run read -0.10, noise) | 29 | +| 8 | 64 | 63.08 | 1.000 | 204.1 | 2.9 | 2,670 | 3.24 | 0.70 | 06fc2593bfb94b4f | yes | 1,024 of 1,024 | +0.05 | 30 | +| 32 | 256 | 63.08 | 1.000 | 207.7 | 6.5 | 2,670 | 3.29 | 0.39 | 26e83a65f519c865 | yes | 1,024 of 1,024 | +0.17 | 29 | +| 128 | 1,024 | 63.08 | 1.000 | 212.7 | 11.5 | 2,670 | 3.37 | 0.17 | 42223c2113188335 | yes | 1,024 of 1,024 | +1.14 | 32 | +| 512 | 4,096 | 63.08 | 1.000 | 215.9 | 14.7 | 2,670 | 3.42 | 0.056 | 02b7002d747f3711 | yes | 1,024 of 1,024 | +4.39 | 36 | + +Sustained rates over the power window 63.07 to 63.03 MH/s at every R; wall and event rates agree to 0.01 MH/s; no spills, no stack, temperature 58 to 64 C, no throttling. The verifier totals in the box's logs (about 10 ms per unit) are a naive scalar interpreter with a lazy `mh_word` per load on a shared Zen 4 core at load 13 and are not comparable to the project's Rust verifier (2.06 ms per unit on an M5 Max core, interleaved); the block delta is the number: about 1.1 microseconds per `mm8` step per unit, 32 lanes x 32 byte products in plain loops. (At R = 512 the card issues about 258 G tiles per second, 2.6 x 10^14 multiply-adds per second, in the region of 40 percent of the 4090's dense int8 tensor peak, approximate from the published TOPS figure, so R = 512 is near the top of the free band, not its middle.) + +What the rows say: + +1. The tensor block is free in hash rate to R = 512 on the 4090: 4,096 tile instructions per hash leave the rate at 63.08 MH/s to the third decimal. The kernel is latency-bound on its 128 dependent loads and the tensor work fills stalls that were already there, as the ALU shadow did on the 5090 to 150,800 ops (`latency-shadow-2026-10-06.md` 5). +2. The block costs the honest card almost nothing in energy: 2.9 to 14.7 W, 0.70 pJ per multiply-add at R = 8 falling to 0.056 pJ at R = 512 (the tensor path's fixed cost amortised), 0.05 to 0.23 microjoules per hash on a 3.19 microjoule hash (+1.6 to +7.2 percent). The ALU shadow at N = 100,000 costs the 5090 0.6 microjoules per hash (11 pJ per counted op, `latency-shadow-2026-10-06.md` 5, item 4); the tensor block at its free-band ceiling costs a third of that. +3. That is the finding, and it is negative for the scheme's purpose (section 5.3): a shadow lever moves the chip's edge only by the joules it makes the HONEST card spend on work the chip cannot do more cheaply. The tensor path is so efficient on the GPU that the block adds 0.23 microjoules at most, so at `k = 1` the chip's edge falls from 6.9x to 4.9x on GDDR7 against this 4090, where the ALU shadow took the 5090 from 5.6x to 2.1x, and the tensor block costs the verifier 26x more per unit of chip-forcing energy (4.39 ms scalar per 0.23 microjoules against 0.17 ms per 0.6 microjoules). The property the design hoped for (`k_mma` near 1 because the GPU's tensor engine is near the floor) is real and is exactly why the lever is weak: there are no joules in it to force. +4. The correctness chain holds at every rung: the PTX fragment read and the plain-integer reference agree on all 2^24 lanes at every R, and the CPU interpreter matches the GPU on 1,024 lanes at every R; the probe's fragment layout (`family-probe.cu` mm8 `warp_ref`) was used as written and needed no correction. Registers 29 to 36, occupancy unchanged. This is the first class-shaped evidence that an `mm8` family is cheap and exact for the honest NVIDIA card, which is what the reserve entry R8 needs; it is not evidence for a class v5. + +Consequences per tier (B at R = 128, the largest R whose scalar verifier delta stays near 1 ms): + +| Tier | Meaning | What to do | +|---|---|---| +| NVIDIA Turing and later (RTX 20 to 50 series), any memory size | rate unchanged, +11.5 W on a 4090 (+5.7 percent), per-pound unchanged, per-watt 0.95x | nothing; the block would not be adopted on this evidence | +| NVIDIA Pascal (GTX 10 series) | emulation at about 16 steps per `mm8` (approximate): 1,024 per hash is about 16,000 counted steps, inside the latency shadow of a 1080-class card by the counted-ops rule, approximate; unmeasured | the emulation path exists in the kernel text (`IGNEUM_MM8_REF`) and is bit-exact; a GTX 1080 row is owed if B ever proceeds | +| AMD RDNA 3 and 4 | the WMMA layout is unverified (status item 6); emulation otherwise at about 12 to 16 steps per `mm8` | the gate of section 7 rank 2 stays open; not worth running unless B proceeds | +| Apple | emulation at about 10 steps per `mm8` (approximate), 10,000 counted steps at R = 128 inside the M5 Max's 130,000 ceiling | owed; not worth running unless B proceeds | +| Rig, pool user | nothing changes in shares; a rig pays the block's watts per card | nothing | +| Node operator (verifier) | +1.14 ms per unit scalar at R = 128 (+55 percent of 2.06 ms), +4.39 ms at R = 512; a SIMD byte-dot path would cut it 4x to 16x (approximate) | the verifier cost per joule forced is the reason the scheme loses to the ALU shadow | +| A chip | must carry a tensor array per 32 lanes in flight; at the measured block energy it pays at most 0.23 microjoules per hash more than today at `k = 1`, 0.07 at `k = 0.3` | the chip's edge barely moves (5.3) | ### 5.2 `state-dataset` (scheme C), box 2 and igneum-build-1 -ROWS_C +Prototype: `proto-newpow/state-dataset/` (README with every command and log; `kernel_sd.cu` = the pack's kernel plus `igneum_leaves` and `igneum_build_sd`, the hash kernel byte for byte the pack's; `verify_sd.c` the 32-lane C interpreter and the item check; `cpu_rows.c` the igneum-build-1 rows). Box 2: RTX 4090 24 GB, driver 595.91, nvcc 12.8, host EPYC 7352; igneum-build-1 EPYC 9454P at load 19 to 32 (shared), one core pinned, nice 19. Run 19:34 to 19:50 UTC. The leaf stand-in: one ChaCha12 block of (sigma, S, t, 0, tag) with `S = K XOR 0x5a5a5a5a` (the README defines it). + +| Row | Control (mx8-genesis) | sd1 | Reading | +|---|---|---|---| +| Hash rate, 10 batches of 2^24, two passes | 63.083, 63.083 MH/s | 63.088, 63.087 MH/s | equal within 0.01 percent: the kernel is the same binary over a dataset of the same shape | +| Watts, mean after 10 s of a 20-s window; SM MHz | 207.6, 205.2 W; 2,745 | 207.9, 206.5 W; 2,745 | equal within the 2 W run-to-run noise; 3.27 microjoules per hash on this 4090 (the 5090 is 2.34 to 2.65) | +| MH/s per W | 0.304, 0.307 | 0.303, 0.305 | unchanged | +| Cache fill, GPU | 1.81, 1.85 ms | 1.85, 1.85 ms | unchanged | +| Leaf array (2^24 ChaCha12 blocks, 1 GiB) on the GPU | none | 5.49 ms | the synthetic stand-in; a real snapshot comes from the node | +| Dataset build, second pass | 30.55 ms | 31.98 ms | +1.43 ms (+4.7 percent): one coalesced 64 B read per item | +| Chunked build (leaves streamed from pinned host memory in 64 MiB or 256 MiB chunks, one stream) | | 75.8 ms, 75.5 ms | PCIe-bound: 62 ms of copy (17.2 GB/s on this box) plus the build, partly serialised; two streams would hide most of the build (not done) | +| Device memory during the build (context 395 MiB included) | 1,675 MiB | 2,699 MiB resident leaves; 1,741 MiB chunked at 64 MiB; 1,933 at 256 MiB | while hashing 1,803 MiB in every mode (leaves freed) | +| 2^24 fingerprint at base 0 | 7c28cfb06c5c65a9 (the pack's, both passes) | d5b0c16390cad0e8 (four runs) | | +| Pack vectors (3 warps, standalone and in batch); dataset head, word [MASK], 64 Mac samples | PASS | n/a (new values; sd1 base 0 lane 0 = b600edbed969becc) | the harness is the pack's | +| Build bit-exact: 1,024 random items, GPU words against the plain C host derivation | 1,024 of 1,024 | 1,024 of 1,024 (also after both chunked rebuilds); 64 device leaves = host leaves | | +| Hash bit-exact: 4 dumped warps (128 lanes) against the C interpreter with lazy `mh_word` / `mh_word_sd` | 128 of 128 (and 32 of 32 against the Mac vector at base 0) | 128 of 128 | | +| Host cache FNV-1a 64 | 48c4f5bf24166b2e = the Mac's | same | | + +The CPU rows (igneum-build-1, ms per unit of 4,096 items, 100 units; two readings at load 19 and 31): + +| Row | Reading 1 | Reading 2 | Per item | +|---|---|---|---| +| (i) 4,096 random 64 B reads from a 2 GiB resident leaf array | 0.108 ms (0.092 to 0.172) | 0.163 ms | 26 to 40 ns | +| (i) the same from an 8 GiB array (the year-12 size) | 0.139 ms | 0.209 ms | 34 to 51 ns | +| (ii) 4,096 leaves derived on the fly (one ChaCha12 block each) | 0.284 ms | 0.285 ms | 69 ns | +| (iii) 4,096 x `mh_item` on the host cache, naive, no interleaving | 9.01 ms | 11.24 ms | 2.2 to 2.7 microseconds | +| 4,096 x (leaf read + `mh_item_sd`), naive | 9.31 ms | 11.64 ms | | +| Daily leaf array on CPU (2^24 blocks): one core / 32 threads | 1.67 s / 0.073 s | 1.68 s / 0.083 s | 100 ns per leaf | +| Host cache fill (256 MiB), one core | 0.447 s | 0.452 s | | +| Light-client sample: distinct items touched by lane 0's 128 loads | 128 of 2^24 | | 128 openings x 25 levels x 32 B + 128 x 64 B = 108 KiB per lane; 3.4 MiB per 32-lane unit before dedup (arithmetic) | + +Reading, against the gate: the project's Rust verifier does the 4,096 derivations in 2.06 ms on an M5 Max core by interleaving the eight dependent cache misses across items (the naive C figure here, 9 to 11 ms, is an upper bound and is not the production verifier); sd1 adds 0.11 to 0.21 ms per unit when the verifier holds the state in RAM (+5 to +10 percent of 2.06 ms; about +0.3 to +0.5 ms on a 2019-class core by the 2.5x rule), so the class v3 verifier plus sd1 sits at about 2.2 ms on the M5 Max core and about 5.7 ms on the 2019-class core, inside the 10 ms gate with the x8 margin intact. The snapshot's own cost is the state read on the node, not the leaf hashing (1.7 s on one core for 2^24 leaves). + +Consequences per tier (sd1): + +| Tier | Meaning | What to do | +|---|---|---| +| Home miner, one 8 GB card | hash rate, watts and MH/s per W unchanged (measured equal); the build must stream the leaves (resident leaves at the designed 2 GiB dataset would peak at about 4.6 GiB, approximate; chunked 64 MiB about 2.7 GiB, approximate, measured 1,741 MiB at 1 GiB), so the 8 GB mine-and-prove profiles of `prover-tiers-real-cards.md` keep their memory | ship chunked only; the kernel already takes an item range, so chunking needed no kernel change | +| 12, 16, 24, 32 GB cards | unchanged in rate and watts; the daily build +1.4 ms resident or about 76 ms streamed (about 150 ms at 2 GiB, approximate) | nothing | +| AMD and Apple | the hash kernel is untouched, so the measured class v3 rates stand (18.9 MH/s on the 9070 XT, 27.1 on the M5 Max); the build kernels gain one 64 B read per item in OpenCL and Metal | the two build kernels in the emitter's other dialects | +| Rig | per card as above; one 2 GiB snapshot per day per rig, shared by its cards | the node beside the rig serves it over loopback | +| Pool user | a 2 GiB download per day from the pool, or the pool ships the built dataset; today a pool user needs nothing but the day key | the pool protocol (spec 09) gains a daily snapshot or dataset fetch | +| Solo miner | must run a node with execution (it already does for templates) and hold the state | nothing new beyond RAM | +| Node operator | +2 GiB RAM for the snapshot (+4 GiB at year 4), a daily serialisation pass, the verifier +0.11 to 0.21 ms per unit | the node RAM budget stays inside a 16 GB home node at launch sizes | +| Light client, holder | a verifiable sample of 128 state leaves per lane per block exists; checking it costs 108 KiB per lane fetched on demand, so it is an availability spot check, not a light verification of PoW | rank 6 of section 7 | +| Prover, rollup customer | nothing | | ### 5.3 The chip rows on the measured numbers -CHIP_ROWS +Method: `sim/horizon/new-pow/chip_rows.py` (the chip-model-v3 section 5.4 f = 1 chip plus the block's measured energy times `k`; the card figures are 5.1's; every chip figure is arithmetic on `chip-model-v3.md`'s cited and approximate memory figures). The denominator here is the 4090 measured tonight (3.19 microjoules per hash at the control, a card whose memory system is half the 5090's), not the 5090, so the R = 0 column reads 6.9x where `chip-model-v3.md` reads 5.1x for the 5090; the movement with R is the row that matters. + +| Scheme and setting | Honest card microjoules per hash | Chip, GDDR7, k = 1 / 0.5 / 0.3 (microjoules) | Edge over the card, GDDR7 | Chip, one HBM3 stack, k = 1 / 0.5 / 0.3 | Edge, HBM3 | +|---|---|---|---|---|---| +| Class v3 today (R = 0, the 4090 control) | 3.19 | 0.466 | 6.9x | 0.321 | 9.9x | +| B at R = 128 (1,024 tiles per hash, block 0.18 microjoules) | 3.37 | 0.646 / 0.556 / 0.520 | 5.2x / 6.1x / 6.5x | 0.501 / 0.411 / 0.375 | 6.7x / 8.2x / 9.0x | +| B at R = 512 (4,096 tiles per hash, block 0.23 microjoules, the free band's top) | 3.42 | 0.696 / 0.581 / 0.535 | 4.9x / 5.9x / 6.4x | 0.551 / 0.436 / 0.390 | 6.2x / 7.8x / 8.8x | +| For comparison, the ALU shadow at N = 100,000 on the 5090 (`latency-shadow-2026-10-06.md` 6, measured 3.27 microjoules at the 431 W cap) | 3.27 | 1.59 / 1.03 / 0.80 | 2.1x / 3.2x / 4.1x | 1.44 / 0.88 / 0.66 | 2.3x / 3.7x / 5.0x | +| C (sd1) at any size | unchanged (measured equal) | 0.466 | unchanged | 0.321 | unchanged; the f = 0 recompute chip must add 2 GiB of DRAM and becomes this chip | + +Reading: B moves the f = 1 chip's edge by 1.4x to 2x at `k = 1` and by 1.1x at `k = 0.3`, at a verifier cost of 1.1 to 4.4 ms per unit (scalar); the ALU shadow moves it by 2.7x at `k = 1` and 1.4x at `k = 0.3` at 0.17 ms per unit. On every axis the measured tensor block is the weaker lever, for the reason stated in 5.1 item 3. C moves nothing and claims nothing against the chip; its merit is elsewhere. ## 6. Verdicts | Scheme | Verdict | Why, in one line | |---|---|---| | A, mining is proving | NEVER (A1, A2); A0 folds into C | one proof per segment is not a distribution of puzzles; the bytes (2.9 MB of openings per block) or the verify (32 to 40 ms) kill every form that is not "hold the trace", and holding the trace is C with a worse data source | -| B, tensor-shaped shadow | VERDICT_B | -| C, stored state | VERDICT_C | +| B, tensor-shaped shadow | NEVER as class v5 content for the anti-chip purpose; the measurement (0.056 to 0.70 pJ per multiply-add, 15 W for 4,096 tiles per hash) is the reason. KEEP the `mm8` family as reserve R8 with the two-output correction, for datapath diversity, not for joules | +| C, stored state | SHIP AS CLASS v5 CANDIDATE (through the spec items of 4.3 and the Devnet 2 gate): hash rate and watts unchanged by construction and measured equal, build +1.4 ms, verifier +0.11 to 0.21 ms per unit, bit-exact on 1,024 items and 128 lanes; a new property per block (a random sample of state) and a new requirement per mining operation (hold the state); the open decision is what a header verifier is asked to hold | **A, in full.** The mandate asked for something never done, and "mining is proving" is the thing everybody has wanted and nobody has shipped; this lane's contribution is the reason, stated as a bound rather than a feeling: the useful fraction of a proving-as-lottery scheme is (proving work per segment) / (network hashes per segment), 8 percent at 1 GH/s and 0.08 percent at 100 GH/s on this chain's measured figures, because gas sets one and the security budget sets the other, and a puzzle whose verifier either recomputes the piece or verifies a 32 to 40 ms proof cannot sit under a 10 ms gate. The 80/20 split stays. Ledger F13's answer stands and gains this bound. What survives (A0) is scheme C. -VERDICT_BC_PROSE +**B, in full.** The scheme asked whether a GPU-shaped puzzle could make a chip into a GPU. The answer the 4090 gives is that the tensor-shaped work is free for the honest card (rate unchanged to R = 512, 15 W at most for 4,096 tiles per hash) and therefore nearly free for the chip too: a lever against the f = 1 memory-controller chip works only through joules the honest card is forced to spend on work the chip cannot do more cheaply, and the ALU shadow of class v4 spends 11 pJ per op where the tensor path spends 0.056 to 0.70 pJ per multiply-add. The chip's edge moves from 6.9x to 4.9x at k = 1 (4090 denominator) where the ALU shadow moved the 5090's by 2.7x (5.6x to 2.1x), and the verifier pays 26x more per unit of forcing energy (4.39 ms per 0.23 microjoules against 0.17 ms per 0.6). There is also the vendor split (native on Turing and later and on RDNA 3 and later, emulated on Pascal, RDNA 2 and every Apple card) and the unverified AMD layout. So: never as class v5 content for this purpose. What the measurement does establish, and what should be kept: an mm8 family is exact on NVIDIA at every R tried (2^24 lanes PTX = reference, 1,024 lanes CPU = GPU at five rungs), cheap for the honest card, and bounded in verifier cost by a known law (1.1 microseconds per step per unit, scalar); that is the evidence the reserve entry R8 lacked, and the two-output form (both tile outputs consumed) is a correction R8 needs so a chip cannot halve the tile. The standing rule this leaves for the programme: a shadow lever only works through joules the honest card is forced to spend, so shadow work goes where the GPU is LEAST efficient per op among the operations a chip cannot do much better (RandomX's argument), never where it is most efficient. + +**C, in full.** The scheme does what it says at a cost that is measured and small: the hash kernel is byte for byte the shipped one, the 4090's rate and watts are equal within noise (63.083 against 63.088 MH/s, 205 to 208 W), the daily build grows by 1.4 ms resident or about 76 ms streamed over PCIe (about 150 ms at the designed 2 GiB, approximate), device memory during the build stays at dataset plus cache plus one 64 MiB chunk, the verifier grows by 0.11 to 0.21 ms per unit with the state in RAM (about 2.2 ms on the M5 Max core, about 5.7 ms on a 2019-class core by the 2.5x rule, inside the 10 ms gate), and every GPU word and lane agrees with the plain C derivation (1,024 of 1,024 items, 128 of 128 lanes). It is never weaker than today's dataset against any chip and it removes the f = 0 recompute chip as a category. What it adds has not been done on a shipped chain in this form: the dataset IS the keyed execution state, so every mining operation holds the chain, and every block names 128 random state leaves per lane that any full node can open against the day's state root. Its limits are stated: a pool can ship the dataset (a 2 GiB daily delivery per pool miner, where today a pool miner needs only the day key), a header verifier must hold the state or be shown 3.4 MiB of openings per unit (so light clients still rely on certificates, as spec 10 says), and the canonical serialisation is new consensus-critical code that must cross a day boundary and a finality pause on the fast-time harness before Devnet 2. Verdict: ship as the class v5 candidate, with the three spec items of 4.3 (the serialisation, the pruning-proof witness, the pause rule), the Devnet 2 gate, and the open decision on what a header verifier is asked to hold in front of it. + +**The ranking after measurement.** C, then nothing else from this lane as class content; B retires into the reserve entry it improves; A is recorded as a bound. The brief's hope of "mining is proving" is answered with the reason it cannot be, which is worth more to the project than a design that pretended otherwise. ## 7. Ranked next steps -Hours are agent hours (the project lead's rule: Claude-side work takes hours). Each gate is a measurable pass line. Consequence per tier is the row's own. +Hours are agent hours (the project lead's rule: Claude-side work takes hours). Each gate is a measurable pass line. Consequence per tier is the row's own. Ranks 2, 3 and 4 were written as B's gates before the ladder landed; after section 5 they are WITHDRAWN (B is not carried forward as class content; the rows stay so the reasoning is visible) and the live order is 1, 5, 6, 7, 8. | Rank | Proposal | Evidence | Model | Hours | Consequence per tier | Gate | |---|---|---|---|---|---|---| @@ -236,4 +325,8 @@ One paragraph each. ## 9. Summary for the coordinator -SUMMARY +This lane wrote three candidate proofs of work for Igneum, reviewed each in two personas, prototyped the two that survived on real cards, and measured. (1) Scheme C, the dataset derived from the chain's execution state, is the one to carry forward as the class v5 candidate: measured on an RTX 4090 the hash rate and watts are unchanged to the third decimal (63.08 MH/s, 207 W, the kernel is byte for byte the shipped one), the daily build grows by 1.4 ms resident or about 76 ms streamed, the verifier by 0.11 to 0.21 ms per unit with the state in RAM, every GPU item and lane agrees with the plain C derivation (1,024 of 1,024, 128 of 128); it forces every mining operation to hold state, names 128 random state leaves per lane per block, and removes the f = 0 recompute chip as a category; its costs are a 2 GiB daily delivery for pool miners and three spec items (canonical serialisation, a pruning-proof witness, the pause rule). (2) Scheme B, a tensor-shaped shadow of int8 8x8x16 tiles, is bit-exact on NVIDIA at every rung (2^24 lanes PTX = reference, 1,024 lanes CPU = GPU, R = 8 to 512) and free in hash rate to 4,096 tiles per hash, and that is exactly why it fails as an anti-chip lever: it costs the honest card 0.056 to 0.70 pJ per multiply-add and at most 0.23 microjoules per hash, so the f = 1 chip's edge moves from 6.9x to 4.9x at k = 1 against the 4090 where the class v4 ALU shadow moved the 5090's from 5.6x to 2.1x, at 26x the verifier cost per joule forced; never as class content, kept as the evidence and the two-output correction for reserve family R8. (3) Scheme A, mining is proving, is never: one proof per segment is not a distribution of puzzles; the useful fraction is bounded by proving work per segment over network hashes per segment (8 percent at 1 GH/s, 0.08 percent at 100 GH/s on this chain's measured figures); every form that is not "hold the trace" dies on 2.9 MB of openings per block or a 32 to 40 ms proof verify, and "hold the trace" is scheme C with a worse data source. + +1. Scheme C costs nothing in hash rate or watts (63.083 against 63.088 MH/s, 205 to 208 W on the 4090, measured) and 0.11 to 0.21 ms per unit of verifier time (measured on igneum-build-1), so class v5 can carry it; the model is `item_sd(t) = item(t) with s ^= leaf(t)` over the day's certified state root. +2. The tensor shadow moves the f = 1 chip's per-joule edge by at most 1.4x (6.9x to 4.9x at k = 1, R = 512) for 4.39 ms of scalar verifier per unit, against 2.7x for 0.17 ms from the ALU shadow; the model is `chip_rows.py` on the measured block energy of 0.23 microjoules per hash. +3. Mining cannot be proving on a lottery with a 10 ms verifier: the useful fraction is bounded by 5 GPU-seconds of proving per 8-second segment over the network's hashes in that segment, 8 percent at 1 GH/s and falling with the hash rate; the model is that ratio on `prover-tiers-real-cards.md` and bench-log 2582. diff --git a/docs/analysis/horizon/polish.md b/docs/analysis/horizon/polish.md new file mode 100644 index 00000000..7cf2f844 --- /dev/null +++ b/docs/analysis/horizon/polish.md @@ -0,0 +1,491 @@ +# Horizon lane 6: polish. Every shipped system against the best in its class + +Date: 6 October 2026, evening UK (written 20:00 to 21:00Z, while the live devnet's finality was still paused). Lane 6 of the Horizon programme. Worktree `/Users/joshm/Projects/igneum-wt-horizon` (branch `horizon`, HEAD c3aa502). Output: this file only. No file outside it was edited; nothing was built, deployed, posted or started; the installed app and every live service were read, never touched. + +the project lead's bar: "a level of polish that has not been seen before." This file is the honest audit: what each shipped system does, the named comparator and the exact screen or feature it has that we lack or do worse, the ledger rows that close the gap (95 rows, ids Q1 to Q105 with gaps, each with a file or screen, a severity, hours of agent work, an owner and a gate), and what is already better than the comparator. The first ten rows are what the project lead will notice first when he opens each product tomorrow. + +## 0. What was read and run + +Read, in the worktree: `app/igneum-app` (src, ui, tests), `app/mac/IgneumMiner.swift`, `app/windows/host.cpp`, `docs/plans/miner-ui-2.md`, `miner-ui-3.md`, `miner-ui-3-audit.md`, `ember-tune.md`, `release-0.3.14.md`, `docs/design/app-screens/`, `docs/design/miner-tuning.md`, `docs/fud-ledger.md` (X17, X19, X21 to X28, M25 to M29, G7, G10, G13), `site/` (every page, `api/`, `lib/`, `verify/`, `build.mjs` read not run, `scrub.mjs`, `forbidden-strings.txt`, `downloads.json`, `journey.json`, `vercel.json`, `sitemap.xml`), `docs/design/site-polish-2026-10-04.md`, `docs/plans/site-ui-3-shots/`, `docs/plans/site-miner-2026-10-06.png`, `docs/review/round-4-reddit-2026-10-06.md`, `docs/plans/explorer.md` and `explorer/`, `relay/` (ui, api, lib, clients, playbooks, tests), `tools/relay.mjs`, `tools/console.mjs`, `tools/jobs.mjs`, `docs/design/relay/`, `docs/design/console/`, `pool/` (src, web, tests, README), `docs/plans/pool.md`, `docs/spec/09-pool-protocol.md`, `tools/community/discord-hooks.mjs` and its tests and fixtures, `docs/community/discord-hooks.md`, `tools/fleet/`, `tools/build-job.mjs`, `tools/build-remote.sh`, `tools/ci/`, `docs/plans/build-job.md`, `build-server.md`, `hands-on-build-1.md`, `node-changes.md`, `packaging/README-ship.md`, `packaging/ota/README.md`, `packaging/mac/README.md`, `packaging/windows/README.md`, `tools/observer/` (observer.mjs, README), `docs/spec/03` section 3.9, and this lane's siblings `finality-and-weight.md` (3.1, 6) and `frontier.md` (3.7). + +Read, read-only, in other agents' worktrees: `/Users/joshm/Projects/igneum-wt-ship0315/docs/plans/release-0.3.15.md` (the newest release notes; `release-0.3.15.md` does not exist on `horizon` or `master`), `/Users/joshm/Projects/igneum-wt-wallet*` (wallet 0.1.4 source, UI 3 branch), `/Users/joshm/Projects/igneum-wt-gpu-fleet/tools/fleet/`, `/Users/joshm/Projects/igneum/vendor/igneum-node-0310/consensus/src/processes/finality.rs` (the node's `finality_reason`). + +Run (nothing live, nothing built): `node --test app/igneum-app/ui/*.test.mjs` (35 pass), `node --test relay/test/*.test.mjs` per file (24 pass), `node --test tools/community/discord-hooks.test.mjs` (31 pass, every live poster stubbed), one `curl -s https://igneum.network/` (the allowed single fetch; saved to the scratchpad), and one read-only SQL pass over the observer's Neon tables (`live_checkpoints`, `live_events`, `live_state`) through the same HTTPS SQL endpoint `site/api/_neon.mjs` uses, to see what every surface had to render during tonight's pause. No browser automation. + +Comparator claims are from memory unless a repository or page is named, and are labelled approximate. + +## 1. The ten rows the project lead will notice first + +| Rank | Id | Row | File or screen | Severity | Hours | Owner | Who it hits | Gate | +|---|---|---|---|---|---|---|---|---| +| 1 | Q1 | The pause has no cause anywhere. The node reports `finality_reason` (`active`, `window filling, N of M`, `paused`) but not the frozen-table share that held tonight's pause; the observer drops even the reason; the API has no reason field; `/live` computes "N% of weight silent" from the sliding table, which read 11 percent silent at 20:05Z while finality was paused (held by the table frozen at lock 6842, 57.3 percent signing). Add `finality_reason`, `held_by` (frozen index, its signing share, its expiry DAA) and lane 3's `finality_provisional` end to end | `vendor/igneum-node-0310/consensus/src/processes/finality.rs:1794-1807` (reason exists, no frozen share), `tools/observer/observer.mjs:720` (copies `finalityActive` only), `site/api/live.mjs:114-122` (no reason), `site/live.html:520` (the silent percentage) | the project lead-visible | 4 (node 1.5, observer and API 1, `/live` and `/api/stats` copy 1, spec 3.9 row 0.5) | consensus-engineer (node), site owner (observer, API, page) | holder, exchange: the one line that says whether a pause is a silent third, a split, or a table waiting to expire; miner: nothing lost, but the app can finally explain itself; operator: a pause with a cause and an end time | A forced pause on Devnet 2 (one slice over a third stopped): `/api/live.finality` carries `reason`, `held_by`, `provisional`; `/live` reads "finality paused since 18:40Z: 89% of the sliding weight is signing, held by the table frozen at lock 6842 (57% signing) until DAA 216,402 (about 20:40Z)"; the exchange guidance in spec 3.9 carries the provisional row (lane 3 proposal 4) | +| 2 | Q2 | Ember never says "paused". The engine keeps only `last_lock`, `last_lock_at`, `age_s`, `votes`; the node line showed "#6842 · 1 h ago" under "a point the miners agreed can never be undone" for the whole two-hour pause. No `finality_active` anywhere in the app | `app/igneum-app/src/state.rs:247-248`, `src/engine.rs:3495-3497, 3896-3902` (lock parsed from the miner's `LOCK checkpoint` line only), `ui/app.js:1350-1351` | the project lead-visible | 3 | app owner (engine reads `getFinalityCheckpoints` or the miner's `FINALITY` line, spec 3.9 row; UI state and view test) | home miner and rig: the app tells them finality is paused and why, instead of a lock age that climbs; pool user: the same on the pool page later | Mock state with `finality_active:false, reason:"paused"` renders "Finality paused since 18:40Z, 89% of the 30-day weight signing, held by the frozen table until about 20:40Z" on the node line and in the pill; `view.test.mjs` case; seen on the Devnet 2 forced pause | +| 3 | Q3 | A fresh machine's first 60 seconds start with a warning. macOS: ad hoc signature, no Developer ID, no notarisation; the engine strips `com.apple.quarantine` from its own bundle on start; the README's "Right-click > Open" bypass is gone on macOS 15 (approximate). Windows: installer and exes unsigned, SmartScreen "Windows protected your PC", then a UAC prompt for the firewall rule 20 to 50 s in before any screen explains it | `packaging/mac/build-dmg.sh:118-122`, `packaging/mac/README.md:51-53`, `app/igneum-app/src/main.rs:78`, `packaging/windows/README.md:76, 111`, `packaging/windows/build-installer.ps1:188`, `app/igneum-app/src/ota.rs:981-1015` | the project lead-visible | 6 (Developer ID signing plus `notarytool` and stapling in `build-dmg.sh` 3; Authenticode `signtool` step in `windows.yml` and `build-installer.ps1` 2; firewall step after `setup_done` with a sentence on the Cards screen 1), plus the project lead: an Apple Developer account and an OV or EV code-signing certificate (purchases, hours of the project lead's time, approximate) | app owner; the project lead (the certificates) | every new miner on every tier, Windows and macOS: Signal's and Tailscale's installers open with no warning (approximate); today ours open with two | Fresh macOS 15 VM: the DMG's app opens on double-click, `spctl -a -vv` says accepted and notarised; fresh Windows 11 VM: no SmartScreen interstitial, `signtool verify /pa` passes; the first UAC prompt appears after the Cards screen names it | +| 4 | Q4 | Every update is "urgent" once an activation height is behind the node: `fork_is_close(Some(198000), 209000)` is true, so the manifest's stale `activation 198000` turned the 0.3.14 update into "the node is 0 blocks away. Installing now", stripped Later and skipped every safe-moment guard (the PC 1 install under a measurement job at 17:52:54Z). The same path has no finality input: an update applies through a pause | `app/igneum-app/src/manifest.rs:350-355` (`daa + 1800 >= h`), `:322-347` (`safe_to_apply`, no finality), `src/ota.rs:484`, `ui/app.js:217`, `docs/plans/release-0.3.14.md:79`, `release-0.3.15.md` section 6 | the project lead-visible | 3 (close means within 1,800 blocks ahead and not behind 1; the publisher drops a passed `activation_height` 0.5; `Moment.finality_paused` holds a non-urgent update 1.5) | app owner | home miner, rig: no surprise restart mid-measurement or mid-pause; operator: an activation that has passed is not an emergency | Unit tests: behind by any amount is not close; `publish-manifest.sh` refuses a passed height; a paused mock holds the update with the words "finality is paused; installing when it resumes"; the 0.3.16 manifest carries no stale height | +| 5 | Q5 | The homepage says "final" while finality is paused, in a 90-word hero, under a share card that renders as a thumbnail. The chain scene takes `src.locked` and draws the dashed line labelled "final" at the newest locked block with no `finality.active` check (R4.6.3 was fixed on `/live` only); the hero is one 90-word paragraph with two bench deep links and "ships when the packaging row lands"; the miner section still says "a 24 GB NVIDIA card proves as well" while the hero and litepaper say 8 GB; home, litepaper, live and explorer share a 256 px `og-small.png` with `summary` cards | `site/index.html:810, 838` (final label), `:345` (hero), `:494` and `site/miner.html:7, 13, 21, 265, 382` (24 GB), `site/index.html:15-19` (OG) | the project lead-visible | 3 (final label 0.5, hero 1, 24 GB sentence 0.5, four 1200x630 cards 1) | site owner | every visitor; a holder or exchange reading "final" during a pause is the worst of them | No "final" label while `finality.active` is false (the `/live` rule); hero under 40 words with one link; one proving-tier sentence on every page; every page's shared card is 1200x630 | +| 6 | Q6 | The hub shows stale data as live, said "active" for the first 20 unlocked checkpoints, says "paused" with no since or cause, and buried the pause under 401 miner_quiet and miner_back events. After the first load an API failure only changes the header to "offline: ..."; tiles, cards and "Refreshed" keep the old values. `finality_active` flips only after `presence_window` (20 on the devnet) indices without a lock, so 18:42 to 18:50Z read "active" in white with no lock forming. No `finality_paused` or `finality_resumed` event exists; `live_events` between 18:25Z and 21:30Z holds 203 `miner_quiet`, 198 `miner_back`, 55 `difficulty`, 30 `checkpoint_locked` (the last at 18:42:10Z) and no pause row. The checkpoint table also showed "% of active" above 100 (102.4 to 113.4 percent on indices 6900 to 6930) | `relay/ui.html:564` (stale), `:367-368` (tile), `relay/api/console.mjs:214`, `vendor/igneum-node-0310/consensus/src/processes/finality.rs:1794` and `vendor/igneum-node/consensus/core/src/finality.rs:63, 75` (`presence_window` 240 mainnet, 20 devnet), `tools/observer/observer.mjs:652` (the only finality event), `live_checkpoints.fraction_active` rows 6900 to 6930 | the project lead-visible | 4 (dim every panel with "last data N min ago" 1; amber on the first under-2/3 checkpoint 0.5; `finality_paused` and `finality_resumed` events, and collapse quiet/back pairs under 5 min 1.5; clamp or explain the active fraction 1) | fleet agent (hub), consensus-engineer (observer events, the fraction) | operator: the hub is the project lead's first screen; a holder reading the public API gets the same events | Cut the network: every hub panel dims within 15 s; a forced Devnet 2 pause turns the tile amber on the first checkpoint under two thirds, posts one `finality_paused` event with the cause and one `finality_resumed` with the duration; no fraction above 100 percent on any row for 24 h | +| 7 | Q7 | Discord said nothing. The bot has no credentials file and its timer is not installed, so tonight's pause produced zero posts; had it been live, a condition already firing at the watcher's first look is marked `preexisting` and never opens an incident; the pulse hides the pause in its description with no colour or title change | `docs/community/discord-hooks.md:81-82`, `tools/community/discord-hooks.mjs:481-485`, `:252-255`, `:158`, `infra/build-server/discord-hooks/install.sh` | the project lead-visible | 3 (install and one live pulse 1; a pre-existing condition opens with "since at least " 1; paused pulse in a distinct colour with a title suffix 1) | miner-community-lead (community owner) | every Discord reader, which on launch day is every miner | `check` prints three "set"; one live pulse in #numbers; a fixture where the pause predates the first tick opens an incident; the paused pulse renders amber with "(finality paused)" in the title | +| 8 | Q8 | The relay's security fixes are not on master. Commit 28c028b (X23 to X28: `relay/lib/guard.mjs`, `handler.mjs`, run signatures, retention) is on `fud-close` and the `ledger-*` branches only; `git merge-base --is-ancestor 28c028b master` is false. On this tree any of the three intake keys (one ships in every miner app) can post, sync and delete console items, read the whole feed, and every client puts the token in the URL path | `relay/api/console.mjs:249, 283-301`, `relay/lib/relay.mjs:34-46`, `relay/clients/agent.sh:10`, `igneum-agent.ps1:13, 72, 154`, `tools/relay.mjs:24` | operator-visible (security) | 2 (merge or cherry-pick with the 47 relay tests 1; handler test that an intake key gets 403 on post, sync, delete 1) | fleet agent (relay owner) | operator: the console is the control plane for every PC and the fleet; a miner app's intake key is in every install | `28c028b` is an ancestor of the deploying branch; 47 relay tests pass; the handler test above passes; `relay/README.md` names the deployed commit | +| 9 | Q86 | The fleet page is blind to the standing fleet, to death and to finality. `page.py:43-47` publishes `standing` and `devnet2` and the page references neither (grep 0); a dead box reads "running" for up to 20 minutes because state comes from `boxes.json` and `standing.jsonl` is never read; no fleet or console script reads `finality_active`, so tonight's pause was found by hand. The night the project lead ruled that 22 boxes stay up permanently, the page that shows them cannot show them | `igneum-wt-gpu-fleet/tools/fleet/page.py:38-47`, dlsite `index.html:243`, `lib/standing.py:54-56`, `tools/console.mjs:124` | the project lead-visible | 6 (standing block with USD per day and the Devnet 2 gate line 2; "unreachable since HH:MM" within 2 minutes 2; finality on the page and in `console.mjs chain` 2) | fleet agent | operator (the project lead reads this page every evening); every tier indirectly: a dead standing box is weight that left silently, the class of tonight's pause | the page shows the standing count and spend; a box killed by hand reads "unreachable since" within 2 minutes; a forced Devnet 2 pause reads on the page and in the console | +| 10 | Q9 | The wallet page describes 0.1.4 while UI 3 (five state words, light mode, pounds line) sits unmerged on `wallet-ui-3`; the MetaMask guide's testnet RPC is still labelled a placeholder, its devnet RPC port is the miner's node (26790) while a wallet-only Mac runs 26800, and `wallet_addEthereumChain` carries `blockExplorerUrls: []` and no `iconUrls`, so MetaMask shows no explorer link and a blank icon | `site/wallet.html:261-266, 312`, `igneum-wt-wallet-ui/docs/plans/wallet-ui-3.md:93, 112`, `site/metamask.html:197, 225, 233, 285-286`, `igneum-wt-wallet/app/igneum-wallet/src/node.rs:24-26` | the project lead-visible | 6 (wallet 0.1.5 with UI 3 published and the page rewritten 4; guide: both ports, the placeholder line removed, explorer URL and icon in the request 2) | app owner (wallet), site owner (guide) | holder: the first non-miner product; a wrong port or a placeholder RPC is a dead end at the first step | `downloads.json` wallet-mac 0.1.5; the page names pending, included, executed, proven, finalised; `eth_chainId` returns 0x116e from the guide's URL; MetaMask shows the explorer link after the add-chain button | + +Next in line, outside the ten: Q10 (the explorer knows nothing about finality and swallows failures after first load, 3 hours), Q80 (the Windows installer pipeline is dead on GitHub billing) and Q12 (Ember's light mode fails contrast on the private key and every live state). + +Hours for the ten: 40 agent hours, plus the project lead's certificate purchases for Q3. + +## 2. Tonight's pause on every surface (the ledger row the project lead asked for) + +Times UTC. The chain-side facts are lane 3's (`finality-and-weight.md` 3.1) and the observer's own rows, read tonight: last lock 6842 at about 18:39:40Z; 6843 proposed at 53.1 percent of total; `finality_active` false from about 18:50Z (index 6862, twenty indices without a lock, `presence_window` 20); no lock after 6842 as of 20:05:03Z (`live_state.updated_at`), `total_weight` 7,167, `active_weight` 6,346 (88.5 percent signing on the sliding table), `voters` 85, and no `finality_reason` key in the stored JSON. Sampled `live_checkpoints` rows: + +| Index | First seen | State | Signed, percent of total | Signed, percent of active | Votes | +|---|---|---|---|---|---| +| 6820 | 18:28:30 | locked | 88.8 | 100.0 | 80 | +| 6840 | 18:42:10 (ingested after the hub came back) | locked | 79.5 | 98.6 | 83 | +| 6850 | 18:43:41 | proposed | 55.2 | 76.8 | 75 | +| 6880 | 19:00:22 | proposed | 50.1 | 81.2 | 49 | +| 6910 | 19:13:55 | proposed | 67.4 | 107.4 | 55 | +| 6920 | 19:19:07 | proposed | 74.8 | 113.4 | 78 | +| 6960 | 19:40:19 | proposed | 86.0 | 100.0 | 78 | +| 7010 | 20:04:32 | proposed | 87.7 | 99.1 | 73 | + +From 6910 (19:13:55Z) every proposed checkpoint carried over two thirds of the sliding total and did not lock: the frozen table of Q5 held it (lane 3, 3.1). No surface could say so, because no field carries it. The "percent of active" above 100 on 6900 to 6930 is a display bug on the hub's checkpoint table (the active denominator lags the signers). + +| Surface | What it showed 18:40Z to 20:05Z (from the code path and the rows above) | Honest? | Fix row | +|---|---|---|---| +| Ember (`app.js:1350`) | "last lock #6842 · 1 h 25 min ago" with "a point the miners agreed can never be undone; locks at 2/3 of the 30-day weight". No pause word anywhere | No: the age climbed and the note contradicted the state | Q2 | +| Site `/live` (`live.html:512-520`) | Status LIVE, blocks flowing, finality bar grey, "finality paused: 45% of weight silent" at 18:50Z falling to "11% of weight silent" by 20:05Z, no final region, no checkpoint ticks; "last lock #6842, 1 h ago" in the stats (`:404`, no active check); a hovered checkpoint block still read "locked" (`:450`) | Half: the pause is named (R4.6.3) but the percentage says the opposite of why it is paused once the stayers passed two thirds | Q1 | +| Homepage (`index.html:810, 838, 879`) | Counter "last lock: paused"; the chain scene still drew the dashed line labelled "final" at the newest locked block | No: "final" and "paused" on one screen | Q5 | +| Explorer (`explorer.html`, `block.html`) | Identical to a normal hour; stars on locked checkpoint rows; block pages of ordinary blocks "not a checkpoint" | No: nothing marked the pause | Q10 | +| Hub (`relay/ui.html:367-368`) | 18:30 to 18:42Z: "observer STALE N s" in red, tiles frozen, finality "active" (the hub itself was down with the Mac). 18:42 to about 18:50Z: "active" in white, no lock forming. About 18:50Z onward: "paused" in amber, "last lock #6842" in amber, eight rows "proposed" at 53 to 89 percent, Events card full of `miner_quiet` and `miner_back` | Half: paused, but no since, no cause, no end; active for ten minutes of no lock | Q6 | +| Discord | Nothing: the bot is not live. Had it been: the 20:00Z pulse would have read "Finality paused since 2026-10-06 18:4x UTC: nothing is final until two thirds of the 30-day weight signs again; mining and proving are paid as usual" in the description, and the watcher would have marked the pause `preexisting` and never opened an incident | No | Q7 | +| Wallet 0.1.4 (`igneum-wt-wallet` `app.js:185, 340`) | Node card "paused" when a local node reports `finality_active` false, or "not checkpoint-checkable without a node" on the public RPC; transaction rows stay "in block N" with no mention | Half | Q35 | +| Pool page (`pool/web/index.html`) | Not deployed; the code has no finality concept; it would have confirmed and paid on blueness throughout | n/a (a design choice, stated in Q50) | Q50 | + +The row for the ledger: **Q1 plus Q2, Q5, Q6, Q7, Q10**: every public and operator surface either hid the pause, named it without a cause, or contradicted it, for two hours, during the first finality pause the project has had in public. The gate across all of them is one forced pause on Devnet 2 with a screenshot of each surface, before the public testnet opens. + +## 3. Per system + +### 3.1 Miner app (Ember) + +**What it does today.** A Rust engine (`app/igneum-app/src/`) runs `igneumd` and one GPU worker per card, serves a tokenised dashboard on 127.0.0.1 (`src/server.rs:11-33`, routes `:184-359`: `api/state`, `api/log`, `detect`, `setup`, `key/saved`, `key/reveal`, `start`, `pause`, `resume`, `cards`, `tune/goal`, `settings`, `prove`, `update/check|install|auto|open`, `jobs/allow|check`, `power/apply|control`, `sweep/*`, `clock/*`, `quit`). Screens (`ui/index.html`): Welcome (line 71), Cards (102), Address (127), the one-time key sheet (438), then a rail with Mine, Earnings, Prove, Settings (164 to 413), a log drawer (460), a status strip (`Notices`, `ui/app.js:7-140`) and an update card (`UpdateCard`, `app.js:154-243`). Ember Tune (`src/ember.rs`, `src/sweep.rs`, `docs/plans/ember-tune.md`) tunes power and clocks per card against a goal. Updates are Ed25519-signed manifests (`src/ota.rs`, `src/manifest.rs`). Hosts: `app/mac/IgneumMiner.swift` (WKWebView, menu bar), `app/windows/host.cpp` (WebView2, tray). The shipped build is 0.3.14 with miner-ui-3; 0.3.15 (miner-ui-4: the big-number hero and the chain scene) is staged, its canary failed on block version 1026 and was rolled back (`release-0.3.15.md` section 5). UI tests: 35 pass. + +**Comparators and the exact gap** (all approximate, from memory of the products). + +| Comparator | Their screen or feature | Ours, and where it falls short | +|---|---|---| +| NiceHash QuickMiner | Profitability per day in fiat on the main screen; Rig Manager web view of your own rigs | Earnings reads "£0.00 earned: nothing is bought or sold on devnet" (`index.html:250`); mined IGN is never shown, only proving `paid_wei` (`app.js:1364`); no owner-facing remote view (the console is the operator's) | +| HiveOS | Hash rate and temperature charts over hours; Telegram or Discord alert on a GPU fault or a rig offline; per-GPU fan and memory clock | A 10-minute blocks strip only (`app.js:1052-1127`); no outbound alert; manual control is the power slider (`app.js:1255`); the memory knob exists in the engine with no control | +| lolMiner, T-Rex consoles | Per-GPU accepted, rejected, invalid and faults on every status line | Per card only "N blocks" (`app.js:306`); `rejected_session` a total in the hero sub-line (`app.js:1290`); `mismatched=` and `faults=`, which X21 made the engine read, never reach the row | +| Nanominer | Restart-on-hash-drop watchdog with a visible threshold | `src/watchdog.rs` exists; the row says "stopped after repeated faults" (`app.js:296`) with no count or threshold | + +**Rows.** + +| Id | Row | File or screen | Severity | Hours | Owner | Gate | +|---|---|---|---|---|---|---| +| Q2 | Finality pause invisible (section 1) | `state.rs:247`, `engine.rs:3495`, `app.js:1350` | the project lead-visible | 3 | app owner | section 1 | +| Q4 | Every update urgent once the activation is behind; no finality hold (section 1) | `manifest.rs:322-355` | the project lead-visible | 3 | app owner | section 1 | +| Q11 | Offline start says "The update failed." A check error with no held manifest calls `set_error`; the cached manifest is loaded for the floor but never into `self.manifest`; the notice offers "Try again", which POSTs install | `ota.rs:649-654, 195-200`, `app.js:29, 1198` | user-visible | 1 | app owner | notices test: a check-class error renders "Could not check for updates" with no install action | +| Q12 | Light mode fails contrast on every live text: molten `#B8731F` on white 3.81:1, on bone 3.38:1; ember `#E04A14` on bone 3.61:1; molten carries the private key, the address and the live states. `miner-ui-3.md` section 9 claims 4.5:1 everywhere | `app.css:29, 263, 338, 431` | user-visible | 1 | app owner | a node script over the token pairs asserts 4.5:1 for every text token in both schemes; CI runs it | +| Q13 | Per-card rejects and faults never reach the row (the lolMiner line) | `app.js:291-309, 1290` | the project lead-visible on PC 1 | 2 | miner-community-lead | row meta "N rejected · M faults" when nonzero; view test | +| Q14 | Windows first run raises a UAC prompt (firewall rule) before any screen explains it (part of Q3) | `ota.rs:981-1015`, `index.html:71-99` | user-visible | 1 | app owner | the firewall step runs after `setup_done`; the Cards screen names it | +| Q15 | Mac first-run instruction stale: "Right-click > Open" (part of Q3) | `packaging/mac/README.md:51`, `site/miner.html` download copy | the project lead-visible on a fresh Mac | 1 | app owner | the download page and README name the Privacy and Security step until the certificate lands | +| Q16 | No rate history: a 10-minute strip against HiveOS's hours | `app.js:1052-1127` | user-visible | 4 | app owner | one-hour per-card sparkline from an engine ring buffer; view test | +| Q17 | Hill climb hidden until `tune_climb` is on the state | `index.html:341`, `app.js:1407` | operator-visible | 1 | app owner | the row shows whenever the engine answers `tune/goal` | +| Q18 | Earnings never shows mined IGN; "£0.00 earned" is the first number on the tab | `index.html:250`, `app.js:1364` | user-visible | 2 | miner-community-lead | the tab shows blocks mined and the subsidy they earned in IGN, with the devnet line under it | +| Q19 | 10 px type in five places, 9 px ruler | `app.css:196, 271, 272, 302, 491, 531` | cosmetic | 1 | app owner | no `font-size` under 11 px except the ruler | +| Q20 | Design screens in `docs/design/app-screens/*.png` are a UI 1 app (v0.3.0 tiles, a Finality card the shipped UI lacks) | `docs/design/app-screens/` | cosmetic | 1 | app owner | screens regenerated from `?screen=` on the current build | +| Q21 | AMD step line prints a percent as watts ("owed: a unit word for AMD") | `ember-tune.md:278` | operator-visible | 1 | miner-community-lead | the line reads "70% (an offset)"; unit test | +| Q22 | A code comment naming the founder ships in the UI bundle (`// The list is ordered by performance (the project lead, 6 October 2026)`), and the ui bundle is outside every forbidden-string check | `app/igneum-app/ui/app.js:270`, `tools/ci/identity-check.sh` | cosmetic (identity rule) | 0.5 | app owner | `tools/ci` forbidden-string check covers `app/igneum-app/ui/`; the comment reads "(ruling of 6 October 2026)" | +| Q23 | Two copy-law slips: "Votes lock the chain; leave it on." (aphorism), "The window can close. The miner keeps going" (two-beat) | `index.html:404, 81` | cosmetic | 0.5 | app owner | reworded; the grep below stays at 0 em dashes | + +**Error states, exact strings.** Engine down (four failed polls): pill "engine away" (`app.js:1437`); node line "Node: no answer", sub "the engine is not answering; the window reconnects by itself"; Mac host "The engine stopped. Quit and open Igneum Miner again." (`IgneumMiner.swift:233`); Windows "The engine stopped. Close this window and open Igneum Miner again." (`host.cpp:301`). Node down: "the node could not start" or the engine message, "the node is not running", "the external node went away" (`app.js:452-453`, `engine.rs:3163`); pill "node failed" (`app.js:1428`); digest box "the node is not running" (`app.js:1341`); canvas "waiting for the node to sync" (`app.js:1099`). Finality paused: nothing (Q2). Update server unreachable: "The update failed. Manifest signature: ." (`app.js:1360`, Q11). GPU lost: "Card removed: . Its worker stopped." (`app.js:95`), row word "removed", sub "unplugged; its worker stopped. The row goes in five minutes." (`app.js:303`); Code 43: ": not usable (Code 43). No worker runs on it." with "reboot with the card attached; if it persists, reinstall the driver with the card attached" (`app.js:100`, `detect.rs:55`). + +**Already better than the comparators.** Automatic efficiency tuning against a goal with the money consequence on screen (`app.js:408-419`; 37 to 41 percent more hashes per watt measured on PC 1, `ember-tune.md:254, 271`). Signed, hash-checked, rolled-back updates with a safe-moment rule and machine slots (`ota.rs`, `manifest.rs`). A reason word on every zero rate and hot-plug words (`ember-tune.md` 6a). A virtualised log drawer with time jump and error marks (`app.js:880-1050`). Confirmations in place, no dialogs. A tokenised local API with origin checks (`server.rs:102-125`). Dark-scheme contrast is good: bone 17.4:1, ash 7.0:1, ember 5.6:1, molten 11.0:1 on obsidian (`app.css:19`). Focus rings, `aria-live` strip, labelled switches and sliders, reduced motion honoured on every animation (`app.css:67, 113, 142, 148, 157, 420, 437, 466`; `app.js:1137`). None of the four comparators ship the first two. + +### 3.2 Wallet + +**What it does today.** One desktop wallet (Igneum Wallet, Rust engine plus a local window; `igneum-wt-wallet/app/igneum-wallet/`: `engine.rs`, `vault.rs`, `hd.rs`, `tx.rs`, `finality.rs`, `node.rs`, `updater.rs`, `server.rs`; ui `index.html`, `app.js`, `lock-screen.js`, `update-card.js`), shipped as 0.1.4 (`site/downloads.json` wallet-mac, 19.6 MB; `release-0.3.15.md:90`). 0.1.5 (bundled 0.3.14 node, RPC on localhost) has a DMG and is unpublished (`release-0.3.14.md:82`). UI 3 is commit ab8dfdc on `wallet-ui-3` and `wallet-0.1.5`, not on master. No wallet inside the miner app (it holds the payout key, `wallet.json`, and a button "Open the wallet" to the site, `app/igneum-app/ui/app.js:826`). No web wallet. A MetaMask guide (`site/metamask.html`), an address page (`site/address.html`, noindex, balance plus blocks mined), a marketing page (`site/wallet.html`). Keys: BIP-39 (12 to 24 words, `hd.rs:23`), BIP-44 Ethereum path; vault Argon2id 64 MiB, 3 passes, XChaCha20-Poly1305 (`vault.rs:14-81`); key zeroised on lock (`engine.rs:392`); 24 words shown once, three typed back (`engine.rs:321`); raw key export behind password or Touch ID (`ui/index.html:271-275`). Chain ids 4461, 4462, 4463 (`metamask.html:233`). Signing: EIP-1559 value transfers only; no EIP-712, no `personal_sign`, no contract calls, no dapp provider, no hardware wallet (spec 8.5 item 2 decided, not built; `docs/fud-ledger.md:2058`). + +**Comparators and the exact gap** (approximate unless a standard is named). + +| Comparator | Their feature | Ours | +|---|---|---| +| MetaMask | Account list and HD derivation; address book; speed up and cancel a pending tx; `window.ethereum` provider; `eth_signTypedData_v4` | One account (`engine.rs:343` refuses a second import); an address field only (`ui/index.html:177`); no replace-by-fee; no provider; no EIP-712 | +| Rabby | Pre-sign simulation and recipient risk flags | Review shows amount, fee and total (`app.js:293-295`) | +| Frame | Ledger, Trezor, GridPlus signers; injected provider for any browser | Neither (spec 8.5 "MUST offer", no code) | +| Monero GUI | View-only wallet; subaddresses; a settable node address with status | Node source engine-chosen, no settable endpoint (`wallet-ui-3.md:112`) | +| KDX, Kaspium | Sync bar with peer count; per-transaction DAA score | "reading the chain, N of M blocks" (`app.js:175`), no peer count | +| EIP-1193 | `request` provider | Used on the guide (`metamask.html:294`) | +| EIP-6963 | Multi-provider discovery | Absent; the guide falls back to whichever extension owns `window.ethereum` (`:292`) | +| EIP-3085 | `wallet_addEthereumChain` with `blockExplorerUrls`, `iconUrls` | `blockExplorerUrls: []`, no icon (`:225, 285-286`) | + +**Rows.** + +| Id | Row | File or screen | Severity | Hours | Owner | Gate | +|---|---|---|---|---|---|---| +| Q9 | Page vs UI 3; guide placeholders; empty explorer URL (section 1) | `site/wallet.html`, `site/metamask.html` | the project lead-visible | 6 | app owner, site owner | section 1 | +| Q30 | Private key export copies to the clipboard with no clear and no timer | `igneum-wt-wallet/.../ui/app.js:129, 463` | user-visible | 2 | app owner | clipboard cleared after 60 s by the host; unit test on the timer | +| Q31 | Wallet OTA trusts one key; no `revoked_keys`, unlike the miner's updater | `docs/plans/consequences-2026-10-05.md:36`, `updater.rs:7, 729` | operator-visible | 3 | app owner | updater carries `OTA_PUBLIC_KEYS [K1, K2]` and honours `revoked_keys`; the three-key test of the rig installer | +| Q32 | No hardware wallet path despite spec 8.5 "MUST offer" | `docs/spec/08-client-security.md:36` | the project lead-visible | 24, or 0.5 to relabel | app owner (cryptographer reviews) | a Ledger signs one devnet transfer through the Ethereum app at chain id 4463; or the spec row reads "Designed, not shipped" | +| Q33 | Single account, no second address, no address book | `engine.rs:343`, `ui/index.html:177` | user-visible | 6 | app owner | two derived accounts, a saved recipient reused in Send | +| Q34 | `confirm()` on Remove wallet never renders in the Mac host | `app.js:598`, `wallet-ui-3-audit.md:189` | user-visible | 1 | app owner | in-page two-step card; snapshot test | +| Q35 | Finality pause: node card "paused" or "not checkpoint-checkable without a node", rows stay "in block N"; public-RPC mode never says what to do | wallet `app.js:185, 321, 340` | user-visible | 2 | app owner | the row reads "in block N; finality is paused on the network" and the public-RPC line names the fix with a button | +| Q36 | Reorged-out transaction: "in a block" then the row vanishes; no resend hint | `app.js:340`, `docs/fud-ledger.md:2154-2158` | user-visible | 3 | execution-engineer (node side), app owner (the row) | a transfer unwound in the P23 conformance run shows "reorged out, resending" and ends executed | +| Q37 | The address page shows no finality state and "n/a" with a bare reason for balance | `site/address.html:205-209, 242-252` | cosmetic | 2 | site owner | the page shows the latest locked checkpoint and whether finality is active | +| Q38 | The permanent phishing sentence differs between surfaces ("words or your key" on the wallet, "your key" in the miner) against spec 8.5's one verbatim sentence | `site/wallet.html:366`, wallet `ui/index.html:54`, `app/igneum-app/ui/index.html:97`, `08-client-security.md:39` | cosmetic | 0.5 | site owner, app owner | one sentence, grep-identical on all three | +| Q39 | Vault file permissions not set explicitly (no `0o600` in `vault.rs`) | `vault.rs` | operator-visible | 0.5 | app owner | the file is created 0600; test | + +**Error states.** Node down: pill "no node", balance note "waiting for a node" (`app.js:160, 176`), the node line's raw engine message (`:186`; UI 3 replaces it with "the node stopped answering; trying again", `wallet-ui-3.md:199`). Pending versus final: "PENDING, waiting for a block", "IN A BLOCK, chain block N; final once a verified checkpoint covers it", "FINAL, under checkpoint N, certificate verified by this wallet", "FAILED, the execution failed; the fee was still paid" (`app.js:340`). Address page: "The API did not answer: " (`address.html:270`), "Not found." (`:247`). MetaMask page: "No Ethereum wallet found", "Waiting for the wallet...", "Cancelled in the wallet.", "The wallet refused: " (`metamask.html:292-296`). + +**Security notes.** Plaintext key only in engine memory, zeroised on lock and after each sign (`engine.rs:392, 563`); the window never holds the key (nonce-gated reveal, `server.rs:108`); local API on 127.0.0.1 with a random port, per-run token path and a host token (`server.rs:1, 39, 71`); chain id checked at quote and send (`engine.rs:549`). Testnet RPC is https (`testnet-go.md:13`); devnet is http to 127.0.0.1. Chain ids 4461 to 4463 were absent from chainid.network on 3 October (`docs/design/execution-layer.md:354`), not yet registered (`frontier.md:645`); from memory no listed chain uses them (approximate). + +**Already better.** The wallet verifies finality certificates itself with the node's consensus code (`finality.rs:101-121`) and never trusts the RPC's "locked"; MetaMask and Rabby trust the RPC's block tag (approximate). Touch ID is bound to the exact quote (address, value, nonce, chain id) with a 30 s one-use nonce (`engine.rs:539`). Argon2id at 64 MiB is above MetaMask's PBKDF2 default (approximate). The QR is drawn locally as SVG with the `ethereum:` URI and a checksummed address (`server.rs:140`). The MetaMask page says plainly that nothing on it asks for a seed (`metamask.html:172`). + +### 3.3 Hub and relay + +**What it does today.** One Vercel project (`igneum-relay`, Neon database, Blob store; `relay/README.md:3-5`). `relay/ui.html` (583 lines) is the console at `/r//` (`relay/vercel.json:5-8`); `relay/index.html` is a placeholder. Functions: `relay/api/relay.mjs` (feed, items, files, inbox, register, name, role), `relay/api/console.mjs` (machines, jobs, builds, chain, log, results, tuning), `relay/api/wake.mjs` (public long-poll for the apps' jobs file, 30 a minute per IP, `relay/lib/wake.mjs:15, 42-54`). Auth: the 20-character token in the path or `x-relay-token`, or any of `RELAY_KEY`, `LOG_INTAKE_KEY`, `LOG_INTAKE_KEY_NEXT` in `x-igneum-key` (`relay/lib/relay.mjs:34-46`); only `task`, a `run` or `task` drop, `name`, `role`, `delete` demand the token (`relay/api/relay.mjs:114-115`). Two job models: relay `run` items polled every 20 s by `relay/clients/igneum-agent.ps1:165` and executed as administrator; and signed app jobs in `igneum-jobs.json`, whose status the console derives from `miner_logs` rows (`relay/api/console.mjs:172-188`). Seven tabs (`relay/ui.html:186-192`): Machines, Jobs, Builds, Chain, Work log, Results, Relay; refreshed every 15 s when visible (`:577`), server cache 10 s (`console.mjs:21`). Chain tiles: blocks, identities, hash estimate, difficulty, last lock, finality, peers, blocks per second (`:363-370`). Mac tools: `tools/relay.mjs`, `tools/console.mjs`, `tools/jobs.mjs`. Tests: 24 pass per file (`node --test relay/test` as a bare directory fails under Node 22.23.2 with MODULE_NOT_FOUND; the README's command needs the glob). + +**Comparators and the exact gap** (approximate). + +| Comparator | Their screen | Ours | +|---|---|---| +| Tailscale admin console, Machines | Last seen, OS and version, key expiry with "Disable key expiry", tags; an ACL editor with tests | Last seen and a red card after 180 s (`relay/ui.html:270, 273`); no credential state (one static token, `relay/lib/relay.mjs:36`), no per-machine key, no revoke; roles are labels never checked (`relay/api/relay.mjs:191-195`) | +| GitHub Actions job view | Live streaming log with line anchors, per-step timing, re-run, artefacts, annotations at the top | One text blob in a modal (`relay/ui.html:319`); `duration_s` and the error lines that `tools/jobs.mjs:41-44` already parses are discarded by `console.mjs:184-187`; no re-run; results are separate items | +| Vercel deploy log | Build steps, deployment list, instant rollback, diff between deployments | Builds tab shows the current manifest, the last CI fetch, 40 posts and a folder listing (`relay/ui.html:323-341`); no rollback, no manifest history, no diff | + +**Rows.** + +| Id | Row | File or screen | Severity | Hours | Owner | Gate | +|---|---|---|---|---|---|---| +| Q6 | Stale shown as live; "active" for 20 unlocked indices; "paused" with no since or cause; event flood (section 1) | `relay/ui.html:564, 367-368` | the project lead-visible | 4 | fleet agent, consensus-engineer | section 1 | +| Q8 | X23 to X28 fixes off master; intake key can write and delete; token in URL (section 1) | `relay/api/console.mjs:249`, `relay/lib/relay.mjs:44` | operator-visible | 2 | fleet agent | section 1 | +| Q40 | A hung job reads "running" forever: no elapsed time, no timeout, no "last line N min ago" | `relay/api/console.mjs:182-187`, `relay/ui.html:317` | the project lead-visible | 2 | fleet agent | a killed job reads "no report for 12 min" in red within one refresh | +| Q41 | Jobs tab trusts the unsigned `igneum-jobs.json` while the apps verify the signed file | `relay/api/console.mjs:170` vs `tools/jobs.mjs:57-60` | operator-visible | 1.5 | fleet agent | the tab shows the envelope's signature state per file | +| Q42 | Job result modal: no error lines first, no duration, no anchors (the Actions view) | `relay/ui.html:319`, `console.mjs:184-187` | the project lead-visible | 3 | fleet agent | a failed run opens with its five error lines and its duration first | +| Q43 | Machines: no per-machine key state, no revoke, roles never enforced (the Tailscale view) | `relay/lib/relay.mjs:14`, `relay/api/relay.mjs:191-195` | operator-visible | 3 (after Q8) | fleet agent | a revoked PC's register is 403 and its card says "key revoked" | +| Q44 | Builds: no rollback, no manifest history (the Vercel view) | `relay/ui.html:328-341` | operator-visible | 4 | fleet agent | one tap republishes the previous manifest and the fleet's OTA state shows it | +| Q45 | "live feed unreachable: live feed unreachable" doubled; the whole Chain tab empties, losing Events and the infra card | `relay/api/console.mjs:210`, `relay/ui.html:357` | cosmetic | 0.5 | fleet agent | live feed down: tiles grey, Events and infra card still render | +| Q46 | Replay: `register` and `task` have no nonce; a captured `task` POST re-queues a run | `relay/api/relay.mjs:153-167` | operator-visible (security) | 1 | fleet agent | a replayed `task` is refused; test | + +**Error states.** Relay API unreachable: header "offline: Failed to fetch" and a grey dot (`relay/ui.html:564, :32`); the panel is replaced only while it still shows "loading". Node down: a machine card goes red after 180 s with "silent 4h" (`:270, 273`; `SILENT_S`, `console.mjs:35`); a deliberate stop reads "stopped (quit) 2h ago" (`relay/lib/parse.mjs:134-140`); the live API down reads "live feed unreachable: " (`:357`); the observer stale reads "observer STALE N s" in red after 30 s (`:361`, `site/api/live.mjs:8`) while every tile keeps its last value. Finality paused: `tile('finality', f.active ? 'active' : 'paused', ...)` in amber `#FFB35C` (`:368, :17, :65`); the "last lock" tile amber too (`:367`); eight checkpoint rows with state and fraction (`:374`). Job hang: "running" until a `SUMMARY` with `finished_at` arrives (`console.mjs:179-182`); relay `run` items show "read" and never "done" (`tools/relay.mjs:58`). + +**Security, X23 to X28 on this tree.** X23: the tier split exists (`relay/api/relay.mjs:114-115`), no run signature, no per-machine HMAC, the intake key still writes the console (`console.mjs:249`). X24: every client builds `/r/$TOKEN/api` (`agent.sh:10`, `send.sh:11`, `igneum-agent.ps1:13`, `tools/relay.mjs:24`, `tools/console.mjs:27`). X25: `Arm-Restart` at top level and `/RL HIGHEST` (`igneum-agent.ps1:154, 72`). X26: feed to 500 with no retention; `delete` leaves blobs (`relay.mjs:48, 148-152`); `__DL_BASE__` substituted into bodies (`tools/relay.mjs:79`). X27: `from` is free text, `register` for any hostname (`relay.mjs:30, 153-167`). X28: constant-time compare and HSTS present (`relay/lib/auth.mjs:5-11`, `relay/vercel.json:38-41`); still a GET that acks (`relay.mjs:85`), `RELAY-REBOOT` anywhere in output (`igneum-agent.ps1:133`), no rate limit, `NOPASSWD:ALL` (`wsl-setup.ps1:40`), `sudo -S` with the password (`prover-setup.ps1:21`). All of this is what Q8 merges. + +**Already better.** One phone-first page, no login flow, seven tabs, 15 s refresh (`relay/ui.html:5, 577`); Tailscale, Actions and Vercel each take several screens on a phone (approximate). The machine card fuses miner STATUS, node log, app telemetry and OTA state and remembers an OTA line after it scrolls out (`console.mjs:61-84`); no comparator shows workload telemetry. Stale marking at 180 s drops the card from the total; stopped and silent are distinct words (`parse.mjs:16-21, 134-140`). Drop-anywhere upload direct to Blob with a progress bar and a 50 MB cap (`ui.html:506-534`). The wake long-poll is dependency-free and tested with a fake clock (`relay/lib/wake.mjs`). Headers: nosniff, frame DENY, noindex, no-referrer, no-store, HSTS on every path (`relay/vercel.json:21-43`). + +### 3.4 Site + +**What it does today.** Thirteen static pages plus 404 (`site/index.html`, `litepaper.html`, `live.html`, `bench.html` 611 KB rendered from `docs/bench-log.md`, `evidence.html`, `ledger.html` 312 KB generated, `miner.html`, `miners.html` from `miner-bench.json`, `wallet.html`, `metamask.html`, `faucet.html`, `explorer.html`, `block.html`, `address.html`). `site/build.mjs:49-50, 357-372` injects `partials/head.html` (self-hosted fonts, tokens), `nav.html`, `footer.html`, stamps download links from `downloads.json`, inlines `journey.json`. `scrub.mjs` runs on bench and miners only (`build.mjs:171, 401`). Live data: index polls `/api/live` every 2 s (`index.html:704`; 10 s while off); the light client fetches `/api/checkpoint` and imports two noble libraries from cdn.jsdelivr (`verify/verify.js:5-6`); `live.html` polls every 2 s (`:665-666`); faucet POSTs `/api/faucet`. APIs (`site/api/`): `live`, `stats`, `supply`, `explorer`, `checkpoint`, `faucet`, `log`, all reading Neon tables written by `tools/observer`. Edge cache: live 1 s, stats and supply 10 s, explorer 5 s (`vercel.json`). The live HTML (one curl, 20:02Z) is 60,009 bytes, served with HSTS (preload), `X-Frame-Options: DENY`, nosniff, `Referrer-Policy: strict-origin-when-cross-origin`, no CSP; download buttons stamped "v0.3.14 · 45.4 MB"; "Public testnet: not yet open; the devnet build is here for people who want to look." + +**Comparators and the exact gap** (approximate unless a page is named). + +| Comparator | Their page or element | Ours | +|---|---|---| +| kaspa.org, ethereum.org | A Developers or Docs nav item | Nav is Litepaper, Live devnet, Engineering log, Miner, Wallet, Evidence, GitHub (`partials/nav.html:14-21`); builders land on a litepaper section (`index.html:335`) | +| getmonero.org/downloads | Every platform, hashes and signing keys on one page | Split across `index.html:363-365`, `/miner#get`, `/wallet`; the homepage's Linux and HiveOS button links `/miner#get`, not a file; sha256 shown only for the HiveOS package (`miner.html:558`), none beside the Windows and Mac buttons, no signing key, no verify line | +| z.cash, aztec.network | Named people, a transparency page | "one founder, pseudonymous" once in the litepaper; the Reddit review's key-powers table (round 4, section 3 row 8) absent | +| kaspa.org, getmonero.org | Media kit, press kit, language switch, newsletter | None (`site/` has no `/press` or `/brand`) | +| ethereum.org, kaspa.org | Light and dark | Dark only; only the litepaper honours `prefers-color-scheme` | +| succinct.xyz, aztec.network | 1200x630 share cards | Home, litepaper, live, explorer use 256 px `og-small.png` with `summary` (`index.html:15-19`); miner, wallet, bench, evidence use the large card | + +**Lighthouse, estimated from source (approximate; no run).** Common: `lang="en"`, a skip link (`partials/nav.html:1`), one `:focus-visible` ring (`head.html:22`), self-hosted fonts with `font-display:swap`, two preloads and fallback metrics (`head.html:1-19`), no render-blocking third-party CSS, canonical, description, OG and twitter tags on every page. + +| Page | Performance | Accessibility | Best practices | SEO | What holds it down | +|---|---|---|---|---|---| +| index.html | 78 to 85 | 88 | 95 | 92 | 89 KB HTML, 35.0 KB inline JS, 24.0 KB CSS; two rAF canvas loops at display rate with no visibility or intersection stop (`:790, 853, 857`); 2,880 px webp with no srcset; h3 before the first h2 (`:344, 359`); ember on bone 3.08:1 at 15 px in the light section (`:119-120`), eyebrow `#7A776F` on bone 3.97:1 at 12 px (`:130`); 256 px OG card | +| explorer.html | 88 to 93 | 90 | 100 | 85 | 32 KB, no images, no third party; `aria-live="polite"` on an 8-tile strip refreshed every 10 s (`:222`); not in the sitemap | +| live.html | 85 to 90 | 88 | 100 | 85 | 64 KB, 35.6 KB JS; the canvas loop is capped at 30 fps and stops off screen (`:605-612`), good; `aria-live="polite"` on the 8-cell strip every 2 s (`:225`); canvas `aria-hidden` with no text alternative (`:241`) | + +**Rows.** + +| Id | Row | File or screen | Severity | Hours | Owner | Gate | +|---|---|---|---|---|---|---| +| Q5 | "final" during a pause; 90-word hero; 24 GB contradiction; thumbnail OG (section 1) | `index.html:345, 494, 810, 838, 15-19` | the project lead-visible | 3 | site owner | section 1 | +| Q50 | No downloads page: hashes only for HiveOS, no signing key, no verify line, the Linux button goes to an anchor | `index.html:363-365, 516`, `miner.html:549-558` | user-visible | 2 | site owner | `/download` lists every artefact with version, size, sha256, the OTA key fingerprint and a verify command per OS; the Linux button links the file | +| Q51 | `/miners` has 6 rows, two card models, "not measured" MH/W on every row; the eleven-card fleet table is not ingested | `miners.html:183`, `miner-bench.json` | user-visible | 2 | miner-community-lead | rows carry W and MH/W for every measured card | +| Q52 | `dl\.igneum` in `forbidden-strings.txt` matches the public download host on three built pages; the scrub guards only bench and miners | `forbidden-strings.txt:25`, `build.mjs:171, 401` | operator-visible | 0.5 | site owner | the pattern becomes `dl\.igneum\.network/dl/(?!public/)` or `/dl/[0-9a-f]{12,}`, and the check runs on every built page | +| Q53 | Sitemap misses `/miner`, `/explorer`, `/faucet`, `/ledger`; every lastmod is 4 or 5 Oct; 404 says "These four pages are everything the site has" | `sitemap.xml`, `404.html:138` | cosmetic | 0.5 | site owner | the sitemap is generated by the build from the page list | +| Q54 | `aria-live="polite"` on strips refreshed every 2 s and 10 s (a screen reader hears eight values every refresh) | `live.html:225`, `explorer.html:222` | user-visible | 0.5 | site owner | `aria-live="off"` on the strips plus one polite status line on state change | +| Q55 | Homepage canvases never stop off screen or cap their rate | `index.html:790, 853, 857` | user-visible | 1 | site owner | 30 fps cap and IntersectionObserver as `live.html:605-612` | +| Q56 | Light-section contrast fails (3.08:1 links, 3.97:1 eyebrow) | `index.html:119-120, 130` | cosmetic | 0.5 | site owner | AA on every token pair; the same node script as Q12 | +| Q57 | "devnet v0" eyebrow on the live page; the chain is v4 | `live.html:219` | cosmetic | 0.1 | site owner | matches the app's network word | +| Q58 | Heading order (h3 before the first h2) | `index.html:359` | cosmetic | 0.2 | site owner | h2 or a styled div | +| Q59 | No team, custody or key-powers page (Reddit round 4 artefact 8); "0 admin keys in consensus" tile unchanged | `index.html:611`; no file | user-visible | 1 (plus the project lead's policy call) | site owner | the key-powers table published; the tile links it | +| Q60 | Roadmap dates versus the testnet: the journey says "Public testnet, Aug to Oct 2027" and the litepaper "Pools and the public testnet are August 2027", while `docs/plans/testnet-go.md` has `igneum-testnet-1` seeds up and a go checklist dated 5 October 2026 | `index.html:679`, `litepaper.html:719`, `docs/plans/testnet-go.md:1-4` | the project lead-visible | 0.5 (after the project lead decides which is true) | site owner | one date for the public testnet on the journey, the litepaper and the download notice | +| Q61 | No Content-Security-Policy header on the site (the relay has none either); inline scripts throughout, two pinned CDN modules on the homepage | `site/vercel.json:7`, `relay/vercel.json:31-43`, `verify/verify.js:5-6` | operator-visible (security) | 2 | site owner | a CSP with hashes or nonces for the inline scripts and `script-src` limited to self and cdn.jsdelivr; every page renders with no console violation | +| Q62 | Copy-law borderline headlines: "Mined by GPUs. Proven by fire." (the brand line, the project lead's call), "GPUs are back · for good", "Last hour's chip is already obsolete.", "Your coins. Final means final.", "Dates slip. Gates do not.", "Install. Start. The card mines and proves.", "Nothing is mined here." | `index.html:343, 344, 424`, `wallet.html` h1, `litepaper.html:678`, `miner.html` h1, `404.html` h1 | cosmetic | 1 | site owner (the project lead rules on the brand line) | each either kept by the project lead's word or reworded | + +**Reddit round 4, still open in the HTML.** Finding 6 (24 GB): half fixed (Q5). Finding 7 (eleven-card table): open (Q51). Finding 8 (admin keys): sentence added to the litepaper (`:659`), tile unchanged, no table (Q59). Finding 15 ("Monero's idea, finished for GPUs"): open (`index.html:444`, `litepaper.html:429`). Finding 16 ("devnet v0"): open (Q57). Finding 22: `/ledger` present, no nav item. Findings 2, 3, 5, 9, 10, 12, 13, 17, 19: fixed in the current files. "0% anyone else in the protocol" legend still live (`index.html:604`). + +**Error states, exact strings.** API unreachable: `/live` after three failed polls shows "OFFLINE", "api unreachable", the canvas frozen with "api unreachable, scene frozen" (`live.html:665, 600`); `/explorer` writes the fetch error into the table on first load and is silent after (`:342-345`); `/block` "The API did not answer: " (`:328`); the homepage "simulated preview · live feed unavailable" and "Live feed unavailable. This is a simulation and its counters count simulated blocks." (`index.html:870-872`). Observer stale over 30 s: "OFFLINE", "observer updated N min ago", "observer offline, last update N min ago" (`live.html:663`); explorer eyebrow "observer offline, last known" (`:318`); homepage "simulated preview · observer offline, last update N s ago" (`:708`). Finality paused: section 2. + +**Already better.** Self-hosted latin subsets with preloads and computed fallback metrics (`head.html:1-19`); ethereum.org and kaspa.org load heavier bundles (approximate). Zero third-party scripts except the two pinned noble modules; no analytics, no cookie banner. Honest live states: "simulated preview · observer offline", "finality paused: N% of weight silent", "api unreachable, scene frozen"; Etherscan shows nothing when its indexer lags (approximate). A browser-side BLS certificate verifier on the homepage (`verify/core.js`), which no comparator homepage has. A public API with a CI field contract (`api/stats.mjs:10-14`) and supply derived from the emission rule with an hourly coinbase check. HSTS with preload, frame DENY, nosniff, immutable font caching, skip link, reduced-motion rule on every page. A dated journey with pass or fail gates inlined at build, and a ledger page of every criticism; none of the six site comparators publish the equivalent. + +### 3.5 Explorer + +**What it does today.** `explorer.html` polls `/api/explorer?blocks=50` every 5 s and `/api/stats` plus `/api/supply` every 10 s (`:342-343`); `block.html` fetches `?block=` or `?height=` once (`:276`); `address.html` fetches `?address=` once; `/block/:id` and `/address/:addr` are Vercel rewrites. Search classification is client-side (`lib/explorer.mjs:19-27`: hash, 0x address, bech32, height) with a server fallback for tx hashes (`api/explorer.mjs:49-59`). The data window is 24 hours of observer rows; balance needs `EXPLORER_EVM_RPC`, unset on the devnet deployment (`docs/plans/explorer.md` section 7). + +**Comparators and the exact gap** (approximate). + +| Comparator | Their feature | Ours | +|---|---|---| +| Etherscan, Blockscout | A transaction page: decoded input, logs, status, fee | None; a tx hash lands on `/block/#tx-` (`api/explorer.mjs:57`); `block.html:310` lists hash, from, to, value, bytes, with from and to "needs an EVM RPC" when unset | +| Etherscan, Blockscout | Address page with transactions, token transfers, nonce, contract tab | Blocks mined, 24-hour earnings, balance only with an RPC (`address.html:209, 281`) | +| Blockscout | Hosted API reference (Swagger), rate-limit statement | A card on `/explorer` (`explorer.html:232-240`) and `docs/api/public-stats.md` in the repo | +| explorer.kaspa.org | DAG view inside the explorer; merge-set order per block | DAG view lives on `/live`; block page has blue work, parents, children, mergeset blues and reds (`block.html:285-325`), no prev and next navigation | +| mempool.space | Mempool as the product, fee charts | Mempool count is one tile on `/live` (`live.html:233`); no charts on the explorer | +| All four | Per-block status (final, confirmations) | None for an ordinary block (Q10) | + +**Rows.** + +| Id | Row | File or screen | Severity | Hours | Owner | Gate | +|---|---|---|---|---|---|---| +| Q10 | No finality state; failures swallowed (named under the ten in section 1) | `api/explorer.mjs:16-25`, `explorer.html:222, 309, 342-345` | user-visible | 3 | site owner | during a forced pause every block row carries "paused" and the strip tile reads the Q1 reason; cut the API: the table dims with "api unreachable, last update N s ago" within 10 s | +| Q63 | No tx page, no hosted API reference | `api/explorer.mjs:57`, `explorer.html:232-240` | user-visible | 4 | site owner | `/tx/` with status, fee, from, to, input bytes, shard and proof state; `/api` page with every field and the cache times | +| Q64 | Address page: no transaction list, no finality (Q37 covers the finality half) | `address.html` | user-visible | 3 | site owner | the last 50 transfers in and out, each with its block and state word | +| Q65 | No prev and next block navigation; no block-by-height neighbours | `block.html` | cosmetic | 1 | site owner | two links from `selected_parent` and the first child | +| Q66 | Blocks per second inverted against the other pages ("1.2 s per block" vs "0.9 blocks / s") and difficulty "126.81 M" with a space against "126.81M" on `/live` | `explorer.html:330-331`, `live.html:329` | cosmetic | 0.5 | site owner | section 4.3's one formatter | + +**Already better.** Block page depth for a DAG: parents, children, mergeset blues and reds, selected parent, shards with prover and lag, checkpoint weight fractions and certificates (`block.html:285-325`); explorer.kaspa.org shows less of the mergeset (approximate). Client-side search that accepts a height, a hash, an 0x or bech32 address with no round trip (`lib/explorer.mjs:19-27`). Five-second edge cache with a stale flag from the observer, so a stale page says so on first load. + +### 3.6 Pool + +**What it does today.** `igneum-pool`, a Rust crate (`pool/Cargo.toml`, 12 source files) speaking the spec 09 protocol in its v0 form: newline JSON over plain TCP (`pool/src/server.rs:1-2`, `protocol.rs:1-9`), the project's own message set (`hello`, `welcome`, `authorize`, `seeds`, `template`, `job`, `share`, `share_result`, `solution`, `stats`; `protocol.rs:21-170`). Every share is CPU-verified on the node's warp verifier (`verify.rs:52-64`; 1.35 ms isolated, 2.1 ms under load, `docs/plans/pool.md` section 5). PPLNS only, window in blocks of weight, default 2 (`pplns.rs:1-5`, `config.rs:67`); fee 1 percent, minimum payout 1 IGN (`config.rs:65-66`); EIP-1559 transfers from the pool's coinbase key, at most 16 per round, failed receipts re-credited (`payout.rs:197-299`). One page, `pool/web/index.html`, baked into the binary (`api.rs:166-174`): a stat strip, connect card, address lookup, blocks, payments, API card. Stats API `/api/stats`, `/api/blocks`, `/api/payments`, `/api/miners/`, `/api/pool-stats`, `/health` (`api.rs:185-200`) with fixture-pinned field contracts (`api.rs:255-272`). Operator surface: a `STATUS` line every 30 s on stdout (`main.rs:152-174`). Deployment: a written, unexecuted systemd unit behind Caddy (`pool/README.md:75-116`; "not executed", `docs/plans/pool.md` section 8). Nothing is deployed. The horizon tree equals `igneum-wt-pool-rebase` HEAD 269364a. + +**Comparators and the exact gap.** + +| Comparator | Their feature | Ours | +|---|---|---| +| 2miners | Per-worker hash rate charts over time; payout history per miner; worker-offline alerts by email or Telegram; estimated earnings per day; luck | A single 10-minute and 1-hour number, samples kept 15 minutes and not persisted (`state.rs:704-705, 779-781`); the API returns 50 payments and the page renders none of them (`api.rs:148`, `web/index.html:181-194`); no contact field (`protocol.rs:50-60`); no earnings estimate; luck defined inverted against MiningPoolStats' convention (`api.rs:72-74`) | +| WoolyPooly | Solo mode; worker offline alerts | `scheme` hard-coded `"pplns"` (`server.rs:53`, `api.rs:80`); no alerts | +| Kaspa acc-pool (approximate) | Prometheus and Grafana | No `/metrics`; stdout only | +| Stratum v2 reference (github.com/stratum-mining/stratum, not cloned, approximate) | Noise-encrypted transport, job negotiation, binary framing, header-only mining | Plain TCP while spec 9.3 demands TLS 1.3 and `binding` is sent empty (`server.rs:1-2`, `protocol.rs:58-60`, `09-pool-protocol.md:37, 43, 49`); mode A only; JSON lines, and the `template` ships the whole `RpcRawBlock` to every member every second (`node.rs:147-164`) | + +**Rows.** + +| Id | Row | File or screen | Severity | Hours | Owner | Gate | +|---|---|---|---|---|---|---| +| Q67 | Member transport is plaintext TCP against spec 9.3's TLS 1.3 with session binding; `binding` ignored | `server.rs:1-2, 115`, `protocol.rs:58-60` | user-visible (hijack risk) | 8 | consensus-engineer | rustls listener; a replayed `authorize` on a second connection is refused (O-9.7) | +| Q68 | Node down shows "difficulty 0, DAA 0" and a ramp-day-0 reward; no "node unreachable" state | `node.rs:350-368`, `web/index.html:163-164` | the project lead-visible (once deployed) | 2 | miner-community-lead | the page renders "Node unreachable since