Merge horizon 9de8645: the economy-and-utility lane

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
igneum-labs 2026-10-06 20:10:22 +00:00
commit 7a4640e2e9
17 changed files with 2475 additions and 0 deletions

View file

@ -0,0 +1,356 @@
# Horizon lane 4: economy and utility. What IGN is for beyond gas, the 80/20 under stress, ten years without a treasury, the dev fee, miner signalling, and what the other chains got wrong
6 October 2026, evening UK. Lane 4 of the Horizon programme. Worktree `/Users/joshm/Projects/igneum-wt-horizon` (branch `horizon`). Every model is in `sim/horizon/economy-and-utility/` with a README line per script; every dollar figure names its inputs and its label. Nothing here is a price prediction, an offer to sell anything, or a change to any consensus parameter.
**Read:** `docs/spec/05-fees-and-economics.md` (whole, 5.10 and 5.11 included), `02-consensus.md` 2.5, `07-execution.md` (7.2 to 7.8); `docs/design/payment-routes.md`, `developer-adoption.md` (2a to 2c), `execution-layer.md` (4.3 to 6), `miner-dev-fee.md`; `docs/plans/funding.md` 1 to 5; `docs/analysis/security-budget.md`, `economy-2026-10-04.md`, `base-fee-floor.md`, `prover-floor.md`, `prover-tiers-real-cards.md`; `sim/economy/` (README, sim.py, security_budget.py, results.md, levers.md); `docs/commercial/prover-customer-brief.md`; `docs/fud-ledger.md` E1 to E8 (E9 to E18 are cross-referenced from the spec and the status updates, the file carries E1 to E8 as sections), P6, P8, P9, P10, P14, P22, D1 to D6, C9, C10, G8, L6, L7; `site/litepaper.html` Building, Economics, Governance; `docs/bench-log.md` lines 1226 (the dev fee measured), 1910 (proving v1), 2349 (aggregation cost), 2582 (rental cost of hash); `docs/plans/counter-asic-3-node.md` section 6 (the P2 rule) and `counter-asic-3-status.md` P2; `docs/analysis/horizon/frontier.md` section 0 (the ranking) and 3.1, 3.2, 3.5, 3.6, 3.11 to 3.13; lane 3's `sim/horizon/consensus-security/cost_results.md` and `signalling_results.md`; `vendor/rusty-kaspa` (main checkout) `consensus/core/src/config/params.rs` and `consensus/src/processes/coinbase.rs`. `block-rate-devnet2.md` was still a template (RUN_A, RUN_B empty) at 21:50 UK; nothing here depends on it.
---
## 1. Method
| Question | What was run | Where |
|---|---|---|
| Task 1, utility beyond gas | Arithmetic: the measured eleven-card table turned into a cost per v1 shard and per billion cycles (alone, beside the miner, rented), against the published prices; five demand curves on a low/base/high grid at three IGN prices | `utility.py`, output `utility_out.md` |
| Task 2, the 80/20 and the burn under stress | `sim/economy/sim.py` copied and changed in six named places (the measured cards, the renter farm, the 25-s window and 120-s timeout, a FIFO backlog with the 600-s record window, burn per day, the new scenarios), 8 scenarios x 3 seeds, 30 days, plus two sensitivity runs; under the main checkout's run lock at nice 19 on the M5 Max, about 18 s a run | `stress.py`, outputs `results/stress_main.md`, `stress_busy.md`, `stress_elecfarm.md` |
| Task 3, ten years without a treasury | Arithmetic: `sim/economy/security_budget.py` extended with the lane's fee grid, the pool line, the sustained hash at the measured card economics and the rented 34% weight attack at USD 281 per GH/s-day | `security_budget_10y.py` |
| Task 4, the dev fee | Arithmetic from `miner-dev-fee.md` and the bench-log measurement | `devfee.py` |
| Task 5, signalling | Arithmetic on the three thresholds; lane 3's `signalling.py` covers the 95-percent rule and is cross-referenced, not re-run | `signal_game.py` |
| Task 6, the other chains | Reading: `vendor/rusty-kaspa` for Kaspa; everything else named and marked approximate where no clone exists | this file, section 5.6 |
No node harness was started and no measurement was taken: the fleet, the PCs and the devnet were on the class v4 rehearsal and the Devnet 2 block-rate runs. Every hardware number is the fleet's from 6 October (`prover-tiers-real-cards.md`) or the bench-log's.
---
## 2. Evidence
### 2.1 Measured inputs
| Input | Value | Source |
|---|---|---|
| v1 shard | 4,717,439 cycles; the adopted `S_p` is 30,000 pgas = 30 M cycles, so the fixture is 16% of a full shard | `prover-tiers-real-cards.md`; spec 5.11 |
| Shard alone, compressed, patched server 2^26 | 3060 14.4 s, 3080 7.1, 3090 14.9, 4060 Ti 16 GB 11.6, 4060 Ti 8 GB 9.6, 4060 18.4, 4070 12.1, 4090 6.3, 5070 4.8, 5090 6.3, A5000 8.3 | `prover-tiers-real-cards.md` table |
| Shard beside the running miner (compressed) | 3060 37.5 s, 3080 25.6, 3090 19.9, 4060 Ti 16 GB 34.6, 4070 27.3, 4090 26.1, 5070 37.2, 5090 10.7, A5000 34.6; the 8 GB cards core-only 22.1 to 26.3 | same |
| Hash and watts mining (rented boxes) | 3060 23.78 MH/s at 103.7 W ... 5090 98.48 at 258.2 (the table) | same; the 4060's 0.0 W reading replaced by its 115 W rating, approximate |
| The miner's loss while its card proves | 5090: 124.72 to 119.74 MH/s, 4.0%, on empty shards; 8 GB cards 17.1 to 16.0 MH/s, about 6%, on v1 shards | bench-log, proving v1 step 1; `prover-tiers-real-cards.md` 8 GB row |
| Watts mining and proving at once | 5090: 328.6 W max against 258 W mining (about 70 W more) | bench-log proving v1 step 1; the fleet row |
| Rental price of hash | USD 0.0117 per MH/s-hour (1,748 MH/s for USD 20.44 an hour, 38 pods); USD 11.7 per GH/s-hour, USD 281 per GH/s-day; the market gave 0 of 20 pods asked at the TH/s scale | bench-log line 2582 |
| Aggregation per block on a mining 5090 | 9.6 to 9.7 s (2.1 s with the card to itself) | bench-log line 2349 |
| Dev fee on a test network | 9 fee blocks in 785 (1.15%; the template rule is exact at 1 in 100) | bench-log line 1226 |
| Proof record sizes | 274 bytes per shard record, 586 per segment record, 1,272,897 bytes per compressed proof | spec 7.7, 7.8; bench-log proving v1 |
### 2.2 Published prices (all approximate or secondary; none cloned)
| Supplier | USD per billion cycles | Label |
|---|---|---|
| Boundless (RISC Zero), Base | about 0.21 median lock price, trailing day 4 Oct 2026 | `developer-adoption.md` 2b, secondary summary; approximate |
| Succinct Prover Network | 0.046 base plus up to 0.46 per billion PGU in the quickstart's EXAMPLE request at USD 0.23 per PROVE | `developer-adoption.md` 2b; example parameters, not a market price |
| RISC Zero Bonsai | never published a per-cycle list price; paid proving moved to Boundless in 2025 | not cloned, approximate |
| Ethereum L1 block at the ethproofs cluster cost, Sep 2026 | sub-half-cent a block; at 0.2 to 1.3 B cycles a block (14 to 44 SP1 cycles per gas, measured on Igneum) about 0.004 to 0.025 | `frontier.md` 2.6 (secondary); `base-fee-floor.md` |
| A Taiko-class rollup per batch | taiko-mono not cloned; Taiko Alethia proves batches through its own prover market with SGX and ZK tiers (SP1 and RISC0 accepted); the ZK proof's cost per batch is of the order of the ethproofs figure times the batch's cycles: cents to tens of cents | approximate |
### 2.3 What the earlier models said that this lane re-tests
| Claim | Source | What changed tonight |
|---|---|---|
| Hybrid loses the whole hash for the proof's duration plus a 5-s swap | `sim/economy/sim.py` TPROVE + 2 x swap | Measured: the miner loses 4 to 6% while the card proves; the lottery wins the card's arbitration and the proof is 3 to 4x slower instead |
| A 3060 proves a shard in 20 s (target) | ledger P1 | Measured: 14.4 s alone, 37.5 s beside the miner, on the 4.7 M-cycle fixture; a full 30 M-cycle shard is unmeasured on it (linear scaling would say 92 s alone, approximate) |
| 10-s window, 300-s claim timeout | spec 7.2 as designed | 25 s and 120 s decided 6 Oct 2026 (ledger P9) |
| The farm pays electricity at USD 0.05 | `sim/economy/sim.py` | The farm is a renter at the measured USD 0.0117 per MH/s-hour |
---
## 3. Model
### 3.1 The supply side of proving
For a card with hash `h` (MH/s), network hash `N` (MH/s), shard time `t` (s), watts `w` and price `P` (USD per IGN):
```
cost_alone = w t / 3.6e6 x 0.10 electricity
+ (h / N) x 0.8 x 31.688 x t x P the subsidy the card forgoes while it proves
cost_beside = 70 t / 3.6e6 x 0.10 + (h / N) x 0.8 x 31.688 x t x 0.04 x P (4% measured on the 5090, approximate elsewhere)
cost_rented = h x 0.0117 / 3600 x t the renter's cost; no subsidy, no electricity
per billion cycles: x 1e9 / 4,717,439
```
### 3.2 Demand curves at the adopted floors (spec 5.11; design 6 for jobs)
```
transfer = 21,000 x 100 gwei + 300 x 10,000 gwei = 0.0051 IGN, burned; tip 21,000 x 1 gwei, 80% miners+provers, 20% burned (no registered frame)
batch post 100 KB = (21,000 + 16 x 100,000) x 100 gwei + 300 x 10,000 gwei = 0.1651 IGN, burned
job of C cycles = C / 1000 x 10,000 gwei x 1.5 = 15 IGN per billion cycles; 90% provers, 10% burned once IGN-settled
payments cap = B_p / 300 = 400 transfers a block = 34.6 M a day; EIP-1559 target half of that, 17.3 M a day
records = 274 x shards + 586 / 8 bytes a block in the coinbase; 1,272,897 bytes per proof on p2p
```
### 3.3 Sustainability
```
sustained hash (GH/s) = miners' USD per day / (electricity + capital per GH/s-day)
electricity = 258.2 W / 98.48 MH/s x 24 / 1000 x USD 0.10 = USD 6.29 per GH/s-day (measured card, the brief's price)
capital = USD 2,000 / 98.48 MH/s / 1,095.75 days = USD 18.53 per GH/s-day (approximate)
total USD 24.8 per GH/s-day; the rental price is USD 281, 11.3x
34% weight attack = rent 1.04 N for 20 days (lane 3's rule, spec 3 headline) = 1.04 x N x 281 x 20; the attacker earns 51% of the producer subsidy meanwhile
```
### 3.4 The stress simulator
`sim/economy/sim.py` with: eleven card classes (`HASH`, `PMINE`, `TPROVE` alone, `TBESIDE`, `CANHYB` from the measured table; `MIX` an approximate installed-base shape), hybrid capacity `cards x T / TBESIDE` and hybrid hash `1 - 0.04 x duty`, operator 0 a renter (`cost = cards x MH/s x 0.0117 x hours`), window 25 s, timeout 120 s, a FIFO of open shards with a 600-s expiry (expired credit stranded), burn per day = 10% of IGN-settled external jobs plus `blocks x content_shards x 0.51 IGN` (paid content 0.03 shards a block at launch traffic), one proving shard a block (measured on the devnet). The thresholds T1 to T5 are the 4 October definitions (hash under 50% of the pre-event mean for an hour; backlog over 600 s; a growing backlog; a day under 90% within 60 s; a 10-point proving-share swing).
---
## 4. Results, task by task
### 4.1 Task 1: what IGN is for beyond gas
#### (a) Proving as a sellable service
| Card | Alone, 1 GH/s | Alone, 100 GH/s | Alone, 1 TH/s | Beside its miner, 100 GH/s | Rented (no subsidy) | Electricity only |
|---|---|---|---|---|---|---|
| 3060 12 GB | 36.81 | 0.377 | 0.046 | 0.054 | 0.236 | 0.0088 |
| 4070 12 GB | 32.50 | 0.331 | 0.039 | 0.041 | 0.208 | 0.0065 |
| 4060 Ti 16 GB | 21.92 | 0.224 | 0.027 | 0.040 | 0.140 | 0.0049 |
| 4090 24 GB | 35.38 | 0.361 | 0.042 | 0.069 | 0.227 | 0.0068 |
| 5090 32 GB | 66.69 | 0.676 | 0.076 | 0.050 | 0.427 | 0.0096 |
| Boundless median (approximate) | 0.21 | 0.21 | 0.21 | 0.21 | 0.21 | |
| ethproofs L1 cluster (approximate) | 0.004 to 0.025 | | | | | |
USD per billion cycles at USD 0.02 per IGN (`utility_out.md` 1.3; the IGN price moves only the opportunity term).
What it says. Electricity is under a cent per billion cycles on every card; "marginal cost close to power" (ledger C10's wording) is true of the electricity and false of the price, because the price a prover must charge is the subsidy it forgoes, and that scales as 1 / network hash. At today's devnet scale (1.16 GH/s) a prover that stops mining to prove must charge 100 to 300x Boundless's median. At 100 GH/s a card proving alone is at 1 to 3x Boundless; a hybrid card beside its miner is at 0.2 to 0.4x (USD 0.04 to 0.08), which is the only row where Igneum undercuts the market, and it rests on the 4% figure measured on one card. The renter's row, USD 0.13 to 0.43, is the floor below which no rented prover ever sells. The floor-priced job (15 IGN per billion cycles) is USD 0.075, 0.30 and 1.50 at the three prices: a third of Boundless at 0.005, 1.4x at 0.02, 7x at 0.10. The floor is denominated in IGN and the market in dollars, and the floor moves by a two-week 60% vote (spec 5.11): it cannot follow a price. Frontier 3.11 (rank 15) already shows the market is three to four orders under year-1 emission; this lane adds that at the adopted floor Igneum overprices the market at any IGN price above about USD 0.014.
| Tier | Consequence |
|---|---|
| Home 8 GB | proves alone only (compressed does not fit beside the miner); its price is the alone row: competitive only above about 300 GH/s of network hash |
| Home 12 GB | mines and proves on headless Linux (37.5 s beside on the 3060, 27.3 s on the 4070); competitive beside its miner at 100 GH/s; a full 30 M-cycle shard beside the miner is unmeasured (about 3 min by linear scaling, approximate, outside the 120-s claim timeout) |
| Home 16 GB | the cheapest beside-row (USD 0.040 per billion at 100 GH/s) |
| Home 24 or 32 GB | the 5090 is the cheapest prover per billion beside its miner above 100 GH/s and the dearest alone (its subsidy is the largest) |
| Rig | eight 4090s beside their miners: USD 0.07 per billion at 100 GH/s; 8 x 26.1 s per shard, so a rig delivers a 30 M-cycle shard in about 21 s with all eight on one shard (approximate; SP1 proves one shard per server) |
| Pool user | nothing: the pool's provers carry the proofs |
| Prover | its quote is a function of network hash it does not control; publish the price as `h/N x subsidy x t`, never as a number |
| Holder | job demand buys IGN only after the proof bridge (phase two); at launch customers pay on their own chain, so (a) is zero IGN demand at launch |
| Rollup customer | the customer brief should carry the band above and the condition (network hash) rather than any price |
#### (b) Rollup settlement, (c) bridges, (d) payments, (e) storage
Dollars per day to miners and provers (`utility_out.md` 3.3) and burn (3.4), base scenario, USD 0.02 per IGN:
| Period | External jobs to provers, USD (own chain) | Rollups settling here, IGN to provers | Bridges, IGN to provers | Payment tips, IGN | IGN flows in USD | Burn, IGN | Burn, % of daily emission |
|---|---|---|---|---|---|---|---|
| launch | 450 | 19,440 (1 rollup) | 3,038 (1 bridge) | 1.68 (100 k transfers) | 450 | 5,748 | 0.21% |
| year 2 | 1,800 | 58,320 (3) | 9,112 (3) | 16.8 (1 M) | 1,349 | 23,316 | 0.85% |
| year 5 | 9,000 | 194,400 (10) | 15,188 (5) | 168 (10 M) | 4,195 | 126,716 | 4.6% |
| year 5 high | 90,000 | 972,000 (50) | 30,375 (10) | 290 (17.3 M, the target) | 20,058 | 711,481 | 26% |
Low scenarios are a tenth to a fifth of these; the full grid at the three prices is in `utility_out.md`. Reading each curve:
- **Rollups** are the only line that pays provers in IGN at scale: one rollup posting a batch a minute with a 1 B-cycle proof job pays 19,440 IGN a day at the floor, 3.6% of the daily pool. Ten of them in year 5 pay 194,400 IGN a day, 36% of the pool before the second halving and 142% of the pool after it. The condition is the floor price staying under the market's (above). The burn it causes: 10% of the job plus the batch's base fee, 2,398 IGN a day per rollup.
- **Bridges** at 225 updates a day pay 3,038 IGN a day each; a tenth of a rollup. No bridge is official (spec 7.3), so the count is anyone's.
- **Payments** cost USD 0.000026 to 0.00051 a transfer (the three prices). A transfer undercuts a 1 bps rail on any payment above USD 0.26 to 5.10 and a 10 bps rail above USD 0.03 to 0.51; a USD 100 payment pays 0.003 to 0.05 bps. The fee is flat in IGN, so payments give the coin burn and almost no income: 10 M transfers a day burn 51,042 IGN (1.9% of emission) and tip 168 IGN at the 1 gwei default. The proving dimension caps the chain at 34.6 M transfers a day and the fee leaves the floor above 17.3 M; above that the burn is set by willingness to pay and no model here knows it, so the year-5 high row is clamped at the target and says so.
- **Storage.** Records are 347 bytes a block at one shard (1,025 at 3.5): 11 to 32 GB a year, USD 0.17 to 0.50 of disk per node per year (approximate HDD price), paid by whoever runs a node and by nobody else. Proof bytes (1.27 MB each) never enter a block; a node keeps the 600-block pool (about 3 GB at four shards a block) and a light client one proof. An archive of every proof would be 80 to 180 TB a year (USD 1,200 to 2,700 of HDD, approximate): a service someone sells, not a protocol cost. Frontier 3.16 (rank 9) is the research-dataset version of the same bytes.
The honest total. In the base scenario all five uses together put USD 450 a day to miners and provers at launch and USD 4,200 in year 5 at 0.02, against USD 54,800 of daily emission in year 1 and 13,700 in year 5. Fees are 1.6% of security spend in year 1 and 48% in year 5 (`security_budget_10y_out.md`, base at 0.02), and most of the year-5 share is the external USD line, which is in dollars and does not move with the coin. Burn is 0.2% to 4.6% of daily emission in base scenarios. The Economics section's "part of every payment on Igneum is burned" is true and small: with the ramp's 37 M never minted, burn under 1% of emission a day leaves the cap's approach unchanged to the second decimal for years.
### 4.2 Task 2: the 80/20 split and the burn under stress
`results/stress_main.md`: 8 scenarios x 3 seeds, 30 days, the eleven measured cards, the farm (20% of hash, 925 to 1,016 5090s) a renter at USD 0.0117 per MH/s-hour, window 25 s, timeout 120 s, one proving shard a block, USD 0.012 at t = 0. The model's network is about 700 GH/s of potential hash (15,000 cards), so every number below is at that scale; the renter's rent against the subsidy is the N_eq of lane 3 (`cost_results.md` section 3): 94 GH/s at USD 0.012.
| Metric | a: baseline | p10: price x10 day 7 | pd10: price /10 day 7 | c: no external | x100: external x100 | cartel: top 10% of weight never proves | refuse: nobody proves days 10 to 20 | halving: 15.844 IGN a block |
|---|---|---|---|---|---|---|---|---|
| Hash min / pre-event (seed min) | 0.99 | 0.99 | 0.55 (0.49) | 0.99 | 0.91 | 0.99 | 0.99 | 0.98 |
| Hash day 30 / pre-event | 1.00 | 1.25 | 0.74 | 1.00 | 0.97 | 1.01 | 0.99 | 1.00 |
| T1 hours under 50% | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| Cards off, day 30 | 9% | 0% | 31% | 9% | 8% | 0% | 9% | 9% |
| Cards proving / hybrid, day 30 | 6% / 58% | 6% / 66% | 14% / 44% | 6% / 48% | 10% / 55% | 6% / 57% | 6% / 76% | 4% / 58% |
| Backlog max, shards; T2 age max, s | 0; 0 | 0; 0 | 0; 0 | 0; 0 | 0; 0 | 0; 0 | 766; 565 (capped by the 600-s expiry) | 0; 0 |
| Blocks within 60 s, mean / T4 worst day | 1.00 / 1.00 | 1.00 / 1.00 | 1.00 / 1.00 | 1.00 / 1.00 | 1.00 / 1.00 | 1.00 / 1.00 | 0.67 / 0.00 | 1.00 / 1.00 |
| T5 proving-share swing, points | 1.6 | 0.1 | 2.0 | 0.6 | 2.9 | 2.2 | 0.0 | 4.4 |
| Burn, IGN a day (of which external) | 19,928 (18,606) | 6,758 (5,437) | 146,737 (145,415) | 1,322 (0) | 1,576,947 (1,575,623) | 16,954 (15,632) | 13,366 (12,044) | 17,247 (15,926) |
| Burn, USD a day at the run's mean price | 214 | 578 | 506 | 15 | 15,535 | 218 | 148 | 224 |
| Share of the 20% pool paid / stranded | 1.00 / 0 | 1.00 / 0 | 1.00 / 0 | 1.00 / 0 | 1.00 / 0 | 1.00 / 0 | 0.67 / 0.33 (182,387 IGN a day averaged; 547,570 a day during the refusal) | 1.00 / 0 |
| Renter farm cards on at day 30; margin over rent | 0; -77% | 984; +114% | 0; -77% | 0; -76% | 54 (0 to 162); -6% | 984 (forced on); -79% | 0; -77% | 0; -88% |
| Flags tripped (of 3 seeds) | none | none | none (T1 min 0.49 in one seed, under an hour) | none | none | none | T4 3/3 | none |
Per card class, USD per card-day (every mode, off included) and the share of shards proven, baseline: 3060 1.30 (0%), 3080 3.09 (5%), 3090 2.69 (11%), 4060 Ti 16 GB 1.63 (4%), 4060 Ti 8 GB 2.03 (16%), 4060 0.85 (1%), 4070 1.96 (12%), 4090 3.86 (11%), 5070 3.36 (12%), 5090 3.95 (24%), A5000 3.30 (4%). The full tables are in `results/stress_main.md`.
What holds and what breaks:
1. **The 80/20 holds under every price and demand shock; the price is what moves hash.** T1 never trips. The /10 price shock is the closest: hash troughs at 55% of pre-event (49% in one seed for under an hour), 31% of cards go off (the 3060 and 4060 classes at median electricity and above), and the chain is at the T1 line with no backlog and every block proven inside 60 s. A x10 shock brings the renter farm on (margin +114%) and hash ends 25% up: rented hash arrives exactly when the subsidy per GH/s-day clears USD 281, which is lane 3's N_eq. The first halving at a flat price changes nothing at this price level (the same 9% off), because the cards that remain are above break-even at 0.013; a halving is a /2 price shock and the /10 row says where /2 would land between the two.
2. **External demand x100 is the burn story and the renter story.** USD 200,000 a day of jobs at 10% burn is 1.58 M IGN a day burned, 58% of daily emission, at the run's price of about USD 0.01; and it brings 0 to 162 rented 5090s on at a -6% margin. Hash troughs at 91%: cards leave the lottery for jobs (the 4 October finding, scenario b), and the renter's cards arriving for jobs do not hash. The burn in that row is a transfer from customers to holders of 15,500 dollars a day; it is the only row where burn is material, and it needs IGN settlement, which is phase two.
3. **A cartel of the top 10% of weight that never proves costs the chain nothing.** In this population the top 10% of weight is one operator, the 20% farm, so the row is a 20% cartel: with 8 draws by weight the chance that every assignee is the cartel's is 0.2^8, under three in a million, so almost every shard still finds an assignee and the rest go open after 25 s; every block is proven inside 60 s, the backlog is zero, and the cartel loses USD 6.77 per card-day (forced on, as a renter, to hold its weight). Sortition with 8 draws is why: the 4 October result (scenario e, 30%) stands with the measured cards.
4. **A refusal by every prover is the one scenario that breaks a threshold, and what breaks is the pool, not the chain.** For ten days no block is proven (T4 0 on every refusal day), execution and finality do not wait (spec 5.3, ledger P9), and the backlog never passes 600 s because the record window expires the shards: 547,570 IGN a day of pool credit is stranded in the escrow, 5.5 M IGN over the ten days, and no rule returns it. When provers come back the queue is at most 600 s deep and clears in minutes; 76% of cards end in hybrid. The refusal's whole cost is the refusers' own income plus a silent supply reduction nobody voted for (proposal 2).
5. **The window excludes the slow hybrids.** At 25 s a 3060 beside its miner (37.5 s on the small fixture), a 4060 Ti 16 GB (34.6), a 5070 (37.2) and an A5000 (34.6) cannot land an assignment and win only open races or prove alone; the 3060 class proves 0% of shards in every scenario, the 4060 1%. The 4060 Ti 8 GB proves 16% by proving alone at 9.6 s. The 4 October proposal (window = the fleet's 90th-percentile shard time plus a swap) would set it near 38 s on this fixture; on a full 30 M-cycle shard the number is unmeasured (proposal 8).
6. **Burn at launch traffic is USD 15 to 220 a day**, 0.05% to 0.7% of emission; the base fee part is 1,322 IGN a day at 0.03 content shards a block. Everything above that is the external 10%, which does not exist until jobs settle in IGN.
Sensitivities (`results/stress_busy.md`, `stress_elecfarm.md`, 2 seeds each). At 3 proving shards a block (the 4 October busy value, 3x the load) nothing changes in a, cartel or x100: backlog 0, every block inside 60 s, hash min 0.95 to 1.00; the refusal strands the same 33% of the pool with a queue of 2,265 shards at its deepest, and the renter farm comes on in x100 at 213 cards (181 to 244). With the farm on electricity at USD 0.05 instead of rent (the 4 October assumption) the baseline has 0% of cards off (the farm's 968 cards stay on) and the /10 shock takes 25% of cards off with hash troughing at 63% of pre-event and ending at 77%: the rent is what decides whether the 20% farm is on at all, and with it 20 points of hash at every price; the `renter farm margin` column of that file is undefined at zero rent and should be read as blank.
| Tier | Consequence of the stress runs |
|---|---|
| Home 8 GB | proves alone and wins open races (16% of shards on the 4060 Ti 8 GB); the first class off in a /10 shock on expensive power |
| Home 12 GB | the 3060 proves 0% at the 25-s window; the 4070 12% (27.3 s beside, loses the window, wins open races alone); first off in a price fall |
| Home 16 GB | 4% of shards; stays on in every row but pd10 |
| Home 24 or 32 GB | 11 to 24% of shards; hybrid is the dominant mode (58% of all cards); the last class off |
| Rig | as the 24 GB card per card; a rented rig is off below N_eq and on above it (p10 row) |
| Pool user | the pool's provers' share; unchanged by any row |
| Prover | income is 20% of emission in every row but refuse, where the refusers strand it; the x100 row is the one where jobs pay more than the pool |
| Holder | burn is 0.05 to 0.7% of emission a day at launch; 58% in the x100 row, phase two only |
| Rollup customer | every job delivered in every row but refuse (67%) |
| Node operator | the backlog never passes the 600-s record window because the window expires it |
### 4.3 Task 3: no-treasury sustainability over ten years
`security_budget_10y_out.md`. The brief's formula gives a sustained hash proportional to the miners' dollars and an attack cost proportional to that hash, so the ratio is a constant: a 20-day 34% weight attack rents 1.04 N at USD 281 per GH/s-day against an honest fleet that costs USD 24.8 per GH/s-day, 11.8x the honest fleet's 20-day cost, minus the 51% of subsidy the attacker earns back. The subsidy never falls under the attack cost in ratio terms; the halvings shrink both until the absolute number is small. The honest statement is the absolute net cost by year:
| Year | Net cost of the 20-day veto, USD, at 0.005 | at 0.02 | at 0.10 | Sustained hash at 0.02, GH/s | Fees as % of total security spend, base at 0.02 |
|---|---|---|---|---|---|
| 1 | 2,375,000 | 9,501,000 | 47,506,000 | 1,699 | 1.6% |
| 3 | 1,233,000 | 4,933,000 | 24,666,000 | 882 | 18.6% |
| 5 | 617,000 | 2,467,000 | 12,334,000 | 441 | 48.3% |
| 7 | 308,000 | 1,234,000 | 6,168,000 | 221 | 65.1% |
| 9 | 154,000 | 617,000 | 3,085,000 | 110 | 78.9% |
The veto's net cost drops under USD 1 M in year 5 at 0.005, year 9 at 0.02, and not within ten years at 0.10; under USD 100 k only after year 10 at 0.005. The rental market's supply, not its price, is the other bound (0 of 20 pods at the TH/s scale on 6 October), and it is not modelled. What the fees change: in the base scenario the proving-pool and job lines reach provers, not miners, and the brief's security line is miners' hash; of the 48% fee share in year 5, under 1% reaches miners (tips at 1 gwei). The chain's security budget after year 5 is the subsidy to miners, and nothing else in the design pays for hash. Frontier 3.1 (rank 7) redirects part of the subsidy to the pool during rental spikes and would lower the miners' line further; this lane's number for it is in 5.1.
The audits. `funding.md` prices the first cryptanalysis at USD 80,000 to 160,000 and nothing prices the second, which a class or era change in year 3 would need. What the entity's own lines earn (every price an input):
| Year | Price | Dev fee, 50% of hash on Ember | Entity's provers at 5% of the pool | Second cryptanalysis (USD 160 k) as % of the dev fee |
|---|---|---|---|---|
| 3 | 0.005 | 10,000 | 25,000 | 1,600% |
| 3 | 0.02 | 40,000 | 100,000 | 400% |
| 3 | 0.10 | 200,000 | 500,000 | 80% |
The dev-fee row here uses 1% of the producer share (80% of emission), because the fee template moves only the `IGNA` payout and the pool is paid per record; `funding.md` section 4 took 1% of all rewards and overstates the ceiling by a quarter (48,000, 193,000 and 963,000 should read 38,520, 154,080 and 770,400). The honest options for the second audit, ranked:
| Rank | Option | What it pays in year 3 at 0.02 | Why this rank |
|---|---|---|---|
| 1 | The entity's own provers (5% of the pool and a share of jobs) | USD 100,000 a year at 5% of the pool, more with jobs | Open-market income the design already names (spec 5.5); scales with the chain, no rule, no switch; the cost is running cards |
| 2 | The Ember dev fee | USD 40,000 a year at 50% of hash on Ember | Exists and is measured; falls with every halving and with every miner who flips the switch; alone it funds a review every four years at 0.02 |
| 3 | A user-paid review market: customers (rollups) co-fund the audit that protects their settlement, as a condition of their integration | unknown; a Taiko-class customer's whole annual proving spend is of the order of USD 100 k (frontier 3.11) | Honest and voluntary; the customer has the motive; it depends on having a customer |
| 4 | Founders' mined coins (the litepaper's own answer for grants) | depends on hash share | Visible addresses; finite; the ledger's E2 and E8 live here |
| 5 | A burn-funded bounty or review escrow | the base-fee burn at launch traffic is 51 to 20,578 IGN a day: USD 1 to 412 at 0.02 | Frontier 3.6 (rank 11, "watch") and its Monero attack: a burn redirect is a payee by rule, which is the switch spec 5.5 removed. The protocol cannot have it because it has no treasury, and that is the contradiction stated plainly: the no-treasury rule means the SECOND audit is paid by whoever earns in the open or it is not paid, and `funding.md` should say so in a row of its own |
### 4.4 Task 4: the dev fee
What it is (`miner-dev-fee.md`): one block template in 100, chosen by an exact counter (templates 99, 199, ...), is requested with the project's payout address in the coinbase extra data; the vote key and the UTXO address stay the user's, so a fee block still votes for the user and only the execution-layer payout moves. Default on; `--dev-fee 0`, the app's Settings switch or HiveOS `DEV_FEE=0` turns it off; the start line prints the state; `igneum-miner payouts` tags the dev address on the chain. Measured: 9 fee blocks in 785 on a test network, the miners' counters and both nodes agreeing (bench-log line 1226).
What it pays (`devfee_out.md`): 1% of the producer share of emission times the share of hash on Ember with the fee on.
| Year | Price | 20% keep it on | 50% | 100% | Home 4070 at 100 GH/s network, a month | Rig 8x 4090, a month |
|---|---|---|---|---|---|---|
| 1 | 0.005 | 7,704 | 19,260 | 38,520 | | |
| 1 | 0.02 | 30,816 | 77,040 | 154,080 | USD 3.33 (6.6 blocks) | USD 55.74 (110 blocks) |
| 1 | 0.10 | 154,080 | 385,200 | 770,400 | | |
| 5 | 0.02 | 8,000 | 20,000 | 40,000 | | |
What share would turn it off. Precedent (approximate, from memory): T-Rex 1%, lolMiner 0.7 to 1.5%, PhoenixMiner 0.65%, TeamRedMiner 0.75 to 2.5% and NBMiner 1 to 2% were not switchable, and together they held the large majority of Ethereum's GPU hash over the fee-free ethminer because they were faster; NiceHash is a marketplace that takes about 2% of the buyer's payment, not a dev fee; nobody measured an opt-out share because none offered one. Igneum's switch is one flag and the miner is open source, so the rational solo miner with any time at all turns it off; the pool operator decides for its members; the one-click app user keeps the default. A working estimate for planning: 20 to 50% of hash keeps it on, which is the devfee table's first two columns and USD 7,700 to 77,000 a year in year 1 at 0.005 to 0.02. This is a planning input, not a measurement, and the first month of the public testnet measures it from the chain (`payouts`).
Is "optional" honest? Ledger E18's charge is "a protocol fee with better PR". Three facts answer it. It is not in the protocol: the chain pays whatever `IGNA` address the template names, and a block with the dev address is indistinguishable in consensus from a block paying any other address. It is switchable in one flag and the chain shows who paid (`payouts`). It is default-on, and defaults are what most users run, so "optional" describes the mechanism and "default-on, switchable" describes the behaviour. The public line should be the second: "1 block in 100 pays the project unless you turn it off". Two things to add to E18: the ceiling correction above, and the fact that the fee buys the project a visible address holding 1% of mined coins, which is the E5 critic's point restated as a holder consequence.
| Tier | Consequence |
|---|---|
| Home 8 to 32 GB on Ember | 1% of blocks unless switched off; USD 2.34 to 13.13 a month at 0.02 and 100 GH/s network |
| Rig | the same per card; a rig operator on HiveOS sets `DEV_FEE=0` once |
| Pool user | the pool's choice: a pool on its own template software pays 0%; a pool on Ember pays 1% of its templates and passes it on or not |
| Prover | untouched: the pool share is paid per record, never through a template |
| Holder | one address accumulates up to 1% of producer emission; the project's incentive to keep Ember the fastest client is the fee |
| Rollup customer | nothing |
### 4.5 Task 5: miner-signalled parameters
What genesis leaves to miners (spec 5.5, 5.9, 5.11): the base-fee floors `f_e` and `f_p`, the proving budget `B_p` (and with it `S_p`), set by a proposal at 60% of blue blocks over 1,209,600 DAA s (two weeks), the BIP 9 model. Upgrades (new code) need 90% (spec 5.7, window open, O-5.3). The P2 rule for a PoW class change needs 95% of blue blocks over a one-day window ending at each epoch's seed block, monotone, with a floor height as the backstop (`counter-asic-3-node.md` section 6: `CLASS_SIGNAL_THRESHOLD_BPS` 9,500, window 86,400 DAA, the fast-time gate green on three cases and its failed case). The documents disagree about the number: spec 5.7, CLAUDE.md's design paragraph and the litepaper's Governance section say 90% for upgrades; the P2 design and the Horizon preamble say 95% for class changes; the litepaper's Mining section says "a 90% miner signal turns one on". One sentence should carry all three (60 parameter, 90 upgrade, 95 class with a floor) or the three should become two.
The game (`signal_game_out.md`; lane 3's `signalling_results.md` for the 95% rule):
| Rule | Who can block | A 30% pool | Renter's cost to force at 100 GH/s | What ends a block |
|---|---|---|---|---|
| 60% over 14 days | over 40% of blue blocks | cannot block alone; needs 11 more points | USD 590,000 (1.5 N for 14 days) | the proposal fails; re-register |
| 90% over 14 days | over 10% | blocks it | USD 3.5 M (9 N) | the proposal fails; re-register |
| 95% over 1 day, floor | over 5% | blocks it | USD 534,000 (19 N for a day) | the floor height |
A 6% holdout costs USD 18 a day at 1 GH/s and USD 1,800 at 100 GH/s on top of the subsidy it earns like anyone, so near zero (lane 3 section 2); it buys delay to the floor and nothing else. A 30% pool holds a permanent veto over upgrades at 90% and over class changes until the floor at 95%; the devnet's top three vote keys held 34.5% of blocks on 4 October (litepaper, Governance). Signal then defect is bounded by what is signalled: a PoW class defector loses its own blocks (its PoW fails, `check_header_version` then the PoW check); a consensus-rule defector forks itself and whoever trusts it; an execution-parameter defector produces VALID blocks with a different state (blocks carry no state claim, design 1.1), which is a silent state fork for that node unless the parameter is in the consensus digest that the handshake refuses (G12, X18): `Params.fees` is in the digest (spec 5.11), so today it is isolated rather than split, and any future miner-signalled execution parameter must enter the digest the same day or the defector is a quiet fork.
What Bitcoin and Kaspa did. BIP 9: version bits, a 95% threshold of 2,016-block retarget periods, states DEFINED, STARTED, LOCKED_IN, ACTIVE, FAILED, a timeout; BIP 8 added a lock-in-on-timeout flag so a flag day ends a holdout (bips repository, bip-0009.mediawiki and bip-0008.mediawiki; not cloned, approximate). Kaspa's Crescendo (1 to 10 BPS) was a fixed DAA score, not a signal: `crescendo_activation: ForkActivation::new(110_165_000)` for mainnet and `88_657_000` for testnet, with `ForkActivation::is_active(daa)` as `current_daa_score >= self.0` (`vendor/rusty-kaspa/consensus/core/src/config/params.rs` lines 28 to 60, 648, 704, main checkout), and the coinbase keeps the activation score for ever to compute the subsidy month across it (`consensus/src/processes/coinbase.rs` lines 40 to 43, 238 to 253); `docs/crescendo-guide.md` tells miners to upgrade before the activation. The P2 rule is BIP 8 in shape: a signal path plus a flag day. Igneum's 6 October incident (DAA 198,000 crossed by a half-updated fleet) is the flag-day hazard, and P2's floor keeps it.
What SHOULD be miner-signalled and is not, with the risk of each:
| Parameter | Today | Should be | Risk if signalled | Risk if not |
|---|---|---|---|---|
| The block rate step (1 to 4 to 10 BPS) | a planned fork with its own test campaign, "as Kaspa's Crescendo" (spec 2.1) | a 90% upgrade signal with a floor, like P2: it is a consensus change crossed by a whole fleet | a 10% pool vetoes the step; a renter forces it a day early for USD 5.3 M at 1 TH/s | a fixed height on a half-updated fleet: the 229-block reorg of 6 October at mainnet scale |
| The dataset growth step | automatic, genesis schedule (spec 1, 2 GiB doubling at years 4, 12, 28) | NOT signalled, by design: it is an anti-ASIC escalator and a chip-holding cartel would vote growth down. Allow a 60% signal to ACCELERATE only (monotone), never to delay | a 40% holdout blocks acceleration: no worse than today | none: the schedule runs |
| The 80/20 lottery/proving split | fixed (spec 2.5) | a 60% parameter inside a hard band [10%, 30%] | 80% of the voters are the lottery; without the band they vote the pool to 0 and the provers go; with the band the worst case is 10% | the simulator says 20% is not load-bearing at launch traffic and 30% helps at 100 shards a block (economy-2026-10-04 5.3); fixed means a 90% upgrade to move it |
| The base-fee floors and `B_p` | 60% over 14 days (spec 5.11) | a bounded per-block dial, Ethereum's gas-limit mechanism (frontier 3.5, rank 8): the dollar market moves faster than two weeks (4.1) | a 51% majority walks the dial to the bound in days; the bound and a cost curve are the defence | the job price is pinned in IGN while the market is in dollars; at 0.10 the floor is 7x Boundless and a two-week vote cannot follow it |
| The job premium 1.5 and the external claim timeout 120 s (O-5.6) | design 6 constants | the same bounded dial | as above | a constant calibrated once on the phase 4 devnet |
| The exclusive window 25 s | a consensus constant (P9) | a function of the fleet's measured shard-time distribution, published per era (economy-2026-10-04 proposal 1; frontier I3) | none: it reads a measurement | a 12 GB fleet whose shard time drifts past the window loses every assignment to the open race (the 4 October finding at 10 s) |
### 4.6 Task 6: what Kaspa, Monero, Ethereum and the zk rollups did and got wrong
| Area | Chain | What it did | Where | What went wrong, or what it costs | Igneum's rule | Avoids or repeats |
|---|---|---|---|---|---|---|
| Emission | Kaspa | A pre-deflationary phase at 500 KAS a block (`pre_deflationary_phase_base_subsidy: 50000000000`, `deflationary_phase_daa_score: 15778800 - 259200`), then the chromatic schedule: 426 monthly steps, each month's subsidy the previous times 2^(-1/12), from 440 KAS a block (`SUBSIDY_BY_MONTH_TABLE[0] = 44000000000`), halving every twelve months smoothly | `vendor/rusty-kaspa/consensus/core/src/config/params.rs` 631 to 638, 687 to 694; `consensus/src/processes/coinbase.rs` 22 to 25, 222 to 253, 280 | Steep and smooth: no halving-day cliff, but the subsidy fell 50% a year and the chain leaned on price appreciation it could not promise; the table is divided by BPS at Crescendo so the per-second rate is unchanged | 1 B a year halving every two years, in DAA seconds; a 30-day ramp; no tail (spec 2.5, 5.10) | Avoids the yearly rate (slower), repeats the cliff (a step, not a glide); E6 concedes it |
| Emission | Monero | A tail emission of 0.6 XMR a block for ever after the main curve | monero repository `src/cryptonote_basic/cryptonote_basic_impl.cpp`, `get_block_reward` (not cloned, approximate) | Security paid for ever at about 0.9% a year inflation falling toward zero; the cost is a soft supply cap critics name | No tail; a review trigger that puts a tail to a 90% vote if proving revenue is under a fifth of the subsidy after year 5 (spec 5.10.3) | Repeats Bitcoin's bet, keeps Monero's door ajar by vote |
| Emission and burn | Ethereum | EIP-1559: the base fee burned, the tip to the proposer; issuance by stake since the Merge, about 0.5 to 1% a year gross, net near zero when burn is high | ethereum/EIPs `EIPS/eip-1559.md`; ethereum/execution-specs `src/ethereum/london/fork.py` (`calculate_base_fee_per_gas`); not cloned, approximate | The burn removes the proposer's incentive to stuff blocks, at the cost that usage pays security nothing; proposers' income moved to tips and MEV | Both base fees burned, tip 80/20 to miners-provers and apps; the same trade-off, stated (security-budget.md section 5) | Repeats on purpose (E3 is the reason); the EIP-1559 step is copied (`next_base_fee`, denominator 8) |
| Fee market | Ethereum | A base fee that cannot fall below 7 wei in practice and has no floor; the gas limit voted per block by proposers within 1/1,024 | execution-specs `fork.py`; geth `core/block_validator.go` VerifyGaslimit; approximate | A near-zero base fee when idle makes spam cheap; the gas-limit vote is the one continuous miner dial that worked for a decade | A floor per dimension (spec 5.11) calibrated for spam; `B_p` and the floors by a two-week 60% vote | Avoids the idle-spam gap; does not take the per-block dial (frontier 3.5 asks for it) |
| Proving market | Aleo | Proof-of-succinct-work: provers compete on proofs for coinbase rewards; the fastest prover (GPUs, then FPGAs and ASICs) took the reward share | AleoNet/snarkOS and snarkVM (not cloned, approximate; CLAUDE.md "the Aleo lesson", ledger C9) | The proving reward centralised to the fastest hardware; small provers earned nothing | The lottery and the proving are separate; shards by sortition on 30-day weight, 8 assignees, 25 s, then open (spec 7.2) | Avoids the race for assigned shards; the open race after the window is where fast cards win beyond their weight (economy-2026-10-04 3.1 item 5) |
| Proving market | Boundless (RISC Zero) | A reverse auction per request; provers post ZKC collateral; PoVW pays ZKC per cycle proven | docs.boundless.network/zkc/mining/overview and provers/performance-optimization (read, not cloned) | A token gate on supply and a stake that scales with work; the median price USD 0.21 per billion (approximate) | No bond for shards; a coin bond only on external jobs (O-5.6); frontier 3.2 (rank 2) replaces even that with work-stake | Avoids the token gate for internal proving; repeats a bond for jobs |
| Proving market | Succinct | A real-time auction settled in PROVE; provers stake PROVE to bid | docs.succinct.xyz/docs/provers (read, not cloned; ledger C10) | The same gate; example prices, no public market price | As above; prices in dollars settled in the token (spec 5.4) | Avoids the gate; repeats "settled in our token" once IGN settlement starts |
| Governance | Monero | Scheduled hard forks (six-monthly, now 9 to 12 monthly), decided by the core team and the community off-chain | getmonero.org and the monero repository's release history (approximate) | Works because the community trusts a small team; the schedule itself is a central clock | No scheduled human releases; automatic escalators at genesis; 90% (or 95%) miner signalling for anything else (spec 5.7) | Avoids the clock; the price is that pools hold the vote (G8) |
| Governance | Kaspa | KIPs discussed off-chain, activated at fixed DAA scores; Crescendo at 110,165,000 after a testnet campaign | `params.rs` 648; `docs/crescendo-guide.md` | A flag day; a node not upgraded forks off; it worked because the community upgraded in time | P2: a signal plus a floor height; Devnet 2 as the staging chain for every cut (CLAUDE.md 6 Oct rules) | Avoids the bare flag day, keeps it as the floor |
| Governance | Ethereum | All Core Devs calls decide; clients ship; activation by timestamp; no on-chain vote | ethereum/pm repository (approximate) | Works by rough consensus among client teams; a single client bug is a chain-wide event (the 2016 Shanghai attacks, the 2020 Geth split, approximate) | One client today; a second independent client is the first priority after launch (litepaper, Governance) | Repeats the single-client risk until the second client exists |
| Rollups | Taiko and the zk rollups | Pay their own prover networks per batch; based sequencing; multi-proof tiers | taiko-mono (approximate) | Proving cost is a line item that falls 3 to 30x a year (frontier 2.6); settlement and proving are bought from two suppliers | Settlement and proving from the same miners in one flow (litepaper, Building) | New; the price condition is 4.1 (a) |
---
## 5. Ranked proposals
| Rank | Proposal | Evidence | Model | Hours | Consequence per tier | Gate |
|---|---|---|---|---|---|---|
| 1 | Decouple the job price from `f_p`: a job's reserve is the measured proving electricity per pgas (USD 4.4e-9 at 0.15 per kWh, base-fee-floor.md 3) converted at a published settlement rate, and the requester bids above it; the 1.5 premium becomes a bid, not a floor | At the adopted floor a billion-cycle job is 15 IGN = USD 0.075 / 0.30 / 1.50 at the three prices against Boundless's 0.21 (4.1 a); a two-week 60% vote cannot follow a dollar market | `utility.py` section 2 | 16: the reserve rule in `Prover.request` (6), the rate oracle as the review-trigger's published reading (spec 5.10.3 already defines it) (4), spec 5.4 and design 6 text (6) | Prover: sells at the market, not at a vote; Rollup customer: a quote it can compare; Holder: job demand for IGN survives a price rise; Miner: nothing; Pool user: nothing | A simulated job book at the three prices clears within 20% of Boundless's median at every price |
| 2 | Define the stranded pool: an unproven shard's credit rolls forward into the next proven segment's pool instead of sitting in the escrow for ever | The spec is silent on credit nobody claims; a 10-day refusal strands 5.5 M IGN (4.2); the devnet already burns the coinbase 20% output (litepaper, Economics) | `stress.py` refuse scenario, `stranded_share` | 8: the roll-forward in `split_pool_credit` (4), spec 5.3 and 7.8 item 7 text (2), a unit test with a 10-segment gap (2) | Prover: a refusal costs the refusers and pays the returners; Holder: no silent burn; Miner: nothing | On the fast-time harness, 100 unproven segments then 10 proven: the escrow returns to zero within the 10 |
| 3 | Publish the prover's price as a formula, never a number: `price per billion = (h / N) x 0.8 x 31.688 x t x P x 212 / cycles`, with N the live network hash | The same card is 100 to 300x Boundless at 1 GH/s and 0.2 to 0.4x at 100 GH/s (4.1 a); the customer brief says "priced in dollars" with no condition | `utility.py` 1.3 | 3: a paragraph in the customer brief and the litepaper's Proving section, with the table | Rollup customer: no promise it cannot hold the project to; Prover: knows when to sell; everyone else: nothing | The brief and the litepaper carry the condition before any customer conversation |
| 4 | Make the 80/20 split a 60% parameter inside a hard band [10%, 30%], and record the three signalling numbers (60 parameter, 90 upgrade, 95 class with floor) in one sentence in spec 5.7, CLAUDE.md and the litepaper | The split is not load-bearing at launch traffic and 30% buys backlog relief at 100 shards a block (economy-2026-10-04 5.3); the documents carry two upgrade thresholds (4.5) | `stress.py` `--set pool=` | 10: the band in `Params` (4), the proposal kind (3), text (3) | Prover: a floor of 10% of emission by rule; Miner: a vote on its own share, bounded; Holder: nothing | The fast-time harness: a 60% vote moves the pool to 30%; a 100% vote cannot pass 30% or go under 10% |
| 5 | Every miner-signalled execution parameter enters the consensus digest the same release, with a CI check that fails a `Params` field marked signalled and absent from the digest | A 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 one is (4.5) | `signal_game.py` section 3 | 6: the check in `tools/ci` (4), a test (2) | Node operator: a defector is isolated, never quietly wrong; everyone else: nothing | The check fails on a planted field and passes on the live set |
| 6 | The block-rate steps become P2-shaped activations (signal plus floor), not fixed heights | Crescendo was a fixed DAA score (`params.rs` 648); the 6 October incident was a fixed height; spec 2.1 still says "a planned fork" (4.5) | lane 3 `cost_results.md` forced-flip row | 4: spec 2.1 text and a line in the Devnet 2 gate | Miner and rig: no flag day crossed while updating; Pool user: nothing | Spec text; the first step's rehearsal on Devnet 2 passes the same gate as the class v4 cut |
| 7 | Correct `funding.md` section 4's dev-fee ceiling (1% of the producer share, not of all rewards) and add a row that names who pays the SECOND cryptanalysis | 48,000 / 193,000 / 963,000 overstate by a quarter (4.4); no row prices a second review (4.3) | `devfee.py`, `security_budget_10y.py` section 3 | 1 | Holder and critic: a number that matches the mechanism | The file's git history |
| 8 | Measure the two numbers every price here rests on: the miner's hash loss while each card proves (4% is one card), and a full 30 M-cycle shard beside the miner on the 12 GB and 16 GB tiers | The hybrid row is the only one that undercuts the market and it rests on one measurement (4.1 a); the 4.7 M fixture is 16% of `S_p` (2.1) | `utility.py` `hybrid_hash_loss` | 6 on the fleet: eleven boxes, two fixtures, `tools/fleet/lib` | Home 12 and 16 GB: whether they are provers at all beside their miner; Rig: the same per card | Eleven rows with both numbers in `prover-tiers-real-cards.md` |
**1. Decouple the job price from `f_p`.** `f_p`'s floor exists to price spam above the electricity it imposes (base-fee-floor.md section 3: 230x the electricity at USD 0.10 per IGN). Design 6 then prices every external job at `maxPgas x f_p x 1.5`, so the same floor that is 230x electricity for spam is the job market's minimum: 15 IGN per billion cycles, which is a third of Boundless at USD 0.005 and 7x at 0.10. A rollup compares in dollars every week; a 60% vote takes two weeks and a quorum. Lane 7 (frontier 3.5, rank 8) proposes the continuous dial for the floors themselves and it would help; this proposal is narrower and independent of it: the job reserve is the electricity, published as a rate the review trigger of spec 5.10.3 already needs ("converted at the window's settlement rate and published with the reading"), and the price above the reserve is the requester's bid against the sortition's assignees. Cost 16 hours. Gate: a simulated job book clearing within 20% of Boundless at all three prices. Per tier: the prover sells at a market price; the rollup customer gets a comparable quote; the holder keeps job demand for IGN through a price rise (at 0.10 and the floor, every rollup leaves); miners, pools and home cards see nothing.
**2. Define the stranded pool.** Spec 5.3 pays "the first valid proof included in a block"; 7.7 item 3 refuses a record older than 600 chain blocks; 7.8 item 7 says an unproven segment's aggregator share "stays in the escrow". Nothing says what happens to the shard credit nobody claimed. In the refusal scenario (4.2) the whole 20% is stranded for ten days: 5.5 M IGN that reach nobody and that nobody decided to burn. A roll-forward (the next proven segment's pool is larger by what was stranded) makes a refusal a transfer from refusers to returners, which is the incentive the design wants, and makes the pool's total over any month equal to 20% of emission as the litepaper's table promises. 8 hours. Gate on the fast-time harness.
**3. The prover's price as a formula.** The whole of 4.1 (a) is one line: price per billion cycles = the subsidy the card forgoes per shard, which is `h/N`. At the devnet's 1.16 GH/s every quote is 100x the market; at 100 GH/s hybrids undercut it. The customer brief's "priced in dollars per proof" and the litepaper's "proofs at the cost of power" need the condition beside them, or the first customer conversation ends with the number. 3 hours of text.
**4. The 80/20 as a bounded parameter, and one sentence for the thresholds.** The 4 October lever study found the pool share not load-bearing at launch traffic and useful at 30% under heavy traffic; tonight's runs (4.2) agree. A band of 10 to 30% lets miners trade lottery for proving capacity when the traffic says so, and the band stops the lottery's 80% from voting the provers out. The same change should carry the three signalling numbers in one place; today a reader finds 90 in spec 5.7 and CLAUDE.md, 95 in P2 and the preamble, 60 in 5.5, and "a 90% miner signal turns one on" in the litepaper's Mining section about a class change the P2 rule sets at 95.
**5. Signalled execution parameters enter the digest, by CI.** Blocks carry transactions only. A node that signalled a fee change and runs the old rule accepts every block and computes a different state; its proof records fail everyone else's statement and everyone else's fail its own, which is loud for provers and silent for a wallet. `Params.fees` is in the digest and the handshake refuses a different digest, so today the defector is cut off. The next signalled parameter has no such guarantee until a check fails without it. 6 hours.
**6. Block-rate steps as signal-plus-floor.** Spec 2.1 names the steps "a planned fork with its own test campaign, as Kaspa's Crescendo". Crescendo was a fixed DAA score (`params.rs` line 648) and Igneum's own fixed height cost it a 229-block reorg on 6 October. P2 exists; the steps should use it. 4 hours of text and a gate line.
**7. The funding corrections.** One number and one row. 1 hour.
**8. Measure the two numbers.** The hybrid row is the only competitive one and it rests on the 5090's 4% and a fixture a sixth of a full shard. Six hours on the fleet, through `tools/fleet/lib`, eleven boxes.
Cross-references to lane 7 by name and rank: 3.1 (rank 7, the rental tax) would move 25 to 75% of a spiking block's subsidy to the pool; against 4.3's constant 11.8x ratio it doubles the renter's break-even and does not change the year the absolute cost gets small. 3.2 (rank 2, work-stake) removes the coin bond this lane's job model carries; the numbers here do not depend on the bond's form. 3.5 (rank 8, continuous dials) is the general form of proposals 1 and 4 here. 3.6 (rank 11, burn bounties) is option 5 of 4.3 and is rejected on the same ground. 3.11 (rank 15) and 3.12 to 3.14 are the market-size and verifiable-compute ceilings this lane's demand grid sits under. I7 (equivocation bounty in sortition slots) is the one treasury-less incentive in lane 7 that this lane's stranded-pool rule could fund without coins: stranded credit to the evidence carrier is a variant worth one line in the ledger, not a proposal here.
---
## 6. Open questions and what I could not run
- **The full-shard beside-the-miner times** on every tier (proposal 8). Linear scaling from the 4.7 M fixture says 92 s alone on a 3060 and 240 s beside the miner; if that holds, no 12 GB card meets the 120-s claim timeout beside its miner and the 25-s window is for 24 GB cards and up. The fleet was on the class v4 rehearsal tonight.
- **The hash loss while proving on Ampere and Ada**: the 5090's 4% is Blackwell with 32 GB; the 8 GB cards showed 6%; the 12 to 24 GB tiers are unmeasured and the hybrid row of 4.1 (a) moves with them.
- **Price elasticity of job demand**: every demand count is an assumption. The customer brief's "low millions a year" is the only market figure and it is approximate.
- **The rental market's supply curve**: 0 of 20 pods at the TH/s scale on 6 October; the attack costs assume the hash can be had at the measured price, which the bench entry says it cannot above about 2 GH/s.
- **The Devnet 2 block-rate runs** (RUN_A, RUN_B) were empty at writing; a 10 BPS chain changes shards per segment, records per block and the per-block fee step, and 4.1 (e) should be re-read when they land.
- **The economy simulator's price process** is exogenous; burns do not move it (4.2's burn is a number, not a feedback).
- **BIP 8 and BIP 9 texts, the Ethereum specs, Monero's reward code, Aleo, Boundless and Succinct** are cited by repository and path from memory or from the project's earlier readings and are marked approximate throughout; no clone exists in `vendor/`.
---
## 7. Summary for the coordinator
Lane 4 turned the eleven measured cards into a price per proof, built five demand curves with dollars and burn, re-ran the economy simulator with the measured table and the measured rental price under eight stresses, extended the security budget ten years with those fees, priced the dev fee and the signalling game, and tabulated what the other chains did. Three findings:
1. **The proving price is `h/N`, not "the cost of power".** Electricity is under a cent per billion cycles on every card; the price a prover must charge is the subsidy it forgoes, which is 100 to 300x Boundless's USD 0.21 at today's 1.16 GH/s and 0.2 to 0.4x at 100 GH/s for a card proving beside its miner (`utility.py` 1.3). And the adopted floor prices a job at 15 IGN per billion cycles, USD 0.075 / 0.30 / 1.50 at the three prices: above USD 0.014 per IGN the chain overprices the market by rule, and a two-week vote cannot follow a dollar market (proposal 1).
2. **Fees are not a security budget for a decade.** All five uses together put USD 450 a day to miners and provers at launch and USD 4,200 in year 5 in the base scenario at 0.02 (`utility.py` 3.3) against USD 54,800 and 13,700 of daily emission; of the 48% fee share in year 5 under 1% reaches miners. The 20-day 34% weight attack costs 11.8x the honest fleet's 20 days at every price and year (`security_budget_10y.py`); its absolute net cost drops under USD 1 M in year 5 at 0.005 and year 9 at 0.02. The second audit has no payer by rule: the entity's own provers (USD 100 k a year at 5% of the pool, year 3, 0.02) are the only line that scales.
3. **The 80/20 survives every stress but one, and that one strands the pool.** With the eleven measured cards, the 25-s window and a renter farm at the measured rent, T1 to T5 hold under price x10 and /10, external zero and x100, a 20% proving cartel and the first halving (`stress.py`, 8 scenarios x 3 seeds; hash troughs at 55% of pre-event under the /10 shock with 31% of cards off, the one row at the T1 line). A ten-day refusal by every prover breaks T4 only, and what it costs is 547,570 IGN a day of pool credit stranded in the escrow with no rule to return it (5.5 M IGN over the ten days): a silent supply cut nobody voted for (proposal 2). The renter farm is off in every row but the x10 price shock (margin +114%) and partly on under x100 external demand (-6%), which is lane 3's N_eq in an agent model.
Rules for main: the customer brief and the litepaper's "proofs at the cost of power" need the `h/N` condition before any customer conversation (proposal 3); `funding.md` section 4's dev-fee ceiling is a quarter too high (the fee moves the producer payout only); the three signalling thresholds are stated inconsistently across spec 5.7, CLAUDE.md, the P2 design and the litepaper's Mining section; and the spec is silent on pool credit nobody claims (proposal 2).

View file

@ -0,0 +1,14 @@
# sim/horizon/economy-and-utility
Models behind `docs/analysis/horizon/economy-and-utility.md` (Horizon lane 4, 6 October 2026). Python 3.10, numpy only for `stress.py`.
| File | What | Run |
|---|---|---|
| `utility.py` | Task 1: proving supply cost per measured card (alone, beside the miner, rented), the published prices it competes with, demand curves (a) to (e) with dollars per day and burn at launch, year 2, year 5 under low/base/high and three IGN prices, proof storage | `python3 utility.py > utility_out.md` (arithmetic, no lock) |
| `stress.py` | Task 2: `sim/economy/sim.py` with the eleven measured cards, the measured rental price for the farm, the 25-s window and 120-s timeout, a FIFO backlog with the 600-s record window (stranded pool credit), burn per day; scenarios a, p10, pd10, c, x100, cartel, refuse, halving (b, d, e, f kept) | `/Users/joshm/Projects/igneum/tools/lock/with-lock.sh run nice -n 19 python3 stress.py --seeds 3 > results/stress_main.md` (about 20 s a run on the M5 Max) |
| `security_budget_10y.py` | Task 3: ten years of subsidy, pool, fees (utility.py section 4), the hash the subsidy sustains at the measured card economics, the 34% weight attack in rented hash at USD 281 per GH/s-day, the audit lines against the entity's income | `python3 security_budget_10y.py > security_budget_10y_out.md` |
| `signal_game.py` | Task 5: the 60/90/95 thresholds, forcing and blocking costs, the 30% pool, signal-then-defect by what is signalled | `python3 signal_game.py > signal_game_out.md` |
| `devfee.py` | Task 4: the Ember dev fee in dollars by year, price and the share of miners who keep it on; per tier | `python3 devfee.py > devfee_out.md` |
| `results/` | The stress runs as they ran tonight (`stress_main.md`: 8 scenarios x 3 seeds; `stress_busy.md`: 3 shards a block; `stress_elecfarm.md`: the farm on electricity instead of rent; its `renter farm margin` row printed a division by zero rent and is blank in the current script) | |
Every input is labelled in the script (measured, cited, designed, approximate). Nothing here is a price prediction.

View file

@ -0,0 +1,62 @@
#!/usr/bin/env python3
"""Horizon lane 4, task 4: the Ember dev fee in dollars, by the share of miners who keep it on.
python3 devfee.py > devfee_out.md
Rule (docs/design/miner-dev-fee.md, measured 4 Oct 2026): one block template in 100 is requested with the dev payout address
(template counter, exact), default on, `--dev-fee 0` or the app switch turns it off; the fee block keeps the user's vote key and
UTXO address, only the execution-layer payout (`IGNA`) moves. So the fee is 1% of the producer subsidy and of the producer's
80% tip share for the templates it takes; the pool share (20%) is untouched (the proving payout is per record, not per template).
Precedents (approximate, from memory; none cloned): T-Rex 1% (not switchable), lolMiner 0.7 to 1.5% by algorithm (not switchable),
PhoenixMiner 0.65% (not switchable), TeamRedMiner 0.75 to 2.5% (not switchable), NBMiner 1 to 2% (not switchable); ethminer and
the open-source Kaspa miners 0%. NiceHash is a marketplace, not a dev fee: it takes 2% of the buyer's payment on its own
exchange (approximate). On Ethereum the closed-source fee miners held the large majority of GPU hash over ethminer, approximate,
because they were faster; nobody measured the opt-out share because none offered one.
"""
RAMP_LOSS = 37_000_000
PRICES = [0.005, 0.02, 0.10]
SHARES = [0.20, 0.50, 0.80, 1.00]
def emission(year):
h = (year - 1) // 2
e = 1_000_000_000 / (2 ** h)
if year == 1:
e -= RAMP_LOSS
return e
def usd(x):
return f"{x:,.0f}"
def main():
print("# The Ember dev fee in dollars (generated by devfee.py)\n")
print("Fee = 1% x 80% of emission (the producer share) x the share of hash on Ember with the fee on. Tips are left out (under 0.01% of emission in every lane scenario). Every price is an input.\n")
print("| Year | Emission, M IGN | Price | Fee at 20% on | at 50% | at 80% | at 100% | 100% as a share of funding.md's unfunded USD 220,000 to 310,000 |")
print("|---|---|---|---|---|---|---|---|")
for y in (1, 2, 3, 4, 5, 7, 10):
for p in PRICES:
e = emission(y)
f = 0.01 * 0.8 * e * p
cells = [usd(f * s) for s in SHARES]
print(f"| {y} | {e / 1e6:,.0f} | {p} | " + " | ".join(cells) + f" | {f / 265_000 * 100:.0f}% |")
print()
print("## What a miner pays\n")
print("| Tier | Hash (measured, prover-tiers) | Blocks a month at 100 GH/s network | Fee blocks a month | Fee at USD 0.02 a month | Switch |")
print("|---|---|---|---|---|---|")
for name, mhs in [("home 8 GB (4060 Ti 8)", 19.07), ("home 12 GB (4070)", 24.99), ("home 16 GB (4060 Ti 16)", 17.58), ("home 24 GB (4090)", 52.25), ("home 32 GB (5090)", 98.48), ("rig 8x 4090", 8 * 52.25), ("pool user (any)", 0)]:
if mhs == 0:
print(f"| {name} | n/a | the pool's | the pool's: a pool on Ember pays 1% of its templates, a pool on its own software 0% | passed through or absorbed by the pool's fee | the pool's choice, not the user's |")
continue
blocks = mhs / 100_000 * 86400 * 30.44
fee_blocks = blocks / 100
fee_usd = fee_blocks * 31.688 * 0.8 * 0.02
print(f"| {name} | {mhs:.2f} MH/s | {blocks:,.1f} | {fee_blocks:.2f} | {fee_usd:,.2f} | Settings switch or `--dev-fee 0`; HiveOS `DEV_FEE=0` |")
print()
if __name__ == "__main__":
main()

View file

@ -0,0 +1,40 @@
# The Ember dev fee in dollars (generated by devfee.py)
Fee = 1% x 80% of emission (the producer share) x the share of hash on Ember with the fee on. Tips are left out (under 0.01% of emission in every lane scenario). Every price is an input.
| Year | Emission, M IGN | Price | Fee at 20% on | at 50% | at 80% | at 100% | 100% as a share of funding.md's unfunded USD 220,000 to 310,000 |
|---|---|---|---|---|---|---|---|
| 1 | 963 | 0.005 | 7,704 | 19,260 | 30,816 | 38,520 | 15% |
| 1 | 963 | 0.02 | 30,816 | 77,040 | 123,264 | 154,080 | 58% |
| 1 | 963 | 0.1 | 154,080 | 385,200 | 616,320 | 770,400 | 291% |
| 2 | 1,000 | 0.005 | 8,000 | 20,000 | 32,000 | 40,000 | 15% |
| 2 | 1,000 | 0.02 | 32,000 | 80,000 | 128,000 | 160,000 | 60% |
| 2 | 1,000 | 0.1 | 160,000 | 400,000 | 640,000 | 800,000 | 302% |
| 3 | 500 | 0.005 | 4,000 | 10,000 | 16,000 | 20,000 | 8% |
| 3 | 500 | 0.02 | 16,000 | 40,000 | 64,000 | 80,000 | 30% |
| 3 | 500 | 0.1 | 80,000 | 200,000 | 320,000 | 400,000 | 151% |
| 4 | 500 | 0.005 | 4,000 | 10,000 | 16,000 | 20,000 | 8% |
| 4 | 500 | 0.02 | 16,000 | 40,000 | 64,000 | 80,000 | 30% |
| 4 | 500 | 0.1 | 80,000 | 200,000 | 320,000 | 400,000 | 151% |
| 5 | 250 | 0.005 | 2,000 | 5,000 | 8,000 | 10,000 | 4% |
| 5 | 250 | 0.02 | 8,000 | 20,000 | 32,000 | 40,000 | 15% |
| 5 | 250 | 0.1 | 40,000 | 100,000 | 160,000 | 200,000 | 75% |
| 7 | 125 | 0.005 | 1,000 | 2,500 | 4,000 | 5,000 | 2% |
| 7 | 125 | 0.02 | 4,000 | 10,000 | 16,000 | 20,000 | 8% |
| 7 | 125 | 0.1 | 20,000 | 50,000 | 80,000 | 100,000 | 38% |
| 10 | 62 | 0.005 | 500 | 1,250 | 2,000 | 2,500 | 1% |
| 10 | 62 | 0.02 | 2,000 | 5,000 | 8,000 | 10,000 | 4% |
| 10 | 62 | 0.1 | 10,000 | 25,000 | 40,000 | 50,000 | 19% |
## What a miner pays
| Tier | Hash (measured, prover-tiers) | Blocks a month at 100 GH/s network | Fee blocks a month | Fee at USD 0.02 a month | Switch |
|---|---|---|---|---|---|
| home 8 GB (4060 Ti 8) | 19.07 MH/s | 501.5 | 5.02 | 2.54 | Settings switch or `--dev-fee 0`; HiveOS `DEV_FEE=0` |
| home 12 GB (4070) | 24.99 MH/s | 657.2 | 6.57 | 3.33 | Settings switch or `--dev-fee 0`; HiveOS `DEV_FEE=0` |
| home 16 GB (4060 Ti 16) | 17.58 MH/s | 462.4 | 4.62 | 2.34 | Settings switch or `--dev-fee 0`; HiveOS `DEV_FEE=0` |
| home 24 GB (4090) | 52.25 MH/s | 1,374.2 | 13.74 | 6.97 | Settings switch or `--dev-fee 0`; HiveOS `DEV_FEE=0` |
| home 32 GB (5090) | 98.48 MH/s | 2,590.0 | 25.90 | 13.13 | Settings switch or `--dev-fee 0`; HiveOS `DEV_FEE=0` |
| rig 8x 4090 | 418.00 MH/s | 10,993.5 | 109.93 | 55.74 | Settings switch or `--dev-fee 0`; HiveOS `DEV_FEE=0` |
| pool user (any) | n/a | the pool's | the pool's: a pool on Ember pays 1% of its templates, a pool on its own software 0% | passed through or absorbed by the pool's fee | the pool's choice, not the user's |

View file

@ -0,0 +1,9 @@
with-lock: holding run-1 for: nice -n 19 python3 stress.py --seeds 2 --scenarios a,cartel,refuse,x100 --set shards_per_block=3
<!-- scenario a seed 1: 14.6 s -->
<!-- scenario a seed 2: 15.4 s -->
<!-- scenario cartel seed 1: 15.2 s -->
<!-- scenario cartel seed 2: 15.5 s -->
<!-- scenario refuse seed 1: 14.8 s -->
<!-- scenario refuse seed 2: 14.3 s -->
<!-- scenario x100 seed 1: 15.6 s -->
<!-- scenario x100 seed 2: 15.4 s -->

View file

@ -0,0 +1,62 @@
# Igneum economy under stress (lane 4), 1000 operators, 30 days, seeds [1, 2], tick 180 s, the eleven measured cards
Parameters: tick=180.0, days=30, n_ops=1000, emission=31.688, pool=0.2, window=25.0, assignees=8, burn=0.1, timeout=120.0, shards_per_block=3.0, content_shards_per_block=0.03, job_work=10.0, ext_usd_day=2000.0, ext_mult=1.0, price0=0.012, vol_day=0.05, hyb_loss=0.04, hyb_extra_w=70.0, aggregation=4.0, waste=0.5, hyst_lo=0.05, hyst_hi=0.25, dwell_ticks=20, decide_every=4, ema_ticks=20.0, farm_share=0.2, renter_usd_mhs_h=0.0117, backlog_rule=600.0, record_window=600.0, burn_per_shard=0.51, cartel_weight=0.1, refuse_from=10.0, refuse_to=20.0
Cards (measured, prover-tiers-real-cards.md): 3060 23.78 MH/s 104 W alone 14.4 s beside 37.5 s; 3080 40.82 MH/s 205 W alone 7.1 s beside 25.6 s; 3090 37.79 MH/s 229 W alone 14.9 s beside 19.9 s; 4060Ti16 17.58 MH/s 72 W alone 11.6 s beside 34.6 s; 4060Ti8 19.07 MH/s 73 W alone 9.6 s beside 26.3 s (core-only beside); 4060 17.07 MH/s 115 W alone 18.4 s beside 22.1 s (core-only beside); 4070 24.99 MH/s 91 W alone 12.1 s beside 27.3 s; 4090 52.25 MH/s 183 W alone 6.3 s beside 26.1 s; 5070 41.89 MH/s 137 W alone 4.8 s beside 37.2 s; 5090 98.48 MH/s 258 W alone 6.3 s beside 10.7 s; A5000 47.70 MH/s 223 W alone 8.3 s beside 34.6 s
- a: baseline (measured cards, 25-s window, 120-s timeout, farm is a renter at USD 0.0117 per MH/s-h)
- cartel: the operators holding the top 10% of weight never answer an assignment or claim
- refuse: every prover refuses (no assignment answered, no open claim) from day 10 to day 20
- x100: external demand pays x100 from day 7
| Metric | a | a min | a max | cartel | cartel min | cartel max | refuse | refuse min | refuse max | x100 | x100 min | x100 max |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| hash mining share (mean) | 0.96 | 0.96 | 0.96 | 0.97 | 0.96 | 0.97 | 0.98 | 0.98 | 0.98 | 0.95 | 0.95 | 0.96 |
| cards proving, day 10 | 0.07 | 0.06 | 0.07 | 0.07 | 0.06 | 0.09 | 0.07 | 0.06 | 0.07 | 0.09 | 0.08 | 0.10 |
| cards proving, day 30 | 0.07 | 0.06 | 0.07 | 0.08 | 0.08 | 0.09 | 0.06 | 0.06 | 0.07 | 0.09 | 0.08 | 0.10 |
| cards hybrid, day 30 | 0.58 | 0.57 | 0.58 | 0.58 | 0.57 | 0.58 | 0.76 | 0.76 | 0.76 | 0.58 | 0.57 | 0.59 |
| cards off, day 30 | 0.09 | 0.09 | 0.09 | 0.00 | 0.00 | 0.00 | 0.09 | 0.09 | 0.09 | 0.07 | 0.07 | 0.07 |
| hash min / pre-event | 1.00 | 1.00 | 1.00 | 0.99 | 0.99 | 1.00 | 1.00 | 1.00 | 1.00 | 0.95 | 0.93 | 0.97 |
| hash day 30 / pre-event | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 1.04 | 1.02 | 1.05 |
| T1: hours hash under 50% | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 |
| backlog max, shards | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 2,265 | 2,251 | 2,279 | 0.00 | 0.00 | 0.00 |
| T2: oldest unproven age max, s | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 565.00 | 565.00 | 565.00 | 0.00 | 0.00 | 0.00 |
| age max day 30, s | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 |
| blocks proven within 60 s | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 0.67 | 0.67 | 0.67 | 1.00 | 1.00 | 1.00 |
| T4: worst day within 60 s | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 0.00 | 0.00 | 0.00 | 1.00 | 1.00 | 1.00 |
| blocks proven within 20 s | 0.14 | 0.14 | 0.14 | 0.12 | 0.11 | 0.14 | 0.08 | 0.08 | 0.09 | 0.18 | 0.17 | 0.18 |
| difficulty end / start | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 1.10 | 0.97 | 1.22 |
| external jobs delivered | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 0.67 | 0.67 | 0.68 | 1.00 | 1.00 | 1.00 |
| price mean, $ | 0.0158 | 0.0101 | 0.0215 | 0.0119 | 0.0108 | 0.0130 | 0.0159 | 0.0109 | 0.0210 | 0.0139 | 0.0109 | 0.0169 |
| price at day 30, $ | 0.0154 | 0.0087 | 0.0220 | 0.0107 | 0.0099 | 0.0115 | 0.0154 | 0.0102 | 0.0206 | 0.0125 | 0.0101 | 0.0150 |
| T5: proving-share 10-90 pct range, points | 0.26 | 0.17 | 0.36 | 2.20 | 0.70 | 3.71 | 0.03 | 0.01 | 0.04 | 1.93 | 1.41 | 2.45 |
| burn, IGN per day (base fee + job burn) | 16,247 | 10,791 | 21,703 | 18,346 | 16,533 | 20,159 | 10,924 | 8,475 | 13,373 | 1,192,701 | 915,866 | 1,469,535 |
| of which external job burn, IGN per day | 14,924 | 9,468 | 20,380 | 17,024 | 15,212 | 18,836 | 9,601 | 7,152 | 12,050 | 1,191,377 | 914,542 | 1,468,211 |
| burn, $ per day at the mean price | 225.56 | 219.04 | 232.08 | 216.28 | 215.33 | 217.23 | 161.71 | 145.15 | 178.28 | 15,767 | 15,473 | 16,061 |
| share of the 20% pool paid out | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 0.67 | 0.67 | 0.67 | 1.00 | 1.00 | 1.00 |
| share of the 20% pool stranded (expired shards) | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.33 | 0.33 | 0.33 | 0.00 | 0.00 | 0.00 |
| stranded, IGN per day | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 182,436 | 182,369 | 182,504 | 0.00 | 0.00 | 0.00 |
| renter farm cards | 968.00 | 925.00 | 1,011 | 968.00 | 925.00 | 1,011 | 968.00 | 925.00 | 1,011 | 968.00 | 925.00 | 1,011 |
| renter farm cards on, day 30 | 0.00 | 0.00 | 0.00 | 968.00 | 925.00 | 1,011 | 0.00 | 0.00 | 0.00 | 212.74 | 181.15 | 244.32 |
| renter farm margin over rent | -0.75 | -0.77 | -0.74 | -0.80 | -0.83 | -0.77 | -0.75 | -0.77 | -0.74 | 0.11 | 0.11 | 0.11 |
| cartel operators | 0.00 | 0.00 | 0.00 | 1.00 | 1.00 | 1.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 |
| mode switches (operator x class) | 43,516 | 41,276 | 45,757 | 108,987 | 53,600 | 164,374 | 19,692 | 18,647 | 20,737 | 47,444 | 42,981 | 51,908 |
| Flag | a | cartel | refuse | x100 |
|---|---|---|---|---|
| T1 miner shortage (hash under 50% of pre-event for 1 h or more) | 0/2 | 0/2 | 0/2 | 0/2 |
| T2 backlog over 600 s | 0/2 | 0/2 | 0/2 | 0/2 |
| T3 growing backlog (positive 10-day trend and over 60 s at day 30) | 0/2 | 0/2 | 0/2 | 0/2 |
| T4 window miss (a day under 90% of blocks within 60 s) | 0/2 | 0/2 | 2/2 | 0/2 |
| T5 oscillation (proving-share 10-90 range over 10 points in the last 10 days) | 0/2 | 0/2 | 0/2 | 0/2 |
Operator profit by card class, $ per card-day (mean over seeds, all modes including off), and the share of shards each class proves:
| Scenario | 3060 | 3080 | 3090 | 4060Ti16 | 4060Ti8 | 4060 | 4070 | 4090 | 5070 | 5090 | A5000 | sh 3060 | sh 3080 | sh 3090 | sh 4060Ti16 | sh 4060Ti8 | sh 4060 | sh 4070 | sh 4090 | sh 5070 | sh 5090 | sh A5000 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| a | 2.004 | 4.570 | 4.177 | 2.343 | 3.246 | 1.356 | 2.958 | 5.684 | 4.449 | 5.750 | 4.922 | 0.00 | 0.05 | 0.11 | 0.04 | 0.22 | 0.01 | 0.11 | 0.11 | 0.06 | 0.24 | 0.04 |
| cartel | 1.149 | 2.887 | 2.523 | 1.556 | 2.057 | 0.762 | 1.850 | 3.574 | 3.333 | -7.105 | 3.031 | 0.00 | 0.05 | 0.11 | 0.04 | 0.15 | 0.03 | 0.11 | 0.10 | 0.14 | 0.23 | 0.04 |
| refuse | 2.155 | 4.088 | 3.686 | 1.990 | 3.021 | 1.355 | 2.600 | 5.192 | 4.103 | 5.201 | 4.544 | 0.05 | 0.05 | 0.10 | 0.03 | 0.26 | 0.00 | 0.10 | 0.10 | 0.05 | 0.22 | 0.04 |
| x100 | 1.629 | 22.473 | 3.329 | 19.229 | 39.967 | 1.086 | 2.382 | 23.427 | 23.886 | 26.253 | 18.805 | 0.00 | 0.05 | 0.10 | 0.04 | 0.16 | 0.01 | 0.10 | 0.10 | 0.16 | 0.26 | 0.04 |
<!-- total 121 s -->

View file

@ -0,0 +1,5 @@
with-lock: holding run-1 for: nice -n 19 python3 stress.py --seeds 2 --scenarios a,pd10 --set renter_usd_mhs_h=0
<!-- scenario a seed 1: 16.0 s -->
<!-- scenario a seed 2: 15.6 s -->
<!-- scenario pd10 seed 1: 15.8 s -->
<!-- scenario pd10 seed 2: 16.5 s -->

View file

@ -0,0 +1,58 @@
# Igneum economy under stress (lane 4), 1000 operators, 30 days, seeds [1, 2], tick 180 s, the eleven measured cards
Parameters: tick=180.0, days=30, n_ops=1000, emission=31.688, pool=0.2, window=25.0, assignees=8, burn=0.1, timeout=120.0, shards_per_block=1.0, content_shards_per_block=0.03, job_work=10.0, ext_usd_day=2000.0, ext_mult=1.0, price0=0.012, vol_day=0.05, hyb_loss=0.04, hyb_extra_w=70.0, aggregation=4.0, waste=0.5, hyst_lo=0.05, hyst_hi=0.25, dwell_ticks=20, decide_every=4, ema_ticks=20.0, farm_share=0.2, renter_usd_mhs_h=0.0, backlog_rule=600.0, record_window=600.0, burn_per_shard=0.51, cartel_weight=0.1, refuse_from=10.0, refuse_to=20.0
Cards (measured, prover-tiers-real-cards.md): 3060 23.78 MH/s 104 W alone 14.4 s beside 37.5 s; 3080 40.82 MH/s 205 W alone 7.1 s beside 25.6 s; 3090 37.79 MH/s 229 W alone 14.9 s beside 19.9 s; 4060Ti16 17.58 MH/s 72 W alone 11.6 s beside 34.6 s; 4060Ti8 19.07 MH/s 73 W alone 9.6 s beside 26.3 s (core-only beside); 4060 17.07 MH/s 115 W alone 18.4 s beside 22.1 s (core-only beside); 4070 24.99 MH/s 91 W alone 12.1 s beside 27.3 s; 4090 52.25 MH/s 183 W alone 6.3 s beside 26.1 s; 5070 41.89 MH/s 137 W alone 4.8 s beside 37.2 s; 5090 98.48 MH/s 258 W alone 6.3 s beside 10.7 s; A5000 47.70 MH/s 223 W alone 8.3 s beside 34.6 s
- a: baseline (measured cards, 25-s window, 120-s timeout, farm is a renter at USD 0.0117 per MH/s-h)
- pd10: coin price /10 at day 7
| Metric | a | a min | a max | pd10 | pd10 min | pd10 max |
|---|---|---|---|---|---|---|
| hash mining share (mean) | 0.98 | 0.97 | 0.99 | 0.91 | 0.91 | 0.91 |
| cards proving, day 10 | 0.04 | 0.02 | 0.07 | 0.17 | 0.17 | 0.17 |
| cards proving, day 30 | 0.05 | 0.03 | 0.07 | 0.15 | 0.15 | 0.15 |
| cards hybrid, day 30 | 0.67 | 0.66 | 0.67 | 0.51 | 0.50 | 0.52 |
| cards off, day 30 | 0.00 | 0.00 | 0.00 | 0.25 | 0.23 | 0.27 |
| hash min / pre-event | 0.99 | 0.98 | 1.00 | 0.63 | 0.62 | 0.64 |
| hash day 30 / pre-event | 1.00 | 1.00 | 1.00 | 0.77 | 0.74 | 0.79 |
| T1: hours hash under 50% | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 |
| backlog max, shards | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 |
| T2: oldest unproven age max, s | 0.00 | 0.00 | 0.00 | 565.00 | 565.00 | 565.00 |
| age max day 30, s | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 |
| blocks proven within 60 s | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 |
| T4: worst day within 60 s | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 |
| blocks proven within 20 s | 0.39 | 0.35 | 0.43 | 0.51 | 0.51 | 0.52 |
| difficulty end / start | 1.00 | 1.00 | 1.00 | 0.77 | 0.74 | 0.79 |
| external jobs delivered | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 |
| price mean, $ | 0.0118 | 0.0113 | 0.0123 | 0.0041 | 0.0040 | 0.0042 |
| price at day 30, $ | 0.0094 | 0.0088 | 0.0101 | 0.0012 | 0.0012 | 0.0013 |
| T5: proving-share 10-90 pct range, points | 1.88 | 0.39 | 3.37 | 2.51 | 2.32 | 2.71 |
| burn, IGN per day (base fee + job burn) | 18,779 | 17,620 | 19,937 | 119,872 | 118,764 | 120,980 |
| of which external job burn, IGN per day | 17,457 | 16,297 | 18,617 | 118,551 | 117,444 | 119,658 |
| burn, $ per day at the mean price | 221.05 | 216.04 | 226.07 | 492.51 | 480.29 | 504.72 |
| share of the 20% pool paid out | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 |
| share of the 20% pool stranded (expired shards) | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 |
| stranded, IGN per day | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 |
| renter farm cards | 968.00 | 925.00 | 1,011 | 968.00 | 925.00 | 1,011 |
| renter farm cards on, day 30 | 968.00 | 925.00 | 1,011 | 968.00 | 925.00 | 1,011 |
| renter farm margin over rent | 204,942,454,880,445 | 200,962,934,166,413 | 208,921,975,594,476 | 83,466,041,323,923 | 80,404,979,462,614 | 86,527,103,185,233 |
| cartel operators | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 |
| mode switches (operator x class) | 84,770 | 46,616 | 122,924 | 340,786 | 313,839 | 367,732 |
| Flag | a | pd10 |
|---|---|---|
| T1 miner shortage (hash under 50% of pre-event for 1 h or more) | 0/2 | 0/2 |
| T2 backlog over 600 s | 0/2 | 0/2 |
| T3 growing backlog (positive 10-day trend and over 60 s at day 30) | 0/2 | 0/2 |
| T4 window miss (a day under 90% of blocks within 60 s) | 0/2 | 0/2 |
| T5 oscillation (proving-share 10-90 range over 10 points in the last 10 days) | 0/2 | 0/2 |
Operator profit by card class, $ per card-day (mean over seeds, all modes including off), and the share of shards each class proves:
| Scenario | 3060 | 3080 | 3090 | 4060Ti16 | 4060Ti8 | 4060 | 4070 | 4090 | 5070 | 5090 | A5000 | sh 3060 | sh 3080 | sh 3090 | sh 4060Ti16 | sh 4060Ti8 | sh 4060 | sh 4070 | sh 4090 | sh 5070 | sh 5090 | sh A5000 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| a | 1.113 | 2.677 | 2.338 | 1.419 | 1.558 | 0.721 | 1.718 | 3.343 | 3.175 | 6.758 | 2.845 | 0.00 | 0.04 | 0.09 | 0.03 | 0.10 | 0.01 | 0.09 | 0.09 | 0.12 | 0.38 | 0.03 |
| pd10 | 0.325 | 0.894 | 0.668 | 0.621 | 0.778 | 0.209 | 0.523 | 1.137 | 1.147 | 2.497 | 0.928 | 0.01 | 0.04 | 0.05 | 0.05 | 0.11 | 0.02 | 0.08 | 0.08 | 0.14 | 0.38 | 0.03 |
<!-- total 64 s -->

View file

@ -0,0 +1,25 @@
with-lock: holding run-1 for: nice -n 19 python3 stress.py --seeds 3
<!-- scenario a seed 1: 20.0 s -->
<!-- scenario a seed 2: 20.3 s -->
<!-- scenario a seed 3: 17.9 s -->
<!-- scenario p10 seed 1: 18.9 s -->
<!-- scenario p10 seed 2: 15.3 s -->
<!-- scenario p10 seed 3: 15.6 s -->
<!-- scenario pd10 seed 1: 15.5 s -->
<!-- scenario pd10 seed 2: 15.6 s -->
<!-- scenario pd10 seed 3: 15.5 s -->
<!-- scenario c seed 1: 17.1 s -->
<!-- scenario c seed 2: 16.1 s -->
<!-- scenario c seed 3: 15.5 s -->
<!-- scenario x100 seed 1: 16.2 s -->
<!-- scenario x100 seed 2: 16.0 s -->
<!-- scenario x100 seed 3: 23.6 s -->
<!-- scenario cartel seed 1: 31.6 s -->
<!-- scenario cartel seed 2: 30.9 s -->
<!-- scenario cartel seed 3: 16.6 s -->
<!-- scenario refuse seed 1: 15.7 s -->
<!-- scenario refuse seed 2: 13.7 s -->
<!-- scenario refuse seed 3: 13.8 s -->
<!-- scenario halving seed 1: 14.7 s -->
<!-- scenario halving seed 2: 14.7 s -->
<!-- scenario halving seed 3: 14.6 s -->

View file

@ -0,0 +1,70 @@
# Igneum economy under stress (lane 4), 1000 operators, 30 days, seeds [1, 2, 3], tick 180 s, the eleven measured cards
Parameters: tick=180.0, days=30, n_ops=1000, emission=31.688, pool=0.2, window=25.0, assignees=8, burn=0.1, timeout=120.0, shards_per_block=1.0, content_shards_per_block=0.03, job_work=10.0, ext_usd_day=2000.0, ext_mult=1.0, price0=0.012, vol_day=0.05, hyb_loss=0.04, hyb_extra_w=70.0, aggregation=4.0, waste=0.5, hyst_lo=0.05, hyst_hi=0.25, dwell_ticks=20, decide_every=4, ema_ticks=20.0, farm_share=0.2, renter_usd_mhs_h=0.0117, backlog_rule=600.0, record_window=600.0, burn_per_shard=0.51, cartel_weight=0.1, refuse_from=10.0, refuse_to=20.0
Cards (measured, prover-tiers-real-cards.md): 3060 23.78 MH/s 104 W alone 14.4 s beside 37.5 s; 3080 40.82 MH/s 205 W alone 7.1 s beside 25.6 s; 3090 37.79 MH/s 229 W alone 14.9 s beside 19.9 s; 4060Ti16 17.58 MH/s 72 W alone 11.6 s beside 34.6 s; 4060Ti8 19.07 MH/s 73 W alone 9.6 s beside 26.3 s (core-only beside); 4060 17.07 MH/s 115 W alone 18.4 s beside 22.1 s (core-only beside); 4070 24.99 MH/s 91 W alone 12.1 s beside 27.3 s; 4090 52.25 MH/s 183 W alone 6.3 s beside 26.1 s; 5070 41.89 MH/s 137 W alone 4.8 s beside 37.2 s; 5090 98.48 MH/s 258 W alone 6.3 s beside 10.7 s; A5000 47.70 MH/s 223 W alone 8.3 s beside 34.6 s
- a: baseline (measured cards, 25-s window, 120-s timeout, farm is a renter at USD 0.0117 per MH/s-h)
- p10: coin price x10 at day 7
- pd10: coin price /10 at day 7
- c: external demand zero for 30 days
- x100: external demand pays x100 from day 7
- cartel: the operators holding the top 10% of weight never answer an assignment or claim
- refuse: every prover refuses (no assignment answered, no open claim) from day 10 to day 20
- halving: the first halving: 15.844 IGN per block throughout
| Metric | a | a min | a max | p10 | p10 min | p10 max | pd10 | pd10 min | pd10 max | c | c min | c max | x100 | x100 min | x100 max | cartel | cartel min | cartel max | refuse | refuse min | refuse max | halving | halving min | halving max |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| hash mining share (mean) | 0.97 | 0.96 | 0.99 | 0.97 | 0.97 | 0.98 | 0.90 | 0.89 | 0.91 | 0.95 | 0.95 | 0.95 | 0.95 | 0.95 | 0.95 | 0.97 | 0.96 | 0.97 | 0.98 | 0.98 | 0.98 | 0.98 | 0.97 | 0.98 |
| cards proving, day 10 | 0.05 | 0.03 | 0.07 | 0.06 | 0.06 | 0.07 | 0.15 | 0.14 | 0.15 | 0.06 | 0.05 | 0.06 | 0.09 | 0.09 | 0.09 | 0.07 | 0.06 | 0.09 | 0.05 | 0.03 | 0.07 | 0.04 | 0.03 | 0.05 |
| cards proving, day 30 | 0.06 | 0.03 | 0.07 | 0.06 | 0.06 | 0.06 | 0.14 | 0.14 | 0.14 | 0.06 | 0.05 | 0.06 | 0.10 | 0.10 | 0.11 | 0.06 | 0.04 | 0.08 | 0.06 | 0.06 | 0.07 | 0.04 | 0.04 | 0.06 |
| cards hybrid, day 30 | 0.58 | 0.58 | 0.58 | 0.66 | 0.66 | 0.66 | 0.44 | 0.42 | 0.47 | 0.48 | 0.47 | 0.48 | 0.55 | 0.53 | 0.56 | 0.57 | 0.57 | 0.58 | 0.76 | 0.76 | 0.76 | 0.58 | 0.58 | 0.58 |
| cards off, day 30 | 0.09 | 0.09 | 0.09 | 0.00 | 0.00 | 0.00 | 0.31 | 0.25 | 0.35 | 0.09 | 0.09 | 0.09 | 0.08 | 0.07 | 0.09 | 0.00 | 0.00 | 0.00 | 0.09 | 0.09 | 0.09 | 0.09 | 0.09 | 0.09 |
| hash min / pre-event | 0.99 | 0.97 | 1.00 | 0.99 | 0.98 | 1.00 | 0.55 | 0.49 | 0.58 | 0.99 | 0.99 | 0.99 | 0.91 | 0.89 | 0.92 | 0.99 | 0.99 | 1.00 | 0.99 | 0.97 | 1.00 | 0.98 | 0.98 | 0.98 |
| hash day 30 / pre-event | 1.00 | 0.99 | 1.00 | 1.25 | 1.23 | 1.26 | 0.74 | 0.70 | 0.81 | 1.00 | 0.99 | 1.00 | 0.97 | 0.92 | 1.01 | 1.01 | 1.00 | 1.01 | 0.99 | 0.98 | 1.00 | 1.00 | 1.00 | 1.00 |
| T1: hours hash under 50% | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 |
| backlog max, shards | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 765.62 | 761.65 | 769.55 | 0.00 | 0.00 | 0.00 |
| T2: oldest unproven age max, s | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 565.00 | 565.00 | 565.00 | 0.00 | 0.00 | 0.00 |
| age max day 30, s | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 |
| blocks proven within 60 s | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 0.67 | 0.67 | 0.67 | 1.00 | 1.00 | 1.00 |
| T4: worst day within 60 s | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 0.00 | 0.00 | 0.00 | 1.00 | 1.00 | 1.00 |
| blocks proven within 20 s | 0.29 | 0.24 | 0.32 | 0.37 | 0.36 | 0.38 | 0.39 | 0.38 | 0.41 | 0.29 | 0.28 | 0.29 | 0.36 | 0.34 | 0.37 | 0.28 | 0.26 | 0.31 | 0.19 | 0.17 | 0.20 | 0.26 | 0.25 | 0.27 |
| difficulty end / start | 1.00 | 1.00 | 1.00 | 1.25 | 1.23 | 1.26 | 0.76 | 0.71 | 0.83 | 1.00 | 0.99 | 1.00 | 0.96 | 0.95 | 0.97 | 1.00 | 1.00 | 1.00 | 0.99 | 0.98 | 1.00 | 0.99 | 0.98 | 1.00 |
| external jobs delivered | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | nan | nan | nan | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 0.67 | 0.67 | 0.68 | 1.00 | 1.00 | 1.00 |
| price mean, $ | 0.0108 | 0.0095 | 0.0120 | 0.0858 | 0.0741 | 0.0968 | 0.0035 | 0.0033 | 0.0037 | 0.0111 | 0.0100 | 0.0127 | 0.0100 | 0.0085 | 0.0108 | 0.0129 | 0.0122 | 0.0135 | 0.0112 | 0.0092 | 0.0133 | 0.0132 | 0.0114 | 0.0154 |
| price at day 30, $ | 0.0108 | 0.0092 | 0.0123 | 0.1096 | 0.0948 | 0.1184 | 0.0011 | 0.0010 | 0.0013 | 0.0095 | 0.0076 | 0.0126 | 0.0081 | 0.0065 | 0.0097 | 0.0137 | 0.0126 | 0.0149 | 0.0109 | 0.0090 | 0.0147 | 0.0129 | 0.0122 | 0.0135 |
| T5: proving-share 10-90 pct range, points | 1.59 | 0.18 | 4.25 | 0.14 | 0.13 | 0.16 | 2.02 | 1.73 | 2.20 | 0.55 | 0.40 | 0.79 | 2.93 | 2.56 | 3.67 | 2.19 | 0.46 | 3.07 | 0.04 | 0.04 | 0.05 | 4.42 | 4.31 | 4.63 |
| burn, IGN per day (base fee + job burn) | 19,928 | 17,925 | 22,668 | 6,758 | 6,348 | 7,165 | 146,737 | 132,955 | 155,837 | 1,322 | 1,321 | 1,323 | 1,576,947 | 1,412,017 | 1,895,647 | 16,954 | 16,184 | 17,747 | 13,366 | 11,984 | 15,369 | 17,247 | 14,881 | 19,620 |
| of which external job burn, IGN per day | 18,606 | 16,604 | 21,346 | 5,437 | 5,027 | 5,844 | 145,415 | 131,633 | 154,516 | 0.00 | 0.00 | 0.00 | 1,575,623 | 1,410,693 | 1,894,324 | 15,632 | 14,862 | 16,425 | 12,044 | 10,663 | 14,047 | 15,926 | 13,560 | 18,299 |
| burn, $ per day at the mean price | 213.60 | 211.83 | 214.53 | 578.25 | 531.08 | 654.07 | 505.68 | 489.33 | 525.24 | 14.66 | 13.22 | 16.74 | 15,535 | 15,204 | 16,184 | 217.72 | 217.11 | 218.48 | 147.76 | 140.77 | 159.19 | 223.76 | 219.33 | 228.72 |
| share of the 20% pool paid out | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 0.67 | 0.67 | 0.67 | 1.00 | 1.00 | 1.00 |
| share of the 20% pool stranded (expired shards) | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.33 | 0.33 | 0.33 | 0.00 | 0.00 | 0.00 |
| stranded, IGN per day | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 182,387 | 182,348 | 182,410 | 0.00 | 0.00 | 0.00 |
| renter farm cards | 984.00 | 925.00 | 1,016 | 984.00 | 925.00 | 1,016 | 984.00 | 925.00 | 1,016 | 984.00 | 925.00 | 1,016 | 984.00 | 925.00 | 1,016 | 984.00 | 925.00 | 1,016 | 984.00 | 925.00 | 1,016 | 984.00 | 925.00 | 1,016 |
| renter farm cards on, day 30 | 0.00 | 0.00 | 0.00 | 984.00 | 925.00 | 1,016 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 53.96 | 0.00 | 161.88 | 984.00 | 925.00 | 1,016 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 |
| renter farm margin over rent | -0.77 | -0.80 | -0.74 | 1.14 | 0.78 | 1.35 | -0.77 | -0.80 | -0.74 | -0.76 | -0.80 | -0.73 | -0.06 | -0.10 | 0.01 | -0.79 | -0.79 | -0.78 | -0.77 | -0.80 | -0.74 | -0.88 | -0.90 | -0.86 |
| cartel operators | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 1.00 | 1.00 | 1.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 |
| mode switches (operator x class) | 68,668 | 43,384 | 115,468 | 45,506 | 40,107 | 53,876 | 299,713 | 255,963 | 325,037 | 124,200 | 119,674 | 126,827 | 62,079 | 53,259 | 78,476 | 110,155 | 52,993 | 141,064 | 27,814 | 20,329 | 41,746 | 153,983 | 143,139 | 166,180 |
| Flag | a | p10 | pd10 | c | x100 | cartel | refuse | halving |
|---|---|---|---|---|---|---|---|---|
| T1 miner shortage (hash under 50% of pre-event for 1 h or more) | 0/3 | 0/3 | 0/3 | 0/3 | 0/3 | 0/3 | 0/3 | 0/3 |
| T2 backlog over 600 s | 0/3 | 0/3 | 0/3 | 0/3 | 0/3 | 0/3 | 0/3 | 0/3 |
| T3 growing backlog (positive 10-day trend and over 60 s at day 30) | 0/3 | 0/3 | 0/3 | 0/3 | 0/3 | 0/3 | 0/3 | 0/3 |
| T4 window miss (a day under 90% of blocks within 60 s) | 0/3 | 0/3 | 0/3 | 0/3 | 0/3 | 0/3 | 3/3 | 0/3 |
| T5 oscillation (proving-share 10-90 range over 10 points in the last 10 days) | 0/3 | 0/3 | 0/3 | 0/3 | 0/3 | 0/3 | 0/3 | 0/3 |
Operator profit by card class, $ per card-day (mean over seeds, all modes including off), and the share of shards each class proves:
| Scenario | 3060 | 3080 | 3090 | 4060Ti16 | 4060Ti8 | 4060 | 4070 | 4090 | 5070 | 5090 | A5000 | sh 3060 | sh 3080 | sh 3090 | sh 4060Ti16 | sh 4060Ti8 | sh 4060 | sh 4070 | sh 4090 | sh 5070 | sh 5090 | sh A5000 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| a | 1.302 | 3.089 | 2.692 | 1.629 | 2.027 | 0.851 | 1.962 | 3.857 | 3.364 | 3.954 | 3.296 | 0.00 | 0.05 | 0.11 | 0.04 | 0.16 | 0.01 | 0.12 | 0.11 | 0.12 | 0.24 | 0.04 |
| p10 | 9.360 | 20.454 | 19.943 | 10.154 | 13.062 | 6.602 | 13.701 | 25.201 | 19.791 | 36.999 | 22.265 | 0.00 | 0.05 | 0.10 | 0.03 | 0.18 | 0.00 | 0.10 | 0.10 | 0.07 | 0.33 | 0.04 |
| pd10 | 0.347 | 0.985 | 0.703 | 0.683 | 0.928 | 0.222 | 0.552 | 1.248 | 1.192 | 1.348 | 1.016 | 0.01 | 0.05 | 0.06 | 0.06 | 0.15 | 0.03 | 0.10 | 0.10 | 0.17 | 0.22 | 0.04 |
| c | 1.368 | 3.037 | 2.945 | 1.373 | 1.126 | 0.901 | 2.149 | 3.824 | 3.010 | 3.792 | 3.251 | 0.00 | 0.06 | 0.12 | 0.03 | 0.01 | 0.03 | 0.14 | 0.12 | 0.20 | 0.24 | 0.04 |
| x100 | 1.148 | 20.443 | 2.313 | 17.528 | 37.192 | 0.742 | 1.701 | 21.189 | 21.968 | 23.114 | 17.119 | 0.00 | 0.05 | 0.09 | 0.04 | 0.14 | 0.01 | 0.10 | 0.10 | 0.18 | 0.25 | 0.04 |
| cartel | 1.223 | 3.059 | 2.684 | 1.655 | 1.836 | 0.808 | 1.971 | 3.779 | 3.839 | -6.770 | 3.217 | 0.00 | 0.05 | 0.10 | 0.04 | 0.11 | 0.03 | 0.11 | 0.10 | 0.17 | 0.23 | 0.04 |
| refuse | 1.452 | 2.815 | 2.461 | 1.405 | 2.085 | 0.883 | 1.784 | 3.606 | 2.968 | 3.653 | 3.110 | 0.05 | 0.05 | 0.09 | 0.03 | 0.23 | 0.00 | 0.10 | 0.10 | 0.08 | 0.22 | 0.04 |
| halving | 0.704 | 1.839 | 1.429 | 1.030 | 0.938 | 0.429 | 1.103 | 2.314 | 2.552 | 2.416 | 1.927 | 0.00 | 0.05 | 0.10 | 0.04 | 0.07 | 0.01 | 0.11 | 0.10 | 0.23 | 0.23 | 0.04 |
<!-- total 426 s -->

View file

@ -0,0 +1,151 @@
#!/usr/bin/env python3
"""Horizon lane 4, task 3: no-treasury sustainability over ten years.
Extends sim/economy/security_budget.py (5 October 2026) with: the fee scenarios of utility.py (section 4 of its
output), the proving-pool share as a separate line, the hash the subsidy sustains at the measured card economics,
the cost of a 34-percent weight attack in rented hash at the measured USD 281 per GH/s-day, and the audit line of
docs/plans/funding.md against what the entity could earn. Pure Python, no numpy; arithmetic, not a measurement.
python3 security_budget_10y.py > security_budget_10y_out.md
Model.
Emission: spec 2.5 (1 B IGN a year in years 1 and 2 minus the ramp's 37 M, halving every two years; 80/20).
Prices: flat USD 0.005, 0.02, 0.10 (security-budget.md inputs; assumptions).
Fees: the lane's low/base/high grid (utility.py section 4) interpolated: launch values in year 1, year-2 values in
year 2, year-5 values from year 5 on, linear between; tips 80% to miners and provers split 50/50 (the earlier
script's convention); job income in IGN 90% to provers; external USD jobs 90% to provers.
Hash the subsidy sustains: cards whose cost the miners' income pays at break-even, where the cost per MH/s-day is
electricity + capital: the 5090 fleet row (98.48 MH/s at 258 W, measured) at USD 0.10 per kWh gives
USD 0.00629 per MH/s-day of electricity; capital USD 2,000 per card over 3 years (approximate) gives
USD 0.0185 per MH/s-day; total USD 0.0248 per MH/s-day, USD 24.8 per GH/s-day. The brief's formula.
Attack: lane 3's rule (sim/horizon/consensus-security/cost_model.py): an attacker at share A rents A/(1-A) x N;
a third of the 30-day weight at 51% of blocks takes 20 days (spec 3 headline); rental USD 281 per GH/s-day
(bench-log 6 Oct 2026, 1,748 MH/s for USD 20.44 an hour). The 34% weight attack = 1.04 N for 20 days.
The attacker earns 51% of the producer subsidy over those 20 days; the net is shown.
Audits: funding.md section 2 rows, priced there (approximate).
"""
YEAR_S = 365.25 * 86400
RAMP_LOSS = 37_000_000
PRICES = [0.005, 0.02, 0.10]
RENTAL_USD_GHS_DAY = 281.0 # measured, bench-log 6 Oct 2026 (USD 0.0117 per MH/s-hour x 24)
ELEC_USD_KWH = 0.10
CARD_MHS, CARD_W = 98.48, 258.2 # RTX 5090 fleet row, measured
CARD_USD, CARD_LIFE_D = 2000.0, 3 * 365.25 # approximate
ELEC_PER_MHS_DAY = CARD_W / CARD_MHS * 24 / 1000 * ELEC_USD_KWH
CAP_PER_MHS_DAY = CARD_USD / CARD_MHS / CARD_LIFE_D
COST_PER_MHS_DAY = ELEC_PER_MHS_DAY + CAP_PER_MHS_DAY
# utility.py section 4 totals, IGN per day (tips to miners+provers, job income to provers) and USD per day (external)
FEES = {
"low": {1: (0.168, 0.0, 0.0), 2: (1.68, 22_478, 500.0), 5: (16.8, 44_955, 1_000.0)},
"base": {1: (1.68, 22_478, 500.0), 2: (16.8, 67_432, 2_000.0), 5: (168.0, 209_588, 10_000.0)},
"high": {1: (16.8, 44_955, 2_000.0), 2: (168.0, 209_588, 10_000.0), 5: (290.3, 1_002_375, 100_000.0)},
}
# the burn per day in IGN from the same table (for the supply line only)
BURN = {"low": {1: 51, 2: 5_748, 5: 15_578}, "base": {1: 5_748, 2: 23_316, 5: 126_716}, "high": {1: 20_578, 2: 126_716, 5: 711_481}}
AUDITS = [
("Finality rule review (phase 4 gate)", 50_000, 100_000, "funding.md section 2"),
("Node fork audit before public testnet", 60_000, 120_000, "funding.md section 2"),
("Mixer cryptanalysis, two reviews (genesis gate)", 80_000, 160_000, "funding.md section 2, the brief B1 to B8"),
("Execution layer and proving audit before mainnet", 80_000, 150_000, "funding.md section 2"),
("Client and release process audit", 20_000, 40_000, "funding.md section 2"),
("SECOND cryptanalysis after a class or era change (year 3)", 80_000, 160_000, "the brief's question; priced as the first"),
]
def emission(year):
h = (year - 1) // 2
e = 1_000_000_000 / (2 ** h)
if year == 1:
e -= RAMP_LOSS
return e
def interp(table, year):
if year <= 1:
return table[1]
if year >= 5:
return table[5]
if year <= 2:
return table[2]
a, b = table[2], table[5]
f = (year - 2) / 3.0
return tuple(x + (y - x) * f for x, y in zip(a, b))
def usd(x):
return f"{x:,.0f}"
def main():
print("# Security budget over ten years with the lane's fee scenarios (generated by security_budget_10y.py)\n")
print(f"Inputs: electricity USD {ELEC_USD_KWH} per kWh; card {CARD_MHS} MH/s at {CARD_W} W (RTX 5090 fleet row, measured) = USD {ELEC_PER_MHS_DAY:.5f} per MH/s-day; "
f"capital USD {CARD_USD:.0f} over {CARD_LIFE_D / 365.25:.0f} years (approximate) = USD {CAP_PER_MHS_DAY:.5f} per MH/s-day; total USD {COST_PER_MHS_DAY:.4f} per MH/s-day "
f"(USD {COST_PER_MHS_DAY * 1000:.1f} per GH/s-day); rental USD {RENTAL_USD_GHS_DAY:.0f} per GH/s-day (measured); the rental premium over the honest cost is {RENTAL_USD_GHS_DAY / (COST_PER_MHS_DAY * 1000):.1f}x.\n")
print("## 1. Per year: subsidy, fees, the hash the subsidy sustains, and the 34% weight attack\n")
for scen in ("low", "base", "high"):
for price in PRICES:
print(f"### Fee scenario {scen}, price USD {price} per IGN\n")
print("| Year | Subsidy to miners, USD/day | Pool (20%) to provers, USD/day | Tips to miners+provers, USD/day | Job income to provers, USD/day (IGN jobs + external) | Burn, USD/day | Sustained hash, GH/s | 34% weight attack: rent 1.04 N for 20 d, USD | Attacker's subsidy over 20 d, USD | Net cost, USD | Fees as % of total security spend |")
print("|---|---|---|---|---|---|---|---|---|---|---|")
for y in range(1, 11):
e_day = emission(y) / 365.25
miners = 0.8 * e_day * price
pool = 0.2 * e_day * price
tips_ign, jobs_ign, ext_usd = interp(FEES[scen], y)
tips = 0.8 * tips_ign * price
jobs = 0.9 * jobs_ign * price + 0.9 * ext_usd
burn = interp({1: (BURN[scen][1],), 2: (BURN[scen][2],), 5: (BURN[scen][5],)}, y)[0] * price
miners_total = miners + 0.5 * tips
hash_ghs = miners_total / (COST_PER_MHS_DAY * 1000)
attack = 1.04 * hash_ghs * RENTAL_USD_GHS_DAY * 20
earned = 0.51 * (miners_total) * 20
fees_share = (tips + jobs) / (miners + pool + tips + jobs) * 100
print(f"| {y} | {usd(miners)} | {usd(pool)} | {usd(tips)} | {usd(jobs)} | {usd(burn)} | {hash_ghs:,.1f} | {usd(attack)} | {usd(earned)} | {usd(attack - earned)} | {fees_share:.1f}% |")
print()
print("## 2. The year the attack gets cheap: the 20-day 34% weight attack's net cost by year and price (base fee scenario)\n")
print("| Year | Net cost at USD 0.005 | at 0.02 | at 0.10 | Sustained hash at 0.005, GH/s | at 0.02 | at 0.10 |")
print("|---|---|---|---|---|---|---|")
for y in range(1, 11):
row = [str(y)]
hashes = []
for price in PRICES:
e_day = emission(y) / 365.25
tips_ign, jobs_ign, ext_usd = interp(FEES["base"], y)
miners_total = 0.8 * e_day * price + 0.5 * 0.8 * tips_ign * price
h = miners_total / (COST_PER_MHS_DAY * 1000)
hashes.append(h)
row.append(usd(1.04 * h * RENTAL_USD_GHS_DAY * 20 - 0.51 * miners_total * 20))
row += [f"{h:,.1f}" for h in hashes]
print("| " + " | ".join(row) + " |")
print()
print("Reading. Because the sustained hash is proportional to the subsidy and the attack cost is proportional to the hash, the ratio of attack cost to "
f"20 days of subsidy is a constant: 1.04 x {RENTAL_USD_GHS_DAY:.0f} / {COST_PER_MHS_DAY * 1000:.1f} = {1.04 * RENTAL_USD_GHS_DAY / (COST_PER_MHS_DAY * 1000):.1f}x the honest fleet's 20-day cost, "
"minus the 51% the attacker earns back. The subsidy never 'falls under' the attack cost in ratio terms; what the halvings do is shrink both until the absolute "
"number is small. The rows name the year the net cost of the veto drops under USD 1 M and under USD 100 k at each price, which is the honest statement.\n")
print("## 3. The audits, and who pays the second one\n")
print("| Review | Low, USD | High, USD | Source |")
print("|---|---|---|---|")
for name, lo, hi, src in AUDITS:
print(f"| {name} | {usd(lo)} | {usd(hi)} | {src} |")
print()
print("What the entity's own lines could earn (every one an input, none a forecast): the Ember dev fee at 1% of the rewards of the miners who keep it on, "
"the entity's own provers' pool and job income, and the app share on the contracts it deploys.\n")
print("| Year | Price | Dev fee ceiling at 100% on Ember, USD/year | At 50% | At 20% | Entity's provers at 5% of the pool, USD/year | Second cryptanalysis as % of the 50% dev fee |")
print("|---|---|---|---|---|---|---|")
for y in (1, 2, 3, 4, 5):
for price in PRICES:
e = emission(y)
fee100 = 0.01 * e * price
pool5 = 0.05 * 0.2 * e * price
print(f"| {y} | {price} | {usd(fee100)} | {usd(fee100 * 0.5)} | {usd(fee100 * 0.2)} | {usd(pool5)} | {160_000 / max(fee100 * 0.5, 1) * 100:.0f}% |")
print()
if __name__ == "__main__":
main()

View file

@ -0,0 +1,189 @@
# Security budget over ten years with the lane's fee scenarios (generated by security_budget_10y.py)
Inputs: electricity USD 0.1 per kWh; card 98.48 MH/s at 258.2 W (RTX 5090 fleet row, measured) = USD 0.00629 per MH/s-day; capital USD 2000 over 3 years (approximate) = USD 0.01853 per MH/s-day; total USD 0.0248 per MH/s-day (USD 24.8 per GH/s-day); rental USD 281 per GH/s-day (measured); the rental premium over the honest cost is 11.3x.
## 1. Per year: subsidy, fees, the hash the subsidy sustains, and the 34% weight attack
### Fee scenario low, price USD 0.005 per IGN
| Year | Subsidy to miners, USD/day | Pool (20%) to provers, USD/day | Tips to miners+provers, USD/day | Job income to provers, USD/day (IGN jobs + external) | Burn, USD/day | Sustained hash, GH/s | 34% weight attack: rent 1.04 N for 20 d, USD | Attacker's subsidy over 20 d, USD | Net cost, USD | Fees as % of total security spend |
|---|---|---|---|---|---|---|---|---|---|---|
| 1 | 10,546 | 2,637 | 0 | 0 | 0 | 424.8 | 2,482,848 | 107,571 | 2,375,277 | 0.0% |
| 2 | 10,951 | 2,738 | 0 | 551 | 29 | 441.1 | 2,578,244 | 111,704 | 2,466,540 | 3.9% |
| 3 | 5,476 | 1,369 | 0 | 735 | 45 | 220.6 | 1,289,125 | 55,852 | 1,233,273 | 9.7% |
| 4 | 5,476 | 1,369 | 0 | 919 | 62 | 220.6 | 1,289,127 | 55,852 | 1,233,275 | 11.8% |
| 5 | 2,738 | 684 | 0 | 1,102 | 78 | 110.3 | 644,569 | 27,926 | 616,642 | 24.4% |
| 6 | 2,738 | 684 | 0 | 1,102 | 78 | 110.3 | 644,569 | 27,926 | 616,642 | 24.4% |
| 7 | 1,369 | 342 | 0 | 1,102 | 78 | 55.1 | 322,288 | 13,963 | 308,325 | 39.2% |
| 8 | 1,369 | 342 | 0 | 1,102 | 78 | 55.1 | 322,288 | 13,963 | 308,325 | 39.2% |
| 9 | 684 | 171 | 0 | 1,102 | 78 | 27.6 | 161,148 | 6,982 | 154,166 | 56.3% |
| 10 | 684 | 171 | 0 | 1,102 | 78 | 27.6 | 161,148 | 6,982 | 154,166 | 56.3% |
### Fee scenario low, price USD 0.02 per IGN
| Year | Subsidy to miners, USD/day | Pool (20%) to provers, USD/day | Tips to miners+provers, USD/day | Job income to provers, USD/day (IGN jobs + external) | Burn, USD/day | Sustained hash, GH/s | 34% weight attack: rent 1.04 N for 20 d, USD | Attacker's subsidy over 20 d, USD | Net cost, USD | Fees as % of total security spend |
|---|---|---|---|---|---|---|---|---|---|---|
| 1 | 42,185 | 10,546 | 0 | 0 | 1 | 1,699.2 | 9,931,394 | 430,285 | 9,501,109 | 0.0% |
| 2 | 43,806 | 10,951 | 0 | 855 | 115 | 1,764.5 | 10,312,976 | 446,817 | 9,866,159 | 1.5% |
| 3 | 21,903 | 5,476 | 0 | 1,139 | 180 | 882.2 | 5,156,499 | 223,409 | 4,933,090 | 4.0% |
| 4 | 21,903 | 5,476 | 0 | 1,424 | 246 | 882.2 | 5,156,509 | 223,410 | 4,933,099 | 4.9% |
| 5 | 10,951 | 2,738 | 0 | 1,709 | 312 | 441.1 | 2,578,275 | 111,706 | 2,466,569 | 11.1% |
| 6 | 10,951 | 2,738 | 0 | 1,709 | 312 | 441.1 | 2,578,275 | 111,706 | 2,466,569 | 11.1% |
| 7 | 5,476 | 1,369 | 0 | 1,709 | 312 | 220.6 | 1,289,153 | 55,854 | 1,233,300 | 20.0% |
| 8 | 5,476 | 1,369 | 0 | 1,709 | 312 | 220.6 | 1,289,153 | 55,854 | 1,233,300 | 20.0% |
| 9 | 2,738 | 684 | 0 | 1,709 | 312 | 110.3 | 644,592 | 27,927 | 616,665 | 33.3% |
| 10 | 2,738 | 684 | 0 | 1,709 | 312 | 110.3 | 644,592 | 27,927 | 616,665 | 33.3% |
### Fee scenario low, price USD 0.1 per IGN
| Year | Subsidy to miners, USD/day | Pool (20%) to provers, USD/day | Tips to miners+provers, USD/day | Job income to provers, USD/day (IGN jobs + external) | Burn, USD/day | Sustained hash, GH/s | 34% weight attack: rent 1.04 N for 20 d, USD | Attacker's subsidy over 20 d, USD | Net cost, USD | Fees as % of total security spend |
|---|---|---|---|---|---|---|---|---|---|---|
| 1 | 210,924 | 52,731 | 0 | 0 | 5 | 8,495.9 | 49,656,968 | 2,151,425 | 47,505,543 | 0.0% |
| 2 | 219,028 | 54,757 | 0 | 2,473 | 575 | 8,822.4 | 51,564,882 | 2,234,087 | 49,330,795 | 0.9% |
| 3 | 109,514 | 27,379 | 1 | 3,297 | 902 | 4,411.2 | 25,782,496 | 1,117,046 | 24,665,451 | 2.4% |
| 4 | 109,514 | 27,379 | 1 | 4,122 | 1,230 | 4,411.2 | 25,782,544 | 1,117,048 | 24,665,496 | 2.9% |
| 5 | 54,757 | 13,689 | 1 | 4,946 | 1,558 | 2,205.6 | 12,891,375 | 558,528 | 12,332,846 | 6.7% |
| 6 | 54,757 | 13,689 | 1 | 4,946 | 1,558 | 2,205.6 | 12,891,375 | 558,528 | 12,332,846 | 6.7% |
| 7 | 27,379 | 6,845 | 1 | 4,946 | 1,558 | 1,102.8 | 6,445,766 | 279,268 | 6,166,499 | 12.6% |
| 8 | 27,379 | 6,845 | 1 | 4,946 | 1,558 | 1,102.8 | 6,445,766 | 279,268 | 6,166,499 | 12.6% |
| 9 | 13,689 | 3,422 | 1 | 4,946 | 1,558 | 551.4 | 3,222,962 | 139,637 | 3,083,325 | 22.4% |
| 10 | 13,689 | 3,422 | 1 | 4,946 | 1,558 | 551.4 | 3,222,962 | 139,637 | 3,083,325 | 22.4% |
### Fee scenario base, price USD 0.005 per IGN
| Year | Subsidy to miners, USD/day | Pool (20%) to provers, USD/day | Tips to miners+provers, USD/day | Job income to provers, USD/day (IGN jobs + external) | Burn, USD/day | Sustained hash, GH/s | 34% weight attack: rent 1.04 N for 20 d, USD | Attacker's subsidy over 20 d, USD | Net cost, USD | Fees as % of total security spend |
|---|---|---|---|---|---|---|---|---|---|---|
| 1 | 10,546 | 2,637 | 0 | 551 | 29 | 424.8 | 2,482,849 | 107,571 | 2,375,278 | 4.0% |
| 2 | 10,951 | 2,738 | 0 | 2,103 | 117 | 441.1 | 2,578,251 | 111,705 | 2,466,547 | 13.3% |
| 3 | 5,476 | 1,369 | 0 | 4,717 | 289 | 220.6 | 1,289,153 | 55,854 | 1,233,300 | 40.8% |
| 4 | 5,476 | 1,369 | 0 | 7,330 | 461 | 220.6 | 1,289,177 | 55,855 | 1,233,322 | 51.7% |
| 5 | 2,738 | 684 | 1 | 9,943 | 634 | 110.3 | 644,640 | 27,930 | 616,710 | 74.4% |
| 6 | 2,738 | 684 | 1 | 9,943 | 634 | 110.3 | 644,640 | 27,930 | 616,710 | 74.4% |
| 7 | 1,369 | 342 | 1 | 9,943 | 634 | 55.2 | 322,360 | 13,966 | 308,393 | 85.3% |
| 8 | 1,369 | 342 | 1 | 9,943 | 634 | 55.2 | 322,360 | 13,966 | 308,393 | 85.3% |
| 9 | 684 | 171 | 1 | 9,943 | 634 | 27.6 | 161,219 | 6,985 | 154,234 | 92.1% |
| 10 | 684 | 171 | 1 | 9,943 | 634 | 27.6 | 161,219 | 6,985 | 154,234 | 92.1% |
### Fee scenario base, price USD 0.02 per IGN
| Year | Subsidy to miners, USD/day | Pool (20%) to provers, USD/day | Tips to miners+provers, USD/day | Job income to provers, USD/day (IGN jobs + external) | Burn, USD/day | Sustained hash, GH/s | 34% weight attack: rent 1.04 N for 20 d, USD | Attacker's subsidy over 20 d, USD | Net cost, USD | Fees as % of total security spend |
|---|---|---|---|---|---|---|---|---|---|---|
| 1 | 42,185 | 10,546 | 0 | 855 | 115 | 1,699.2 | 9,931,396 | 430,285 | 9,501,111 | 1.6% |
| 2 | 43,806 | 10,951 | 0 | 3,014 | 466 | 1,764.5 | 10,313,005 | 446,819 | 9,866,186 | 5.2% |
| 3 | 21,903 | 5,476 | 1 | 6,267 | 1,156 | 882.3 | 5,156,613 | 223,414 | 4,933,199 | 18.6% |
| 4 | 21,903 | 5,476 | 2 | 9,520 | 1,845 | 882.3 | 5,156,708 | 223,418 | 4,933,290 | 25.8% |
| 5 | 10,951 | 2,738 | 3 | 12,773 | 2,534 | 441.2 | 2,578,560 | 111,718 | 2,466,842 | 48.3% |
| 6 | 10,951 | 2,738 | 3 | 12,773 | 2,534 | 441.2 | 2,578,560 | 111,718 | 2,466,842 | 48.3% |
| 7 | 5,476 | 1,369 | 3 | 12,773 | 2,534 | 220.6 | 1,289,438 | 55,866 | 1,233,572 | 65.1% |
| 8 | 5,476 | 1,369 | 3 | 12,773 | 2,534 | 220.6 | 1,289,438 | 55,866 | 1,233,572 | 65.1% |
| 9 | 2,738 | 684 | 3 | 12,773 | 2,534 | 110.3 | 644,877 | 27,940 | 616,937 | 78.9% |
| 10 | 2,738 | 684 | 3 | 12,773 | 2,534 | 110.3 | 644,877 | 27,940 | 616,937 | 78.9% |
### Fee scenario base, price USD 0.1 per IGN
| Year | Subsidy to miners, USD/day | Pool (20%) to provers, USD/day | Tips to miners+provers, USD/day | Job income to provers, USD/day (IGN jobs + external) | Burn, USD/day | Sustained hash, GH/s | 34% weight attack: rent 1.04 N for 20 d, USD | Attacker's subsidy over 20 d, USD | Net cost, USD | Fees as % of total security spend |
|---|---|---|---|---|---|---|---|---|---|---|
| 1 | 210,924 | 52,731 | 0 | 2,473 | 575 | 8,495.9 | 49,656,982 | 2,151,426 | 47,505,556 | 0.9% |
| 2 | 219,028 | 54,757 | 1 | 7,869 | 2,332 | 8,822.4 | 51,565,024 | 2,234,093 | 49,330,931 | 2.8% |
| 3 | 109,514 | 27,379 | 5 | 14,534 | 5,778 | 4,411.3 | 25,783,066 | 1,117,071 | 24,665,995 | 9.6% |
| 4 | 109,514 | 27,379 | 9 | 21,198 | 9,225 | 4,411.4 | 25,783,541 | 1,117,091 | 24,666,449 | 13.4% |
| 5 | 54,757 | 13,689 | 13 | 27,863 | 12,672 | 2,205.9 | 12,892,799 | 558,590 | 12,334,209 | 28.9% |
| 6 | 54,757 | 13,689 | 13 | 27,863 | 12,672 | 2,205.9 | 12,892,799 | 558,590 | 12,334,209 | 28.9% |
| 7 | 27,379 | 6,845 | 13 | 27,863 | 12,672 | 1,103.1 | 6,447,190 | 279,329 | 6,167,861 | 44.9% |
| 8 | 27,379 | 6,845 | 13 | 27,863 | 12,672 | 1,103.1 | 6,447,190 | 279,329 | 6,167,861 | 44.9% |
| 9 | 13,689 | 3,422 | 13 | 27,863 | 12,672 | 551.7 | 3,224,386 | 139,699 | 3,084,687 | 62.0% |
| 10 | 13,689 | 3,422 | 13 | 27,863 | 12,672 | 551.7 | 3,224,386 | 139,699 | 3,084,687 | 62.0% |
### Fee scenario high, price USD 0.005 per IGN
| Year | Subsidy to miners, USD/day | Pool (20%) to provers, USD/day | Tips to miners+provers, USD/day | Job income to provers, USD/day (IGN jobs + external) | Burn, USD/day | Sustained hash, GH/s | 34% weight attack: rent 1.04 N for 20 d, USD | Attacker's subsidy over 20 d, USD | Net cost, USD | Fees as % of total security spend |
|---|---|---|---|---|---|---|---|---|---|---|
| 1 | 10,546 | 2,637 | 0 | 2,002 | 103 | 424.8 | 2,482,856 | 107,572 | 2,375,285 | 13.2% |
| 2 | 10,951 | 2,738 | 1 | 9,943 | 634 | 441.1 | 2,578,322 | 111,708 | 2,466,615 | 42.1% |
| 3 | 5,476 | 1,369 | 1 | 38,132 | 1,608 | 220.6 | 1,289,220 | 55,856 | 1,233,364 | 84.8% |
| 4 | 5,476 | 1,369 | 1 | 66,322 | 2,583 | 220.6 | 1,289,239 | 55,857 | 1,233,382 | 90.6% |
| 5 | 2,738 | 684 | 1 | 94,511 | 3,557 | 110.3 | 644,698 | 27,932 | 616,766 | 96.5% |
| 6 | 2,738 | 684 | 1 | 94,511 | 3,557 | 110.3 | 644,698 | 27,932 | 616,766 | 96.5% |
| 7 | 1,369 | 342 | 1 | 94,511 | 3,557 | 55.2 | 322,417 | 13,969 | 308,448 | 98.2% |
| 8 | 1,369 | 342 | 1 | 94,511 | 3,557 | 55.2 | 322,417 | 13,969 | 308,448 | 98.2% |
| 9 | 684 | 171 | 1 | 94,511 | 3,557 | 27.6 | 161,277 | 6,987 | 154,289 | 99.1% |
| 10 | 684 | 171 | 1 | 94,511 | 3,557 | 27.6 | 161,277 | 6,987 | 154,289 | 99.1% |
### Fee scenario high, price USD 0.02 per IGN
| Year | Subsidy to miners, USD/day | Pool (20%) to provers, USD/day | Tips to miners+provers, USD/day | Job income to provers, USD/day (IGN jobs + external) | Burn, USD/day | Sustained hash, GH/s | 34% weight attack: rent 1.04 N for 20 d, USD | Attacker's subsidy over 20 d, USD | Net cost, USD | Fees as % of total security spend |
|---|---|---|---|---|---|---|---|---|---|---|
| 1 | 42,185 | 10,546 | 0 | 2,609 | 412 | 1,699.2 | 9,931,425 | 430,286 | 9,501,138 | 4.7% |
| 2 | 43,806 | 10,951 | 3 | 12,773 | 2,534 | 1,764.5 | 10,313,290 | 446,831 | 9,866,459 | 18.9% |
| 3 | 21,903 | 5,476 | 3 | 44,529 | 6,433 | 882.3 | 5,156,880 | 223,426 | 4,933,454 | 61.9% |
| 4 | 21,903 | 5,476 | 4 | 76,286 | 10,331 | 882.3 | 5,156,957 | 223,429 | 4,933,528 | 73.6% |
| 5 | 10,951 | 2,738 | 5 | 108,043 | 14,230 | 441.2 | 2,578,790 | 111,728 | 2,467,062 | 88.8% |
| 6 | 10,951 | 2,738 | 5 | 108,043 | 14,230 | 441.2 | 2,578,790 | 111,728 | 2,467,062 | 88.8% |
| 7 | 5,476 | 1,369 | 5 | 108,043 | 14,230 | 220.7 | 1,289,668 | 55,876 | 1,233,793 | 94.0% |
| 8 | 5,476 | 1,369 | 5 | 108,043 | 14,230 | 220.7 | 1,289,668 | 55,876 | 1,233,793 | 94.0% |
| 9 | 2,738 | 684 | 5 | 108,043 | 14,230 | 110.4 | 645,108 | 27,950 | 617,158 | 96.9% |
| 10 | 2,738 | 684 | 5 | 108,043 | 14,230 | 110.4 | 645,108 | 27,950 | 617,158 | 96.9% |
### Fee scenario high, price USD 0.1 per IGN
| Year | Subsidy to miners, USD/day | Pool (20%) to provers, USD/day | Tips to miners+provers, USD/day | Job income to provers, USD/day (IGN jobs + external) | Burn, USD/day | Sustained hash, GH/s | 34% weight attack: rent 1.04 N for 20 d, USD | Attacker's subsidy over 20 d, USD | Net cost, USD | Fees as % of total security spend |
|---|---|---|---|---|---|---|---|---|---|---|
| 1 | 210,924 | 52,731 | 1 | 5,846 | 2,058 | 8,495.9 | 49,657,124 | 2,151,432 | 47,505,692 | 2.2% |
| 2 | 219,028 | 54,757 | 13 | 27,863 | 12,672 | 8,822.6 | 51,566,448 | 2,234,155 | 49,332,293 | 9.2% |
| 3 | 109,514 | 27,379 | 17 | 78,647 | 32,164 | 4,411.5 | 25,784,399 | 1,117,128 | 24,667,271 | 36.5% |
| 4 | 109,514 | 27,379 | 20 | 129,430 | 51,656 | 4,411.6 | 25,784,783 | 1,117,145 | 24,667,638 | 48.6% |
| 5 | 54,757 | 13,689 | 23 | 180,214 | 71,148 | 2,206.1 | 12,893,950 | 558,640 | 12,335,310 | 72.5% |
| 6 | 54,757 | 13,689 | 23 | 180,214 | 71,148 | 2,206.1 | 12,893,950 | 558,640 | 12,335,310 | 72.5% |
| 7 | 27,379 | 6,845 | 23 | 180,214 | 71,148 | 1,103.3 | 6,448,342 | 279,379 | 6,168,963 | 84.0% |
| 8 | 27,379 | 6,845 | 23 | 180,214 | 71,148 | 1,103.3 | 6,448,342 | 279,379 | 6,168,963 | 84.0% |
| 9 | 13,689 | 3,422 | 23 | 180,214 | 71,148 | 551.9 | 3,225,538 | 139,749 | 3,085,789 | 91.3% |
| 10 | 13,689 | 3,422 | 23 | 180,214 | 71,148 | 551.9 | 3,225,538 | 139,749 | 3,085,789 | 91.3% |
## 2. The year the attack gets cheap: the 20-day 34% weight attack's net cost by year and price (base fee scenario)
| Year | Net cost at USD 0.005 | at 0.02 | at 0.10 | Sustained hash at 0.005, GH/s | at 0.02 | at 0.10 |
|---|---|---|---|---|---|---|
| 1 | 2,375,278 | 9,501,111 | 47,505,556 | 424.8 | 1,699.2 | 8,495.9 |
| 2 | 2,466,547 | 9,866,186 | 49,330,931 | 441.1 | 1,764.5 | 8,822.4 |
| 3 | 1,233,300 | 4,933,199 | 24,665,995 | 220.6 | 882.3 | 4,411.3 |
| 4 | 1,233,322 | 4,933,290 | 24,666,449 | 220.6 | 882.3 | 4,411.4 |
| 5 | 616,710 | 2,466,842 | 12,334,209 | 110.3 | 441.2 | 2,205.9 |
| 6 | 616,710 | 2,466,842 | 12,334,209 | 110.3 | 441.2 | 2,205.9 |
| 7 | 308,393 | 1,233,572 | 6,167,861 | 55.2 | 220.6 | 1,103.1 |
| 8 | 308,393 | 1,233,572 | 6,167,861 | 55.2 | 220.6 | 1,103.1 |
| 9 | 154,234 | 616,937 | 3,084,687 | 27.6 | 110.3 | 551.7 |
| 10 | 154,234 | 616,937 | 3,084,687 | 27.6 | 110.3 | 551.7 |
Reading. Because the sustained hash is proportional to the subsidy and the attack cost is proportional to the hash, the ratio of attack cost to 20 days of subsidy is a constant: 1.04 x 281 / 24.8 = 11.8x the honest fleet's 20-day cost, minus the 51% the attacker earns back. The subsidy never 'falls under' the attack cost in ratio terms; what the halvings do is shrink both until the absolute number is small. The rows name the year the net cost of the veto drops under USD 1 M and under USD 100 k at each price, which is the honest statement.
## 3. The audits, and who pays the second one
| Review | Low, USD | High, USD | Source |
|---|---|---|---|
| Finality rule review (phase 4 gate) | 50,000 | 100,000 | funding.md section 2 |
| Node fork audit before public testnet | 60,000 | 120,000 | funding.md section 2 |
| Mixer cryptanalysis, two reviews (genesis gate) | 80,000 | 160,000 | funding.md section 2, the brief B1 to B8 |
| Execution layer and proving audit before mainnet | 80,000 | 150,000 | funding.md section 2 |
| Client and release process audit | 20,000 | 40,000 | funding.md section 2 |
| SECOND cryptanalysis after a class or era change (year 3) | 80,000 | 160,000 | the brief's question; priced as the first |
What the entity's own lines could earn (every one an input, none a forecast): the Ember dev fee at 1% of the rewards of the miners who keep it on, the entity's own provers' pool and job income, and the app share on the contracts it deploys.
| Year | Price | Dev fee ceiling at 100% on Ember, USD/year | At 50% | At 20% | Entity's provers at 5% of the pool, USD/year | Second cryptanalysis as % of the 50% dev fee |
|---|---|---|---|---|---|---|
| 1 | 0.005 | 48,150 | 24,075 | 9,630 | 48,150 | 665% |
| 1 | 0.02 | 192,600 | 96,300 | 38,520 | 192,600 | 166% |
| 1 | 0.1 | 963,000 | 481,500 | 192,600 | 963,000 | 33% |
| 2 | 0.005 | 50,000 | 25,000 | 10,000 | 50,000 | 640% |
| 2 | 0.02 | 200,000 | 100,000 | 40,000 | 200,000 | 160% |
| 2 | 0.1 | 1,000,000 | 500,000 | 200,000 | 1,000,000 | 32% |
| 3 | 0.005 | 25,000 | 12,500 | 5,000 | 25,000 | 1280% |
| 3 | 0.02 | 100,000 | 50,000 | 20,000 | 100,000 | 320% |
| 3 | 0.1 | 500,000 | 250,000 | 100,000 | 500,000 | 64% |
| 4 | 0.005 | 25,000 | 12,500 | 5,000 | 25,000 | 1280% |
| 4 | 0.02 | 100,000 | 50,000 | 20,000 | 100,000 | 320% |
| 4 | 0.1 | 500,000 | 250,000 | 100,000 | 500,000 | 64% |
| 5 | 0.005 | 12,500 | 6,250 | 2,500 | 12,500 | 2560% |
| 5 | 0.02 | 50,000 | 25,000 | 10,000 | 50,000 | 640% |
| 5 | 0.1 | 250,000 | 125,000 | 50,000 | 250,000 | 128% |

View file

@ -0,0 +1,80 @@
#!/usr/bin/env python3
"""Horizon lane 4, task 5: miner-signalled parameters, the arithmetic of the thresholds.
Lane 3 (sim/horizon/consensus-security/signalling.py, signalling_results.md) already prices the 95-percent
one-day rule: the 6-percent holdout, the forced flip at 19 N for a day, signal-then-defect on a PoW class.
This script adds the other two thresholds the spec carries (60 percent over two weeks for a parameter the
genesis rules leave to miners, spec 5.5 and 5.8; 90 percent for an upgrade, spec 5.7), the 30-percent pool,
and the cost of forcing or blocking each. Pure Python; arithmetic.
python3 signal_game.py > signal_game_out.md
Inputs: rental USD 11.7 per GH/s-hour (measured, bench-log 6 Oct 2026); network hash N at 1, 10, 100, 1000 GH/s;
subsidy 31.688 IGN/s, 80 percent to producers; IGN prices 0.005, 0.02, 0.10 (assumptions).
"""
RENT_GHS_H = 11.7
SUBSIDY = 31.688 * 0.8 * 3600 # IGN per hour to producers
PRICES = [0.005, 0.02, 0.10]
NETS = [1, 10, 100, 1000]
RULES = [
("parameter (60% of blue blocks, 14-day window)", 0.60, 14 * 24, "spec 5.5, 5.8"),
("upgrade (90% of blue blocks, window open O-5.3; 14 days assumed)", 0.90, 14 * 24, "spec 5.7"),
("class activation (95% of blue blocks, 1-day window, floor height)", 0.95, 24, "P2, counter-asic-3-node.md section 6"),
("class activation, 7 consecutive days (lane 3's proposal)", 0.95, 7 * 24, "signalling_results.md section 5"),
]
def usd(x):
return f"{x:,.0f}"
def main():
print("# Signalling thresholds: what it costs to force, what it costs to block (generated by signal_game.py)\n")
print("## 1. Forcing a signal with rented hash against a fleet that does not want it\n")
print("A renter must hold the threshold share of the window's blue blocks: rent = T/(1-T) x N for the whole window. Earns its own producer subsidy meanwhile.\n")
print("| Rule | Threshold | Window h | Rented hash as a multiple of N | Rent at N = 1 GH/s | 10 | 100 | 1000 | Subsidy earned back at USD 0.02, N = 100 GH/s |")
print("|---|---|---|---|---|---|---|---|---|")
for name, th, hours, src in RULES:
mult = th / (1 - th)
row = [name, f"{th:.0%}", f"{hours}", f"{mult:.1f} N"]
for n in NETS:
row.append(usd(mult * n * RENT_GHS_H * hours))
earned = th * SUBSIDY * hours * 0.02
row.append(usd(earned))
print("| " + " | ".join(row) + " |")
print()
print("## 2. Blocking a signal: the holdout a rule tolerates, and what a 30% pool can do\n")
print("| Rule | Holdout that blocks | Rented holdout as a multiple of N | Rent per day at 1 GH/s | at 100 GH/s | A 30% pool alone | A 30% pool plus a 6% renter | What ends the block |")
print("|---|---|---|---|---|---|---|---|")
for name, th, hours, src in RULES:
hold = 1 - th
mult = (hold + 0.001) / (1 - hold - 0.001)
can30 = "blocks it" if 0.30 > hold else "cannot block"
can36 = "blocks it" if 0.36 > hold else "cannot block"
ends = "the proposal fails and is re-registered; nothing else" if th < 0.95 else "the floor height (the fixed height returns with its 6 October hazard)"
print(f"| {name} | over {hold:.0%} of blue blocks | {mult:.3f} N | {usd(mult * 1 * RENT_GHS_H * 24)} | {usd(mult * 100 * RENT_GHS_H * 24)} | {can30} | {can36} | {ends} |")
print()
print("Reading. At 60% a 30% pool can block nothing on its own and needs 11 more points of blocks; at 90% and 95% any pool above 10% or 5% holds a veto, "
"and the devnet's top three vote keys held 34.5% of blocks on 4 October 2026 (litepaper, Governance). A holdout's cost is near zero because it mines and is paid "
"like anyone (lane 3 section 2); the only thing that ends a holdout against a class change is the floor height, and against a parameter or upgrade proposal, nothing: "
"the proposal fails. So the 95% rule hands every pool above 5% a veto over class changes until the floor, and the 90% rule hands every pool above 10% a permanent veto over upgrades.\n")
print("## 3. Signal then defect, by what is being signalled\n")
print("| What is signalled | What a defector (signalled, then runs the old rule) produces after the flip | Who is hurt | Bound |")
print("|---|---|---|---|")
print("| A PoW class (P2) | headers whose PoW fails under the new program: refused by every node (`check_header_version`, then the PoW check; lane 3 section 4) | the defector alone: its blocks are lost | its own income |")
print("| An execution parameter (`B_p`, a floor, the pgas table; spec 5.11) | valid blocks (blocks carry transactions only, design 1.1) but a different execution state: every proof record it sees fails its native statement and pays nothing; every record it signs fails everyone else's | the defector's provers earn nothing; its users read wrong balances until it notices. The consensus digest carries `Params.fees` (spec 5.11), so a node whose digest differs is refused at the handshake (G12, X18): the defector is isolated, not split | zero to others IF the parameter is in the digest and the digest is in the handshake; a parameter outside the digest is a silent state fork for that node |")
print("| A consensus rule (an upgrade, 5.7) | blocks valid under the old rule and invalid under the new: a chain split for the defector and for anyone following it | the defector's hashers and anyone who trusts its blocks | the defector's share of hash; the 2/3 finality floor keeps the split's locks from certifying while honest weight stays |")
print()
print("## 4. The two-week 60% window against a day of 95%: what each buys\n")
print("| Rule | Honest latency from 'the fleet is ready' to 'in force' | Renter's cost to force at 100 GH/s | Who can block | Suits |")
print("|---|---|---|---|---|")
print(f"| 60% over 14 days | 14 days | {usd(1.5 * 100 * RENT_GHS_H * 14 * 24)} | over 40% of blocks | economic dials where a hostile majority can only move a bounded number (the floors, `B_p`, the window) |")
print(f"| 90% over 14 days | 14 days | {usd(9 * 100 * RENT_GHS_H * 14 * 24)} | over 10% of blocks | rule changes where a split is the danger |")
print(f"| 95% over 1 day | 1 day | {usd(19 * 100 * RENT_GHS_H * 24)} | over 5% of blocks, until the floor | the PoW class flip, where the floor is the backstop and the danger is a half-updated fleet |")
print(f"| 95% over 7 days | 7 days | {usd(19 * 100 * RENT_GHS_H * 7 * 24)} | over 5% of blocks, until the floor | the same, with a week of public warning |")
print()
if __name__ == "__main__":
main()

View file

@ -0,0 +1,41 @@
# Signalling thresholds: what it costs to force, what it costs to block (generated by signal_game.py)
## 1. Forcing a signal with rented hash against a fleet that does not want it
A renter must hold the threshold share of the window's blue blocks: rent = T/(1-T) x N for the whole window. Earns its own producer subsidy meanwhile.
| Rule | Threshold | Window h | Rented hash as a multiple of N | Rent at N = 1 GH/s | 10 | 100 | 1000 | Subsidy earned back at USD 0.02, N = 100 GH/s |
|---|---|---|---|---|---|---|---|---|
| parameter (60% of blue blocks, 14-day window) | 60% | 336 | 1.5 N | 5,897 | 58,968 | 589,680 | 5,896,800 | 367,966 |
| upgrade (90% of blue blocks, window open O-5.3; 14 days assumed) | 90% | 336 | 9.0 N | 35,381 | 353,808 | 3,538,080 | 35,380,800 | 551,949 |
| class activation (95% of blue blocks, 1-day window, floor height) | 95% | 24 | 19.0 N | 5,335 | 53,352 | 533,520 | 5,335,200 | 41,615 |
| class activation, 7 consecutive days (lane 3's proposal) | 95% | 168 | 19.0 N | 37,346 | 373,464 | 3,734,640 | 37,346,400 | 291,307 |
## 2. Blocking a signal: the holdout a rule tolerates, and what a 30% pool can do
| Rule | Holdout that blocks | Rented holdout as a multiple of N | Rent per day at 1 GH/s | at 100 GH/s | A 30% pool alone | A 30% pool plus a 6% renter | What ends the block |
|---|---|---|---|---|---|---|---|
| parameter (60% of blue blocks, 14-day window) | over 40% of blue blocks | 0.669 N | 188 | 18,798 | cannot block | cannot block | the proposal fails and is re-registered; nothing else |
| upgrade (90% of blue blocks, window open O-5.3; 14 days assumed) | over 10% of blue blocks | 0.112 N | 32 | 3,155 | blocks it | blocks it | the proposal fails and is re-registered; nothing else |
| class activation (95% of blue blocks, 1-day window, floor height) | over 5% of blue blocks | 0.054 N | 15 | 1,509 | blocks it | blocks it | the floor height (the fixed height returns with its 6 October hazard) |
| class activation, 7 consecutive days (lane 3's proposal) | over 5% of blue blocks | 0.054 N | 15 | 1,509 | blocks it | blocks it | the floor height (the fixed height returns with its 6 October hazard) |
Reading. At 60% a 30% pool can block nothing on its own and needs 11 more points of blocks; at 90% and 95% any pool above 10% or 5% holds a veto, and the devnet's top three vote keys held 34.5% of blocks on 4 October 2026 (litepaper, Governance). A holdout's cost is near zero because it mines and is paid like anyone (lane 3 section 2); the only thing that ends a holdout against a class change is the floor height, and against a parameter or upgrade proposal, nothing: the proposal fails. So the 95% rule hands every pool above 5% a veto over class changes until the floor, and the 90% rule hands every pool above 10% a permanent veto over upgrades.
## 3. Signal then defect, by what is being signalled
| What is signalled | What a defector (signalled, then runs the old rule) produces after the flip | Who is hurt | Bound |
|---|---|---|---|
| A PoW class (P2) | headers whose PoW fails under the new program: refused by every node (`check_header_version`, then the PoW check; lane 3 section 4) | the defector alone: its blocks are lost | its own income |
| An execution parameter (`B_p`, a floor, the pgas table; spec 5.11) | valid blocks (blocks carry transactions only, design 1.1) but a different execution state: every proof record it sees fails its native statement and pays nothing; every record it signs fails everyone else's | the defector's provers earn nothing; its users read wrong balances until it notices. The consensus digest carries `Params.fees` (spec 5.11), so a node whose digest differs is refused at the handshake (G12, X18): the defector is isolated, not split | zero to others IF the parameter is in the digest and the digest is in the handshake; a parameter outside the digest is a silent state fork for that node |
| A consensus rule (an upgrade, 5.7) | blocks valid under the old rule and invalid under the new: a chain split for the defector and for anyone following it | the defector's hashers and anyone who trusts its blocks | the defector's share of hash; the 2/3 finality floor keeps the split's locks from certifying while honest weight stays |
## 4. The two-week 60% window against a day of 95%: what each buys
| Rule | Honest latency from 'the fleet is ready' to 'in force' | Renter's cost to force at 100 GH/s | Who can block | Suits |
|---|---|---|---|---|
| 60% over 14 days | 14 days | 589,680 | over 40% of blocks | economic dials where a hostile majority can only move a bounded number (the floors, `B_p`, the window) |
| 90% over 14 days | 14 days | 3,538,080 | over 10% of blocks | rule changes where a split is the danger |
| 95% over 1 day | 1 day | 533,520 | over 5% of blocks, until the floor | the PoW class flip, where the floor is the backstop and the danger is a half-updated fleet |
| 95% over 7 days | 7 days | 3,734,640 | over 5% of blocks, until the floor | the same, with a week of public warning |

View file

@ -0,0 +1,777 @@
#!/usr/bin/env python3
"""Igneum economy under stress, Horizon lane 4 (6 October 2026): sim/economy/sim.py re-run with the
measured card table and the measured rental price.
A copy of sim/economy/sim.py (4 October 2026) with these changes, and nothing else in the market model:
1. The four approximate card classes are replaced by the ELEVEN cards measured on rented GPUs on
6 October 2026 (docs/analysis/prover-tiers-real-cards.md): hash rate and watts while mining, the
v1 shard time proving alone (patched server, 2^26, compressed) and the shard time BESIDE the running
miner. Hybrid mode uses the beside time, and costs the miner the measured 4% of hash (5090,
bench-log proving v1 step 1; approximate for the other cards) plus about 70 W, instead of the
earlier model's full hash loss for the proof's duration and a 5-s program swap.
2. Operator 0 (the 20% farm) is a RENTER: its cost is USD 0.0117 per MH/s-hour for every card-hour it
keeps on (bench-log "Rental cost of hash, 6 October 2026"), not electricity. It switches off when
the rent beats its income like any operator.
3. The exclusive window is 25 s and the external claim timeout 120 s (the project lead's decision of 6 October
2026, ledger P9), the design values the phase 4 devnet starts from.
4. The backlog is a FIFO of shard arrivals; a shard unproven for longer than the 600-block record
window (spec 7.7 item 3) expires: its pool credit is STRANDED in the escrow (no rule returns it),
which is what the 20% pool does when nobody claims.
5. Burn per day is reported: 10% of IGN-settled external jobs plus the base fee of the chain's
paid traffic at the adopted floors (0.51 IGN per 30,000-pgas shard of transfers: 0.30 proving + 0.21
execution; spec 5.11). The proving load is one shard per block (every block has its shard, measured
on the devnet) and the paid content a fraction of a shard (0.03 at launch traffic).
6. New scenarios: price x10 and /10, external demand zero (c) and x100, a cartel of the top 10% of
weight that never proves, a refusal by every prover for days 10 to 20, and the first halving.
Usage
python3 stress.py the lane's eight scenarios, 3 seeds, markdown
python3 stress.py --scenarios a,p10 --seeds 1 a subset
python3 stress.py --set pool=0.3,window=20 override parameters
Run on the Mac under the main checkout's lock: /Users/joshm/Projects/igneum/tools/lock/with-lock.sh run nice -n 19 python3 stress.py
All figures are approximate unless the source line says measured. See docs/analysis/horizon/economy-and-utility.md.
"""
import argparse
import math
import sys
import time
import numpy as np
# ---------------------------------------------------------------- parameters
P = dict(
tick=180.0, # s per tick
days=30,
n_ops=1000,
emission=31.688, # IGN per block, pre-halving, ramp complete (spec 2.5, designed)
pool=0.20, # proving pool share of emission (spec 2.5, 5.3)
window=25.0, # sortition exclusive window, s (P9 decision 6 Oct 2026; was 10)
assignees=8, # provers drawn per shard (spec 7.2)
burn=0.10, # external job burn (spec 5.4)
timeout=120.0, # external job claim timeout, s (P9 decision 6 Oct 2026; was 300)
shards_per_block=1.0, # proving load: shards per block; every block has at least its one (empty) shard (measured on the devnet, bench-log proving v1: one shard a block); 3 = the 4 October busy value
content_shards_per_block=0.03, # shards of paid transactions per block at launch traffic (utility.py: 1 M transfers a day = 0.039); only these burn a base fee
job_work=10.0, # shard-equivalents of work per external job
ext_usd_day=2000.0, # external demand, dollars per day (customer brief: market low millions/yr)
ext_mult=1.0, # scenario multiplier on the dollar price per job
price0=0.012, # $ per IGN at t=0 (assumption)
vol_day=0.05, # price daily volatility
hyb_loss=0.04, # hash lost while a hybrid card proves (measured 5090; approximate elsewhere)
hyb_extra_w=70.0, # extra watts while a hybrid card proves (measured 5090; approximate elsewhere)
aggregation=4.0, # aggregation plus inclusion, s, added to every block proof (approximate)
waste=0.5, # wasted duplicate attempts per open-claim shard (race losers)
hyst_lo=0.05, hyst_hi=0.25,
dwell_ticks=20, # minimum ticks between switches of one (operator, class): 1 h
decide_every=4, # ticks between decisions: 12 min
ema_ticks=20.0, # observation window for realised rates: 1 h
farm_share=0.20, # operator 0 share of total hash
renter_usd_mhs_h=0.0117, # the renter's cost per MH/s-hour (measured, bench-log 6 Oct 2026); 0 = electricity
backlog_rule=600.0, # s of oldest age per halving of B_p (design 4.3)
record_window=600.0, # s after which an unproven shard's credit is stranded (spec 7.7 item 3)
burn_per_shard=0.51, # IGN of base fee burned per 30,000-pgas shard of transfers at the floors (spec 5.11)
cartel_weight=0.10, # the cartel scenario: the top operators holding this share of weight never prove
refuse_from=10.0, refuse_to=20.0, # the refusal scenario: no prover answers or claims between these days
)
# card classes: the eleven measured cards (docs/analysis/prover-tiers-real-cards.md, 6 October 2026)
CLS = ["3060", "3080", "3090", "4060Ti16", "4060Ti8", "4060", "4070", "4090", "5070", "5090", "A5000"]
HASH = np.array([23.78, 40.82, 37.79, 17.58, 19.07, 17.07, 24.99, 52.25, 41.89, 98.48, 47.70]) # MH/s, measured
PMINE = np.array([103.7, 204.9, 228.8, 72.3, 72.6, 115.0, 91.1, 183.1, 137.0, 258.2, 222.7]) # W mining, measured (4060 read 0 W: 115 W rated, approximate)
PPROVE = PMINE.copy() # W proving alone, approximate (the mining draw)
PIDLE = np.maximum(15.0, 0.2 * PMINE) # W proving-ready idle, approximate
TPROVE = np.array([14.4, 7.1, 14.9, 11.6, 9.6, 18.4, 12.1, 6.3, 4.8, 6.3, 8.3]) # s per v1 shard alone, measured
TBESIDE = np.array([37.5, 25.6, 19.9, 34.6, 26.3, 22.1, 27.3, 26.1, 37.2, 10.7, 34.6]) # s per shard beside the miner, measured (8 GB rows: core-only)
CANHYB = np.array([True, True, True, True, False, False, True, True, True, True, True]) # compressed shard beside the miner fits (8 GB: core-only, not paid as compressed)
CANPROVE = np.ones(11, bool)
MIX = np.array([0.20, 0.05, 0.08, 0.05, 0.07, 0.10, 0.12, 0.10, 0.08, 0.10, 0.05]) # fleet mix by count, approximate (installed-base shape)
OFF, MINE, PROVE, HYB = 0, 1, 2, 3
NC = len(CLS)
def build_population(rng, p, extra=0):
n = p["n_ops"] + extra
size = np.maximum(1, np.round(rng.lognormal(math.log(5.0), 1.2, n))).astype(int)
cards = np.zeros((n, NC), dtype=float)
for i in range(n):
cards[i] = rng.multinomial(size[i], MIX)
z = (np.log(size) - np.log(5.0)) / 1.2
elec = np.exp(rng.normal(math.log(0.10) - 0.25 * z, 0.40, n))
elec = np.clip(elec, 0.02, 0.40)
# operator 0: the farm, all 5090s, sized to farm_share of total hash; a renter at the measured rental price
cards[0] = 0
rest = (cards[1:p["n_ops"]] * HASH).sum()
farm_hash = rest * p["farm_share"] / (1 - p["farm_share"])
cards[0, CLS.index("5090")] = max(1, round(farm_hash / HASH[CLS.index("5090")]))
elec[0] = 0.05
hyst = rng.uniform(p["hyst_lo"], p["hyst_hi"], n)
phase = rng.integers(0, p["decide_every"], n)
return cards, elec, hyst, phase
class Sim:
def __init__(self, scenario, seed, p):
self.p = dict(p)
self.sc = scenario
self.rng = np.random.default_rng(seed)
p = self.p
extra = 0
if scenario == "f":
extra = 200
if scenario == "e":
p["farm_share"] = 0.30
if scenario == "halving":
p["emission"] = 31.688 / 2
self.cards, self.elec, self.hyst, self.phase = build_population(self.rng, p, extra)
self.n = self.cards.shape[0]
self.n_base = p["n_ops"]
self.active = np.ones(self.n, bool)
if extra:
self.active[self.n_base:] = False
base_hash = (self.cards[: self.n_base] * HASH).sum()
pool_hash = (self.cards[self.n_base:] * HASH).sum()
self.cards[self.n_base:] *= base_hash / pool_hash
self.cards[self.n_base:] = np.round(self.cards[self.n_base:])
self.elec[self.n_base:] = np.clip(self.elec[self.n_base:], 0.02, 0.08)
self.mode = np.full((self.n, NC), MINE, dtype=int)
self.mode[self.cards == 0] = OFF
for i in range(self.n):
for c in range(NC):
if self.cards[i, c] > 0 and CANPROVE[c] and self.rng.random() < 0.20:
self.mode[i, c] = PROVE
if not self.active.all():
self.mode[~self.active] = OFF
self.fixed_mine = np.zeros(self.n, bool)
self.withhold = np.zeros(self.n, bool)
if scenario == "e":
self.fixed_mine[0] = True
self.withhold[0] = True
self.mode[0] = np.where(self.cards[0] > 0, MINE, OFF)
self.dead = np.zeros(self.n, bool)
self.hist = np.zeros((self.n, 30))
h0 = (self.cards * HASH * (self.mode != OFF)).sum(1)
h0[~self.active] = 0
self.hist[:] = (h0 / h0.sum() * 86400.0)[:, None]
self.w = self.hist.sum(1)
if scenario == "cartel":
# the operators holding the top cartel_weight of the 30-day weight never answer an assignment and never claim
order = np.argsort(-self.w)
cum = np.cumsum(self.w[order]) / self.w.sum()
k = int(np.searchsorted(cum, p["cartel_weight"])) + 1
self.withhold[order[:k]] = True
self.fixed_mine[order[:k]] = True
for i in order[:k]:
self.mode[i] = np.where(self.cards[i] > 0, MINE, OFF)
self.cartel_n = k
else:
self.cartel_n = 0
self.cards_active = self.cards[self.active].sum()
self.day = 0
self.last_switch = np.full((self.n, NC), -10**9, dtype=int)
self.price = p["price0"]
self.H_est = h0.sum() * 1e6
self.D = self.H_est * 1.0
self.queue = [] # FIFO of [arrival tick, shards remaining, pool credit per shard in IGN]
self.q = 0.0
self.age = 0.0
self.duty = np.zeros((self.n, NC))
self.R_hash = p["emission"] * (1 - p["pool"]) / self.H_est
self.open_pc = np.zeros(NC)
self.asg_pw = 0.0
self.asg_pw_h = 0.0
self.asg_ext_pw = 0.0
self.t_open = p["window"] + TPROVE.min()
self.eligible = CANPROVE.copy()
self.ok60 = 1.0
self.ok20 = 1.0
self.hourly = []
self.hour_acc = []
self.cls_profit = np.zeros(NC)
self.cls_cards = np.zeros(NC)
self.cls_shards = np.zeros(NC)
self.ext_served = 0.0
self.ext_demand = 0.0
self.burned_ign = 0.0
self.burn_ext_ign = 0.0
self.burn_base_ign = 0.0
self.stranded_ign = 0.0
self.pool_paid_ign = 0.0
self.farm_rent_usd = 0.0
self.farm_income_usd = 0.0
self.switches = 0
def refusing(self, day_f):
return self.sc == "refuse" and self.p["refuse_from"] <= day_f < self.p["refuse_to"]
# ------------------------------------------------------------ one tick
def step(self, t):
p = self.p
T = p["tick"]
day_f = t * T / 86400.0
rng = self.rng
if self.sc == "d" and day_f >= 10 and not self.dead[0]:
self.dead[0] = True
self.mode[0] = OFF
if self.sc == "f" and day_f >= 10 and not self.active[self.n_base]:
self.active[self.n_base:] = True
self.mode[self.n_base:] = np.where(self.cards[self.n_base:] > 0, MINE, OFF)
self.cards_active = self.cards[self.active].sum()
ext_mult = p["ext_mult"]
if self.sc == "b" and day_f >= 7:
ext_mult = 10.0
if self.sc == "x100" and day_f >= 7:
ext_mult = 100.0
if self.sc == "c":
ext_mult = 0.0
# price
dt = T / 86400.0
self.price *= math.exp(p["vol_day"] * math.sqrt(dt) * rng.standard_normal() - 0.5 * p["vol_day"] ** 2 * dt)
if self.sc == "b" and 7 <= day_f < 14:
self.price *= math.exp(math.log(0.30) / (7 * 86400.0 / T))
if self.sc in ("p10", "pd10") and day_f >= 7 and not getattr(self, "shocked", False):
self.shocked = True
self.price *= 10.0 if self.sc == "p10" else 0.1
price = self.price
refusing = self.refusing(day_f)
cards, mode = self.cards, self.mode
is_m = mode == MINE
is_p = mode == PROVE
is_h = mode == HYB
hcards = cards * HASH * 1e6
h_op = (hcards * (is_m + is_h * (1 - p["hyb_loss"] * self.duty))).sum(1)
H = h_op.sum()
if H <= 0:
H = 1.0
lam = T * H / self.D
blocks = rng.poisson(lam)
self.H_est += (H - self.H_est) * min(1.0, blocks / 120.0)
ratio = self.H_est / self.D
ratio = min(max(ratio, 0.90 ** blocks), 1.03 ** blocks)
self.D *= ratio
E = p["emission"]
mined = rng.multinomial(blocks, h_op / H) if blocks > 0 else np.zeros(self.n, int)
self.hist[:, self.day % 30] += mined
self.w += mined
w = self.w
W = w.sum()
mine_ign = mined * E * (1 - p["pool"])
pool_ign = blocks * E * p["pool"]
spb = p["shards_per_block"] * 0.5 ** math.floor(self.age / p["backlog_rule"])
shards = rng.poisson(blocks * spb) if blocks > 0 else 0
pool_per_shard = pool_ign / shards if shards > 0 else 0.0
self.burn_base_ign += blocks * p["content_shards_per_block"] * p["burn_per_shard"]
capP = np.where(is_p, cards * T / TPROVE, 0.0) * CANPROVE
capH = np.where(is_h, cards * T / TBESIDE, 0.0) * CANHYB
capP[self.dead | ~self.active] = 0
capH[self.dead | ~self.active] = 0
if refusing:
capP[:] = 0
capH[:] = 0
capP_op = capP.sum(1)
capH_op = capH.sum(1)
resp = ((capP_op + capH_op) >= 1.0) & ~self.withhold & ~self.dead
a = (w * resp).sum() / W if W > 0 else 0.0
p_resp = 1 - (1 - a) ** p["assignees"]
shards_served = np.zeros((self.n, NC))
jobs_usd = p["ext_usd_day"] * ext_mult
job_price = 50.0 * ext_mult
jobs = rng.poisson(p["ext_usd_day"] / 50.0 * T / 86400.0) if jobs_usd > 0 else 0
ext_work = jobs * p["job_work"]
ext_pay_per_shard = job_price / p["job_work"] * (1 - p["burn"])
eligible = (p["job_work"] * TPROVE <= p["timeout"]) & CANPROVE
ext_served = np.zeros((self.n, NC))
ext_asg = [0.0]
ext_open = np.zeros(NC)
self.ext_demand += ext_work
internal_first = pool_per_shard * price >= ext_pay_per_shard
def serve_external():
nonlocal ext_work
if ext_work <= 0:
return
capE_P = capP * eligible
capE_H = capH * eligible
capE_op = capE_P.sum(1) + capE_H.sum(1)
respE = (capE_op >= 1.0) & ~self.withhold & ~self.dead
aE = (w * respE).sum() / W if W > 0 else 0.0
pE = 1 - (1 - aE) ** p["assignees"]
s_asg = ext_work * pE
over = 0.0
wrE = w * respE
WrE = wrE.sum()
if s_asg > 0 and WrE > 0:
x = s_asg * wrE / WrE
got = np.minimum(x, capE_op)
over = s_asg - got.sum()
capE = capE_P + capE_H
frac = capE / np.maximum(capE.sum(1, keepdims=True), 1e-9)
alloc = got[:, None] * frac
ext_served[:] += alloc
ext_asg[0] += got.sum()
usedP = alloc * (capE_P / np.maximum(capE, 1e-9))
capP[:] -= usedP
capH[:] -= alloc - usedP
else:
over = s_asg
open_w = ext_work - s_asg + over
for lat, cap, c in self.open_order(capP, capH):
if not eligible[c] or open_w <= 0:
continue
tot = cap[:, c].sum()
if tot <= 0:
continue
take = min(open_w, tot / (1 + p["waste"]))
alloc = cap[:, c] * (take / tot)
ext_served[:, c] += alloc
ext_open[c] += take
cap[:, c] -= alloc * (1 + p["waste"])
open_w -= take
ext_work = open_w
if not internal_first:
serve_external()
wr = w * resp
Wr = wr.sum()
if Wr > 0:
capPH = capP + capH
mixP = (wr[:, None] * (capP / np.maximum(capPH.sum(1, keepdims=True), 1e-9))).sum(0) / Wr
mixH = (wr[:, None] * (capH / np.maximum(capPH.sum(1, keepdims=True), 1e-9))).sum(0) / Wr
else:
mixP = np.zeros(NC)
mixH = np.zeros(NC)
t_open = p["window"] + np.inf
for lat, cap, c in self.open_order(capP, capH):
if cap[:, c].sum() > 0:
t_open = p["window"] + lat
break
LA_P = TPROVE
LA_H = TBESIDE
winP = (LA_P <= t_open) * mixP
winH = (LA_H <= t_open) * mixH
p_win = winP.sum() + winH.sum()
s_asg = shards * p_resp * p_win
if s_asg > 0 and Wr > 0:
x = s_asg * wr / Wr
capPH_op = capP.sum(1) + capH.sum(1)
got = np.minimum(x, capPH_op)
overflow = s_asg - got.sum()
capPH = capP + capH
frac = capPH / np.maximum(capPH.sum(1, keepdims=True), 1e-9)
alloc = got[:, None] * frac
shards_served += alloc
usedP = alloc * (capP / np.maximum(capPH, 1e-9))
usedH = alloc - usedP
capP -= usedP
capH -= usedH
else:
overflow = s_asg
open_new = shards - s_asg + overflow
# the FIFO backlog: new open shards join the queue, the queue is served oldest first, the expired strand their credit
if open_new > 0:
self.queue.append([t, float(open_new), pool_per_shard])
q_prev = self.q
open_total = sum(e[1] for e in self.queue)
served_open = 0.0
open_by_cls = np.zeros(NC)
open_lat = np.zeros(NC)
for lat, cap, c in self.open_order(capP, capH):
tot = cap[:, c].sum()
if tot <= 0 or open_total - served_open <= 0:
continue
take = min(open_total - served_open, tot / (1 + p["waste"]))
alloc = cap[:, c] * (take / tot)
shards_served[:, c] += alloc
open_by_cls[c] += take
open_lat[c] += take * lat
cap[:, c] -= alloc * (1 + p["waste"])
served_open += take
# drain the FIFO by what was served (oldest first), paying each served shard its own credit
remaining = served_open
paid_open_ign = 0.0
while remaining > 1e-12 and self.queue:
e = self.queue[0]
take = min(remaining, e[1])
e[1] -= take
remaining -= take
paid_open_ign += take * e[2]
if e[1] <= 1e-12:
self.queue.pop(0)
# expire what has waited longer than the record window
keep = []
for e in self.queue:
if (t - e[0]) * T > p["record_window"]:
self.stranded_ign += e[1] * e[2]
else:
keep.append(e)
self.queue = keep
self.q = sum(e[1] for e in self.queue)
self.age = ((t - self.queue[0][0]) * T + p["window"]) if self.queue else 0.0
if internal_first:
serve_external()
self.ext_served += ext_served.sum()
wait = q_prev / max(served_open / T, 1e-9) if q_prev > 0 else 0.0
thr60 = 60.0 - p["aggregation"]
thr20 = 20.0 - p["aggregation"]
p_q = min(1.0, max(0.0, (open_new - max(0.0, served_open - q_prev)) / max(shards, 1e-9))) if shards > 0 else 0.0
pa = p_resp
p_assigned_won = pa * p_win
okA60 = pa * (((LA_P <= t_open) & (LA_P <= thr60)) * mixP).sum() + pa * (((LA_H <= t_open) & (LA_H <= thr60)) * mixH).sum()
okA20 = pa * (((LA_P <= t_open) & (LA_P <= thr20)) * mixP).sum() + pa * (((LA_H <= t_open) & (LA_H <= thr20)) * mixH).sum()
if open_by_cls.sum() > 0:
l_open = p["window"] + wait + open_lat / np.maximum(open_by_cls, 1e-9)
sh = open_by_cls / open_by_cls.sum()
okO60 = (sh * (l_open <= thr60)).sum()
okO20 = (sh * (l_open <= thr20)).sum()
else:
okO60 = okO20 = 0.0
p_open_served = max(0.0, 1 - p_assigned_won - p_q)
ps60 = min(1.0, okA60 + p_open_served * okO60)
ps20 = min(1.0, okA20 + p_open_served * okO20)
lam_s = max(spb, 1e-9)
def block_ok(ps):
return (math.exp(-lam_s * (1 - ps)) - math.exp(-lam_s)) / (1 - math.exp(-lam_s))
self.ok60 = block_ok(ps60) if not refusing else 0.0
self.ok20 = block_ok(ps20) if not refusing else 0.0
# income and power
# assigned shards are paid this tick's credit; open shards are paid the credit of the shard they cleared (FIFO)
asg_served = shards_served.sum() - open_by_cls.sum()
prove_ign = shards_served * pool_per_shard
# correct the open part to the FIFO credit: scale the open-served rows by paid_open / (served_open x pool_per_shard)
if served_open > 0 and pool_per_shard > 0:
corr = paid_open_ign / (served_open * pool_per_shard)
else:
corr = 1.0
# the fluid allocation cannot tell assigned from open per operator; apply the correction to the whole row proportionally
tot_served = max(shards_served.sum(), 1e-9)
prove_ign *= (asg_served + served_open * corr) / tot_served if tot_served > 1e-9 else 1.0
self.pool_paid_ign += prove_ign.sum()
ext_usd = ext_served * ext_pay_per_shard
self.burn_ext_ign += ext_served.sum() * (job_price / p["job_work"]) * p["burn"] / max(price, 1e-9)
self.burned_ign = self.burn_ext_ign + self.burn_base_ign
busyP = np.where(is_p, np.minimum(1.0, (shards_served + ext_served) * TPROVE / np.maximum(cards * T, 1e-9)), 0.0) * CANPROVE
dutyH = np.where(is_h, np.minimum(1.0, (shards_served + ext_served) * TBESIDE / np.maximum(cards * T, 1e-9)), 0.0) * CANHYB
self.duty = dutyH
kwh = cards * T / 3600.0 / 1000.0
power_w = is_m * PMINE + is_p * (busyP * PPROVE + (1 - busyP) * PIDLE) + is_h * (PMINE + dutyH * p["hyb_extra_w"])
cost = kwh * power_w * self.elec[:, None]
if p["renter_usd_mhs_h"] > 0:
# the renter pays per card-hour whatever the card does, unless it is off
on = (mode[0] != OFF)
cost[0] = cards[0] * on * HASH * p["renter_usd_mhs_h"] * T / 3600.0
self.farm_rent_usd += cost[0].sum()
hsh = hcards * (is_m + is_h * (1 - p["hyb_loss"] * self.duty))
hfrac = hsh / np.maximum(hsh.sum(1, keepdims=True), 1e-9)
mine_usd = (mine_ign * price)[:, None] * hfrac
inc = mine_usd + prove_ign * price + ext_usd
self.farm_income_usd += inc[0].sum()
profit = inc - cost
al = 1.0 / p["ema_ticks"]
self.R_hash += ((blocks * E * (1 - p["pool"]) / H / T) - self.R_hash) * al
nPH_cls = (np.where(is_p | is_h, cards, 0.0)).sum(0)
open_inc = open_by_cls * pool_per_shard * price * corr + ext_open * ext_pay_per_shard
open_pc = np.where(nPH_cls > 0, open_inc / np.maximum(nPH_cls, 1e-9), 0.0) * 3600.0 / T
self.open_pc += (open_pc - self.open_pc) * al
asg_usd = (s_asg - overflow) * pool_per_shard * price
asg_pw = asg_usd / max(Wr, 1e-9) * 3600.0 / T
self.asg_pw += (asg_pw - self.asg_pw) * al
asg_ext_pw = ext_asg[0] * ext_pay_per_shard / max(W, 1e-9) * 3600.0 / T
self.asg_ext_pw += (asg_ext_pw - self.asg_ext_pw) * al
self.t_open = t_open
self.eligible = eligible
self.decide(t, price, w, Wr, p_resp, p_win, t_open)
ca = max(self.cards_active, 1)
n_p = (cards * is_p).sum()
n_h = (cards * is_h).sum()
n_m = (cards * is_m).sum()
self.hour_acc.append((
H / 1e6, (hcards * (mode != OFF)).sum() / 1e6, n_p / ca,
n_h / ca, 1 - (n_p + n_h + n_m) / ca,
blocks / T, self.D, self.q, self.age, self.ok60, self.ok20, price,
ext_served.sum(), ext_work, shards,
(self.burn_ext_ign + self.burn_base_ign), self.stranded_ign, float((cards[0] * (mode[0] != OFF)).sum()),
))
self.cls_profit += profit.sum(0)
self.cls_cards += (cards * self.active[:, None]).sum(0)
self.cls_shards += shards_served.sum(0)
if len(self.hour_acc) * T >= 3600:
arr = np.array(self.hour_acc)
self.hourly.append(arr.mean(0).tolist() + [arr[:, 8].max(), arr[:, 0].min()])
self.hour_acc = []
if (t + 1) * T % 86400 == 0:
self.day += 1
self.w -= self.hist[:, self.day % 30]
self.hist[:, self.day % 30] = 0
def open_order(self, capP, capH):
"""Open-claim service order: the fastest responder wins the race. PROVE cards at their alone shard
time, HYBRID cards at their beside-the-miner shard time."""
order = [(TPROVE[c], capP, c) for c in range(NC) if CANPROVE[c]] + [(TBESIDE[c], capH, c) for c in range(NC) if CANHYB[c]]
order.sort(key=lambda x: x[0])
return order
# ------------------------------------------------------------ decisions
def decide(self, t, price, w, Wr, p_resp, p_win, t_open):
p = self.p
due = ((t + self.phase) % p["decide_every"] == 0) & self.active & ~self.dead & ~self.fixed_mine
if not due.any():
return
idx = np.nonzero(due)[0]
cards = self.cards[idx]
elec = self.elec[idx][:, None]
n_pcards = (self.cards[idx] * ((self.mode[idx] == PROVE) | (self.mode[idx] == HYB))).sum(1, keepdims=True)
v_mine = HASH * 1e6 * self.R_hash * price * 3600.0 - PMINE * elec / 1000.0
per_w = w[idx][:, None] / np.maximum(n_pcards + (n_pcards == 0) * cards, 1e-9)
asg_int = self.asg_pw * per_w
asg_ext = self.asg_ext_pw * per_w * self.eligible
v_prove = self.open_pc + asg_ext + asg_int * (TPROVE <= t_open) - PIDLE * elec / 1000.0 - 0.3 * (PPROVE - PIDLE) * elec / 1000.0
v_hyb = v_mine * (1 - p["hyb_loss"] * np.minimum(1.0, self.duty[idx])) + 0.9 * (asg_ext + asg_int * (TBESIDE <= t_open)) - p["hyb_extra_w"] * np.minimum(1.0, self.duty[idx]) * elec / 1000.0
# the renter's cost replaces electricity for operator 0
if p["renter_usd_mhs_h"] > 0:
r0 = np.nonzero(idx == 0)[0]
if len(r0):
rent = HASH * p["renter_usd_mhs_h"]
v_mine[r0] = HASH * 1e6 * self.R_hash * price * 3600.0 - rent
v_prove[r0] = self.open_pc + asg_ext[r0] + asg_int[r0] * (TPROVE <= t_open) - rent
v_hyb[r0] = v_mine[r0] + 0.9 * (asg_ext[r0] + asg_int[r0] * (TBESIDE <= t_open))
v_prove[:, ~CANPROVE] = -1e9
v_hyb[:, ~CANHYB] = -1e9
v_off = np.zeros_like(v_mine)
V = np.stack([v_off, v_mine, v_prove, v_hyb], 0)
cur = self.mode[idx]
v_cur = np.take_along_axis(V, cur[None], 0)[0]
best = V.argmax(0)
v_best = V.max(0)
hy = self.hyst[idx][:, None]
gain = v_best - v_cur
ok = (gain > hy * np.abs(v_cur) + 1e-4) & (cards > 0) & ((t - self.last_switch[idx]) >= p["dwell_ticks"])
ok &= best != cur
if ok.any():
newmode = np.where(ok, best, cur)
self.mode[idx] = newmode
ls = self.last_switch[idx]
ls[ok] = t
self.last_switch[idx] = ls
self.switches += int(ok.sum())
def run(self):
nt = int(self.p["days"] * 86400 / self.p["tick"])
for t in range(nt):
self.step(t)
return np.array(self.hourly)
COLS = ["hash_MHs", "hash_pot", "frac_prove", "frac_hyb", "frac_off", "bps", "D", "q", "age", "ok60", "ok20",
"price", "ext_served", "ext_lost", "shards", "burn_cum", "stranded_cum", "farm_cards_on", "age_max", "hash_min"]
def metrics(hourly, sim):
h = hourly
d = {}
hrs = h.shape[0]
nd = hrs // 24
def day_mean(col, d0, d1):
d0, d1 = min(d0, nd - 1), min(d1, nd)
return h[d0 * 24:d1 * 24, COLS.index(col)].mean()
def day_max(col, dd):
dd = min(dd, nd)
return h[(dd - 1) * 24:dd * 24, COLS.index(col)].max()
pre_hash = day_mean("hash_MHs", 1, 7) if nd >= 7 else h[:, COLS.index("hash_MHs")].mean()
d["hash_pre"] = pre_hash
d["hash_min_ratio"] = h[24:, COLS.index("hash_min")].min() / pre_hash if nd >= 2 else 1.0
d["hash_d30_ratio"] = day_mean("hash_MHs", 29, 30) / pre_hash
d["hours_hash_lt50"] = int((h[24:, COLS.index("hash_MHs")] < 0.5 * pre_hash).sum())
d["mining_hash_share"] = (h[:, COLS.index("hash_MHs")] / np.maximum(h[:, COLS.index("hash_pot")], 1e-9)).mean()
for dd in (1, 10, 20, 30):
d[f"prove_d{dd}"] = day_mean("frac_prove", dd - 1, dd)
d[f"hyb_d{dd}"] = day_mean("frac_hyb", dd - 1, dd)
d[f"off_d{dd}"] = day_mean("frac_off", dd - 1, dd)
d["age_max"] = h[:, COLS.index("age_max")].max()
d["q_max"] = h[:, COLS.index("q")].max()
d["age_d30"] = day_max("age_max", 30)
daily_age = h[:, COLS.index("age_max")].reshape(-1, 24).max(1)
x = np.arange(10)
slope = np.polyfit(x, daily_age[-10:], 1)[0] if nd >= 10 else 0.0
d["backlog_growing"] = bool(slope > 0 and daily_age[-1] > 60)
d["backlog_600"] = bool(d["age_max"] > 600)
d["ok60_mean"] = h[:, COLS.index("ok60")].mean()
d["ok60_min_day"] = h[:, COLS.index("ok60")].reshape(-1, 24).mean(1).min()
d["ok20_mean"] = h[:, COLS.index("ok20")].mean()
d["bps_mean"] = h[:, COLS.index("bps")].mean()
d["D_end_over_start"] = h[-1, COLS.index("D")] / h[min(24, hrs - 1), COLS.index("D")]
d["price_end"] = h[-1, COLS.index("price")]
d["price_mean"] = h[:, COLS.index("price")].mean()
es, el = h[:, COLS.index("ext_served")].sum(), h[:, COLS.index("ext_lost")].sum()
d["ext_delivered"] = es / (es + el) if es + el > 0 else float("nan")
fp = h[:, COLS.index("frac_prove")]
ma = np.convolve(fp, np.ones(24) / 24, mode="same")
dev = (fp - ma)[-240:]
d["osc_cross_per_day"] = float((np.diff(np.sign(dev)) != 0).sum() / 10.0)
d["osc_amp_pts"] = float((np.percentile(fp[-240:], 90) - np.percentile(fp[-240:], 10)) * 100)
days = sim.p["days"]
d["profit_card_day"] = sim.cls_profit / np.maximum(sim.cls_cards / (days * 86400 / sim.p["tick"]), 1e-9) / days
d["shard_share"] = sim.cls_shards / max(sim.cls_shards.sum(), 1e-9)
d["switches"] = sim.switches
d["miner_shortage"] = bool(d["hours_hash_lt50"] >= 1)
d["window_miss"] = bool(d["ok60_min_day"] < 0.90)
d["oscillation"] = bool(d["osc_amp_pts"] > 10 and d["osc_cross_per_day"] >= 1)
# burn and the pool
d["burn_ign_day"] = sim.burned_ign / days
d["burn_ext_ign_day"] = sim.burn_ext_ign / days
d["burn_base_ign_day"] = sim.burn_base_ign / days
d["burn_usd_day"] = d["burn_ign_day"] * d["price_mean"]
d["stranded_ign"] = sim.stranded_ign
d["stranded_ign_day"] = sim.stranded_ign / days
pool_total = sim.p["emission"] * sim.p["pool"] * 86400 * days
d["pool_paid_share"] = sim.pool_paid_ign / pool_total
d["stranded_share"] = sim.stranded_ign / pool_total
d["farm_cards_on_d30"] = day_mean("farm_cards_on", 29, 30)
d["farm_cards"] = float(sim.cards[0].sum())
d["farm_margin"] = (sim.farm_income_usd - sim.farm_rent_usd) / sim.farm_rent_usd if sim.farm_rent_usd > 0 else float("nan") # undefined when the farm pays electricity (renter_usd_mhs_h=0)
d["cartel_n"] = sim.cartel_n
return d
SCEN = {
"a": "baseline (measured cards, 25-s window, 120-s timeout, farm is a renter at USD 0.0117 per MH/s-h)",
"p10": "coin price x10 at day 7",
"pd10": "coin price /10 at day 7",
"c": "external demand zero for 30 days",
"x100": "external demand pays x100 from day 7",
"cartel": "the operators holding the top 10% of weight never answer an assignment or claim",
"refuse": "every prover refuses (no assignment answered, no open claim) from day 10 to day 20",
"halving": "the first halving: 15.844 IGN per block throughout",
"b": "external pays 10x from day 7, coin price falls 70% over days 7 to 14 (the 4 October worst case)",
"d": "the 20% operator disappears at day 10",
"e": "a 30% operator never fulfils its assignments",
"f": "a pool with hash equal to the network's arrives at day 10 (2x hash)",
}
DEFAULT = "a,p10,pd10,c,x100,cartel,refuse,halving"
def fmt(v, nd=2):
if isinstance(v, (bool, np.bool_)):
return "yes" if v else "no"
if isinstance(v, (int, np.integer)):
return f"{v:,}"
if isinstance(v, float):
if abs(v) >= 1000:
return f"{v:,.0f}"
return f"{v:.{nd}f}"
return str(v)
def agg(ms, key):
vals = np.array([m[key] for m in ms], dtype=float)
return vals.mean(), vals.min(), vals.max()
def run_scenarios(scen, seeds, p, quiet=False):
out = {}
for s in scen:
ms = []
for seed in seeds:
t0 = time.time()
sim = Sim(s, seed, p)
h = sim.run()
ms.append(metrics(h, sim))
if not quiet:
print(f"<!-- scenario {s} seed {seed}: {time.time() - t0:.1f} s -->", file=sys.stderr)
out[s] = ms
return out
def table_scenarios(out):
keys = [
("mining_hash_share", "hash mining share (mean)"),
("prove_d10", "cards proving, day 10"), ("prove_d30", "cards proving, day 30"),
("hyb_d30", "cards hybrid, day 30"), ("off_d30", "cards off, day 30"),
("hash_min_ratio", "hash min / pre-event"), ("hash_d30_ratio", "hash day 30 / pre-event"), ("hours_hash_lt50", "T1: hours hash under 50%"),
("q_max", "backlog max, shards"), ("age_max", "T2: oldest unproven age max, s"), ("age_d30", "age max day 30, s"),
("ok60_mean", "blocks proven within 60 s"), ("ok60_min_day", "T4: worst day within 60 s"), ("ok20_mean", "blocks proven within 20 s"),
("D_end_over_start", "difficulty end / start"),
("ext_delivered", "external jobs delivered"), ("price_mean", "price mean, $"), ("price_end", "price at day 30, $"),
("osc_amp_pts", "T5: proving-share 10-90 pct range, points"),
("burn_ign_day", "burn, IGN per day (base fee + job burn)"), ("burn_ext_ign_day", "of which external job burn, IGN per day"),
("burn_usd_day", "burn, $ per day at the mean price"),
("pool_paid_share", "share of the 20% pool paid out"), ("stranded_share", "share of the 20% pool stranded (expired shards)"),
("stranded_ign_day", "stranded, IGN per day"),
("farm_cards", "renter farm cards"), ("farm_cards_on_d30", "renter farm cards on, day 30"), ("farm_margin", "renter farm margin over rent"),
("cartel_n", "cartel operators"),
("switches", "mode switches (operator x class)"),
]
scen = list(out)
lines = ["| Metric | " + " | ".join(f"{s} | {s} min | {s} max" for s in scen) + " |",
"|---|" + "---|" * (3 * len(scen))]
for k, label in keys:
row = [label]
for s in scen:
m, lo, hi = agg(out[s], k)
nd = 4 if k.startswith("price") else 2
row += [fmt(float(m), nd), fmt(float(lo), nd), fmt(float(hi), nd)]
lines.append("| " + " | ".join(row) + " |")
print("\n".join(lines))
print()
print("| Flag | " + " | ".join(scen) + " |")
print("|---|" + "---|" * len(scen))
for k, label in [("miner_shortage", "T1 miner shortage (hash under 50% of pre-event for 1 h or more)"),
("backlog_600", "T2 backlog over 600 s"),
("backlog_growing", "T3 growing backlog (positive 10-day trend and over 60 s at day 30)"),
("window_miss", "T4 window miss (a day under 90% of blocks within 60 s)"),
("oscillation", "T5 oscillation (proving-share 10-90 range over 10 points in the last 10 days)")]:
row = [label]
for s in scen:
n = sum(1 for m in out[s] if m[k])
row.append(f"{n}/{len(out[s])}")
print("| " + " | ".join(row) + " |")
print()
print("Operator profit by card class, $ per card-day (mean over seeds, all modes including off), and the share of shards each class proves:")
print()
print("| Scenario | " + " | ".join(CLS) + " | " + " | ".join("sh " + c for c in CLS) + " |")
print("|---|" + "---|" * (2 * NC))
for s in scen:
pr = np.mean([m["profit_card_day"] for m in out[s]], 0)
sh = np.mean([m["shard_share"] for m in out[s]], 0)
print(f"| {s} | " + " | ".join(f"{v:.3f}" for v in pr) + " | " + " | ".join(f"{v:.2f}" for v in sh) + " |")
print()
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--scenarios", default=DEFAULT)
ap.add_argument("--seeds", type=int, default=3)
ap.add_argument("--seed0", type=int, default=1)
ap.add_argument("--set", default="", help="k=v,k=v parameter overrides")
ap.add_argument("--days", type=int, default=None)
args = ap.parse_args()
p = dict(P)
for kv in filter(None, args.set.split(",")):
k, v = kv.split("=")
p[k] = float(v)
if args.days:
p["days"] = args.days
seeds = range(args.seed0, args.seed0 + args.seeds)
scen = args.scenarios.split(",")
print(f"# Igneum economy under stress (lane 4), {p['n_ops']} operators, {p['days']} days, seeds {list(seeds)}, tick {p['tick']:.0f} s, the eleven measured cards")
print()
print("Parameters: " + ", ".join(f"{k}={v}" for k, v in p.items()))
print()
print("Cards (measured, prover-tiers-real-cards.md): " + "; ".join(f"{c} {HASH[i]:.2f} MH/s {PMINE[i]:.0f} W alone {TPROVE[i]:.1f} s beside {TBESIDE[i]:.1f} s{'' if CANHYB[i] else ' (core-only beside)'}" for i, c in enumerate(CLS)))
print()
for s in scen:
print(f"- {s}: {SCEN[s]}")
print()
t0 = time.time()
out = run_scenarios(scen, seeds, p)
table_scenarios(out)
print(f"<!-- total {time.time() - t0:.0f} s -->")
if __name__ == "__main__":
main()

View file

@ -0,0 +1,385 @@
#!/usr/bin/env python3
"""Horizon lane 4, task 1: what IGN is for beyond gas, as demand curves with dollar numbers.
Pure Python, no numpy. Arithmetic only; not run under the lock (it measures nothing).
python3 utility.py > utility_out.md
Every input is labelled in INPUTS: measured (a bench-log or analysis file), cited (another project's
document, named), designed (the spec), or approximate (from memory or a secondary source). Prices of
IGN are the three inputs of docs/analysis/security-budget.md (USD 0.005, 0.02, 0.10) and are
assumptions, never forecasts.
Sections printed:
1. The supply side of proving: cost per v1 shard and per billion cycles, per measured card, alone
and beside its miner, at four network hash sizes; the renter's cost at the measured rental price.
2. The published prices this competes with (Boundless, Succinct, Bonsai, Ethereum L1 trackers, Taiko).
3. Demand curves (a) to (e): dollars per day at launch, year 2, year 5 under low/base/high, and the
burn each causes, at the three IGN prices.
4. The totals: fee income to miners and provers, external income to provers, and burn, per scenario,
which security_budget_10y.py reads as its fee scenarios.
"""
# ------------------------------------------------------------------ inputs
INPUTS = {
# chain constants (designed, spec 2.5 and 5.11)
"subsidy_ign_per_s": 31.688, # designed, spec 2.5, pre-halving, ramp complete
"producer_share": 0.80, # designed, spec 2.5
"pool_share": 0.20, # designed
"blocks_per_day": 86_400, # designed, 1 block/s
"f_e_floor_ign_per_gas": 100e-9, # adopted 5 Oct 2026, spec 5.11: 100 gwei
"f_p_floor_ign_per_pgas": 10_000e-9, # adopted: 10,000 gwei per pgas
"intrinsic_pgas": 300, # adopted
"transfer_gas": 21_000, # Ethereum's rule
"B_e": 30_000_000, # implemented
"B_p": 120_000, # adopted
"S_p": 30_000, # adopted (30 M cycles)
"job_premium": 1.5, # designed, execution-layer design 6, parameter open
"job_burn": 0.10, # designed, spec 5.4
"cycles_per_pgas": 1_000, # designed unit
# the v1 shard fixture (measured)
"v1_shard_cycles": 4_717_439, # measured, prover-tiers-real-cards.md (fees-v1-shards2 shard 0)
# energy and rental
"elec_usd_per_kwh": 0.10, # the brief's input
"rental_usd_per_mhs_hour": 0.0117, # measured, bench-log "Rental cost of hash, 6 October 2026"
"hybrid_hash_loss": 0.04, # measured on the 5090 only (bench-log, proving v1 step 1: 124.7 -> 119.7 MH/s); approximate for other cards
"hybrid_extra_w": 70.0, # measured on the 5090 only: power max 328.6 W mining+proving against 258 W mining (prover-tiers fleet row); approximate elsewhere
# IGN price inputs (assumptions, security-budget.md)
"prices": [0.005, 0.02, 0.10],
# proof record sizes (implemented, spec 7.7 and 7.8)
"shard_record_bytes": 274,
"segment_record_bytes": 586,
"segment_blocks": 8,
"compressed_proof_bytes": 1_272_897, # measured, bench-log proving v1 (shard proof); the segment proof is 1,272,909
# storage prices (approximate)
"hdd_usd_per_tb": 15.0, # approximate, consumer HDD 2026
"object_store_usd_per_gb_month": 0.02, # approximate, S3-standard class
}
# measured card table, docs/analysis/prover-tiers-real-cards.md (6 October 2026, rented Vast.ai boxes).
# (name, vram GB, MH/s mining, W mining, shard alone s (patched 2^26 compressed), shard beside the miner s (compressed peak row),
# core-only beside s, mines-and-proves-compressed-beside?)
CARDS = [
("RTX 3060", 12, 23.78, 103.7, 14.4, 37.5, 27.2, True),
("RTX 3080", 10, 40.82, 204.9, 7.1, 25.6, 19.2, True),
("RTX 3090", 24, 37.79, 228.8, 14.9, 19.9, 13.3, True),
("RTX 4060 Ti 16", 16, 17.58, 72.3, 11.6, 34.6, 25.9, True),
("RTX 4060 Ti 8", 8, 19.07, 72.6, 9.6, None, 26.3, False),
("RTX 4060", 8, 17.07, 115.0, 18.4, None, 22.1, False), # power read 0.0 W on the box; 115 W is the card's rated draw, approximate
("RTX 4070", 12, 24.99, 91.1, 12.1, 27.3, 14.3, True),
("RTX 4090", 24, 52.25, 183.1, 6.3, 26.1, 10.6, True),
("RTX 5070", 12, 41.89, 137.0, 4.8, 37.2, 19.8, True),
("RTX 5090", 32, 98.48, 258.2, 6.3, 10.7, 7.4, True),
("RTX A5000", 24, 47.70, 222.7, 8.3, 34.6, 18.2, True),
]
NET_HASH_GHS = [1, 10, 100, 1000]
# ------------------------------------------------------------------ published prices (section 2)
# Every row is cited to the file in this repository that recorded it, or marked approximate.
COMPETITORS = [
("Boundless (RISC Zero), Base mainnet", 0.21,
"median lock price about USD 0.21 per billion cycles, 8 provers, 8.4 T cycles in the explorer's trailing day; docs/design/developer-adoption.md 2b, secondary summary of 4 Oct 2026; approximate"),
("Succinct Prover Network, example request", 0.046 + 0.46,
"quickstart example: base fee 0.2 PROVE plus up to 2.0 PROVE per billion PGU at USD 0.23 per PROVE = USD 0.046 plus up to 0.46 per billion PGU; example parameters, not a market price; developer-adoption.md 2b; approximate"),
("RISC Zero Bonsai (hosted API)", None,
"no public per-cycle list price was ever published; Bonsai was priced on request and RISC Zero moved paid proving to Boundless in 2025; not cloned, approximate"),
("Ethereum L1 block, ethproofs tracker cost, Sep 2026", 0.005 / 0.9,
"sub-half-cent per L1 block (frontier.md 2.6, secondary); an L1 block of about 15 to 30 M gas at the 14 to 44 SP1 cycles per gas measured on Igneum (base-fee-floor.md, developer-adoption.md 2b) is 0.2 to 1.3 B cycles, so about USD 0.004 to 0.025 per billion cycles at cluster cost; approximate"),
("Taiko-class rollup, per batch proof", None,
"taiko-mono not cloned; Taiko Alethia proves batches through its own prover market (SGX plus ZK tiers, SP1 and RISC0 accepted); the ZK proof's cost per batch in 2026 is of the order of the ethproofs L1 figure times the batch's cycles, cents to tens of cents; approximate"),
]
def usd(x, nd=2):
if x is None:
return "n/a"
if x == 0:
return "0"
if abs(x) >= 1000:
return f"{x:,.0f}"
if abs(x) >= 1:
return f"{x:,.{nd}f}"
return f"{x:.6f}".rstrip("0").rstrip(".") if abs(x) >= 1e-6 else f"{x:.2e}"
def ign(x):
if abs(x) >= 1000:
return f"{x:,.0f}"
if abs(x) >= 1:
return f"{x:,.3f}"
return f"{x:.6f}".rstrip("0").rstrip(".")
# ------------------------------------------------------------------ section 1: supply side
def shard_cost_alone(card, net_ghs, price):
"""Cost to prove one v1 shard on a card that stops mining to prove: electricity plus the mining income forgone."""
name, vram, mhs, w, t_alone, t_beside, t_core, hyb = card
elec = w * t_alone / 3600.0 / 1000.0 * INPUTS["elec_usd_per_kwh"]
share = mhs / (net_ghs * 1000.0)
opp_ign = share * INPUTS["subsidy_ign_per_s"] * INPUTS["producer_share"] * t_alone
return elec, opp_ign * price, opp_ign
def shard_cost_beside(card, net_ghs, price):
"""Cost to prove one v1 shard beside the running miner: the measured 4% hash loss for the proof's duration plus the extra watts."""
name, vram, mhs, w, t_alone, t_beside, t_core, hyb = card
t = t_beside if t_beside else t_core
elec = INPUTS["hybrid_extra_w"] * t / 3600.0 / 1000.0 * INPUTS["elec_usd_per_kwh"]
share = mhs / (net_ghs * 1000.0)
opp_ign = share * INPUTS["subsidy_ign_per_s"] * INPUTS["producer_share"] * t * INPUTS["hybrid_hash_loss"]
return elec, opp_ign * price, opp_ign, t
def renter_cost_per_shard(card):
name, vram, mhs, w, t_alone, t_beside, t_core, hyb = card
return mhs * INPUTS["rental_usd_per_mhs_hour"] / 3600.0 * t_alone
def per_billion(x_per_shard):
return x_per_shard * 1e9 / INPUTS["v1_shard_cycles"]
def section1():
print("## 1. The supply side: what one v1 shard (4,717,439 cycles) costs to prove, per measured card\n")
print("Inputs: the eleven cards of `docs/analysis/prover-tiers-real-cards.md` (measured 6 Oct 2026); electricity USD 0.10 per kWh (the brief); "
"the renter's cost USD 0.0117 per MH/s-hour (measured, bench-log 'Rental cost of hash'); the miner's opportunity cost = its hash share x 80% of 31.688 IGN/s "
"x the proof's seconds; beside the miner the loss is the measured 4% of hash (5090 only, approximate for the others) and about 70 extra watts.\n")
print("### 1.1 Proving alone (the card stops mining), cost per shard in USD at USD 0.02 per IGN, by network hash\n")
print("| Card | MH/s | W | Shard alone s | Electricity per shard | Forgone subsidy per shard, IGN at 1 GH/s | USD at 1 GH/s | 10 GH/s | 100 GH/s | 1 TH/s | Renter's cost per shard |")
print("|---|---|---|---|---|---|---|---|---|---|---|")
for c in CARDS:
elec, _, opp1 = shard_cost_alone(c, 1, 0.02)
row = [c[0], f"{c[2]:.2f}", f"{c[3]:.0f}", f"{c[4]:.1f}", usd(elec), ign(opp1)]
for n in NET_HASH_GHS:
e, o, _ = shard_cost_alone(c, n, 0.02)
row.append(usd(e + o))
row.append(usd(renter_cost_per_shard(c)))
print("| " + " | ".join(row) + " |")
print()
print("### 1.2 Proving beside the running miner (hybrid), cost per shard in USD at USD 0.02 per IGN\n")
print("| Card | Shard beside s | Extra electricity per shard | USD at 1 GH/s | 10 GH/s | 100 GH/s | 1 TH/s | USD per billion cycles at 100 GH/s |")
print("|---|---|---|---|---|---|---|---|")
for c in CARDS:
if not c[7]:
continue
e, o, oi, t = shard_cost_beside(c, 1, 0.02)
row = [c[0], f"{t:.1f}", usd(e)]
for n in NET_HASH_GHS:
e, o, oi, t = shard_cost_beside(c, n, 0.02)
row.append(usd(e + o))
e, o, oi, t = shard_cost_beside(c, 100, 0.02)
row.append(usd(per_billion(e + o)))
print("| " + " | ".join(row) + " |")
print()
print("### 1.3 Per billion cycles, the unit the market quotes (USD at 0.02 per IGN; the three IGN prices move only the opportunity term)\n")
print("| Card | Alone, 1 GH/s | Alone, 100 GH/s | Alone, 1 TH/s | Beside, 100 GH/s | Renter (rental price, no subsidy) | Electricity only |")
print("|---|---|---|---|---|---|---|")
for c in CARDS:
e1, o1, _ = shard_cost_alone(c, 1, 0.02)
e100, o100, _ = shard_cost_alone(c, 100, 0.02)
e1000, o1000, _ = shard_cost_alone(c, 1000, 0.02)
if c[7]:
eb, ob, _, _ = shard_cost_beside(c, 100, 0.02)
beside = usd(per_billion(eb + ob))
else:
beside = "core-only"
print(f"| {c[0]} | {usd(per_billion(e1 + o1))} | {usd(per_billion(e100 + o100))} | {usd(per_billion(e1000 + o1000))} | {beside} | {usd(per_billion(renter_cost_per_shard(c)))} | {usd(per_billion(e1))} |")
print()
print("Reading. The electricity of a shard is under a thousandth of a cent on every card. What a prover charges is set by the subsidy it forgoes, "
"which scales as 1 / network hash: at 1 GH/s a 5090 that stops mining to prove gives up 15.7 IGN per shard (USD 0.31 at 0.02), at 1 TH/s 0.016 IGN. "
"Beside its miner the same card gives up 4% of that for 10.7 s: at 100 GH/s about USD 0.0003 per shard, USD 0.06 per billion cycles, "
"which is under Boundless's USD 0.21 median and above the ethproofs cluster cost. The renter's row is the hard floor nobody beats for long: "
"USD 0.0020 per shard on a rented 5090, USD 0.43 per billion cycles.\n")
def section2():
print("## 2. The published prices this competes with (per billion cycles)\n")
print("| Supplier | USD per billion cycles | Source and label |")
print("|---|---|---|")
for name, p, src in COMPETITORS:
print(f"| {name} | {usd(p) if p else 'not published'} | {src} |")
print()
print("Igneum's own floor as a quote: a billion-cycle job at the adopted floor is 1,000,000 pgas x 10,000 gwei x 1.5 = 15 IGN "
f"(USD {usd(15 * 0.005)}, {usd(15 * 0.02)}, {usd(15 * 0.10)} at the three prices). At USD 0.02 that is 1.4x Boundless's median; at 0.005 it is "
"a third of it; at 0.10 it is 7x. The floor was calibrated for spam, not for the market (base-fee-floor.md section 3), and it is a miner-signalled parameter.\n")
# ------------------------------------------------------------------ section 3: demand curves
def job_fee_ign(cycles):
pgas = cycles / INPUTS["cycles_per_pgas"]
return pgas * INPUTS["f_p_floor_ign_per_pgas"] * INPUTS["job_premium"]
def transfer_fee_ign():
return INPUTS["transfer_gas"] * INPUTS["f_e_floor_ign_per_gas"] + INPUTS["intrinsic_pgas"] * INPUTS["f_p_floor_ign_per_pgas"]
def batch_post_fee_ign(calldata_bytes):
gas = 21_000 + 16 * calldata_bytes
return gas * INPUTS["f_e_floor_ign_per_gas"] + INPUTS["intrinsic_pgas"] * INPUTS["f_p_floor_ign_per_pgas"]
# scenario grid: (launch, year 2, year 5) x (low, base, high)
SCEN = {
# (a) external proving demand, USD per day paid by customers (at launch on their own chain; IGN-settled from phase two)
"ext_usd_day": {"launch": (0, 500, 2000), "y2": (500, 2000, 10000), "y5": (1000, 10000, 100000)},
# (b) rollups settling on Igneum: count, batches per day each, calldata bytes per batch, cycles per batch proof job
"rollups": {"launch": (0, 1, 2), "y2": (1, 3, 10), "y5": (2, 10, 50)},
"batches_per_rollup_day": 1440, # one a minute, approximate
"calldata_per_batch": 100_000, # 100 KB, approximate
"cycles_per_batch_job": 1e9, # a batch proof job of 1 B cycles, approximate (a Helios-class program is of that order, developer-adoption.md 2b)
# (c) bridges: light-client update jobs
"bridges": {"launch": (0, 1, 2), "y2": (1, 3, 5), "y5": (2, 5, 10)},
"bridge_updates_day": 225, # one sync-committee finality update per 6.4-min epoch, approximate
"cycles_per_bridge_job": 1e9, # approximate
# (d) payments: plain transfers per day
"transfers_day": {"launch": (10_000, 100_000, 1_000_000), "y2": (100_000, 1_000_000, 10_000_000), "y5": (1_000_000, 10_000_000, 30_000_000)}, # 30 M is above the EIP-1559 target of 17.28 M a day: the fee leaves the floor
"tip_gwei": 1, # the default wallet tip; node policy proposal developer-adoption.md 2a
# (e) shards per block (proof records in blocks), from the transfer traffic
}
LABELS = ["low", "base", "high"]
PERIODS = ["launch", "y2", "y5"]
def demand_rows():
"""Return per period x scenario: dict of flows in IGN per day and USD-denominated external income."""
out = {}
tf = transfer_fee_ign()
for per in PERIODS:
for i, lab in enumerate(LABELS):
ext_usd = SCEN["ext_usd_day"][per][i]
n_roll = SCEN["rollups"][per][i]
n_br = SCEN["bridges"][per][i]
n_tx = SCEN["transfers_day"][per][i]
# (b) rollups
batches = n_roll * SCEN["batches_per_rollup_day"]
roll_burn_base = batches * batch_post_fee_ign(SCEN["calldata_per_batch"])
roll_job_fee = batches * job_fee_ign(SCEN["cycles_per_batch_job"])
roll_job_burn = roll_job_fee * INPUTS["job_burn"]
roll_job_provers = roll_job_fee * (1 - INPUTS["job_burn"])
# (c) bridges
upd = n_br * SCEN["bridge_updates_day"]
br_job_fee = upd * job_fee_ign(SCEN["cycles_per_bridge_job"])
br_burn = br_job_fee * INPUTS["job_burn"] + upd * batch_post_fee_ign(2_000)
br_provers = br_job_fee * (1 - INPUTS["job_burn"])
# (d) payments
# above the EIP-1559 target (B_p/2 = 200 transfers a block = 17.28 M a day) the fee leaves the floor; multiplier approximate
# above the target the fee leaves the floor and rations demand; the burn is then volume x a fee set by
# willingness to pay, which no model here knows, so the served volume is clamped at the target and flagged
target_day = INPUTS["B_p"] / 2 / INPUTS["intrinsic_pgas"] * INPUTS["blocks_per_day"]
served = min(n_tx, target_day)
mult = 1.0 if n_tx <= target_day else float("nan")
pay_burn = served * tf
pay_tip = served * INPUTS["transfer_gas"] * SCEN["tip_gwei"] * 1e-9
pay_tip_miners = pay_tip * 0.8
pay_tip_burn = pay_tip * 0.2 # a plain transfer has no registered frame: the app share burns
# (e) proof records: shards per block from pgas traffic
pgas_day = n_tx * INPUTS["intrinsic_pgas"]
shards_per_block = max(1.0, pgas_day / INPUTS["blocks_per_day"] / INPUTS["S_p"])
rec_bytes_block = shards_per_block * INPUTS["shard_record_bytes"] + INPUTS["segment_record_bytes"] / INPUTS["segment_blocks"]
proof_bytes_block = (shards_per_block + 1) * INPUTS["compressed_proof_bytes"]
out[(per, lab)] = dict(
ext_usd=ext_usd, n_roll=n_roll, n_br=n_br, n_tx=n_tx,
roll_burn=roll_burn_base + roll_job_burn, roll_provers=roll_job_provers, roll_fee=roll_job_fee + roll_burn_base,
br_burn=br_burn, br_provers=br_provers, br_fee=br_job_fee,
pay_burn=pay_burn + pay_tip_burn, pay_miners=pay_tip_miners, pay_mult=mult,
shards_per_block=shards_per_block, rec_bytes_block=rec_bytes_block, proof_bytes_block=proof_bytes_block,
)
return out
def section3():
tf = transfer_fee_ign()
print("## 3. Demand curves (a) to (e): dollars per day and the burn each causes\n")
print("Scenario grid (every count is an assumption, labelled approximate): external proving USD per day "
f"{SCEN['ext_usd_day']}; rollups settling here {SCEN['rollups']} at {SCEN['batches_per_rollup_day']} batches a day, {SCEN['calldata_per_batch'] // 1000} KB calldata and a {SCEN['cycles_per_batch_job'] / 1e9:.0f} B-cycle proof job each; "
f"bridges {SCEN['bridges']} at {SCEN['bridge_updates_day']} updates a day of {SCEN['cycles_per_bridge_job'] / 1e9:.0f} B cycles; plain transfers {SCEN['transfers_day']} at a {SCEN['tip_gwei']} gwei tip. "
"Fees at the adopted floors (spec 5.11); a job = pgas x f_p x 1.5 (design 6), 90% to provers, 10% burned once IGN-settled (spec 5.4).\n")
print("### 3.1 Unit prices at the floors\n")
print("| Item | IGN | USD at 0.005 | at 0.02 | at 0.10 |")
print("|---|---|---|---|---|")
items = [
("Plain transfer, base fee (burned)", tf),
("Plain transfer, 1 gwei tip (80% miners and provers, 20% burned: no registered frame)", INPUTS["transfer_gas"] * 1e-9),
("Rollup batch post, 100 KB calldata, base fee (burned)", batch_post_fee_ign(SCEN["calldata_per_batch"])),
("Rollup batch proof job, 1 B cycles (90% provers, 10% burned)", job_fee_ign(SCEN["cycles_per_batch_job"])),
("Bridge update job, 1 B cycles", job_fee_ign(SCEN["cycles_per_bridge_job"])),
("A v1 shard as a job (4.7 M cycles)", job_fee_ign(INPUTS["v1_shard_cycles"])),
]
for name, v in items:
print(f"| {name} | {ign(v)} | {usd(v * 0.005)} | {usd(v * 0.02)} | {usd(v * 0.10)} |")
print()
print("### 3.2 Payments: what a transfer costs against 1 bps and 10 bps rails\n")
print("| IGN price | Transfer fee at the floor, USD | Payment size at which the fee is 10 bps | At which it is 1 bps | Fee on a USD 100 payment, bps |")
print("|---|---|---|---|---|")
for p in INPUTS["prices"]:
f = tf * p
print(f"| {p} | {usd(f)} | {usd(f / 0.001)} | {usd(f / 0.0001)} | {f / 100 * 10000:.3f} |")
print()
print("Reading: at the floor a transfer undercuts a 1 bps rail on any payment above USD 0.26 (at 0.005) to USD 5.10 (at 0.10); a USD 100 payment pays 0.003 to 0.05 bps. "
"The fee is flat in IGN, so the chain takes nothing proportional to value; what payments give the coin is burn, not income (table 3.4). "
"The proving dimension binds first: 400 transfers a block (`B_p` / 300), 34.6 M a day, and the EIP-1559 step leaves the floor above 17.3 M a day.\n")
rows = demand_rows()
print("### 3.3 Dollars per day to miners and provers, by source, scenario and IGN price\n")
print("| Period | Scenario | External jobs to provers, USD (any price) | Rollup settlement to provers, IGN | Bridges to provers, IGN | Payments tips to miners and provers, IGN | IGN flows in USD at 0.005 | at 0.02 | at 0.10 |")
print("|---|---|---|---|---|---|---|---|---|")
for per in PERIODS:
for lab in LABELS:
r = rows[(per, lab)]
ignflow = r["roll_provers"] + r["br_provers"] + r["pay_miners"]
print(f"| {per} | {lab} | {usd(r['ext_usd'] * 0.9)} | {ign(r['roll_provers'])} | {ign(r['br_provers'])} | {ign(r['pay_miners'])} | {usd(ignflow * 0.005)} | {usd(ignflow * 0.02)} | {usd(ignflow * 0.10)} |")
print()
print("### 3.4 Burn per day, by source, scenario and IGN price\n")
print("| Period | Scenario | Rollup burn, IGN (base fee + 10% of jobs) | Bridge burn, IGN | Payments burn, IGN (base fee + unregistered tip share) | Fee multiplier (payments) | External job burn, IGN at 0.02 (phase two only) | Total burn, IGN | USD at 0.005 | at 0.02 | at 0.10 | Burn as % of daily emission (2,737,851 IGN) |")
print("|---|---|---|---|---|---|---|---|---|---|---|---|")
for per in PERIODS:
for lab in LABELS:
r = rows[(per, lab)]
ext_burn_002 = r["ext_usd"] * INPUTS["job_burn"] / 0.02
tot = r["roll_burn"] + r["br_burn"] + r["pay_burn"] + ext_burn_002
print(f"| {per} | {lab} | {ign(r['roll_burn'])} | {ign(r['br_burn'])} | {ign(r['pay_burn'])} | {'at the floor' if r['pay_mult'] == 1.0 else 'ABOVE TARGET: fee leaves the floor, volume clamped at 17.28 M'} | {ign(ext_burn_002)} | {ign(tot)} | {usd(tot * 0.005)} | {usd(tot * 0.02)} | {usd(tot * 0.10)} | {tot / 2_737_851 * 100:.3f}% |")
print()
print("### 3.5 Storage of proofs: bytes per block and per year, and who pays\n")
print("| Period | Scenario | Transfers a day | Shards per block | Record bytes per block (in coinbase, kept to the pruning depth) | Record GB per year | Proof bytes per block (p2p, not in blocks) | Proof TB per year if every proof were kept | Who pays |")
print("|---|---|---|---|---|---|---|---|---|")
bpy = INPUTS["blocks_per_day"] * 365.25
for per in PERIODS:
for lab in LABELS:
r = rows[(per, lab)]
print(f"| {per} | {lab} | {r['n_tx']:,} | {r['shards_per_block']:.1f} | {r['rec_bytes_block']:.0f} | {r['rec_bytes_block'] * bpy / 1e9:.1f} | {r['proof_bytes_block'] / 1e6:.1f} MB | {r['proof_bytes_block'] * bpy / 1e12:.0f} | node operators; no fee reaches them for it |")
print()
print(f"Reading: the records are small ({INPUTS['shard_record_bytes']} bytes a shard, {INPUTS['segment_record_bytes']} per 8-block segment): 11 to 37 GB a year at the floor, "
f"USD {11 * INPUTS['hdd_usd_per_tb'] / 1000:.2f} to {37 * INPUTS['hdd_usd_per_tb'] / 1000:.2f} of disk a year per node (approximate HDD price), paid by whoever runs a node and by nobody else; "
"no fee is charged for them because they ride in the coinbase extra data. The proof bytes (1.27 MB each) never enter a block and are discardable once the next "
"segment proof has verified them by recursion (spec 7.8 item 1): a node keeps the proof pool for the 600-block record window (about 3 GB at four shards a block) "
"and a light client keeps one 1.27 MB proof. Nobody is paid to archive proofs; an archive that kept every proof would hold 80 to 200 TB a year "
f"(USD {80 * INPUTS['hdd_usd_per_tb']:,.0f} to {200 * INPUTS['hdd_usd_per_tb']:,.0f} a year of HDD, approximate), which is a service someone sells, not a protocol cost.\n")
return rows
def section4(rows):
print("## 4. The fee scenarios security_budget_10y.py reads (totals per day)\n")
print("| Period | Scenario | Tips to miners and provers, IGN/day | Job income to provers in IGN/day (rollups + bridges) | External jobs, USD/day (90% to provers) | Burn, IGN/day (at 0.02 for the external part) |")
print("|---|---|---|---|---|---|")
for per in PERIODS:
for lab in LABELS:
r = rows[(per, lab)]
tips = r["pay_miners"]
jobs = r["roll_provers"] + r["br_provers"]
burn = r["roll_burn"] + r["br_burn"] + r["pay_burn"] + r["ext_usd"] * INPUTS["job_burn"] / 0.02
print(f"| {per} | {lab} | {ign(tips)} | {ign(jobs)} | {usd(r['ext_usd'])} | {ign(burn)} |")
print()
def main():
print("# Lane 4 utility model: what IGN is for beyond gas (generated by sim/horizon/economy-and-utility/utility.py)\n")
print("INPUTS (label in the comment of each line of the script): " + ", ".join(f"{k}={v}" for k, v in INPUTS.items() if k != "prices") + "\n")
section1()
section2()
rows = section3()
section4(rows)
if __name__ == "__main__":
main()

View file

@ -0,0 +1,151 @@
# Lane 4 utility model: what IGN is for beyond gas (generated by sim/horizon/economy-and-utility/utility.py)
INPUTS (label in the comment of each line of the script): subsidy_ign_per_s=31.688, producer_share=0.8, pool_share=0.2, blocks_per_day=86400, f_e_floor_ign_per_gas=1e-07, f_p_floor_ign_per_pgas=1e-05, intrinsic_pgas=300, transfer_gas=21000, B_e=30000000, B_p=120000, S_p=30000, job_premium=1.5, job_burn=0.1, cycles_per_pgas=1000, v1_shard_cycles=4717439, elec_usd_per_kwh=0.1, rental_usd_per_mhs_hour=0.0117, hybrid_hash_loss=0.04, hybrid_extra_w=70.0, shard_record_bytes=274, segment_record_bytes=586, segment_blocks=8, compressed_proof_bytes=1272897, hdd_usd_per_tb=15.0, object_store_usd_per_gb_month=0.02
## 1. The supply side: what one v1 shard (4,717,439 cycles) costs to prove, per measured card
Inputs: the eleven cards of `docs/analysis/prover-tiers-real-cards.md` (measured 6 Oct 2026); electricity USD 0.10 per kWh (the brief); the renter's cost USD 0.0117 per MH/s-hour (measured, bench-log 'Rental cost of hash'); the miner's opportunity cost = its hash share x 80% of 31.688 IGN/s x the proof's seconds; beside the miner the loss is the measured 4% of hash (5090 only, approximate for the others) and about 70 extra watts.
### 1.1 Proving alone (the card stops mining), cost per shard in USD at USD 0.02 per IGN, by network hash
| Card | MH/s | W | Shard alone s | Electricity per shard | Forgone subsidy per shard, IGN at 1 GH/s | USD at 1 GH/s | 10 GH/s | 100 GH/s | 1 TH/s | Renter's cost per shard |
|---|---|---|---|---|---|---|---|---|---|---|
| RTX 3060 | 23.78 | 104 | 14.4 | 0.000041 | 8.681 | 0.173657 | 0.017403 | 0.001778 | 0.000215 | 0.001113 |
| RTX 3080 | 40.82 | 205 | 7.1 | 0.00004 | 7.347 | 0.146982 | 0.014735 | 0.00151 | 0.000187 | 0.000942 |
| RTX 3090 | 37.79 | 229 | 14.9 | 0.000095 | 14.274 | 0.285576 | 0.028643 | 0.00295 | 0.00038 | 0.00183 |
| RTX 4060 Ti 16 | 17.58 | 72 | 11.6 | 0.000023 | 5.170 | 0.103416 | 0.010363 | 0.001057 | 0.000127 | 0.000663 |
| RTX 4060 Ti 8 | 19.07 | 73 | 9.6 | 0.000019 | 4.641 | 0.092838 | 0.009301 | 0.000948 | 0.000112 | 0.000595 |
| RTX 4060 | 17.07 | 115 | 18.4 | 0.000059 | 7.962 | 0.159304 | 0.015983 | 0.001651 | 0.000218 | 0.001021 |
| RTX 4070 | 24.99 | 91 | 12.1 | 0.000031 | 7.665 | 0.153339 | 0.015361 | 0.001564 | 0.000184 | 0.000983 |
| RTX 4090 | 52.25 | 183 | 6.3 | 0.000032 | 8.345 | 0.166926 | 0.016721 | 0.001701 | 0.000199 | 0.00107 |
| RTX 5070 | 41.89 | 137 | 4.8 | 0.000018 | 5.097 | 0.101963 | 0.010213 | 0.001038 | 0.00012 | 0.000653 |
| RTX 5090 | 98.48 | 258 | 6.3 | 0.000045 | 15.728 | 0.314605 | 0.031501 | 0.003191 | 0.00036 | 0.002016 |
| RTX A5000 | 47.70 | 223 | 8.3 | 0.000051 | 10.036 | 0.200781 | 0.020124 | 0.002059 | 0.000252 | 0.001287 |
### 1.2 Proving beside the running miner (hybrid), cost per shard in USD at USD 0.02 per IGN
| Card | Shard beside s | Extra electricity per shard | USD at 1 GH/s | 10 GH/s | 100 GH/s | 1 TH/s | USD per billion cycles at 100 GH/s |
|---|---|---|---|---|---|---|---|
| RTX 3060 | 37.5 | 0.000073 | 0.018158 | 0.001881 | 0.000254 | 0.000091 | 0.053793 |
| RTX 3080 | 25.6 | 0.00005 | 0.021243 | 0.002169 | 0.000262 | 0.000071 | 0.055476 |
| RTX 3090 | 19.9 | 0.000039 | 0.01529 | 0.001564 | 0.000191 | 0.000054 | 0.040532 |
| RTX 4060 Ti 16 | 34.6 | 0.000067 | 0.012403 | 0.001301 | 0.000191 | 0.00008 | 0.040411 |
| RTX 4070 | 27.3 | 0.000053 | 0.013889 | 0.001437 | 0.000191 | 0.000067 | 0.040582 |
| RTX 4090 | 26.1 | 0.000051 | 0.027708 | 0.002816 | 0.000327 | 0.000078 | 0.069385 |
| RTX 5070 | 37.2 | 0.000072 | 0.031675 | 0.003233 | 0.000388 | 0.000104 | 0.082325 |
| RTX 5090 | 10.7 | 0.000021 | 0.021391 | 0.002158 | 0.000235 | 0.000042 | 0.049711 |
| RTX A5000 | 34.6 | 0.000067 | 0.033538 | 0.003414 | 0.000402 | 0.000101 | 0.085213 |
### 1.3 Per billion cycles, the unit the market quotes (USD at 0.02 per IGN; the three IGN prices move only the opportunity term)
| Card | Alone, 1 GH/s | Alone, 100 GH/s | Alone, 1 TH/s | Beside, 100 GH/s | Renter (rental price, no subsidy) | Electricity only |
|---|---|---|---|---|---|---|
| RTX 3060 | 36.81 | 0.376823 | 0.045596 | 0.053793 | 0.235913 | 0.008793 |
| RTX 3080 | 31.16 | 0.320053 | 0.039715 | 0.055476 | 0.199668 | 0.008566 |
| RTX 3090 | 60.54 | 0.625236 | 0.08059 | 0.040532 | 0.387918 | 0.020074 |
| RTX 4060 Ti 16 | 21.92 | 0.224111 | 0.026856 | 0.040411 | 0.140493 | 0.004938 |
| RTX 4060 Ti 8 | 19.68 | 0.200861 | 0.02378 | core-only | 0.126124 | 0.004104 |
| RTX 4060 | 33.77 | 0.350027 | 0.046216 | core-only | 0.216386 | 0.01246 |
| RTX 4070 | 32.50 | 0.331473 | 0.038989 | 0.040582 | 0.208319 | 0.006491 |
| RTX 4090 | 35.38 | 0.360574 | 0.042171 | 0.069385 | 0.22678 | 0.006792 |
| RTX 5070 | 21.61 | 0.219975 | 0.025482 | 0.082325 | 0.138525 | 0.003872 |
| RTX 5090 | 66.69 | 0.676381 | 0.076259 | 0.049711 | 0.427431 | 0.009578 |
| RTX A5000 | 42.56 | 0.436389 | 0.053435 | 0.085213 | 0.272756 | 0.010884 |
Reading. The electricity of a shard is under a thousandth of a cent on every card. What a prover charges is set by the subsidy it forgoes, which scales as 1 / network hash: at 1 GH/s a 5090 that stops mining to prove gives up 15.7 IGN per shard (USD 0.31 at 0.02), at 1 TH/s 0.016 IGN. Beside its miner the same card gives up 4% of that for 10.7 s: at 100 GH/s about USD 0.0003 per shard, USD 0.06 per billion cycles, which is under Boundless's USD 0.21 median and above the ethproofs cluster cost. The renter's row is the hard floor nobody beats for long: USD 0.0020 per shard on a rented 5090, USD 0.43 per billion cycles.
## 2. The published prices this competes with (per billion cycles)
| Supplier | USD per billion cycles | Source and label |
|---|---|---|
| Boundless (RISC Zero), Base mainnet | 0.21 | median lock price about USD 0.21 per billion cycles, 8 provers, 8.4 T cycles in the explorer's trailing day; docs/design/developer-adoption.md 2b, secondary summary of 4 Oct 2026; approximate |
| Succinct Prover Network, example request | 0.506 | quickstart example: base fee 0.2 PROVE plus up to 2.0 PROVE per billion PGU at USD 0.23 per PROVE = USD 0.046 plus up to 0.46 per billion PGU; example parameters, not a market price; developer-adoption.md 2b; approximate |
| RISC Zero Bonsai (hosted API) | not published | no public per-cycle list price was ever published; Bonsai was priced on request and RISC Zero moved paid proving to Boundless in 2025; not cloned, approximate |
| Ethereum L1 block, ethproofs tracker cost, Sep 2026 | 0.005556 | sub-half-cent per L1 block (frontier.md 2.6, secondary); an L1 block of about 15 to 30 M gas at the 14 to 44 SP1 cycles per gas measured on Igneum (base-fee-floor.md, developer-adoption.md 2b) is 0.2 to 1.3 B cycles, so about USD 0.004 to 0.025 per billion cycles at cluster cost; approximate |
| Taiko-class rollup, per batch proof | not published | taiko-mono not cloned; Taiko Alethia proves batches through its own prover market (SGX plus ZK tiers, SP1 and RISC0 accepted); the ZK proof's cost per batch in 2026 is of the order of the ethproofs L1 figure times the batch's cycles, cents to tens of cents; approximate |
Igneum's own floor as a quote: a billion-cycle job at the adopted floor is 1,000,000 pgas x 10,000 gwei x 1.5 = 15 IGN (USD 0.075, 0.3, 1.50 at the three prices). At USD 0.02 that is 1.4x Boundless's median; at 0.005 it is a third of it; at 0.10 it is 7x. The floor was calibrated for spam, not for the market (base-fee-floor.md section 3), and it is a miner-signalled parameter.
## 3. Demand curves (a) to (e): dollars per day and the burn each causes
Scenario grid (every count is an assumption, labelled approximate): external proving USD per day {'launch': (0, 500, 2000), 'y2': (500, 2000, 10000), 'y5': (1000, 10000, 100000)}; rollups settling here {'launch': (0, 1, 2), 'y2': (1, 3, 10), 'y5': (2, 10, 50)} at 1440 batches a day, 100 KB calldata and a 1 B-cycle proof job each; bridges {'launch': (0, 1, 2), 'y2': (1, 3, 5), 'y5': (2, 5, 10)} at 225 updates a day of 1 B cycles; plain transfers {'launch': (10000, 100000, 1000000), 'y2': (100000, 1000000, 10000000), 'y5': (1000000, 10000000, 30000000)} at a 1 gwei tip. Fees at the adopted floors (spec 5.11); a job = pgas x f_p x 1.5 (design 6), 90% to provers, 10% burned once IGN-settled (spec 5.4).
### 3.1 Unit prices at the floors
| Item | IGN | USD at 0.005 | at 0.02 | at 0.10 |
|---|---|---|---|---|
| Plain transfer, base fee (burned) | 0.0051 | 0.000026 | 0.000102 | 0.00051 |
| Plain transfer, 1 gwei tip (80% miners and provers, 20% burned: no registered frame) | 0.000021 | 1.05e-07 | 4.20e-07 | 0.000002 |
| Rollup batch post, 100 KB calldata, base fee (burned) | 0.1651 | 0.000825 | 0.003302 | 0.01651 |
| Rollup batch proof job, 1 B cycles (90% provers, 10% burned) | 15.000 | 0.075 | 0.3 | 1.50 |
| Bridge update job, 1 B cycles | 15.000 | 0.075 | 0.3 | 1.50 |
| A v1 shard as a job (4.7 M cycles) | 0.070762 | 0.000354 | 0.001415 | 0.007076 |
### 3.2 Payments: what a transfer costs against 1 bps and 10 bps rails
| IGN price | Transfer fee at the floor, USD | Payment size at which the fee is 10 bps | At which it is 1 bps | Fee on a USD 100 payment, bps |
|---|---|---|---|---|
| 0.005 | 0.000026 | 0.0255 | 0.255 | 0.003 |
| 0.02 | 0.000102 | 0.102 | 1.02 | 0.010 |
| 0.1 | 0.00051 | 0.51 | 5.10 | 0.051 |
Reading: at the floor a transfer undercuts a 1 bps rail on any payment above USD 0.26 (at 0.005) to USD 5.10 (at 0.10); a USD 100 payment pays 0.003 to 0.05 bps. The fee is flat in IGN, so the chain takes nothing proportional to value; what payments give the coin is burn, not income (table 3.4). The proving dimension binds first: 400 transfers a block (`B_p` / 300), 34.6 M a day, and the EIP-1559 step leaves the floor above 17.3 M a day.
### 3.3 Dollars per day to miners and provers, by source, scenario and IGN price
| Period | Scenario | External jobs to provers, USD (any price) | Rollup settlement to provers, IGN | Bridges to provers, IGN | Payments tips to miners and provers, IGN | IGN flows in USD at 0.005 | at 0.02 | at 0.10 |
|---|---|---|---|---|---|---|---|---|
| launch | low | 0 | 0 | 0 | 0.168 | 0.00084 | 0.00336 | 0.0168 |
| launch | base | 450.00 | 19,440 | 3,038 | 1.680 | 112.40 | 449.58 | 2,248 |
| launch | high | 1,800 | 38,880 | 6,075 | 16.800 | 224.86 | 899.44 | 4,497 |
| y2 | low | 450.00 | 19,440 | 3,038 | 1.680 | 112.40 | 449.58 | 2,248 |
| y2 | base | 1,800 | 58,320 | 9,112 | 16.800 | 337.25 | 1,349 | 6,745 |
| y2 | high | 9,000 | 194,400 | 15,188 | 168.000 | 1,049 | 4,195 | 20,976 |
| y5 | low | 900.00 | 38,880 | 6,075 | 16.800 | 224.86 | 899.44 | 4,497 |
| y5 | base | 9,000 | 194,400 | 15,188 | 168.000 | 1,049 | 4,195 | 20,976 |
| y5 | high | 90,000 | 972,000 | 30,375 | 290.304 | 5,013 | 20,053 | 100,267 |
### 3.4 Burn per day, by source, scenario and IGN price
| Period | Scenario | Rollup burn, IGN (base fee + 10% of jobs) | Bridge burn, IGN | Payments burn, IGN (base fee + unregistered tip share) | Fee multiplier (payments) | External job burn, IGN at 0.02 (phase two only) | Total burn, IGN | USD at 0.005 | at 0.02 | at 0.10 | Burn as % of daily emission (2,737,851 IGN) |
|---|---|---|---|---|---|---|---|---|---|---|---|
| launch | low | 0 | 0 | 51.042 | at the floor | 0 | 51.042 | 0.25521 | 1.02 | 5.10 | 0.002% |
| launch | base | 2,398 | 339.368 | 510.420 | at the floor | 2,500 | 5,748 | 28.74 | 114.95 | 574.75 | 0.210% |
| launch | high | 4,795 | 678.735 | 5,104 | at the floor | 10,000 | 20,578 | 102.89 | 411.57 | 2,058 | 0.752% |
| y2 | low | 2,398 | 339.368 | 510.420 | at the floor | 2,500 | 5,748 | 28.74 | 114.95 | 574.75 | 0.210% |
| y2 | base | 7,193 | 1,018 | 5,104 | at the floor | 10,000 | 23,316 | 116.58 | 466.31 | 2,332 | 0.852% |
| y2 | high | 23,977 | 1,697 | 51,042 | at the floor | 50,000 | 126,716 | 633.58 | 2,534 | 12,672 | 4.628% |
| y5 | low | 4,795 | 678.735 | 5,104 | at the floor | 5,000 | 15,578 | 77.89 | 311.57 | 1,558 | 0.569% |
| y5 | base | 23,977 | 1,697 | 51,042 | at the floor | 50,000 | 126,716 | 633.58 | 2,534 | 12,672 | 4.628% |
| y5 | high | 119,887 | 3,394 | 88,201 | ABOVE TARGET: fee leaves the floor, volume clamped at 17.28 M | 500,000 | 711,481 | 3,557 | 14,230 | 71,148 | 25.987% |
### 3.5 Storage of proofs: bytes per block and per year, and who pays
| Period | Scenario | Transfers a day | Shards per block | Record bytes per block (in coinbase, kept to the pruning depth) | Record GB per year | Proof bytes per block (p2p, not in blocks) | Proof TB per year if every proof were kept | Who pays |
|---|---|---|---|---|---|---|---|---|
| launch | low | 10,000 | 1.0 | 347 | 11.0 | 2.5 MB | 80 | node operators; no fee reaches them for it |
| launch | base | 100,000 | 1.0 | 347 | 11.0 | 2.5 MB | 80 | node operators; no fee reaches them for it |
| launch | high | 1,000,000 | 1.0 | 347 | 11.0 | 2.5 MB | 80 | node operators; no fee reaches them for it |
| y2 | low | 100,000 | 1.0 | 347 | 11.0 | 2.5 MB | 80 | node operators; no fee reaches them for it |
| y2 | base | 1,000,000 | 1.0 | 347 | 11.0 | 2.5 MB | 80 | node operators; no fee reaches them for it |
| y2 | high | 10,000,000 | 1.2 | 390 | 12.3 | 2.7 MB | 87 | node operators; no fee reaches them for it |
| y5 | low | 1,000,000 | 1.0 | 347 | 11.0 | 2.5 MB | 80 | node operators; no fee reaches them for it |
| y5 | base | 10,000,000 | 1.2 | 390 | 12.3 | 2.7 MB | 87 | node operators; no fee reaches them for it |
| y5 | high | 30,000,000 | 3.5 | 1025 | 32.3 | 5.7 MB | 180 | node operators; no fee reaches them for it |
Reading: the records are small (274 bytes a shard, 586 per 8-block segment): 11 to 37 GB a year at the floor, USD 0.17 to 0.56 of disk a year per node (approximate HDD price), paid by whoever runs a node and by nobody else; no fee is charged for them because they ride in the coinbase extra data. The proof bytes (1.27 MB each) never enter a block and are discardable once the next segment proof has verified them by recursion (spec 7.8 item 1): a node keeps the proof pool for the 600-block record window (about 3 GB at four shards a block) and a light client keeps one 1.27 MB proof. Nobody is paid to archive proofs; an archive that kept every proof would hold 80 to 200 TB a year (USD 1,200 to 3,000 a year of HDD, approximate), which is a service someone sells, not a protocol cost.
## 4. The fee scenarios security_budget_10y.py reads (totals per day)
| Period | Scenario | Tips to miners and provers, IGN/day | Job income to provers in IGN/day (rollups + bridges) | External jobs, USD/day (90% to provers) | Burn, IGN/day (at 0.02 for the external part) |
|---|---|---|---|---|---|
| launch | low | 0.168 | 0 | 0 | 51.042 |
| launch | base | 1.680 | 22,478 | 500.00 | 5,748 |
| launch | high | 16.800 | 44,955 | 2,000 | 20,578 |
| y2 | low | 1.680 | 22,478 | 500.00 | 5,748 |
| y2 | base | 16.800 | 67,432 | 2,000 | 23,316 |
| y2 | high | 168.000 | 209,588 | 10,000 | 126,716 |
| y5 | low | 16.800 | 44,955 | 1,000 | 15,578 |
| y5 | base | 168.000 | 209,588 | 10,000 | 126,716 |
| y5 | high | 290.304 | 1,002,375 | 100,000 | 711,481 |