Horizon: lane 3 (finality-and-weight) lands: tonight's pause reconstructed, the departure announcement, weight capture priced

The 6 October 2026 pause from the observer rows: 20 keys holding 42.7 percent of the frozen
voter table left in three minutes; locks formed without the hub; the 2/3 rule paused at
checkpoint 6843 (53.1 percent of total) and the frozen table (Q5) held the pause for a window
where rule v2 would have locked after 35 minutes. Candidate rules run in a copy of the finality
simulator (seeds 7, 11, 13): only the departure announcement keeps 0 conflicting locks and ends
the pause under an hour. Weight capture priced at the measured USD 11.7 per GH/s-hour: the veto
0.52 x N for 30 days (USD 4,300 per GH/s of network), a lock alone 2.03 x N. Lock delay by voter
count measured on node 1; the ZK light client and prover attestations costed.

Models: sim/horizon/finality-and-weight/ (finality_horizon.py, weight_capture.py,
lightclient_cost.py, merge_results.py, results/).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
igneum-labs 2026-10-06 19:57:01 +00:00
parent a718d8e3af
commit f99d1905ff
21 changed files with 3501 additions and 0 deletions

View file

@ -0,0 +1,389 @@
# Horizon lane 3: finality and weight
Date: 6 October 2026, evening UK (written 19:30Z to 21:00Z, while the live devnet's finality was paused). Lane: finality-and-weight (the measured behaviour of the weight rule and the designs that extend it; lane 1 holds the attack catalogue and the 51 percent paper, cross-referenced by name). Worktree: `/Users/joshm/Projects/igneum-wt-horizon` (branch `horizon`). Models: `sim/horizon/finality-and-weight/` (README there says how to run every number).
What was read: `docs/spec/03-finality.md` (whole, 3.11 included), `04-seeds-and-vdf.md`, `10-light-client.md`, `06-open-items.md` (O-3.1 to O-3.19), the fud-close worktree's `docs/spec/03-finality.md` 3.4.2 (the proposed vote and bitmap bounds, decided 6 Oct 2026 per `docs/plans/ledger-decisions.md` line 57); `docs/fud-ledger.md` F1 to F25 (F9, F14, F16, F18, F19, F20, F21 via its status lines, F22, F23, F24), P3, P4, P22, X20; `sim/README.md`, `sim/results_v2.md` (A to M), `sim/finality_v2.py` (this worktree's and fud-close `1544c63` with the block reading and scenario O); `docs/benchmarks/finality-v3-2026-10-04/` (fold-v2, fold-v3, split50-v2, split50-v3, split70-v3), `docs/benchmarks/round4-consensus-2026-10-04/results-final2.md`; `tools/finality-attacks/README.md`; `docs/plans/finality-v3-rollout-devnet.md`, `finality-v3-devnet-publish.md`; `docs/bench-log.md` entries of 4 to 6 October mentioning finality (floor 2/3, first live lock, rule v3, the C4 fix, round-4 items, the finality route, the rental cost of hash at line 2582); the gpu-fleet worktree's `docs/bench-log.md`, `docs/plans/`, `tools/fleet/` (grep for finality, lock, pause, voters, weight: the fleet has written no lock-delay or voter-count row yet; `docs/analysis/block-rate-devnet2.md` is still the template with RUN_A and RUN_B empty at 19:45Z), `docs/analysis/prover-tiers-real-cards.md`; the observer database (read-only SELECTs over `live_checkpoints`, `live_certificates`, `live_blocks`, `live_events`, `live_state`), node 1's log `/tmp/igneum-devnet/node1.out` on the Mac; `vendor/igneum-node` `Cargo.toml` and `consensus/core/src/finality.rs` for the BLS crate; the SP1 6.8.1 crates in the cargo registry (no SP1 clone exists under `vendor/`).
## 1. The three findings first
1. **Tonight's pause was the rule, not the aggregation path, and it was the frozen table that held it past 19:14Z.** The 20 keys that left the live chain between 17:20Z and 18:30Z held 3,026 of 7,083 blue blocks of the table frozen at the last lock (42.7 percent; the 13 that left with the 18:27 to 18:30Z rehearsal job alone 36.5 percent). The first unlocked checkpoint, 6843 at DAA 209,233 (about 18:40Z), had 75 of 93 voters' votes and 53.1 percent of total weight on the observer's node, under the two-thirds floor; certificates had kept forming for 18 checkpoints while node 1 and the observer were down (6824 to 6842, 18:30 to 18:39Z, 83 signers, 78.5 to 79.7 percent of total). From 19:14:53Z (checkpoint 6912) the stayers held 74.9 percent of the sliding table and still did not lock, because they hold 57.3 percent of the frozen table of lock 6842, which stands until DAA 216,402 (about 20:40Z). Under rule v2 the first lock would have come at 6912, 35 minutes after the last; under v3 the pause is one window, 2 hours on the devnet and 30 days on mainnet (spec 3.7 item 2, the price the project lead took on 4 October).
2. **Of the four candidate rules, only the departure announcement keeps the one-third bound.** In the simulator (3 seeds, mainnet scale) the decaying denominator and the hysteresis floor both restore liveness after tonight's departure in under an hour and both reopen the partition double lock (fast decay: both sides of every 360-minute partition lock alone from minute 120, 467 to 473 conflicting locks in the 50/50 honest split and 17 to 264 in the poisoned eclipse; slow decay: both sides of the 12-day splits lock alone at day 0.5 to 0.9, 31,545 to 32,246 conflicts; hysteresis: a 20 percent equivocator conflicts from minute 60, 565 to 597 locks, the 13.3 percent bound of 3 October back). The leave rule locks 1 hour after the departure (0.04 days; under 4 devnet minutes) with 0 conflicting locks in every partition, eclipse and equivocator row, and an attacker who buys keys to make them leave gains nothing it would not get by signing with them (w + L must still reach 2/3). The two-tier report never conflicts in its final tier by construction and shows 516 to 1,062 conflicting PROVISIONAL locks in every 360-minute partition and about 34,000 in the 12-day splits, so it is a reporting layer with a health warning, not a rule.
3. **Weight costs USD 8,424 x N x W / (1 - W) to rent for the full window** at the measured USD 11.7 per GH/s-hour: a veto (34 percent) against a 1 GH/s network is USD 4,300 over 30 days (0.52 x N of hash, a +52 percent step on the chart from day 1), against 1 TH/s USD 4.3 M; locking alone (67 percent) is 2.03 x N for 30 days (USD 17,100 per GH/s of network, USD 17 M at 1 TH/s), and faster is dearer (22 days: 10.6 x N). Buying old keys costs the seller's own rental equivalent, decays to nothing in 30 days (sim K), and nothing in the protocol makes weight unbuyable; what keeps the price at the rental cost is that the seller keeps a copy and one equivocation strips the key.
## 2. Method
Measured: the observer's Neon database (tables written by `tools/observer/observer.mjs`: `live_checkpoints` per index with state, signed and total weight, votes seen and voter count; `live_certificates` with the voter table and bitmap per certificate; `live_blocks` with `vote_key_hash` per block; `live_events`), read with SELECTs only through a scratchpad script (`fetch` to the Neon SQL endpoint, refusing any statement that is not SELECT; the queries are quoted inline). Node 1's log on the Mac for determination-to-lock delays (the `determined` and `LOCKED` lines per index; the log ends at 18:46:32Z when node 1 stopped). The departed keys were matched from certificate voter tables (48-byte public keys) to block producers (`vote_key_hash`) with BLAKE2b-256 keyed by `IgneumVoteKeyHash` (the fork's domain, `consensus/core/src/finality.rs`), 93 of 93 keys matched.
Simulated: `sim/horizon/finality-and-weight/finality_horizon.py`, a copy of `sim/finality_v2.py` (fud-close `1544c63`) with four candidate rules and three scenarios (T, P, Q), run on igneum-build-1 (`/srv/builds/horizon-finality-and-weight/sim/`, Python 3.12, numpy 1.26.4, `nice -n 19`, one process per candidate, seeds 7, 11 and 13, about 25 minutes wall while the box carried other agents' builds at load 20 to 60); the smoke run on the Mac under `tools/lock/with-lock.sh run`. The model is the one `sim/results_v2.md` describes (1,000 Pareto keys, three regions, 2-s inter-region delay, 97 and 99.5 percent uptime, no DAG) at mainnet scale (30-day window); the devnet's window is 7,200 DAA s, so a mainnet day is four devnet minutes. Arithmetic scripts: `weight_capture.py`, `lightclient_cost.py`.
Not run: the fast-time node harness (`tools/finality-attacks`) for the leave message (no such message exists in the node); any BLS timing on this machine (the figures are approximate from the crate's published benchmarks, anchored to the one measured pure-JavaScript verifier); the fleet's block-rate run (its file was still a template at 19:45Z).
## 3. Evidence
### 3.1 Tonight's pause, from the observer rows and node 1's log
Times UTC. DAA scores advance about 1 per second on the live devnet. The observer's node is the view throughout; "votes" are the votes that node had seen for the index.
| When | What | Source |
|---|---|---|
| 17:20 to 17:55Z | Four keys mine their last blocks on the live chain (92376b1a 105 blocks of the later frozen table, d1ee753c 22, 25dbfb0b 155, c4eb3431 131: 413 blocks, 5.8 percent) | `live_blocks`, max(received_at) per key before 18:43Z |
| 18:27 to 18:30Z | Thirteen fleet keys mine their last blocks (d5996917 248, 39d2dafc 246, 52d9d8c8 236, 3ca93140 227, 8debbb2d 219, 92dabf9d 216, f155fac3 215, 6cd0935e 212, 0cf69e5c 208, 11047080 200, b42641ab 144, cf860e4d 121, 2aaa021b 91: 2,583 blocks, 36.5 percent of the frozen table): the class v4 rehearsal job stopping their miners | `live_blocks`; CLAUDE.md 6 Oct rules |
| 18:30:02Z | Node 1's last own lock, 6823 (DAA 208,631), 78 signers, 68.7 percent of total; node 1 and the observer go down with the desktop app until 18:42:08Z (`Observer reconnected to the node`) | node1.out; `live_events` |
| 18:30 to about 18:39Z | Locks 6824 to 6842 form without the hub (DAA 208,660 to 209,202): 83 signers, 78.5 to 79.7 percent of total; aggregators 3ca93140, 8debbb2d, 69570532 and the zero fallback | `live_checkpoints` (ingested at 18:42:09Z), `live_certificates` bitmaps |
| about 18:39:40Z | The last lock, 6842 at DAA 209,202, 79 of 91 signers. Its frozen table (the voter table at 6850 the observer stored with it): 93 keys, 7,083 blocks; the 20 departed keys hold 3,026 (42.7 percent), the stayers 4,057 (57.3 percent) | `live_certificates` 6842 voters, matched to `live_blocks` |
| about 18:40Z | 6843 at DAA 209,233 determined and never locked: 75 votes, 3,757 of 7,080 = 53.1 percent of total. The departed boxes' nodes have left the live chain (their blocks had already stopped), the signing weight is under two thirds: the rule pauses | `live_checkpoints` |
| 18:42:08Z | The observer reconnects and the pause becomes visible on the hub; node 1 ingests 6828's certificate by gossip (`70.3% of the table frozen at lock 6827`) | `live_events`, node1.out |
| about 18:50Z | Twenty indices without a lock (6862, DAA 209,800): `finality_active` false, reason `paused` (spec 3.9) | `live_checkpoints` |
| 18:58 to 19:03Z | Votes seen fall to 49 (48.5 percent): five more keys quiet for five minutes (the hands' own restarts, approximate) | `live_checkpoints` 6876 to 6885 |
| 19:14:53Z | 6912 at DAA 211,301: 79 votes, 5,355 of 7,152 = 74.9 percent of the SLIDING table, over two thirds; no lock. The stayers hold 57.3 percent of the table frozen at 6842 (Q5), under two thirds: "held by the frozen table" | `live_checkpoints`; spec Q5 |
| 19:29Z (write-up) | Still paused: 6938 proposed at 79.2 percent with 78 votes. Expected first lock when the frozen table expires at DAA 216,402 (209,202 + 7,200), about 20:40Z, or when departed keys holding 9.4 points of the frozen table return | `live_checkpoints`; arithmetic |
Under rule v2 (sliding table only) the stayers' share rises as the departed blocks age out: from 53.1 percent at 6843 to two thirds after 7,200 x (1 - 1/(3 x 0.469)) = 2,082 DAA (spec 3.3.1's churn formula at the devnet window), which is checkpoint 6912 at 19:14:53Z: a 35-minute pause. Under v3 it is one window: 2 hours here, 30 days on mainnet (spec 3.7 item 2; `sim/results_v2.md` M4). The coordinator's working hypothesis of 19:3xZ (topology: the hub was down and the fleet's votes could not reach the VRF-picked aggregators) is refuted by three rows above: certificates formed while the hub was down (6824 to 6842), the zero-aggregator fallback of Q4 is in routine use (64 of the 251 certificates stored between 16:30 and 19:00Z name aggregator `00000000`, the next most frequent key 34), and the pause began at the checkpoint where the signing weight fell to 53.1 percent, which is the rule's threshold and not a routing failure. The observer's node itself held 74.9 percent of the sliding weight in votes from 19:14Z and did not certify, which only Q5 explains.
Per tier: a home miner, rig or pool user on the live devnet saw `finality_active` false for 2 hours and lost nothing (blocks, execution and payouts continued; the exchange guidance of 3.9 applies); a prover's records were still paid; the fleet operator learned that a standing box never leaves the live chain for an experiment (CLAUDE.md, the standing-fleet rule). On mainnet the same event, 43 percent of weight leaving in three minutes, is a 30-day pause under v3 and a 7.7-day one under v2.
### 3.2 Lock delay against voter count (measured)
Determination-to-lock on node 1 (the `determined` and `LOCKED` lines per index, 6 October 2026, per UTC hour; voter counts from the observer's `live_checkpoints` for the same hour).
| Voters above dust | UTC hours | Locks | Delay p50 | p90 | p99 | Max | Source |
|---|---|---|---|---|---|---|---|
| 4 to 9 | 01 to 06 | 119 to 120 per hour | 0.86 to 0.97 s | 1.17 to 1.31 s | 1.53 to 1.80 s | 1.59 to 2.29 s | node1.out |
| 17 to 24 | 07 to 11 | 118 to 120 | 0.82 to 0.97 s | 1.09 to 1.29 s | 1.53 to 1.83 s | 2.18 s (the 111-s p90 of 08Z is a restart) | node1.out |
| 24 to 30 | 12 to 14 | 104 to 122 | 0.94 to 1.32 s | 1.36 s (quiet hours) | 48 s (restarts) | | node1.out |
| 43 | 16 | 54 (hour cut by a restart) | 0.92 s | 1.17 s | | | node1.out |
| 63 | 17 | 124 | 1.13 s | 1.44 s | 1.47 s | 8.8 s | node1.out |
| 92 to 93 | 18 | 125 (to 18:30Z) | 1.26 s | 1.50 s | 1.82 s | 1.82 s | node1.out |
| 6 (fast time, 300-ms proxied links) | 4 Oct | 11 to 12 per node | 1.008 s | | | | `docs/benchmarks/finality-v3-2026-10-04/fold-v3.md` |
| 12 (cloud devnet, 5 locations) | 4 Oct | 212 indices | 1.24 s to the first certificate (p99 1.71 s), the last vote 1.45 s (p90 2.36 s) | | | | ledger F22, `infra/cloud-devnet/results/2026-10-04/f22-vote-timing.md` |
| 1,000 (simulator, 2-s inter-region delay) | | 172,883 | 2.5 s | | 4.6 s | 6.1 s | `sim/results_v2.md` A |
The devnet's delay is the miner's 1-s template poll plus one gossip round (the 250-ms gossip pump per hop, ledger F22): 0.9 s at a handful of voters, 1.26 s at 93. A straight line through the devnet rows is 0.9 s + 4 ms per voter (approximate; 93 points on one topology), which puts 1,000 voters near 5 s and 8,192 near 34 s, past the 30-s interval. Section 4.3 says why that line does not hold and what does.
### 3.3 Sizes and crates (from the fork)
| Item | Value | Source |
|---|---|---|
| BLS crate | `blst = "0.3.17"` (workspace), min-pubkey variant: 48-byte G1 keys, 96-byte G2 signatures | `vendor/igneum-node/Cargo.toml` line 238, `consensus/core/Cargo.toml` line 20, `consensus/core/src/finality.rs` line 17 (`blst::min_pk`) |
| Aggregation and verification | `AggregateSignature::aggregate` over the votes' signatures (line 230); `fast_aggregate_verify` over the summed public keys and one vote message (line 239 to 253) | `consensus/core/src/finality.rs` |
| Vote item | 281 B (tag 1, index 8, checkpoint 32, key 48, signature 96, sortition proof 96) | spec 3.4.2 item 1 (fud-close), the fork's `Vote::LEN` |
| Certificate | 273 B plus ceil(V/8) B of bitmap: 285 B at 93 voters, 398 at 1,000, 1,297 at 8,192, 8,465 at 65,536 | same |
| Bitmap wire bound | 1 MiB today (`Certificate::read`, `bitmap_len > 1 << 20`, line 429); 8,192 B proposed and decided (65,536 voters, 8x the S2 switch) | finality.rs; spec 3.4.2 item 3; ledger-decisions line 57 |
| Per-block vote bound | 48 on the devnet; 384 on mainnet proposed and decided (107,904 B, 21.6 percent of the compute mass; a checkpoint's 8,192 votes drain in 21.3 blocks) | spec 3.10 Q1/Q2 row; 3.4.2 item 2 |
| Votes per checkpoint, single-vote carriage | 93 voters: 26,133 B; 1,000: 281,000 B; 8,192: 2,301,952 B (4.6x one block's mass); 65,536: 18.4 MB | 281 x V |
| Votes per checkpoint, aggregated carriage (Q2 allows one BLS signature plus a bitmap per (index, hash)) | about 0.4 KB at 93 voters, 1.2 KB at 8,192, 8.6 KB at 65,536 | spec 3.4.2 item 2 |
### 3.4 What the simulator already measured (cited, `sim/results_v2.md`)
| Fact | Value | Section |
|---|---|---|
| Churn under v2: first lock after a set holding x stops mining and signing | 35 percent: 1.7 days (analytic 1.4); 50 percent: 10.1 to 10.3 days (analytic 10.0) | L2, D |
| Churn under v3 | 30.00 days at 35 and 50 percent (the frozen table's expiry) | M4 |
| Silent set that keeps mining | 34 percent and above: no lock for as long as it is silent; first lock 0 min after it returns | J, L1 |
| Equivocator across a 50/50 split | 33 percent: 0 conflicts; 34 percent: 2 to 54 conflicts from minute 2 to 77 (the one-third bound) | H, M5 |
| Long honest partition, view-local weight, v2 against v3 | 50/50: both sides lock alone from day 10.1 to 10.3 under v2, never in 12 days under v3, both at day 30.00 of a 31-day split | L4, M2, M3 |
| Acquired keys worth 40 percent, attacker at 30 percent of hash | veto from day 1 to day 19 or 20, 30 percent on day 30; silent, 63,307 to 68,716 of 86,400 checkpoints stalled | K |
## 4. Model
### 4.1 The pause arithmetic (spec 3.3.1 and 3.7, restated with tonight's inputs)
Let x be the share of the window weight that stops mining and signing at once, W the window (2,592,000 DAA s on mainnet, 7,200 on the devnet).
| Rule | First lock after the departure | Tonight (x = 0.469 on the observer's node at 6843, W = 7,200) | Mainnet, same x |
|---|---|---|---|
| v2, sliding table | W x (1 - 1/(3x)) (never for x at or under 1/3) | 2,082 DAA, 35 min: measured as the moment the sliding share crossed two thirds (6912) | 7.7 days |
| v3, frozen table (live) | W after the last lock, whatever x over 1/3 | 7,200 DAA, 2 h (expected 20:40Z) | 30 days |
| (iv) leave, delay D | D after the signed leave (0 if sent D before the stop) | 1 h, or 0 with notice | 1 h |
| (i) decay, grace T, rate r per hour | at most T + (1/r) x (1 - (1 - x)/(2x)) hours: the departed weight decays until the stayers hold two thirds of what is left | T 1 h, r 0.5: 1 h 17 min (x 0.469); T 6 h, r 1/24: 20 h | the same hours |
| (iii) hysteresis, H hours, low floor f | H hours, then only if the stayers hold f x 2/3 of total | f = 0.85: 56.7 percent needed, the stayers held 53.1 then 57.3 percent: after H plus the ageing to 56.7 percent, 1 to 1.5 h | about 1 day |
| (ii) two-tier | provisional at once (2/3 of the active denominator); final as v3 | provisional 0 min, final 2 h | provisional minutes, final 30 days |
### 4.2 Why the view-dependent candidates fail (and the sim's confirmation)
Spec 3.11.2's bound comes from counting: two certificates at one index need 2/3 of the denominator each, 4/3 in all, so a third signed both and that third is equivocating. The denominator has to be the same number on both sides of a partition for that sum to mean anything. A rule that removes weight on what a view has not seen (a vote missing for T hours, blocks missing) gives each side a different denominator: side A removes side B's keys, side B removes A's, and both sides' own share rises toward 1 at the same rate. For a 50/50 split under decay(T, r) each side's own share reaches 2/3 when the other side's factor is 0.5, at T + 1/(2r) hours (2 hours at T 1 h, r 0.5; 18 hours at T 6 h, r 1/24), and every checkpoint after that is a conflicting lock, the hazard of `sim/results_v2.md` E in a new coat. The frozen table does not save it when the decay is applied to the frozen table too (which is the only way decay helps tonight). The hysteresis floor is view-dependent in the same way (each side measures its own connected share), so after H hours both sides run the 0.85 rule and the 13.3 percent equivocator bound of 3 October returns (the sim's 33 percent row under `hyst` shows one side locking at minute 59). A rule that removes weight on what a view has seen, a signed leave carried in the DAG or equivocation evidence, is seen by both sides at the heal and by at most one side during the split; during the split the side that saw the leave removes L from its denominator and needs 2/3 (1 - L) of signers while the other side still needs 2/3 of the full table; both locking needs s_A + s_B at least 2/3 (2 - L), more than the 1 - L available without an equivocator, so the one-third bound survives (with an equivocator a, the bound is a at least (1 - L)/3 of the remaining weight, the same statement over the reduced table).
Why leaving bought keys buys nothing (Q2 in the sim and arithmetic): an attacker holding w of the window who buys L and makes it leave holds w / (1 - L) of what remains; to lock alone it needs w at least 2/3 (1 - L), so w + L at least 2/3 + L/3, never under two thirds of the window, and signing with the bought keys (w + L at least 2/3) is the cheaper use of the same purchase. In the model the attacker's share after leaving its bought 40 percent was 4.8 percent on day 3, the position of its own hash alone.
### 4.3 Lock delay as a function of voters and message delay
delay = template poll (1 s on the devnet; the node's own determination on mainnet, 0) + hop_1 (block to voter) + hop_2 (vote to aggregator) + processing + certificate gossip (one hop). The simulator's two hops at Delta give median 0.7 s at 0.5 s, 2.5 s at 2 s, 6.2 s at 5 s (A); the devnet's 0.9 s is the poll plus a 250-ms pump. The per-voter term is the aggregator's verification of each vote as it arrives: one BLS verify is a pairing, about 1.6 ms (approximate, blst 0.3.17 published figures; the fork verifies each vote on ingest, `verify_vote_signature`, finality.rs line 192), which is 150 ms per checkpoint at 93 voters, 1.6 s at 1,000, 13 s at 8,192 and 105 s at 65,536 on one core: past 1,000 voters the single-vote path is a CPU bound before it is a bandwidth bound, and at 8,192 it is more than a quarter of every core's time on every node (every node verifies every vote it relays). The fix is already in the spec's text (Q2 aggregated carriage) and in the crate (`AggregateSignature::aggregate` then one `fast_aggregate_verify`): aggregate first, verify once per (index, hash), which costs V G1 additions (about 1 us each) plus one pairing: 1.7 ms at 93, 2.6 ms at 1,000, 10 ms at 8,192, 67 ms at 65,536. Its price is the batch-poisoning vector (one invalid vote fails the batch and forces bisection); S2's sub-user sortition at 8,192 bounds the signer count at about 4,000 expected either way.
| Voters | Vote bytes per checkpoint (single) | Verify per checkpoint, single votes (approximate) | Aggregate-first (approximate) | Lock delay, model (Delta 2 s) | Bitmap |
|---|---|---|---|---|---|
| 12 | 3.4 KB | 19 ms | 1.6 ms | 2.5 s (sim A, 1,000 keys) ; 1.24 s measured | 2 B |
| 40 | 11 KB | 64 ms | 1.6 ms | about 2.5 s | 5 B |
| 100 | 28 KB | 160 ms | 1.7 ms | about 2.6 s; 1.26 s measured at 93 on the devnet | 13 B |
| 1,000 | 281 KB | 1.6 s | 2.6 ms | about 4 s single, 2.5 s aggregated | 125 B |
| 8,192 | 2.3 MB (4.6x block mass) | 13 s per node per checkpoint: breaks the 30-s cadence on a shared core | 10 ms | 2.5 s aggregated; S2 switches to about 4,000 sub-users here | 1,024 B |
| 65,536 | 18.4 MB | 105 s: impossible single | 67 ms (3.9 s of key decompression once) | 2.5 s aggregated | 8,192 B, the proposed wire bound |
Where the aggregator path breaks down: not at the 8 VRF-picked aggregators (anyone MAY aggregate, Q4's fallback at 15 DAA is in routine use tonight: 26 percent of certificates) but at per-vote verification above about 1,000 voters and at the per-block vote carriage above 8,192 (spec 3.4.2 item 2's 384-per-block bound drains a checkpoint in 21 blocks; participation accounting lags, locks do not, because certificates form from gossiped votes). The message-delay term scales the two hops and nothing else; at 5 s inter-region delay the slowest region already loses participation to the 15-s grace (A).
### 4.4 Weight capture cost (task 3; `weight_capture.py`)
share(t) = (t/30) x A/(N + A) for A rented against N for t days (spec 3.1, sim B within 0.04 points). To hold W at day t: A = N q/(1 - q), q = 30W/t (needs t over 30W). Cost = A x t x 24 x USD 11.7 per GH/s-hour (measured 6 Oct 2026, bench-log "Rental cost of hash": 1,748 MH/s for USD 20.44/h on RunPod community pods; the 8x 4090 rig USD 5.92/h for 459 MH/s). Cost falls with t, so the cheapest attack takes the full window: A = N W/(1 - W), cost = 8,424 x N x W/(1 - W) USD per GH/s of network.
| Target | Hash to rent | Day noticed (the chart step) | N = 1 GH/s | N = 10 GH/s | N = 100 GH/s | N = 1 TH/s |
|---|---|---|---|---|---|---|
| 34 percent in 30 days (veto) | 0.52 x N | day 1: +52 percent | USD 4,300 | USD 43 k | USD 434 k | USD 4.3 M |
| 34 percent in 21 days | 0.94 x N | day 1: +94 percent | USD 6 k | USD 56 k | USD 557 k | USD 5.6 M |
| 51 percent in 30 days | 1.04 x N | +104 percent | USD 8.8 k | USD 88 k | USD 877 k | USD 8.8 M |
| 67 percent in 30 days (locks alone) | 2.03 x N | +203 percent | USD 17 k | USD 171 k | USD 1.71 M | USD 17.1 M |
| 67 percent in 25 days | 4.10 x N | +410 percent | USD 29 k | USD 288 k | USD 2.9 M | USD 28.8 M |
| 67 percent in 22 days | 10.6 x N | +1,058 percent | USD 65 k | USD 654 k | USD 6.5 M | USD 65 M |
What the market supplies: RunPod gave 0 of 20 pods asked at 18:59Z to 19:15Z (bench-log); 38 pods were 1.75 GH/s. So at tonight's 1.16 GH/s devnet every row of the first column is a dinner; at 100 GH/s the 52 GH/s for a veto did not exist on the one market asked (approximate). The alarm that sees the step is lane 1's detector.
Buying old keys (F19): a key is a 32-byte scalar named in headers by `vote_key_hash`; it can be handed over, W5 succession moves its history once (not implemented, O-3.11), and the seller can keep a copy. Its worth is its blocks: keys worth b of the window are the position of having rented b/(1 - b) x N for 30 days (USD 2,100 per GH/s of network at b 20 percent, 4,300 at 34, 5,600 at 40), and that position decays as b (1 - t/30) + r t/30 (sim K, within 0.6 points). What makes weight unbuyable: nothing in the protocol. What makes bought weight a bad buy: it ages out in 30 days whatever the buyer does, a seller's copy can equivocate it away (3.6), and a pool's key is its payout identity, so the price is the pool. What the header does not do: it does not tell anyone the key changed hands until its blocks stop matching its old profile (the detector's job). A rule that would make it harder, decaying a key whose block profile breaks, is view-dependent in a partition (section 4.2) and is not recommended.
Per tier: a home miner's or rig's key (one per machine under 3.4.2 item 4) is worth its 30 days of blocks and nothing a buyer would pay for; a pool's key is the only one worth buying and the only one whose sale is visible; a holder's finality rests on the 2/3 of weight no one can rent cheaply past a few GH/s; a rollup customer's bridge inherits the same bound.
### 4.5 Long-range and checkpoint sync for light clients (task 4; `lightclient_cost.py`)
What a node joining after 60 days trusts (spec 10.1 and 10.3): the trusted checkpoint shipped in its release, refreshed from N of M seed nodes (M 5, N 3, O-10.5), and from there every certificate it fetches is verified against the voter set, which in checkpoint mode it takes from nodes (N of M agreement) and in full-header mode recomputes from 30 days of headers (W2). The cold-sync node of X20 selects the heaviest DAG then follows certificates found in it (F5), so its first 30 days of history are proof of work in the sense of 3.9. The weak-subjectivity window Igneum has in fact is one weight window: a certificate older than 30 days can be checked only against a voter table the client cannot recompute from less than 30 days of headers, and under v3 the frozen table expires 30 days after a lock, so a node offline longer than 30 days cannot tell a certified chain from a chain certified by keys that have since left the window; the same class of assumption as Ethereum's weak-subjectivity period for a sync-committee checkpoint (not cloned here; approximate), with the window the parameter.
| Mode, per year of chain | 93 voters | 1,000 | 8,192 | 65,536 | Source |
|---|---|---|---|---|---|
| Every certificate (1,051,200) plus its header, bytes | 720 MB | 839 MB | 1.78 GB | 9.3 GB | 285 to 8,465 B per certificate plus 400 B header |
| Verify time, one laptop core (approximate: V G1 adds plus hash-to-G2 plus two pairings, 1.8 to 67 ms each) | 32 min | 48 min | 2.9 h | 20 h | `lightclient_cost.py` |
| On a phone core (3x, approximate) | 1.6 h | 2.4 h | 8.7 h | 59 h | |
| One certificate per presence window (4,380 a year, spec 10.3 item 3) | 3.0 MB, 8 s | 3.5 MB, 12 s | 7.4 MB, 44 s | 39 MB, 4.9 min | the voter set at each stop is not paid for |
| Full-header mode, headers alone | 12.6 GB a year at 400 B per header | | | | |
The measured anchor: the pure-JavaScript verifier of a 16-signer certificate took 58 to 68 ms warm on the M5 Max (bench-log, sweep round 6, P3), about 30x the native estimate here; a phone has not been measured (O-10.3).
The ZK light client (phase two, O-10.8), designed: one recursive proof per checkpoint whose step statement is "certificate i verifies under voter table T_i; T_i follows from T_(i-1) by the interval's 30 blue headers and the window's ageing; the signers hold at least two thirds of T_i and of the frozen table; C_i's selected chain passes through C_(i-1)", with the previous step's proof verified inside (SP1's deferred-proof path, `VERIFY_SP1_PROOF` in `sp1-core-executor-6.8.1/src/syscall_code.rs`; the recursion crates are `sp1-recursion-{circuit,compiler,executor,machine,gnark-ffi}-6.8.1` in the cargo registry, no SP1 clone under `vendor/`). What the circuit costs, approximate: key aggregation V x BLS12381_ADD (about 500 cycles each: 46,500 cycles at 93 voters, 0.5 M at 1,000, 4.1 M at 8,192); hash-to-G2 about 0.3 M (SHA-256 is precompiled, the Fp2 arithmetic is BLS12381_FP2_*); the two pairings 10 to 30 M cycles, the dominant term, because 6.8.1 has Fp and Fp2 precompiles for BLS12-381 (ADD, DOUBLE, FP_ADD/SUB/MUL, FP2_ADD/SUB/MUL) and no pairing precompile; 30 BLAKE2b header hashes and the table transition 1 to 2 M (no BLAKE2b precompile). Against the measured shard curve (`docs/analysis/prover-tiers-real-cards.md`: 4.7 M cycles compressed in 4.8 to 14.4 s alone, 10.7 to 37.5 s beside the miner) a 15 to 35 M cycle step is 15 to 100 s alone and 40 to 260 s beside a miner, plus the recursion step measured at 2.2 to 2.5 s idle and 7.9 to 9.7 s beside the miner on the 5090 (bench-log, agg-cost and `chain-pc2-pv1c`). One checkpoint every 30 s therefore needs 1 to 4 proving-only cards (or 2 to 9 mining ones) at it continuously. A Groth16 wrap for the phone is the unbuilt R4 (P3).
What it buys each tier: a phone wallet verifies one wrapped proof per open (about 400 B, milliseconds once the wrapper exists) instead of a certificate chain and a trusted voter set, and the "voter set: from nodes" status disappears (spec 10.4); a bridge verifies one proof per checkpoint it settles on and never a BLS certificate on-chain (an on-chain BLS12-381 aggregate verify at 1,000 voters is about 1,000 G1 additions and one pairing, which on Ethereum is the point-evaluation and pairing precompile budget, approximate); a rollup customer gets a finality statement its own verifier can check without Igneum's voter list; a node operator pays nothing (full nodes keep the native rule); a prover tier gains a steady job (one proof per 30 s) at the cycle counts above; a home miner with one 12 GB card beside its miner (27 to 37 s per 4.7 M-cycle shard) cannot keep up with a 30-s cadence alone and joins as one of several; a 24 or 32 GB card alone does it in the interval.
### 4.6 Prover attestations as a second finality leg (task 5)
Design: a checkpoint locks when (a) its certificate carries two thirds of weight (Q3, Q5) AND (b) proof records covering every chain block in (C_(i-1), C_i] from at least k distinct prover keys are in the past of some block the certificate's signers could see. Measured inputs: the proof lag on the live devnet, block to carried record, p50 44 s, p90 52 s, p99 62 s, max 65 s (bench-log, proving v1 coverage windows, 5 Oct); coverage 2.4 to 4.7 percent of blocks with one prover (the same rows); the chain-mode cost 17 s per empty block on a mining 5090, about 5 s proving-only; a 12 GB card beside its miner 27 to 37 s per v1 shard (prover-tiers); a mandatory rule needs about 6 proving-only 5090s or 18 mining ones for an empty-block chain at 1 block/s (bench-log table), 45 proving-only at B_p.
| Measure | Weight alone (today) | Weight AND k-prover attestations | Label |
|---|---|---|---|
| Lock delay after the checkpoint block | 1.26 s at 93 voters (3.2) | at least the slowest block's proof lag inside the interval: p99 62 s today, so about 60 to 70 s; the transaction-to-lock figure of C1 rises from 90 to 120 s to about 150 to 190 s | measured lag, derived sum |
| Checkpoints that could lock on tonight's devnet | all with two thirds signing | 2.4 to 4.7 percent (one prover): finality paused 95 percent of the time until proving is mandatory and the fleet is 6 to 18 cards | measured coverage |
| What it stops that weight does not | nothing for a full node: it re-executes and vetoes a statement that is not the native one (spec 7.2 item 5, the native veto) | a two-thirds weight holder cannot lock a checkpoint whose execution has no valid proof, which protects the LIGHT client, who trusts certificates and cannot execute (10.1); the design already gives the light client that by requiring the segment proof beside the certificate (10.4 item 4), so the leg moves the requirement from the client into the lock | design |
| Withholding to pause | a silent third pauses (L1) | a prover set that withholds proofs pauses finality for as long as no one else proves; the shard sortition names 8 provers by weight with a 10-s exclusive window and then anyone MAY prove (spec 7.2), so the price of a pause is out-proving every honest card for the whole pause, which in a thin market (tonight: one prover at times) is one card's outage | design, measured market |
| Per tier | unchanged | a 12 GB card beside its miner proves one 4.7 M-cycle shard in 27 to 37 s, so k = 2 provers per block means 37k mining 12 GB cards (or 10k proving-only 4070s at 12 s) kept busy for an empty chain, approximate; a pool user nothing; a holder a longer wait; a rollup customer the same proof it already needs | prover-tiers, derived |
Verdict: not as a lock condition now. The leg converts "locked" into "locked and proven" at the cost of a minute of lock delay and a pause whenever proving coverage drops, which tonight is almost always. The design's four-state interface (included, executed, proven, locked; O-7.2) already gives the exchange and the wallet the conjunction as a reading. Gate before it could become a rule: 99 percent of chain blocks proven within 60 s for 7 days on the public testnet with at least 3 distinct provers per block, measured by `tools/proving-v1/coverage.mjs`.
## 5. Results of the candidate runs (`finality_horizon.py`, seeds 7, 11, 13)
### 5.1 T. Tonight's departure: first lock after x of weight stops mining and signing at once (31 days, seeds 7, 11, 13)
| departed weight | rule | first final lock after the departure | provisional tier | stalled checkpoints | stalled after the first lock | conflicting final locks | conflicting provisional locks |
|---|---|---|---|---|---|---|---|
| 34% | v2 | 0.77 to 0.87 d (devnet 3 to 3 min) | | 4057 to 4727 | 1531 to 2489 | 0 | |
| 34% | v3 | 30.00 d (devnet 120 min) | | 86388 to 86472 | 0 to 31 | 0 | |
| 34% | leave | 0.04 to 0.04 d (devnet 0 to 0 min) | | 119 to 521 | 0 to 398 | 0 | |
| 34% | decay1 | 0.05 to 0.05 d (devnet 0 to 0 min) | | 133 to 534 | 0 to 398 | 0 | |
| 34% | decay6 | 0.30 to 0.30 d (devnet 1 to 1 min) | | 897 to 1298 | 24 to 442 | 0 | |
| 34% | hyst | 0.04 to 0.04 d (devnet 0 to 0 min) | | 881 to 1445 | 761 to 1325 | 0 | |
| 34% | twotier | 30.00 d (devnet 120 min) | provisional 0.000 to 0.004 d (devnet 0.0 to 0.0 min) | 86388 to 86472 | 0 to 31 | 0 | 0 |
| 45% | v2 | 7.85 to 8.03 d (devnet 31 to 32 min) | | 23947 to 25153 | 1369 to 2065 | 0 | |
| 45% | v3 | 30.00 d (devnet 120 min) | | 86382 to 86420 | 0 to 22 | 0 | |
| 45% | leave | 0.04 d (devnet 0 min) | | 119 to 204 | 0 to 83 | 0 | |
| 45% | decay1 | 0.07 to 0.08 d (devnet 0 to 0 min) | | 224 to 296 | 9 to 83 | 0 | |
| 45% | decay6 | 0.64 to 0.67 d (devnet 3 to 3 min) | | 1946 to 1985 | 0 to 129 | 0 | |
| 45% | hyst | 30.00 d (devnet 120 min) | | 86382 to 86420 | 0 to 22 | 0 | |
| 45% | twotier | 30.00 d (devnet 120 min) | provisional 0.031 to 0.034 d (devnet 0.1 to 0.1 min) | 86382 to 86420 | 0 to 22 | 0 | 0 |
| 50% | v2 | 10.09 to 10.34 d (devnet 40 to 41 min) | | 30107 to 31264 | 1065 to 1456 | 0 | |
| 50% | v3 | 30.00 d (devnet 120 min) | | 86387 to 86514 | 0 to 10 | 0 | |
| 50% | leave | 0.04 d (devnet 0 min) | | 118 to 490 | 0 to 371 | 0 | |
| 50% | decay1 | 0.08 to 0.09 d (devnet 0 to 0 min) | | 244 to 609 | 0 to 371 | 0 | |
| 50% | decay6 | 0.76 to 0.77 d (devnet 3 to 3 min) | | 2231 to 2543 | 0 to 371 | 0 | |
| 50% | hyst | 30.00 d (devnet 120 min) | | 86387 to 86514 | 0 to 10 | 0 | |
| 50% | twotier | 30.00 d (devnet 120 min) | provisional 0.042 d (devnet 0.2 min) | 86387 to 86514 | 0 to 10 | 0 | 0 |
### 5.2 P1. Partitions of 360 minutes (each side retargets and counts only its own blocks)
| rule | case | partition min | conflicting final locks | conflicting provisional locks | first conflict, min | first lock per side, min | every pre-heal lock kept | first lock after heal, min | stalls in 2 h after heal |
|---|---|---|---|---|---|---|---|---|---|
| v2 | 50/50 honest, 0% attacker | 360 | 0 | | never | never / never | yes | 0 | 0 |
| v2 | 50/50 + 20% equivocator (sides 60/60) | 360 | 0 | | never | never / never | yes | 0 | 0 |
| v2 | 50/50 + 33% equivocator (sides 66.5/66.5) | 360 | 0 | | never | never / 279 (never in 2 of 3) | yes | 0 to 0 | 0 |
| v2 | 50/50 + 34% equivocator (sides 67/67, the bound) | 360 | 142 to 291 | | 14 to 77 | 11 to 48 / 0 to 76 | yes | 0 to 0 | 0 |
| v2 | 40/40/20 honest | 360 | 0 | | never | never / never / never | yes | 0 to 0 | 0 |
| v2 | 60/40 honest | 360 | 0 | | never | never / never | yes | 0 to 0 | 0 |
| v2 | 70/30 honest | 360 | 0 | | never | 0 to 4 / never | yes | 0 | 0 |
| v3 | 50/50 honest, 0% attacker | 360 | 0 | | never | never / never | yes | 0 | 0 |
| v3 | 50/50 + 20% equivocator (sides 60/60) | 360 | 0 | | never | never / never | yes | 0 | 0 |
| v3 | 50/50 + 33% equivocator (sides 66.5/66.5) | 360 | 0 | | never | never / never | yes | 0 to 0 | 0 |
| v3 | 50/50 + 34% equivocator (sides 67/67, the bound) | 360 | 139 to 283 | | 14 to 78 | 12 to 50 / 0 to 77 | yes | 0 to 0 | 0 |
| v3 | 40/40/20 honest | 360 | 0 | | never | never / never / never | yes | 0 to 0 | 0 |
| v3 | 60/40 honest | 360 | 0 | | never | never / never | yes | 0 to 0 | 0 |
| v3 | 70/30 honest | 360 | 0 | | never | 0 to 4 / never | yes | 0 | 0 |
| leave | 50/50 honest, 0% attacker | 360 | 0 | | never | never / never | yes | 0 | 0 |
| leave | 50/50 + 20% equivocator (sides 60/60) | 360 | 0 | | never | never / never | yes | 0 | 0 |
| leave | 50/50 + 33% equivocator (sides 66.5/66.5) | 360 | 0 | | never | never / never | yes | 0 to 0 | 0 |
| leave | 50/50 + 34% equivocator (sides 67/67, the bound) | 360 | 139 to 283 | | 14 to 78 | 12 to 50 / 0 to 77 | yes | 0 to 0 | 0 |
| leave | 40/40/20 honest | 360 | 0 | | never | never / never / never | yes | 0 to 0 | 0 |
| leave | 60/40 honest | 360 | 0 | | never | never / never | yes | 0 to 0 | 0 |
| leave | 70/30 honest | 360 | 0 | | never | 0 to 4 / never | yes | 0 | 0 |
| decay1 | 50/50 honest, 0% attacker | 360 | 467 to 473 | | 121 to 126 | 120 to 122 / 121 to 123 | yes | 0 | 0 |
| decay1 | 50/50 + 20% equivocator (sides 60/60) | 360 | 528 to 535 | | 93 to 94 | 91 / 92 to 94 | yes | 0 | 0 |
| decay1 | 50/50 + 33% equivocator (sides 66.5/66.5) | 360 | 587 to 595 | | 64 to 65 | 62 / 63 to 65 | yes | 0 to 0 | 0 |
| decay1 | 50/50 + 34% equivocator (sides 67/67, the bound) | 360 | 575 to 612 | | 14 to 62 | 12 to 50 / 0 to 62 | yes | 0 to 0 | 0 |
| decay1 | 40/40/20 honest | 360 | 815 to 822 | | 142 to 144 | 140 to 142 / 140 to 140 / 166 to 166 | yes | 0 to 0 | 0 |
| decay1 | 60/40 honest | 360 | 435 to 436 | | 142 to 142 | 92 to 96 / 141 to 142 | yes | 0 to 0 | 0 |
| decay1 | 70/30 honest | 360 | 403 to 414 | | 154 to 156 | 0 to 4 / 154 to 156 | yes | 0 | 0 |
| decay6 | 50/50 honest, 0% attacker | 360 | 0 | | never | never / never | yes | 0 | 0 |
| decay6 | 50/50 + 20% equivocator (sides 60/60) | 360 | 0 | | never | never / never | yes | 0 | 0 |
| decay6 | 50/50 + 33% equivocator (sides 66.5/66.5) | 360 | 0 | | never | never / never | yes | 0 to 0 | 0 |
| decay6 | 50/50 + 34% equivocator (sides 67/67, the bound) | 360 | 139 to 283 | | 14 to 78 | 12 to 50 / 0 to 77 | yes | 0 to 0 | 0 |
| decay6 | 40/40/20 honest | 360 | 0 | | never | never / never / never | yes | 0 to 0 | 0 |
| decay6 | 60/40 honest | 360 | 0 | | never | never / never | yes | 0 to 0 | 0 |
| decay6 | 70/30 honest | 360 | 0 | | never | 0 to 4 / never | yes | 0 | 0 |
| hyst | 50/50 honest, 0% attacker | 360 | 0 | | never | never / never | yes | 0 | 0 |
| hyst | 50/50 + 20% equivocator (sides 60/60) | 360 | 565 to 597 | | 60 to 61 | 58 to 61 / 58 to 61 | yes | 0 | 0 |
| hyst | 50/50 + 33% equivocator (sides 66.5/66.5) | 360 | 596 to 603 | | 60 to 62 | 59 to 60 / 60 to 62 | yes | 0 to 0 | 0 |
| hyst | 50/50 + 34% equivocator (sides 67/67, the bound) | 360 | 160 to 342 | | 14 to 60 | 12 to 50 / 0 to 60 | yes | 0 to 0 | 0 |
| hyst | 40/40/20 honest | 360 | 0 | | never | never / never / never | yes | 0 to 0 | 0 |
| hyst | 60/40 honest | 360 | 0 | | never | 58 to 61 / never | yes | 0 to 0 | 0 |
| hyst | 70/30 honest | 360 | 0 | | never | 0 to 4 / never | yes | 0 | 0 |
| twotier | 50/50 honest, 0% attacker | 360 | 0 | 589 to 603 | never | never / never | yes | 0 | 0 |
| twotier | 50/50 + 20% equivocator (sides 60/60) | 360 | 0 | 653 to 660 | never | never / never | yes | 0 | 0 |
| twotier | 50/50 + 33% equivocator (sides 66.5/66.5) | 360 | 0 | 705 to 715 | never | never / never | yes | 0 to 0 | 0 |
| twotier | 50/50 + 34% equivocator (sides 67/67, the bound) | 360 | 139 to 283 | 713 to 720 | 14 to 78 | 12 to 50 / 0 to 77 | yes | 0 to 0 | 0 |
| twotier | 40/40/20 honest | 360 | 0 | 1056 to 1062 | never | never / never / never | yes | 0 to 0 | 0 |
| twotier | 60/40 honest | 360 | 0 | 516 to 564 | never | never / never | yes | 0 to 0 | 0 |
| twotier | 70/30 honest | 360 | 0 | 523 to 532 | never | 0 to 4 / never | yes | 0 | 0 |
P2. The poisoned eclipse (a 34% attacker plus a 20% pool; the eclipsed side holds 54% of total)
| rule | eclipse h | conflicting final locks | conflicting provisional locks | first conflict, min | locks on the eclipsed side | honest-side stalls during | stalls after heal |
|---|---|---|---|---|---|---|---|
| v2 | 1 | 0 | | never | 0 | 0 | 0 |
| v2 | 2 | 0 | | never | 0 | 0 | 0 |
| v2 | 4 | 0 | | never | 0 | 0 | 0 |
| v3 | 1 | 0 | | never | 0 | 0 | 0 |
| v3 | 2 | 0 | | never | 0 | 0 | 0 |
| v3 | 4 | 0 | | never | 0 | 0 | 0 |
| leave | 1 | 0 | | never | 0 | 0 | 0 |
| leave | 2 | 0 | | never | 0 | 0 | 0 |
| leave | 4 | 0 | | never | 0 | 0 | 0 |
| decay1 | 1 | 0 | | never | 0 | 0 | 0 |
| decay1 | 2 | 17 to 24 | | 109 to 111 | 22 to 24 | 0 | 0 |
| decay1 | 4 | 257 to 264 | | 109 to 111 | 263 to 265 | 0 | 0 |
| decay6 | 1 | 0 | | never | 0 | 0 | 0 |
| decay6 | 2 | 0 | | never | 0 | 0 | 0 |
| decay6 | 4 | 0 | | never | 0 | 0 | 0 |
| hyst | 1 | 0 | | never | 0 | 0 | 0 |
| hyst | 2 | 0 | | never | 0 | 0 | 0 |
| hyst | 4 | 0 | | never | 0 | 0 | 0 |
| twotier | 1 | 0 | 19 to 24 | never | 0 | 0 | 0 |
| twotier | 2 | 0 | 137 to 145 | never | 0 | 0 | 0 |
| twotier | 4 | 0 | 377 to 385 | never | 0 | 0 | 0 |
P3. Long honest partitions with view-local weight, 12 days
| rule | honest split | days | first lock per side, day | conflicting final locks | conflicting provisional locks | first conflict | every pre-heal lock kept | first lock after heal, min |
|---|---|---|---|---|---|---|---|---|
| v2 | 50/50 | 12 | 10.15 to 10.18 / 10.12 to 10.34 | 2890 to 3550 | | 10.22 to 10.35 d | yes | 0 |
| v2 | 60/40 | 12 | 5.15 to 5.27 / never | 0 | | never | yes | 0 to 0 |
| v3 | 50/50 | 12 | never / never | 0 | | never | yes | 0 |
| v3 | 60/40 | 12 | never / never | 0 | | never | yes | 0 to 0 |
| leave | 50/50 | 12 | never / never | 0 | | never | yes | 0 |
| leave | 60/40 | 12 | never / never | 0 | | never | yes | 0 to 0 |
| decay1 | 50/50 | 12 | 0.08 to 0.08 / 0.08 to 0.09 | 34146 to 34212 | | 0.08 to 0.09 d | yes | 0 |
| decay1 | 60/40 | 12 | 0.06 to 0.07 / 0.10 to 0.10 | 33931 to 34254 | | 0.10 to 0.10 d | yes | 0 to 0 |
| decay6 | 50/50 | 12 | 0.77 to 0.77 / 0.75 to 0.78 | 32120 to 32246 | | 0.77 to 0.78 d | yes | 0 |
| decay6 | 60/40 | 12 | 0.52 / 0.92 to 0.93 | 31545 to 31873 | | 0.93 to 0.93 d | yes | 0 to 0 |
| hyst | 50/50 | 12 | never / never | 0 | | never | yes | 0 |
| hyst | 60/40 | 12 | 0.04 to 0.04 / never | 0 | | never | yes | 0 to 0 |
| twotier | 50/50 | 12 | never / never | 0 | 34267 to 34418 | never | yes | 0 |
| twotier | 60/40 | 12 | never / never | 0 | 34094 to 34381 | never | yes | 0 to 0 |
### 5.3 Q1. Silent weight that keeps mining for 6 hours, then resumes
| rule | silent weight | silent hours | first lock after the stop, min | stalled while silent | longest gap, min | first lock after resume, min | conflicting locks |
|---|---|---|---|---|---|---|---|
| v2 | 30% | 6 | 0 | 0 to 40 | 1 to 8 | 0 to 0 | 0 |
| v2 | 34% | 6 | never | 714 to 722 | 360 | 0 to 0 | 0 |
| v2 | 45% | 6 | never | 714 to 722 | 360 | 0 to 0 | 0 |
| v3 | 30% | 6 | 0 | 0 to 40 | 1 to 8 | 0 to 0 | 0 |
| v3 | 34% | 6 | never | 714 to 722 | 360 | 0 to 0 | 0 |
| v3 | 45% | 6 | never | 714 to 722 | 360 | 0 to 0 | 0 |
| leave | 30% | 6 | 0 | 0 to 40 | 1 to 8 | 0 to 0 | 0 |
| leave | 34% | 6 | never | 714 to 722 | 360 | 0 to 0 | 0 |
| leave | 45% | 6 | never | 714 to 722 | 360 | 0 to 0 | 0 |
| decay1 | 30% | 6 | 0 | 0 | 1 | 0 to 0 | 0 |
| decay1 | 34% | 6 | 66 to 68 | 134 to 135 | 66 to 68 | 0 to 0 | 0 |
| decay1 | 45% | 6 | 108 to 112 | 217 to 229 | 108 to 112 | 0 to 0 | 0 |
| decay6 | 30% | 6 | 0 | 0 to 40 | 1 to 8 | 0 to 0 | 0 |
| decay6 | 34% | 6 | never | 714 to 722 | 360 | 0 to 0 | 0 |
| decay6 | 45% | 6 | never | 714 to 722 | 360 | 0 to 0 | 0 |
| hyst | 30% | 6 | 0 | 0 to 40 | 1 to 8 | 0 to 0 | 0 |
| hyst | 34% | 6 | 59 to 61 | 120 | 59 to 61 | 0 to 0 | 0 |
| hyst | 45% | 6 | never | 714 to 722 | 360 | 0 to 0 | 0 |
| twotier | 30% | 6 | 0 | 0 to 40 | 1 to 8 | 0 to 0 | 0 |
| twotier | 34% | 6 | never | 714 to 722 | 360 | 0 to 0 | 0 |
| twotier | 45% | 6 | never | 714 to 722 | 360 | 0 to 0 | 0 |
Q2. Acquired keys that sign, stay silent or leave, under v3 + leave 1 h (30 days)
| bought weight | bought keys | attacker's peak share of the denominator | share at the end | holds the veto (1/3) | stalled checkpoints | conflicting locks |
|---|---|---|---|---|---|---|
| 40% | sign | 39.8 to 39.8% | 30.0 to 30.0% | day 1 to 19 | 0 | 0 |
| 40% | silent | 39.8 to 39.8% | 30.0 to 30.0% | day 1 to 19 | 86402 to 86491 | 0 |
| 40% | leave | 30.0 to 30.0% | 30.0 to 30.0% | never | 119 to 144 | 0 |
| 49% | sign | 48.5 to 48.6% | 30.0 to 30.0% | day 1 to 23 to 24 | 0 | 0 |
| 49% | silent | 48.5 to 48.6% | 30.0 to 30.0% | day 1 to 23 to 24 | 86371 to 86408 | 0 |
| 49% | leave | 30.0 to 30.0% | 30.0 to 30.0% | never | 143 to 188 | 0 |
Under v3 the silent bought 40 percent stalls every checkpoint of the 30 days (86,402 to 86,491), where `sim/results_v2.md` K at v2 measured 63,307 to 68,716: the frozen table keeps the bought weight in the denominator after the sliding table has aged it out, so a silent buyer pauses finality for a window, not until day 19 to 20. The leaving buyer holds 30.0 percent at most (its own hash), never the veto.
### 5.4 Reading
What holds. Every candidate keeps 0 conflicting final locks in every honest partition under two thirds (50/50, 40/40/20, 60/40, 70/30 for 360 minutes) except the fast decay, and every candidate conflicts at the 34 percent equivocator (139 to 612 locks in 360 minutes, the one-third bound of 3.11.2, unchanged). The leave rule's rows equal v3's in every partition, eclipse and equivocator case (no key leaves in those scenarios, which is the point: a partition does not sign leaves) and it is the only candidate that both ends tonight's pause in under an hour (0.04 days at 34, 45 and 50 percent: the one-hour delay) and keeps 0 conflicts in the 12-day splits.
What breaks. The fast decay (T 1 h, r 0.5/h) conflicts in EVERY 360-minute partition, including 50/50 honest with no attacker (467 to 473 conflicting locks, both sides locking alone at minute 120 to 123, exactly T + 1/(2r) = 2 h) and the poisoned eclipse at 2 and 4 hours (17 to 264 conflicts, the eclipsed side locking the attacker's fork after 109 to 111 minutes); it also fails the 12-day splits in 2 hours (34,146 to 34,254 conflicts). The slow decay (T 6 h, r 1/24 per h) passes every 360-minute row because the decay has not started, then both sides of the 50/50 split lock alone at day 0.75 to 0.78 and the 60/40 at 0.52 and 0.93 (31,545 to 32,246 conflicts in 12 days): the hazard moved to the day scale, not removed. The hysteresis floor keeps the honest splits clean (the 60 side of 60/40 locks alone at minute 58 to 61, 0 conflicts) and reopens the equivocator bound: 20 percent across a 50/50 split gives 565 to 597 conflicting locks from minute 60 (the 13.3 percent bound of 3 October is back after H hours), and it does nothing for tonight's 45 percent departure (the stayers' 55 percent is under its 56.7 percent floor: 30.00 days, the same as v3). The two-tier's provisional tier conflicts in every partition and eclipse (516 to 1,062 provisional locks per 360 minutes, 19 to 385 in the eclipses, 34,000 in 12 days) while its final tier equals v3; it is a report of "the connected majority agrees", never a lock.
The pass line (0 conflicting final locks in every scenario AND tonight's pause under an hour) is met by one candidate: the departure announcement. v2 would have met the hour on the devnet (35 minutes measured, 31 to 32 simulated at 45 percent) and not on mainnet (7.85 to 8.03 days); v3 meets neither (30.00 days, 120 devnet minutes, the frozen table's expiry, as the live chain is showing at the time of writing: 89.2 percent of the sliding table signing at 19:50Z and no lock).
### 5.5 The aggregation path tonight (the coordinator's question of 19:3xZ)
Measured: the 8 VRF-picked aggregators are drawn by weight (spec S1, fin-fixes); the fallback (any node, 15 DAA after the determination, aggregator `00000000`) produced 64 of the 251 certificates stored between 16:30 and 19:00Z; locks 6824 to 6842 formed while node 1 and the observer were down; the observer's node received 75 then 79 of 93 voters' votes through its 3 peers during the pause. The star the fleet forms is around the seed (`docs/bench-log.md`, the finality route: "on a Vast box the seed is the only peer"), not the Mac; if the seed fell, a one-peer box would lose blocks as well as votes, and the right fix is a peer floor (at least 4 outbound peers from the address book before a node reports synced), which is lane 5's bandwidth and p2p lane. Model of P(certificate | hub down) under the rule as written: with the fallback, a certificate forms whenever any node connected to two thirds of the signing weight exists, so the probability is 1 for any topology in which votes reach any node; without the fallback and with a star through the hub it is 0 for 8 aggregators or 800. The rule is already the right one; the harness case to add is `tools/finality-attacks` s4's shape (the eclipse) with the hub cut instead of a pool: N boxes peered to the seed and the hub, the hub killed for 300 s, pass line a lock within 2 checkpoints of the cut while two thirds of weight stays connected through the seed, and 0 conflicting certificates at the hub's return. Vote bytes per checkpoint are in 3.3 (26 KB at 93 voters, 2.3 MB at 8,192 single, about 1.2 KB aggregated).
## 6. Ranked proposals
| Rank | Proposal | Evidence | Model | Hours | Consequence per tier | Gate |
|---|---|---|---|---|---|---|
| 1 | The departure announcement (candidate iv): a `leave` item (key, DAA score, signature) carried in blocks; D = 1 h after inclusion the key is in no denominator (sliding and frozen) and its votes are invalid; the fleet and app send it on a clean stop | tonight's pause (3.1): 42.7 percent left in three minutes and the frozen table held finality for a window; sim T: first lock 1 h after the departure at 34, 45 and 50 percent, 0 conflicts in every P row, Q2: leaving bought keys gains the attacker nothing | section 4.2 arithmetic; `finality_horizon.py` `leave` | 6 (spec text 3.1 W7 and 3.3; node: the item, its carriage, `voters_at` and `frozen_table` exclusion, unit test; app and fleet library: send on stop; fast-time harness case) | home miner, rig: the app sends the leave on Stop, so a clean exit never holds the network; a crash still ages out over 30 days (v3) unless the operator sends the leave on return, which the app offers; pool: one leave per server on maintenance; holder: fewer and shorter pauses; rollup customer: the same; node operator: one more item type | harness: 45 percent of weight stops with leaves, first lock within D + 1 checkpoint, 0 conflicts in the 50/50 and 60/40 splits and the 34 percent eclipse; sim T and P rows reproduced on the node |
| 2 | Operational rule, no protocol change: a standing box never leaves the live chain for an experiment, and any orchestrated departure over 10 percent of weight is staged in slices under 10 percent an hour | the rehearsal took 36.5 percent at once; sim L2 and M4: a gradual departure costs nothing (every lock re-freezes the table) | spec 3.7 item 2 | 1 (the fleet library refuses to swap a standing box's chain; a `--slice` on the rehearsal script) | fleet operator: the swap takes longer; everyone else: no pause | the next rehearsal: `finality_active` stays true throughout |
| 3 | Aggregate-first vote verification and aggregated in-block carriage as the mainnet default (spec 3.4.2 item 2, decided) | 4.3: per-vote verification is 1.6 s per checkpoint per node at 1,000 voters and 13 s at 8,192 (approximate); the crate already has `aggregate` and `fast_aggregate_verify` | 4.3 table | 8 (node: batch the votes for one (index, hash) and verify once, bisect on failure; the in-block aggregate item; measure on the fast-time harness at 1,000 synthetic keys) | home miner, rig, pool: a node that stays under one core at 1,000 voters; node operator: the same; holder: lock delay flat at 2 to 3 s to 8,192 voters | fast-time harness with 1,000 and 8,000 synthetic voters: lock delay p50 under 3 s, CPU under 25 percent of one core, 0 conflicts |
| 4 | Report the two-tier state (candidate ii) as `finality_provisional` beside `finality_active`, never as a lock | sim P: 516 to 1,062 provisional conflicts per 360-minute partition, 19 to 385 per eclipse, about 34,000 in 12 days, final 0; sim T: provisional 0 to 0.2 devnet minutes after tonight's departure | 4.1 | 3 (node RPC field, explorer and wallet copy; spec 3.9 row) | exchanges: a third row in the guidance table ("provisional: proof of work plus a majority of the connected weight; credit nothing on it"); a holder sees why the pause is a pause | the explorer shows the field through a forced pause on the devnet; the guidance text reviewed by an operator (O-3.13) |
| 5 | The ZK light client's circuit as a phase-two design doc with a cycle measurement | 4.5: 15 to 35 M cycles per step, approximate; the pairing is the term to measure | `lightclient_cost.py` | 10 (an SP1 guest that verifies one certificate at 93 and 1,000 voters with the Fp2 precompiles; cycle count on the 5090 and a 12 GB card) | phone, bridge, rollup customer: the per-year columns of 4.5 become one proof; prover tiers: a steady 30-s job | measured cycles within 2x of the estimate; proof per checkpoint under 30 s on a proving-only 5090 |
| 6 | Hub-cut harness case for the aggregation path | 5.5: the fallback carried 26 percent of tonight's certificates; the seed, not the Mac, is the fleet's star | 5.5 | 3 | fleet operator: a proven answer to tonight's question | the case passes as written in 5.5 |
| 7 | Do NOT adopt the decaying denominator (i) or the hysteresis floor (iii) | sim P1 and P3 (5.2): decay1 467 to 473 conflicting locks in a 360-minute 50/50 honest split and 34,146 to 34,254 in 12 days; decay6 31,545 to 32,246 in 12 days; hyst 565 to 597 at a 20 percent equivocator from minute 60 | 4.2 | 0 | a holder keeps the one-third bound in every view | none: a negative result |
| 8 | Do NOT make prover attestations a lock condition before the coverage gate | 4.6: coverage 2.4 to 4.7 percent tonight, lag p99 62 s | 4.6 table | 0 now; 12 after the gate | holder: no new pause source; rollup customer: nothing lost, the four-state reading exists | 99 percent of blocks proven within 60 s for 7 days, 3 provers per block |
**1. The departure announcement.** Tonight's cost was a window-long pause caused by keys that left on purpose, under a job that knew it was taking them. The frozen table (F21) exists so that a side of a partition cannot fill its own table, and it does that; its price is that it cannot tell a departure from a partition. A signed leave is the one thing a departing key can give that a partitioned key cannot: it is seen, not inferred. Section 4.2 shows the one-third bound survives it in both halves of a split, the sim shows 0 conflicting locks in every partition, eclipse and equivocator row under it and a first lock one hour after a 34, 45 or 50 percent departure where v3 waits 30 days, and Q2 shows an attacker who buys keys to leave them is worse off than one who signs with them. The hour is a parameter: it must exceed the certificate relay plus one presence of the leave in blocks (minutes), and shorter is better for the operator; one hour matches the merge-depth bound and gives a key that leaves by mistake time to see it. What it does not cover: a crash, a power cut, a region going dark, which still age out as today; the app's Stop button and the fleet library send the leave, and a node that restarts after an unplanned outage can send it on return to shorten the pause from that point.
**2. Staged departures.** Every lock re-freezes the table, so weight that leaves while locks continue ages out of the frozen table as it ages out of the sliding one (M4's "gradual departure costs nothing"). Ten percent an hour keeps the stayers over two thirds at every step for any total departure under a third per three hours. This is a fleet-library rule, one afternoon, and it would have kept tonight's finality on without any protocol change; proposal 1 covers the case where staging is not possible.
**3. Aggregate-first verification.** The delay data of 3.2 is flat to 93 voters because the devnet's cost is the poll and the pump, not the pairings; the arithmetic of 4.3 says the pairings take over near 1,000 voters and break the 30-s cadence near 8,192. The crate the fork already uses has both halves of the fix; what is missing is the batching in `ingest` and the in-block aggregate item that 3.4.2 item 2 proposes. The gate is a synthetic-voter harness, which `tools/finality-attacks` can host (its `lib/net.mjs` starts nodes; a vmine with 1,000 keys is a flag away).
**4. The two-tier report.** The provisional tier is the active-denominator rule the project rejected on 3 October, and the sim says again why: it conflicts in every partition. As a reported state it is useful to a holder who wants to know whether the pause is a silent third (provisional true: the connected majority still agrees) or a split (provisional conflicting on the two sides), and useless to an exchange, which must credit nothing on it. Three hours, mostly copy.
**5 and 6** are measurements with a design attached, priced above. **7 and 8** are the negatives this lane is confident about: a denominator that shrinks on silence is the 3 October rule under another name, and a lock that waits for proofs is a lock that pauses whenever the proving market is thin, which it is.
## 7. Open questions and what could not be run
- The fleet wrote no lock-delay or voter-count row by 19:45Z (`block-rate-devnet2.md` is a template); the 93-voter figures here are node 1's own log. When RUN_A and RUN_B land, the 10 blocks/s row should be checked against 4.3's claim that the hop term, not the voter term, sets the delay.
- The leave item does not exist in the node; the harness case of proposal 1 is designed, not run. Its interaction with W5 succession (O-3.11) and with a key that leaves and keeps mining (its blocks earn nothing, as the spec says for a succeeded key) needs the spec text.
- BLS costs are approximate (crate benchmarks from memory, anchored by one measured JavaScript run). A `cargo bench` of `fast_aggregate_verify` at 93, 1,000 and 8,192 keys on the Mac is one hour and belongs with proposal 3.
- The ZK light client's pairing cost in SP1 6.8.1 is the number the whole of 4.5 turns on; it is approximate until the guest of proposal 5 is counted.
- The pause's end was not observed at the time of writing (expected about 20:40Z at the frozen table's expiry, or earlier if the departed boxes return); the observer rows will show which.
- The box ran the sims at nice 19 under other agents' load; the tables are counts and days, not timings, so the load does not touch them.
## 8. Summary for the coordinator
Tonight's finality pause (first unlocked checkpoint 6843 at about 18:40Z, still paused at 19:30Z) is the two-thirds rule doing what it says, then the frozen table doing what F21 asked: 20 keys holding 42.7 percent of the frozen table left the live chain, the stayers held 53.1 percent at the first unlocked checkpoint and 74.9 percent of the sliding table from 19:14:53Z, and only Q5 explains why 74.9 percent did not lock; certificates formed through the hub outage and the aggregator fallback carried a quarter of them, so the topology hypothesis is refuted. The three findings: (1) under v2 the pause would have ended at 19:14:53Z, 35 minutes in, and on mainnet the same event is 7.7 days (v2) or 30 days (v3); (2) of the four candidate rules only the departure announcement keeps the one-third bound (0 conflicts in every partition row, first lock one hour after the departure), while decay and hysteresis reopen the double lock and the two-tier is a report; (3) renting a veto costs USD 8,424 x N x 0.52 for 30 days (USD 4,300 per GH/s of network), locking alone 2.03 x N for 30 days, and bought keys cost the same and decay in 30 days.

View file

@ -0,0 +1,45 @@
# sim/horizon/finality-and-weight
Models behind `docs/analysis/horizon/finality-and-weight.md` (Horizon lane 3, 6 October 2026).
## finality_horizon.py
A copy of `sim/finality_v2.py` as of fud-close `1544c63` (the block reading of Q2, scenario O) plus four candidate rules and three scenarios. The base rule in every candidate is the live one: active denominator under the block reading, floor 1.0 (a lock needs 2/3 of total), each partition side retargets (+daa) and counts only its own blocks (+local), and `frozen=True` for rule v3 (the frozen table, ledger F21).
Candidates (`--cands`, comma-separated keys or `all`):
| key | rule |
|---|---|
| `v2` | sliding table only (the rule before F21) |
| `v3` | plus the frozen table (the live rule since N3) |
| `leave` | (iv) departure announcement: a key's signed leave removes it from every denominator 1 h later |
| `decay1` | (i) decaying denominator: a key the view has not seen vote for 1 h loses 0.5 of its denominator weight per hour (gone after 3 h) |
| `decay6` | (i) slow decay: 6 h grace, 1/24 per hour (gone after 30 h) |
| `hyst` | (iii) floor with hysteresis: after 1 h under 2/3 signing the floor drops to 0.85 x 2/3 (sliding and frozen), back after 1 h at or above 2/3 |
| `twotier` | (ii) a provisional lock at 2/3 of the active denominator (block reading, no floor), reported beside the final lock, never certifying |
Scenarios: `T` tonight's departure (34, 45, 50 percent of weight stops mining and signing at once, 31 days), `P` the partition suite (H, I, E, L3 eclipse, L4 12-day splits), `Q` silent weight (L1) and acquired keys that leave (Q2, leave candidate only).
Runs behind the analysis (igneum-build-1, Python 3.12, numpy 1.26.4, `nice -n 19`, one process per candidate, seeds 7, 11, 13; about 25 minutes wall):
for c in v2 v3 leave decay1 decay6 hyst twotier; do
nice -n 19 python3 finality_horizon.py --scenarios T,P,Q --seeds 7,11,13 --cands $c > out-$c.md 2> err-$c.log &
done
Smoke run on the Mac (under the main checkout's run lock, about one minute):
/Users/joshm/Projects/igneum/tools/lock/with-lock.sh run nice -n 19 python3 finality_horizon.py --quick --scenarios T,P,Q --seeds 7
Outputs of the full run are in `results/` (`out-<cand>.md`, `err-<cand>.log` with the per-scenario timings); `python3 merge_results.py` merges them into `results/combined.md`, the tables section 5 of the analysis quotes.
## weight_capture.py
Arithmetic of task 3 (rented hash against the 30-day window at the measured USD 11.7 per GH/s-hour; bought keys). `python3 weight_capture.py`.
## lightclient_cost.py
Arithmetic of task 4 (bytes and verification time of a year of certificates by voter count; the ZK light client's per-checkpoint cycles, approximate). `python3 lightclient_cost.py`.
## obs-query.mjs (not in this directory)
The observer rows of tonight's pause were read with read-only SELECTs against the observer's Neon database (the script lived in the session scratchpad; it is `fetch` to the Neon SQL endpoint with `DATABASE_URL` from `~/.config/igneum/env`, refusing anything but SELECT). The queries are quoted in the analysis document.

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,94 @@
#!/usr/bin/env python3
"""Light-client cost of a certificate chain (Horizon lane 3, task 4): bytes and verification time per year of chain, by voter
count and mode, and the ZK light client's per-checkpoint work. Inputs labelled measured / cited / approximate.
Sizes (fork, consensus/core/src/finality.rs, cited in spec 3.4.2 item 1): vote item 281 B; certificate 273 B plus ceil(V/8) B of
bitmap; header 400 B working figure (spec 10.5, approximate); 2,880 checkpoints a day at 1 block/s (spec C1).
Verification (approximate, blst 0.3.17 min_pk, the crate the fork uses: vendor/igneum-node/Cargo.toml line 238; per-operation costs
from memory of blst's published benchmarks, labelled approximate): one G1 affine addition about 1 us, hash-to-G2 about 150 us,
two pairings about 1.6 ms, decompressing and subgroup-checking one 48-byte G1 key about 60 us (done once per key when the voter
list is loaded, not per certificate). Measured anchor: the pure-JavaScript verifier of a 16-signer certificate took 58 to 68 ms warm
on the M5 Max (docs/bench-log.md, FUD ledger sweep round 6, P3), about 30x the native estimate.
Run: python3 lightclient_cost.py
"""
VOTE_B = 281
CERT_FIXED_B = 273
HEADER_B = 400
CP_PER_DAY = 2880
DAYS = 365
G1_ADD_US = 1.0
H2G2_US = 150.0
PAIRINGS_US = 1600.0
KEY_DECOMP_US = 60.0
def cert_bytes(V):
return CERT_FIXED_B + (V + 7) // 8
def verify_ms(V):
return (V * G1_ADD_US + H2G2_US + PAIRINGS_US) / 1000.0
def fmt_b(b):
if b >= 1e9:
return "%.2f GB" % (b / 1e9)
if b >= 1e6:
return "%.1f MB" % (b / 1e6)
return "%.0f KB" % (b / 1e3)
def fmt_t(ms):
if ms >= 3.6e6:
return "%.1f h" % (ms / 3.6e6)
if ms >= 60e3:
return "%.1f min" % (ms / 60e3)
if ms >= 1e3:
return "%.1f s" % (ms / 1e3)
return "%.1f ms" % ms
def main():
n = CP_PER_DAY * DAYS
print("## A year of certificates, checkpoint mode (spec 10.3 item 3), one certificate per index")
print()
print("| voters | certificate bytes | per year: certificates + headers | verify per certificate (native, approximate) | per year on one laptop core | on a phone core (3x, approximate) | voter list once |")
print("|---|---|---|---|---|---|---|")
for V in (12, 93, 1000, 8192, 65536):
print("| %d | %d B | %s | %.2f ms | %s | %s | %s |" % (V, cert_bytes(V), fmt_b(n * (cert_bytes(V) + HEADER_B)), verify_ms(V),
fmt_t(n * verify_ms(V)), fmt_t(3 * n * verify_ms(V)), fmt_b(V * 60 + 0.0) + " plus %s of key decompression" % fmt_t(V * KEY_DECOMP_US / 1000.0)))
print()
print("## Skipping: one certificate per presence window (240 indices), spec 10.3 item 3's allowance")
print()
print("| voters | certificates per year | bytes per year | verify per year, laptop core |")
print("|---|---|---|---|")
for V in (93, 1000, 8192, 65536):
k = n // 240
print("| %d | %d | %s | %s |" % (V, k, fmt_b(k * (cert_bytes(V) + HEADER_B)), fmt_t(k * verify_ms(V))))
print()
print("What skipping does not pay for: the voter set at each skipped-to checkpoint (spec 10.4 item 3), which is 30 days of headers (W2) "
"or a trusted answer from N of M nodes. Full-header mode per year: %s of headers at 1 block/s (86,400 x 365 x %d B), plus the bodies' finality sections (O-10.1)." % (fmt_b(86400 * 365 * HEADER_B), HEADER_B))
print()
print("## The ZK light client: one recursive proof per checkpoint")
print()
print("Per-step statement: certificate i verifies under the voter table T_i; T_i follows from T_(i-1) by the 30 blue headers of the interval and the window's ageing; "
"signers hold at least 2/3 of T_i and of the frozen table (Q3, Q5); C_i's selected chain passes through C_(i-1). Cycle estimates in the SP1 6.8.1 zkVM, approximate, "
"from the precompile list of sp1-core-executor-6.8.1/src/syscall_code.rs (BLS12381_ADD, BLS12381_DOUBLE, BLS12381_FP_ADD/SUB/MUL, BLS12381_FP2_ADD/SUB/MUL; no pairing precompile):")
print()
print("| Part | Work | Approximate cycles | Note |")
print("|---|---|---|---|")
for V in (93, 1000, 8192):
print("| key aggregation, %d voters | %d x BLS12381_ADD | %s | one precompile call per set bit, about 500 cycles each (approximate) |" % (V, V, "{:,}".format(V * 500)))
print("| hash to G2 | SHA-256 (precompiled) plus Fp2 arithmetic | about 300,000 | approximate |")
print("| two pairings | Miller loops and the final exponentiation in Fp2/Fp6/Fp12 arithmetic built from the Fp2 precompiles | 10 to 30 million | approximate; no pairing precompile in 6.8.1, this is the dominant term |")
print("| 30 header hashes and the table transition | 30 x BLAKE2b (no precompile, about 30,000 cycles each) plus a Merkle update per key touched | about 1 to 2 million | approximate |")
print("| recursion: verify the previous step's proof | VERIFY_SP1_PROOF (the deferred-proof path) | the measured aggregation step | measured on the RTX 5090: 2.2 to 2.5 s idle, 7.9 to 9.7 s beside the miner (bench-log, agg-cost and chain-pc2-pv1c) |")
print()
print("Prover time per checkpoint, from the measured shard curve (docs/analysis/prover-tiers-real-cards.md: 4,717,439 cycles compressed in 4.8 to 14.4 s alone, 10.7 to 37.5 s beside the miner): "
"a 15 to 35 million cycle step is about 3 to 7 shards' worth, so 15 to 100 s alone and 40 to 260 s beside a miner, approximate; one checkpoint every 30 s therefore needs 1 to 4 proving-only cards, or 2 to 9 mining cards, kept at it continuously, plus the recursion step. "
"A phone then verifies one wrapped proof (phase two, spec 10.4), the certificate chain never: the per-year columns above fall to one proof of about 400 B (approximate, O-10.7).")
if __name__ == "__main__":
main()

View file

@ -0,0 +1,77 @@
#!/usr/bin/env python3
"""Merge the per-candidate outputs (results/out-<cand>.md) of finality_horizon.py into combined tables, one per scenario
section, in candidate order. Writes results/combined.md. Run after the box runs: python3 merge_results.py"""
import os
import re
HERE = os.path.dirname(os.path.abspath(__file__))
RES = os.path.join(HERE, "results")
ORDER = ["v2", "v3", "leave", "decay1", "decay6", "hyst", "twotier"]
SECTIONS = [("T", "### T."), ("P1", "P1."), ("P2", "P2."), ("P3", "P3."), ("Q1", "Q1."), ("Q2", "Q2.")]
def tables(text):
"""Return {section: (header lines, row lines)} for the section markers above."""
out = {}
lines = text.splitlines()
for key, marker in SECTIONS:
try:
i = next(k for k, l in enumerate(lines) if l.startswith(marker))
except StopIteration:
continue
j = i
while j < len(lines) and not lines[j].startswith("|"):
j += 1
head = lines[j:j + 2] if j + 1 < len(lines) else []
rows = []
k = j + 2
while k < len(lines) and lines[k].startswith("|"):
rows.append(lines[k])
k += 1
out[key] = (head, rows, lines[i])
return out
def main():
per = {}
for c in ORDER:
p = os.path.join(RES, "out-%s.md" % c)
if os.path.exists(p) and os.path.getsize(p) > 0:
per[c] = tables(open(p).read())
titles = {"T": "T. Tonight's departure: first lock after x of weight stops mining and signing at once (31 days, seeds 7, 11, 13)",
"P1": "P1. Partitions of 360 minutes (each side retargets and counts only its own blocks)",
"P2": "P2. The poisoned eclipse (a 34% attacker plus a 20% pool; the eclipsed side holds 54% of total)",
"P3": "P3. Long honest partitions with view-local weight, 12 days",
"Q1": "Q1. Silent weight that keeps mining for 6 hours, then resumes",
"Q2": "Q2. Acquired keys that sign, stay silent or leave, under v3 + leave 1 h (30 days)"}
out = ["# Combined results of finality_horizon.py (candidates: %s)" % ", ".join(per), ""]
for key, _ in SECTIONS:
head = None
rows = []
for c in ORDER:
if c in per and key in per[c]:
h, r, _ = per[c][key]
head = head or h
if key == "T":
# rows are per departed weight then rule; keep the candidate's rows, sorted later by weight
rows.extend(r)
else:
rows.extend(r)
if head is None:
continue
if key == "T":
def wkey(row):
m = re.match(r"\| (\d+)% \| (\w+) ", row)
return (int(m.group(1)), ORDER.index(m.group(2))) if m else (99, 99)
rows.sort(key=wkey)
out.append("## " + titles[key])
out.append("")
out.extend(head)
out.extend(rows)
out.append("")
open(os.path.join(RES, "combined.md"), "w").write("\n".join(out))
print("\n".join(out))
if __name__ == "__main__":
main()

View file

@ -0,0 +1,163 @@
# Combined results of finality_horizon.py (candidates: v2, v3, leave, decay1, decay6, hyst, twotier)
## T. Tonight's departure: first lock after x of weight stops mining and signing at once (31 days, seeds 7, 11, 13)
| departed weight | rule | first final lock after the departure | provisional tier | stalled checkpoints | stalled after the first lock | conflicting final locks | conflicting provisional locks |
|---|---|---|---|---|---|---|---|
| 34% | v2 | 0.77 to 0.87 d (devnet 3 to 3 min) | | 4057 to 4727 | 1531 to 2489 | 0 | |
| 34% | v3 | 30.00 d (devnet 120 min) | | 86388 to 86472 | 0 to 31 | 0 | |
| 34% | leave | 0.04 to 0.04 d (devnet 0 to 0 min) | | 119 to 521 | 0 to 398 | 0 | |
| 34% | decay1 | 0.05 to 0.05 d (devnet 0 to 0 min) | | 133 to 534 | 0 to 398 | 0 | |
| 34% | decay6 | 0.30 to 0.30 d (devnet 1 to 1 min) | | 897 to 1298 | 24 to 442 | 0 | |
| 34% | hyst | 0.04 to 0.04 d (devnet 0 to 0 min) | | 881 to 1445 | 761 to 1325 | 0 | |
| 34% | twotier | 30.00 d (devnet 120 min) | provisional 0.000 to 0.004 d (devnet 0.0 to 0.0 min) | 86388 to 86472 | 0 to 31 | 0 | 0 |
| 45% | v2 | 7.85 to 8.03 d (devnet 31 to 32 min) | | 23947 to 25153 | 1369 to 2065 | 0 | |
| 45% | v3 | 30.00 d (devnet 120 min) | | 86382 to 86420 | 0 to 22 | 0 | |
| 45% | leave | 0.04 d (devnet 0 min) | | 119 to 204 | 0 to 83 | 0 | |
| 45% | decay1 | 0.07 to 0.08 d (devnet 0 to 0 min) | | 224 to 296 | 9 to 83 | 0 | |
| 45% | decay6 | 0.64 to 0.67 d (devnet 3 to 3 min) | | 1946 to 1985 | 0 to 129 | 0 | |
| 45% | hyst | 30.00 d (devnet 120 min) | | 86382 to 86420 | 0 to 22 | 0 | |
| 45% | twotier | 30.00 d (devnet 120 min) | provisional 0.031 to 0.034 d (devnet 0.1 to 0.1 min) | 86382 to 86420 | 0 to 22 | 0 | 0 |
| 50% | v2 | 10.09 to 10.34 d (devnet 40 to 41 min) | | 30107 to 31264 | 1065 to 1456 | 0 | |
| 50% | v3 | 30.00 d (devnet 120 min) | | 86387 to 86514 | 0 to 10 | 0 | |
| 50% | leave | 0.04 d (devnet 0 min) | | 118 to 490 | 0 to 371 | 0 | |
| 50% | decay1 | 0.08 to 0.09 d (devnet 0 to 0 min) | | 244 to 609 | 0 to 371 | 0 | |
| 50% | decay6 | 0.76 to 0.77 d (devnet 3 to 3 min) | | 2231 to 2543 | 0 to 371 | 0 | |
| 50% | hyst | 30.00 d (devnet 120 min) | | 86387 to 86514 | 0 to 10 | 0 | |
| 50% | twotier | 30.00 d (devnet 120 min) | provisional 0.042 d (devnet 0.2 min) | 86387 to 86514 | 0 to 10 | 0 | 0 |
## P1. Partitions of 360 minutes (each side retargets and counts only its own blocks)
| rule | case | partition min | conflicting final locks | conflicting provisional locks | first conflict, min | first lock per side, min | every pre-heal lock kept | first lock after heal, min | stalls in 2 h after heal |
|---|---|---|---|---|---|---|---|---|---|
| v2 | 50/50 honest, 0% attacker | 360 | 0 | | never | never / never | yes | 0 | 0 |
| v2 | 50/50 + 20% equivocator (sides 60/60) | 360 | 0 | | never | never / never | yes | 0 | 0 |
| v2 | 50/50 + 33% equivocator (sides 66.5/66.5) | 360 | 0 | | never | never / 279 (never in 2 of 3) | yes | 0 to 0 | 0 |
| v2 | 50/50 + 34% equivocator (sides 67/67, the bound) | 360 | 142 to 291 | | 14 to 77 | 11 to 48 / 0 to 76 | yes | 0 to 0 | 0 |
| v2 | 40/40/20 honest | 360 | 0 | | never | never / never / never | yes | 0 to 0 | 0 |
| v2 | 60/40 honest | 360 | 0 | | never | never / never | yes | 0 to 0 | 0 |
| v2 | 70/30 honest | 360 | 0 | | never | 0 to 4 / never | yes | 0 | 0 |
| v3 | 50/50 honest, 0% attacker | 360 | 0 | | never | never / never | yes | 0 | 0 |
| v3 | 50/50 + 20% equivocator (sides 60/60) | 360 | 0 | | never | never / never | yes | 0 | 0 |
| v3 | 50/50 + 33% equivocator (sides 66.5/66.5) | 360 | 0 | | never | never / never | yes | 0 to 0 | 0 |
| v3 | 50/50 + 34% equivocator (sides 67/67, the bound) | 360 | 139 to 283 | | 14 to 78 | 12 to 50 / 0 to 77 | yes | 0 to 0 | 0 |
| v3 | 40/40/20 honest | 360 | 0 | | never | never / never / never | yes | 0 to 0 | 0 |
| v3 | 60/40 honest | 360 | 0 | | never | never / never | yes | 0 to 0 | 0 |
| v3 | 70/30 honest | 360 | 0 | | never | 0 to 4 / never | yes | 0 | 0 |
| leave | 50/50 honest, 0% attacker | 360 | 0 | | never | never / never | yes | 0 | 0 |
| leave | 50/50 + 20% equivocator (sides 60/60) | 360 | 0 | | never | never / never | yes | 0 | 0 |
| leave | 50/50 + 33% equivocator (sides 66.5/66.5) | 360 | 0 | | never | never / never | yes | 0 to 0 | 0 |
| leave | 50/50 + 34% equivocator (sides 67/67, the bound) | 360 | 139 to 283 | | 14 to 78 | 12 to 50 / 0 to 77 | yes | 0 to 0 | 0 |
| leave | 40/40/20 honest | 360 | 0 | | never | never / never / never | yes | 0 to 0 | 0 |
| leave | 60/40 honest | 360 | 0 | | never | never / never | yes | 0 to 0 | 0 |
| leave | 70/30 honest | 360 | 0 | | never | 0 to 4 / never | yes | 0 | 0 |
| decay1 | 50/50 honest, 0% attacker | 360 | 467 to 473 | | 121 to 126 | 120 to 122 / 121 to 123 | yes | 0 | 0 |
| decay1 | 50/50 + 20% equivocator (sides 60/60) | 360 | 528 to 535 | | 93 to 94 | 91 / 92 to 94 | yes | 0 | 0 |
| decay1 | 50/50 + 33% equivocator (sides 66.5/66.5) | 360 | 587 to 595 | | 64 to 65 | 62 / 63 to 65 | yes | 0 to 0 | 0 |
| decay1 | 50/50 + 34% equivocator (sides 67/67, the bound) | 360 | 575 to 612 | | 14 to 62 | 12 to 50 / 0 to 62 | yes | 0 to 0 | 0 |
| decay1 | 40/40/20 honest | 360 | 815 to 822 | | 142 to 144 | 140 to 142 / 140 to 140 / 166 to 166 | yes | 0 to 0 | 0 |
| decay1 | 60/40 honest | 360 | 435 to 436 | | 142 to 142 | 92 to 96 / 141 to 142 | yes | 0 to 0 | 0 |
| decay1 | 70/30 honest | 360 | 403 to 414 | | 154 to 156 | 0 to 4 / 154 to 156 | yes | 0 | 0 |
| decay6 | 50/50 honest, 0% attacker | 360 | 0 | | never | never / never | yes | 0 | 0 |
| decay6 | 50/50 + 20% equivocator (sides 60/60) | 360 | 0 | | never | never / never | yes | 0 | 0 |
| decay6 | 50/50 + 33% equivocator (sides 66.5/66.5) | 360 | 0 | | never | never / never | yes | 0 to 0 | 0 |
| decay6 | 50/50 + 34% equivocator (sides 67/67, the bound) | 360 | 139 to 283 | | 14 to 78 | 12 to 50 / 0 to 77 | yes | 0 to 0 | 0 |
| decay6 | 40/40/20 honest | 360 | 0 | | never | never / never / never | yes | 0 to 0 | 0 |
| decay6 | 60/40 honest | 360 | 0 | | never | never / never | yes | 0 to 0 | 0 |
| decay6 | 70/30 honest | 360 | 0 | | never | 0 to 4 / never | yes | 0 | 0 |
| hyst | 50/50 honest, 0% attacker | 360 | 0 | | never | never / never | yes | 0 | 0 |
| hyst | 50/50 + 20% equivocator (sides 60/60) | 360 | 565 to 597 | | 60 to 61 | 58 to 61 / 58 to 61 | yes | 0 | 0 |
| hyst | 50/50 + 33% equivocator (sides 66.5/66.5) | 360 | 596 to 603 | | 60 to 62 | 59 to 60 / 60 to 62 | yes | 0 to 0 | 0 |
| hyst | 50/50 + 34% equivocator (sides 67/67, the bound) | 360 | 160 to 342 | | 14 to 60 | 12 to 50 / 0 to 60 | yes | 0 to 0 | 0 |
| hyst | 40/40/20 honest | 360 | 0 | | never | never / never / never | yes | 0 to 0 | 0 |
| hyst | 60/40 honest | 360 | 0 | | never | 58 to 61 / never | yes | 0 to 0 | 0 |
| hyst | 70/30 honest | 360 | 0 | | never | 0 to 4 / never | yes | 0 | 0 |
| twotier | 50/50 honest, 0% attacker | 360 | 0 | 589 to 603 | never | never / never | yes | 0 | 0 |
| twotier | 50/50 + 20% equivocator (sides 60/60) | 360 | 0 | 653 to 660 | never | never / never | yes | 0 | 0 |
| twotier | 50/50 + 33% equivocator (sides 66.5/66.5) | 360 | 0 | 705 to 715 | never | never / never | yes | 0 to 0 | 0 |
| twotier | 50/50 + 34% equivocator (sides 67/67, the bound) | 360 | 139 to 283 | 713 to 720 | 14 to 78 | 12 to 50 / 0 to 77 | yes | 0 to 0 | 0 |
| twotier | 40/40/20 honest | 360 | 0 | 1056 to 1062 | never | never / never / never | yes | 0 to 0 | 0 |
| twotier | 60/40 honest | 360 | 0 | 516 to 564 | never | never / never | yes | 0 to 0 | 0 |
| twotier | 70/30 honest | 360 | 0 | 523 to 532 | never | 0 to 4 / never | yes | 0 | 0 |
## P2. The poisoned eclipse (a 34% attacker plus a 20% pool; the eclipsed side holds 54% of total)
| rule | eclipse h | conflicting final locks | conflicting provisional locks | first conflict, min | locks on the eclipsed side | honest-side stalls during | stalls after heal |
|---|---|---|---|---|---|---|---|
| v2 | 1 | 0 | | never | 0 | 0 | 0 |
| v2 | 2 | 0 | | never | 0 | 0 | 0 |
| v2 | 4 | 0 | | never | 0 | 0 | 0 |
| v3 | 1 | 0 | | never | 0 | 0 | 0 |
| v3 | 2 | 0 | | never | 0 | 0 | 0 |
| v3 | 4 | 0 | | never | 0 | 0 | 0 |
| leave | 1 | 0 | | never | 0 | 0 | 0 |
| leave | 2 | 0 | | never | 0 | 0 | 0 |
| leave | 4 | 0 | | never | 0 | 0 | 0 |
| decay1 | 1 | 0 | | never | 0 | 0 | 0 |
| decay1 | 2 | 17 to 24 | | 109 to 111 | 22 to 24 | 0 | 0 |
| decay1 | 4 | 257 to 264 | | 109 to 111 | 263 to 265 | 0 | 0 |
| decay6 | 1 | 0 | | never | 0 | 0 | 0 |
| decay6 | 2 | 0 | | never | 0 | 0 | 0 |
| decay6 | 4 | 0 | | never | 0 | 0 | 0 |
| hyst | 1 | 0 | | never | 0 | 0 | 0 |
| hyst | 2 | 0 | | never | 0 | 0 | 0 |
| hyst | 4 | 0 | | never | 0 | 0 | 0 |
| twotier | 1 | 0 | 19 to 24 | never | 0 | 0 | 0 |
| twotier | 2 | 0 | 137 to 145 | never | 0 | 0 | 0 |
| twotier | 4 | 0 | 377 to 385 | never | 0 | 0 | 0 |
## P3. Long honest partitions with view-local weight, 12 days
| rule | honest split | days | first lock per side, day | conflicting final locks | conflicting provisional locks | first conflict | every pre-heal lock kept | first lock after heal, min |
|---|---|---|---|---|---|---|---|---|
| v2 | 50/50 | 12 | 10.15 to 10.18 / 10.12 to 10.34 | 2890 to 3550 | | 10.22 to 10.35 d | yes | 0 |
| v2 | 60/40 | 12 | 5.15 to 5.27 / never | 0 | | never | yes | 0 to 0 |
| v3 | 50/50 | 12 | never / never | 0 | | never | yes | 0 |
| v3 | 60/40 | 12 | never / never | 0 | | never | yes | 0 to 0 |
| leave | 50/50 | 12 | never / never | 0 | | never | yes | 0 |
| leave | 60/40 | 12 | never / never | 0 | | never | yes | 0 to 0 |
| decay1 | 50/50 | 12 | 0.08 to 0.08 / 0.08 to 0.09 | 34146 to 34212 | | 0.08 to 0.09 d | yes | 0 |
| decay1 | 60/40 | 12 | 0.06 to 0.07 / 0.10 to 0.10 | 33931 to 34254 | | 0.10 to 0.10 d | yes | 0 to 0 |
| decay6 | 50/50 | 12 | 0.77 to 0.77 / 0.75 to 0.78 | 32120 to 32246 | | 0.77 to 0.78 d | yes | 0 |
| decay6 | 60/40 | 12 | 0.52 / 0.92 to 0.93 | 31545 to 31873 | | 0.93 to 0.93 d | yes | 0 to 0 |
| hyst | 50/50 | 12 | never / never | 0 | | never | yes | 0 |
| hyst | 60/40 | 12 | 0.04 to 0.04 / never | 0 | | never | yes | 0 to 0 |
| twotier | 50/50 | 12 | never / never | 0 | 34267 to 34418 | never | yes | 0 |
| twotier | 60/40 | 12 | never / never | 0 | 34094 to 34381 | never | yes | 0 to 0 |
## Q1. Silent weight that keeps mining for 6 hours, then resumes
| rule | silent weight | silent hours | first lock after the stop, min | stalled while silent | longest gap, min | first lock after resume, min | conflicting locks |
|---|---|---|---|---|---|---|---|
| v2 | 30% | 6 | 0 | 0 to 40 | 1 to 8 | 0 to 0 | 0 |
| v2 | 34% | 6 | never | 714 to 722 | 360 | 0 to 0 | 0 |
| v2 | 45% | 6 | never | 714 to 722 | 360 | 0 to 0 | 0 |
| v3 | 30% | 6 | 0 | 0 to 40 | 1 to 8 | 0 to 0 | 0 |
| v3 | 34% | 6 | never | 714 to 722 | 360 | 0 to 0 | 0 |
| v3 | 45% | 6 | never | 714 to 722 | 360 | 0 to 0 | 0 |
| leave | 30% | 6 | 0 | 0 to 40 | 1 to 8 | 0 to 0 | 0 |
| leave | 34% | 6 | never | 714 to 722 | 360 | 0 to 0 | 0 |
| leave | 45% | 6 | never | 714 to 722 | 360 | 0 to 0 | 0 |
| decay1 | 30% | 6 | 0 | 0 | 1 | 0 to 0 | 0 |
| decay1 | 34% | 6 | 66 to 68 | 134 to 135 | 66 to 68 | 0 to 0 | 0 |
| decay1 | 45% | 6 | 108 to 112 | 217 to 229 | 108 to 112 | 0 to 0 | 0 |
| decay6 | 30% | 6 | 0 | 0 to 40 | 1 to 8 | 0 to 0 | 0 |
| decay6 | 34% | 6 | never | 714 to 722 | 360 | 0 to 0 | 0 |
| decay6 | 45% | 6 | never | 714 to 722 | 360 | 0 to 0 | 0 |
| hyst | 30% | 6 | 0 | 0 to 40 | 1 to 8 | 0 to 0 | 0 |
| hyst | 34% | 6 | 59 to 61 | 120 | 59 to 61 | 0 to 0 | 0 |
| hyst | 45% | 6 | never | 714 to 722 | 360 | 0 to 0 | 0 |
| twotier | 30% | 6 | 0 | 0 to 40 | 1 to 8 | 0 to 0 | 0 |
| twotier | 34% | 6 | never | 714 to 722 | 360 | 0 to 0 | 0 |
| twotier | 45% | 6 | never | 714 to 722 | 360 | 0 to 0 | 0 |
## Q2. Acquired keys that sign, stay silent or leave, under v3 + leave 1 h (30 days)
| bought weight | bought keys | attacker's peak share of the denominator | share at the end | holds the veto (1/3) | stalled checkpoints | conflicting locks |
|---|---|---|---|---|---|---|
| 40% | sign | 39.8 to 39.8% | 30.0 to 30.0% | day 1 to 19 | 0 | 0 |
| 40% | silent | 39.8 to 39.8% | 30.0 to 30.0% | day 1 to 19 | 86402 to 86491 | 0 |
| 40% | leave | 30.0 to 30.0% | 30.0 to 30.0% | never | 119 to 144 | 0 |
| 49% | sign | 48.5 to 48.6% | 30.0 to 30.0% | day 1 to 23 to 24 | 0 | 0 |
| 49% | silent | 48.5 to 48.6% | 30.0 to 30.0% | day 1 to 23 to 24 | 86371 to 86408 | 0 |
| 49% | leave | 30.0 to 30.0% | 30.0 to 30.0% | never | 143 to 188 | 0 |

View file

@ -0,0 +1,3 @@
(T took 212 s)
(P took 77 s)
(Q took 4 s)

View file

@ -0,0 +1,3 @@
(T took 215 s)
(P took 78 s)
(Q took 4 s)

View file

@ -0,0 +1,3 @@
(T took 196 s)
(P took 68 s)
(Q took 3 s)

View file

@ -0,0 +1,3 @@
(T took 205 s)
(P took 68 s)
(Q took 508 s)

View file

@ -0,0 +1,3 @@
(T took 204 s)
(P took 73 s)
(Q took 3 s)

View file

@ -0,0 +1,3 @@
(T took 173 s)
(P took 59 s)
(Q took 3 s)

View file

@ -0,0 +1,3 @@
(T took 191 s)
(P took 66 s)
(Q took 3 s)

View file

@ -0,0 +1,56 @@
# finality_horizon output (sim/horizon/finality-and-weight/finality_horizon.py, a copy of sim/finality_v2.py at fud-close 1544c63 plus the candidate rules)
seed 7, slot 30 s, 30 blocks per checkpoint, window 720 h, presence 240 checkpoints, dust 100, quorum 2/3, floor factor 1.00 (a lock needs 66.7% of total under the rule as specified), inter-region delay 2.0 s (intra 0.1 s, jitter sigma 0.25), grace 15 s, uptime 0.97 (mean outage 20 slots), uptime 0.995 for keys at or above 1% of hashrate, honest keys 1000 Pareto 1.0, geography 45%/35%/20%
### T. Tonight's departure (6 Oct 2026, 18:40Z): a set holding x of weight stops mining and signing at once; first lock after it under each candidate rule; 31 days, seeds 7,11,13
Mainnet scale (30-day window). The devnet's window is 7,200 DAA s, so divide by 360: one mainnet day is four devnet minutes. Analytic: v2 recovers at 30 (1 - 1/(3x)) days (never at x <= 1/3), v3 at day 30 (the frozen table's expiry), the leave rule after its delay, the decay rule after T + 1/r x (1 - (1 - x) / (2 x)) hours at the latest (the departed weight has to decay until the stayers hold 2/3 of what is left).
| departed weight | rule | first final lock after the departure | provisional tier | stalled checkpoints | stalled after the first lock | conflicting final locks | conflicting provisional locks |
|---|---|---|---|---|---|---|---|
| 34% | decay1 | 0.05 to 0.05 d (devnet 0 to 0 min) | | 133 to 534 | 0 to 398 | 0 | |
| 45% | decay1 | 0.07 to 0.08 d (devnet 0 to 0 min) | | 224 to 296 | 9 to 83 | 0 | |
| 50% | decay1 | 0.08 to 0.09 d (devnet 0 to 0 min) | | 244 to 609 | 0 to 371 | 0 | |
### P. The partition suite under each candidate rule (every side retargets and counts only its own blocks), seeds 7,11,13
Pass line: 0 conflicting FINAL locks in every row. A candidate that removes weight from a denominator on what a view does NOT see (silence, missing blocks) is view-dependent: each side of a partition sees the other side as silent, so both denominators shrink and the two sides lock alone, the active-denominator hazard of `sim/results_v2.md` E in a new coat. A candidate that removes weight on what a view DOES see (a signed leave, equivocation evidence) keeps the 4/3 arithmetic of spec 3.11.2 over the weight both sides count.
P1. Partitions of 360 minutes:
| rule | case | partition min | conflicting final locks | conflicting provisional locks | first conflict, min | first lock per side, min | every pre-heal lock kept | first lock after heal, min | stalls in 2 h after heal |
|---|---|---|---|---|---|---|---|---|---|
| decay1 | 50/50 honest, 0% attacker | 360 | 467 to 473 | | 121 to 126 | 120 to 122 / 121 to 123 | yes | 0 | 0 |
| decay1 | 50/50 + 20% equivocator (sides 60/60) | 360 | 528 to 535 | | 93 to 94 | 91 / 92 to 94 | yes | 0 | 0 |
| decay1 | 50/50 + 33% equivocator (sides 66.5/66.5) | 360 | 587 to 595 | | 64 to 65 | 62 / 63 to 65 | yes | 0 to 0 | 0 |
| decay1 | 50/50 + 34% equivocator (sides 67/67, the bound) | 360 | 575 to 612 | | 14 to 62 | 12 to 50 / 0 to 62 | yes | 0 to 0 | 0 |
| decay1 | 40/40/20 honest | 360 | 815 to 822 | | 142 to 144 | 140 to 142 / 140 to 140 / 166 to 166 | yes | 0 to 0 | 0 |
| decay1 | 60/40 honest | 360 | 435 to 436 | | 142 to 142 | 92 to 96 / 141 to 142 | yes | 0 to 0 | 0 |
| decay1 | 70/30 honest | 360 | 403 to 414 | | 154 to 156 | 0 to 4 / 154 to 156 | yes | 0 | 0 |
P2. The poisoned eclipse (L3: a 34% attacker feeds a 20% pool a private fork and signs both; the eclipsed side holds 54% of total):
| rule | eclipse h | conflicting final locks | conflicting provisional locks | first conflict, min | locks on the eclipsed side | honest-side stalls during | stalls after heal |
|---|---|---|---|---|---|---|---|
| decay1 | 1 | 0 | | never | 0 | 0 | 0 |
| decay1 | 2 | 17 to 24 | | 109 to 111 | 22 to 24 | 0 | 0 |
| decay1 | 4 | 257 to 264 | | 109 to 111 | 263 to 265 | 0 | 0 |
P3. Long honest partitions with view-local weight (L4), 12 days:
| rule | honest split | days | first lock per side, day | conflicting final locks | conflicting provisional locks | first conflict | every pre-heal lock kept | first lock after heal, min |
|---|---|---|---|---|---|---|---|---|
| decay1 | 50/50 | 12 | 0.08 to 0.08 / 0.08 to 0.09 | 34146 to 34212 | | 0.08 to 0.09 d | yes | 0 |
| decay1 | 60/40 | 12 | 0.06 to 0.07 / 0.10 to 0.10 | 33931 to 34254 | | 0.10 to 0.10 d | yes | 0 to 0 |
### Q. Silent weight that keeps mining (L1) under each candidate, and acquired keys that LEAVE under the leave rule; seeds 7,11,13
Q1. A set holding x of weight stops signing but keeps mining for 6 h, then resumes (its weight never ages out):
| rule | silent weight | silent hours | first lock after the stop, min | stalled while silent | longest gap, min | first lock after resume, min | conflicting locks |
|---|---|---|---|---|---|---|---|
| decay1 | 30% | 6 | 0 | 0 | 1 | 0 to 0 | 0 |
| decay1 | 34% | 6 | 66 to 68 | 134 to 135 | 66 to 68 | 0 to 0 | 0 |
| decay1 | 45% | 6 | 108 to 112 | 217 to 229 | 108 to 112 | 0 to 0 | 0 |

View file

@ -0,0 +1,56 @@
# finality_horizon output (sim/horizon/finality-and-weight/finality_horizon.py, a copy of sim/finality_v2.py at fud-close 1544c63 plus the candidate rules)
seed 7, slot 30 s, 30 blocks per checkpoint, window 720 h, presence 240 checkpoints, dust 100, quorum 2/3, floor factor 1.00 (a lock needs 66.7% of total under the rule as specified), inter-region delay 2.0 s (intra 0.1 s, jitter sigma 0.25), grace 15 s, uptime 0.97 (mean outage 20 slots), uptime 0.995 for keys at or above 1% of hashrate, honest keys 1000 Pareto 1.0, geography 45%/35%/20%
### T. Tonight's departure (6 Oct 2026, 18:40Z): a set holding x of weight stops mining and signing at once; first lock after it under each candidate rule; 31 days, seeds 7,11,13
Mainnet scale (30-day window). The devnet's window is 7,200 DAA s, so divide by 360: one mainnet day is four devnet minutes. Analytic: v2 recovers at 30 (1 - 1/(3x)) days (never at x <= 1/3), v3 at day 30 (the frozen table's expiry), the leave rule after its delay, the decay rule after T + 1/r x (1 - (1 - x) / (2 x)) hours at the latest (the departed weight has to decay until the stayers hold 2/3 of what is left).
| departed weight | rule | first final lock after the departure | provisional tier | stalled checkpoints | stalled after the first lock | conflicting final locks | conflicting provisional locks |
|---|---|---|---|---|---|---|---|
| 34% | decay6 | 0.30 to 0.30 d (devnet 1 to 1 min) | | 897 to 1298 | 24 to 442 | 0 | |
| 45% | decay6 | 0.64 to 0.67 d (devnet 3 to 3 min) | | 1946 to 1985 | 0 to 129 | 0 | |
| 50% | decay6 | 0.76 to 0.77 d (devnet 3 to 3 min) | | 2231 to 2543 | 0 to 371 | 0 | |
### P. The partition suite under each candidate rule (every side retargets and counts only its own blocks), seeds 7,11,13
Pass line: 0 conflicting FINAL locks in every row. A candidate that removes weight from a denominator on what a view does NOT see (silence, missing blocks) is view-dependent: each side of a partition sees the other side as silent, so both denominators shrink and the two sides lock alone, the active-denominator hazard of `sim/results_v2.md` E in a new coat. A candidate that removes weight on what a view DOES see (a signed leave, equivocation evidence) keeps the 4/3 arithmetic of spec 3.11.2 over the weight both sides count.
P1. Partitions of 360 minutes:
| rule | case | partition min | conflicting final locks | conflicting provisional locks | first conflict, min | first lock per side, min | every pre-heal lock kept | first lock after heal, min | stalls in 2 h after heal |
|---|---|---|---|---|---|---|---|---|---|
| decay6 | 50/50 honest, 0% attacker | 360 | 0 | | never | never / never | yes | 0 | 0 |
| decay6 | 50/50 + 20% equivocator (sides 60/60) | 360 | 0 | | never | never / never | yes | 0 | 0 |
| decay6 | 50/50 + 33% equivocator (sides 66.5/66.5) | 360 | 0 | | never | never / never | yes | 0 to 0 | 0 |
| decay6 | 50/50 + 34% equivocator (sides 67/67, the bound) | 360 | 139 to 283 | | 14 to 78 | 12 to 50 / 0 to 77 | yes | 0 to 0 | 0 |
| decay6 | 40/40/20 honest | 360 | 0 | | never | never / never / never | yes | 0 to 0 | 0 |
| decay6 | 60/40 honest | 360 | 0 | | never | never / never | yes | 0 to 0 | 0 |
| decay6 | 70/30 honest | 360 | 0 | | never | 0 to 4 / never | yes | 0 | 0 |
P2. The poisoned eclipse (L3: a 34% attacker feeds a 20% pool a private fork and signs both; the eclipsed side holds 54% of total):
| rule | eclipse h | conflicting final locks | conflicting provisional locks | first conflict, min | locks on the eclipsed side | honest-side stalls during | stalls after heal |
|---|---|---|---|---|---|---|---|
| decay6 | 1 | 0 | | never | 0 | 0 | 0 |
| decay6 | 2 | 0 | | never | 0 | 0 | 0 |
| decay6 | 4 | 0 | | never | 0 | 0 | 0 |
P3. Long honest partitions with view-local weight (L4), 12 days:
| rule | honest split | days | first lock per side, day | conflicting final locks | conflicting provisional locks | first conflict | every pre-heal lock kept | first lock after heal, min |
|---|---|---|---|---|---|---|---|---|
| decay6 | 50/50 | 12 | 0.77 to 0.77 / 0.75 to 0.78 | 32120 to 32246 | | 0.77 to 0.78 d | yes | 0 |
| decay6 | 60/40 | 12 | 0.52 / 0.92 to 0.93 | 31545 to 31873 | | 0.93 to 0.93 d | yes | 0 to 0 |
### Q. Silent weight that keeps mining (L1) under each candidate, and acquired keys that LEAVE under the leave rule; seeds 7,11,13
Q1. A set holding x of weight stops signing but keeps mining for 6 h, then resumes (its weight never ages out):
| rule | silent weight | silent hours | first lock after the stop, min | stalled while silent | longest gap, min | first lock after resume, min | conflicting locks |
|---|---|---|---|---|---|---|---|
| decay6 | 30% | 6 | 0 | 0 to 40 | 1 to 8 | 0 to 0 | 0 |
| decay6 | 34% | 6 | never | 714 to 722 | 360 | 0 to 0 | 0 |
| decay6 | 45% | 6 | never | 714 to 722 | 360 | 0 to 0 | 0 |

View file

@ -0,0 +1,56 @@
# finality_horizon output (sim/horizon/finality-and-weight/finality_horizon.py, a copy of sim/finality_v2.py at fud-close 1544c63 plus the candidate rules)
seed 7, slot 30 s, 30 blocks per checkpoint, window 720 h, presence 240 checkpoints, dust 100, quorum 2/3, floor factor 1.00 (a lock needs 66.7% of total under the rule as specified), inter-region delay 2.0 s (intra 0.1 s, jitter sigma 0.25), grace 15 s, uptime 0.97 (mean outage 20 slots), uptime 0.995 for keys at or above 1% of hashrate, honest keys 1000 Pareto 1.0, geography 45%/35%/20%
### T. Tonight's departure (6 Oct 2026, 18:40Z): a set holding x of weight stops mining and signing at once; first lock after it under each candidate rule; 31 days, seeds 7,11,13
Mainnet scale (30-day window). The devnet's window is 7,200 DAA s, so divide by 360: one mainnet day is four devnet minutes. Analytic: v2 recovers at 30 (1 - 1/(3x)) days (never at x <= 1/3), v3 at day 30 (the frozen table's expiry), the leave rule after its delay, the decay rule after T + 1/r x (1 - (1 - x) / (2 x)) hours at the latest (the departed weight has to decay until the stayers hold 2/3 of what is left).
| departed weight | rule | first final lock after the departure | provisional tier | stalled checkpoints | stalled after the first lock | conflicting final locks | conflicting provisional locks |
|---|---|---|---|---|---|---|---|
| 34% | hyst | 0.04 to 0.04 d (devnet 0 to 0 min) | | 881 to 1445 | 761 to 1325 | 0 | |
| 45% | hyst | 30.00 d (devnet 120 min) | | 86382 to 86420 | 0 to 22 | 0 | |
| 50% | hyst | 30.00 d (devnet 120 min) | | 86387 to 86514 | 0 to 10 | 0 | |
### P. The partition suite under each candidate rule (every side retargets and counts only its own blocks), seeds 7,11,13
Pass line: 0 conflicting FINAL locks in every row. A candidate that removes weight from a denominator on what a view does NOT see (silence, missing blocks) is view-dependent: each side of a partition sees the other side as silent, so both denominators shrink and the two sides lock alone, the active-denominator hazard of `sim/results_v2.md` E in a new coat. A candidate that removes weight on what a view DOES see (a signed leave, equivocation evidence) keeps the 4/3 arithmetic of spec 3.11.2 over the weight both sides count.
P1. Partitions of 360 minutes:
| rule | case | partition min | conflicting final locks | conflicting provisional locks | first conflict, min | first lock per side, min | every pre-heal lock kept | first lock after heal, min | stalls in 2 h after heal |
|---|---|---|---|---|---|---|---|---|---|
| hyst | 50/50 honest, 0% attacker | 360 | 0 | | never | never / never | yes | 0 | 0 |
| hyst | 50/50 + 20% equivocator (sides 60/60) | 360 | 565 to 597 | | 60 to 61 | 58 to 61 / 58 to 61 | yes | 0 | 0 |
| hyst | 50/50 + 33% equivocator (sides 66.5/66.5) | 360 | 596 to 603 | | 60 to 62 | 59 to 60 / 60 to 62 | yes | 0 to 0 | 0 |
| hyst | 50/50 + 34% equivocator (sides 67/67, the bound) | 360 | 160 to 342 | | 14 to 60 | 12 to 50 / 0 to 60 | yes | 0 to 0 | 0 |
| hyst | 40/40/20 honest | 360 | 0 | | never | never / never / never | yes | 0 to 0 | 0 |
| hyst | 60/40 honest | 360 | 0 | | never | 58 to 61 / never | yes | 0 to 0 | 0 |
| hyst | 70/30 honest | 360 | 0 | | never | 0 to 4 / never | yes | 0 | 0 |
P2. The poisoned eclipse (L3: a 34% attacker feeds a 20% pool a private fork and signs both; the eclipsed side holds 54% of total):
| rule | eclipse h | conflicting final locks | conflicting provisional locks | first conflict, min | locks on the eclipsed side | honest-side stalls during | stalls after heal |
|---|---|---|---|---|---|---|---|
| hyst | 1 | 0 | | never | 0 | 0 | 0 |
| hyst | 2 | 0 | | never | 0 | 0 | 0 |
| hyst | 4 | 0 | | never | 0 | 0 | 0 |
P3. Long honest partitions with view-local weight (L4), 12 days:
| rule | honest split | days | first lock per side, day | conflicting final locks | conflicting provisional locks | first conflict | every pre-heal lock kept | first lock after heal, min |
|---|---|---|---|---|---|---|---|---|
| hyst | 50/50 | 12 | never / never | 0 | | never | yes | 0 |
| hyst | 60/40 | 12 | 0.04 to 0.04 / never | 0 | | never | yes | 0 to 0 |
### Q. Silent weight that keeps mining (L1) under each candidate, and acquired keys that LEAVE under the leave rule; seeds 7,11,13
Q1. A set holding x of weight stops signing but keeps mining for 6 h, then resumes (its weight never ages out):
| rule | silent weight | silent hours | first lock after the stop, min | stalled while silent | longest gap, min | first lock after resume, min | conflicting locks |
|---|---|---|---|---|---|---|---|
| hyst | 30% | 6 | 0 | 0 to 40 | 1 to 8 | 0 to 0 | 0 |
| hyst | 34% | 6 | 59 to 61 | 120 | 59 to 61 | 0 to 0 | 0 |
| hyst | 45% | 6 | never | 714 to 722 | 360 | 0 to 0 | 0 |

View file

@ -0,0 +1,66 @@
# finality_horizon output (sim/horizon/finality-and-weight/finality_horizon.py, a copy of sim/finality_v2.py at fud-close 1544c63 plus the candidate rules)
seed 7, slot 30 s, 30 blocks per checkpoint, window 720 h, presence 240 checkpoints, dust 100, quorum 2/3, floor factor 1.00 (a lock needs 66.7% of total under the rule as specified), inter-region delay 2.0 s (intra 0.1 s, jitter sigma 0.25), grace 15 s, uptime 0.97 (mean outage 20 slots), uptime 0.995 for keys at or above 1% of hashrate, honest keys 1000 Pareto 1.0, geography 45%/35%/20%
### T. Tonight's departure (6 Oct 2026, 18:40Z): a set holding x of weight stops mining and signing at once; first lock after it under each candidate rule; 31 days, seeds 7,11,13
Mainnet scale (30-day window). The devnet's window is 7,200 DAA s, so divide by 360: one mainnet day is four devnet minutes. Analytic: v2 recovers at 30 (1 - 1/(3x)) days (never at x <= 1/3), v3 at day 30 (the frozen table's expiry), the leave rule after its delay, the decay rule after T + 1/r x (1 - (1 - x) / (2 x)) hours at the latest (the departed weight has to decay until the stayers hold 2/3 of what is left).
| departed weight | rule | first final lock after the departure | provisional tier | stalled checkpoints | stalled after the first lock | conflicting final locks | conflicting provisional locks |
|---|---|---|---|---|---|---|---|
| 34% | leave | 0.04 to 0.04 d (devnet 0 to 0 min) | | 119 to 521 | 0 to 398 | 0 | |
| 45% | leave | 0.04 d (devnet 0 min) | | 119 to 204 | 0 to 83 | 0 | |
| 50% | leave | 0.04 d (devnet 0 min) | | 118 to 490 | 0 to 371 | 0 | |
### P. The partition suite under each candidate rule (every side retargets and counts only its own blocks), seeds 7,11,13
Pass line: 0 conflicting FINAL locks in every row. A candidate that removes weight from a denominator on what a view does NOT see (silence, missing blocks) is view-dependent: each side of a partition sees the other side as silent, so both denominators shrink and the two sides lock alone, the active-denominator hazard of `sim/results_v2.md` E in a new coat. A candidate that removes weight on what a view DOES see (a signed leave, equivocation evidence) keeps the 4/3 arithmetic of spec 3.11.2 over the weight both sides count.
P1. Partitions of 360 minutes:
| rule | case | partition min | conflicting final locks | conflicting provisional locks | first conflict, min | first lock per side, min | every pre-heal lock kept | first lock after heal, min | stalls in 2 h after heal |
|---|---|---|---|---|---|---|---|---|---|
| leave | 50/50 honest, 0% attacker | 360 | 0 | | never | never / never | yes | 0 | 0 |
| leave | 50/50 + 20% equivocator (sides 60/60) | 360 | 0 | | never | never / never | yes | 0 | 0 |
| leave | 50/50 + 33% equivocator (sides 66.5/66.5) | 360 | 0 | | never | never / never | yes | 0 to 0 | 0 |
| leave | 50/50 + 34% equivocator (sides 67/67, the bound) | 360 | 139 to 283 | | 14 to 78 | 12 to 50 / 0 to 77 | yes | 0 to 0 | 0 |
| leave | 40/40/20 honest | 360 | 0 | | never | never / never / never | yes | 0 to 0 | 0 |
| leave | 60/40 honest | 360 | 0 | | never | never / never | yes | 0 to 0 | 0 |
| leave | 70/30 honest | 360 | 0 | | never | 0 to 4 / never | yes | 0 | 0 |
P2. The poisoned eclipse (L3: a 34% attacker feeds a 20% pool a private fork and signs both; the eclipsed side holds 54% of total):
| rule | eclipse h | conflicting final locks | conflicting provisional locks | first conflict, min | locks on the eclipsed side | honest-side stalls during | stalls after heal |
|---|---|---|---|---|---|---|---|
| leave | 1 | 0 | | never | 0 | 0 | 0 |
| leave | 2 | 0 | | never | 0 | 0 | 0 |
| leave | 4 | 0 | | never | 0 | 0 | 0 |
P3. Long honest partitions with view-local weight (L4), 12 days:
| rule | honest split | days | first lock per side, day | conflicting final locks | conflicting provisional locks | first conflict | every pre-heal lock kept | first lock after heal, min |
|---|---|---|---|---|---|---|---|---|
| leave | 50/50 | 12 | never / never | 0 | | never | yes | 0 |
| leave | 60/40 | 12 | never / never | 0 | | never | yes | 0 to 0 |
### Q. Silent weight that keeps mining (L1) under each candidate, and acquired keys that LEAVE under the leave rule; seeds 7,11,13
Q1. A set holding x of weight stops signing but keeps mining for 6 h, then resumes (its weight never ages out):
| rule | silent weight | silent hours | first lock after the stop, min | stalled while silent | longest gap, min | first lock after resume, min | conflicting locks |
|---|---|---|---|---|---|---|---|
| leave | 30% | 6 | 0 | 0 to 40 | 1 to 8 | 0 to 0 | 0 |
| leave | 34% | 6 | never | 714 to 722 | 360 | 0 to 0 | 0 |
| leave | 45% | 6 | never | 714 to 722 | 360 | 0 to 0 | 0 |
Q2. Under v3 + leave 1 h: an attacker at 30% of hashrate buys keys worth 40% or 49% of the window and makes them sign, stay silent, or LEAVE (the leave removes the bought weight from every denominator after 1 h, so the attacker's own share of what remains is w / (1 - L)). Arithmetic: to lock alone it needs w >= 2/3 (1 - L), so w + L >= 2/3 + L/3 >= 2/3 of the window either way; leaving bought keys is never cheaper than signing with them. 30 days:
| bought weight | bought keys | attacker's peak share of the denominator | share at the end | holds the veto (1/3) | stalled checkpoints | conflicting locks |
|---|---|---|---|---|---|---|
| 40% | sign | 39.8 to 39.8% | 30.0 to 30.0% | day 1 to 19 | 0 | 0 |
| 40% | silent | 39.8 to 39.8% | 30.0 to 30.0% | day 1 to 19 | 86402 to 86491 | 0 |
| 40% | leave | 30.0 to 30.0% | 30.0 to 30.0% | never | 119 to 144 | 0 |
| 49% | sign | 48.5 to 48.6% | 30.0 to 30.0% | day 1 to 23 to 24 | 0 | 0 |
| 49% | silent | 48.5 to 48.6% | 30.0 to 30.0% | day 1 to 23 to 24 | 86371 to 86408 | 0 |
| 49% | leave | 30.0 to 30.0% | 30.0 to 30.0% | never | 143 to 188 | 0 |

View file

@ -0,0 +1,56 @@
# finality_horizon output (sim/horizon/finality-and-weight/finality_horizon.py, a copy of sim/finality_v2.py at fud-close 1544c63 plus the candidate rules)
seed 7, slot 30 s, 30 blocks per checkpoint, window 720 h, presence 240 checkpoints, dust 100, quorum 2/3, floor factor 1.00 (a lock needs 66.7% of total under the rule as specified), inter-region delay 2.0 s (intra 0.1 s, jitter sigma 0.25), grace 15 s, uptime 0.97 (mean outage 20 slots), uptime 0.995 for keys at or above 1% of hashrate, honest keys 1000 Pareto 1.0, geography 45%/35%/20%
### T. Tonight's departure (6 Oct 2026, 18:40Z): a set holding x of weight stops mining and signing at once; first lock after it under each candidate rule; 31 days, seeds 7,11,13
Mainnet scale (30-day window). The devnet's window is 7,200 DAA s, so divide by 360: one mainnet day is four devnet minutes. Analytic: v2 recovers at 30 (1 - 1/(3x)) days (never at x <= 1/3), v3 at day 30 (the frozen table's expiry), the leave rule after its delay, the decay rule after T + 1/r x (1 - (1 - x) / (2 x)) hours at the latest (the departed weight has to decay until the stayers hold 2/3 of what is left).
| departed weight | rule | first final lock after the departure | provisional tier | stalled checkpoints | stalled after the first lock | conflicting final locks | conflicting provisional locks |
|---|---|---|---|---|---|---|---|
| 34% | twotier | 30.00 d (devnet 120 min) | provisional 0.000 to 0.004 d (devnet 0.0 to 0.0 min) | 86388 to 86472 | 0 to 31 | 0 | 0 |
| 45% | twotier | 30.00 d (devnet 120 min) | provisional 0.031 to 0.034 d (devnet 0.1 to 0.1 min) | 86382 to 86420 | 0 to 22 | 0 | 0 |
| 50% | twotier | 30.00 d (devnet 120 min) | provisional 0.042 d (devnet 0.2 min) | 86387 to 86514 | 0 to 10 | 0 | 0 |
### P. The partition suite under each candidate rule (every side retargets and counts only its own blocks), seeds 7,11,13
Pass line: 0 conflicting FINAL locks in every row. A candidate that removes weight from a denominator on what a view does NOT see (silence, missing blocks) is view-dependent: each side of a partition sees the other side as silent, so both denominators shrink and the two sides lock alone, the active-denominator hazard of `sim/results_v2.md` E in a new coat. A candidate that removes weight on what a view DOES see (a signed leave, equivocation evidence) keeps the 4/3 arithmetic of spec 3.11.2 over the weight both sides count.
P1. Partitions of 360 minutes:
| rule | case | partition min | conflicting final locks | conflicting provisional locks | first conflict, min | first lock per side, min | every pre-heal lock kept | first lock after heal, min | stalls in 2 h after heal |
|---|---|---|---|---|---|---|---|---|---|
| twotier | 50/50 honest, 0% attacker | 360 | 0 | 589 to 603 | never | never / never | yes | 0 | 0 |
| twotier | 50/50 + 20% equivocator (sides 60/60) | 360 | 0 | 653 to 660 | never | never / never | yes | 0 | 0 |
| twotier | 50/50 + 33% equivocator (sides 66.5/66.5) | 360 | 0 | 705 to 715 | never | never / never | yes | 0 to 0 | 0 |
| twotier | 50/50 + 34% equivocator (sides 67/67, the bound) | 360 | 139 to 283 | 713 to 720 | 14 to 78 | 12 to 50 / 0 to 77 | yes | 0 to 0 | 0 |
| twotier | 40/40/20 honest | 360 | 0 | 1056 to 1062 | never | never / never / never | yes | 0 to 0 | 0 |
| twotier | 60/40 honest | 360 | 0 | 516 to 564 | never | never / never | yes | 0 to 0 | 0 |
| twotier | 70/30 honest | 360 | 0 | 523 to 532 | never | 0 to 4 / never | yes | 0 | 0 |
P2. The poisoned eclipse (L3: a 34% attacker feeds a 20% pool a private fork and signs both; the eclipsed side holds 54% of total):
| rule | eclipse h | conflicting final locks | conflicting provisional locks | first conflict, min | locks on the eclipsed side | honest-side stalls during | stalls after heal |
|---|---|---|---|---|---|---|---|
| twotier | 1 | 0 | 19 to 24 | never | 0 | 0 | 0 |
| twotier | 2 | 0 | 137 to 145 | never | 0 | 0 | 0 |
| twotier | 4 | 0 | 377 to 385 | never | 0 | 0 | 0 |
P3. Long honest partitions with view-local weight (L4), 12 days:
| rule | honest split | days | first lock per side, day | conflicting final locks | conflicting provisional locks | first conflict | every pre-heal lock kept | first lock after heal, min |
|---|---|---|---|---|---|---|---|---|
| twotier | 50/50 | 12 | never / never | 0 | 34267 to 34418 | never | yes | 0 |
| twotier | 60/40 | 12 | never / never | 0 | 34094 to 34381 | never | yes | 0 to 0 |
### Q. Silent weight that keeps mining (L1) under each candidate, and acquired keys that LEAVE under the leave rule; seeds 7,11,13
Q1. A set holding x of weight stops signing but keeps mining for 6 h, then resumes (its weight never ages out):
| rule | silent weight | silent hours | first lock after the stop, min | stalled while silent | longest gap, min | first lock after resume, min | conflicting locks |
|---|---|---|---|---|---|---|---|
| twotier | 30% | 6 | 0 | 0 to 40 | 1 to 8 | 0 to 0 | 0 |
| twotier | 34% | 6 | never | 714 to 722 | 360 | 0 to 0 | 0 |
| twotier | 45% | 6 | never | 714 to 722 | 360 | 0 to 0 | 0 |

View file

@ -0,0 +1,56 @@
# finality_horizon output (sim/horizon/finality-and-weight/finality_horizon.py, a copy of sim/finality_v2.py at fud-close 1544c63 plus the candidate rules)
seed 7, slot 30 s, 30 blocks per checkpoint, window 720 h, presence 240 checkpoints, dust 100, quorum 2/3, floor factor 1.00 (a lock needs 66.7% of total under the rule as specified), inter-region delay 2.0 s (intra 0.1 s, jitter sigma 0.25), grace 15 s, uptime 0.97 (mean outage 20 slots), uptime 0.995 for keys at or above 1% of hashrate, honest keys 1000 Pareto 1.0, geography 45%/35%/20%
### T. Tonight's departure (6 Oct 2026, 18:40Z): a set holding x of weight stops mining and signing at once; first lock after it under each candidate rule; 31 days, seeds 7,11,13
Mainnet scale (30-day window). The devnet's window is 7,200 DAA s, so divide by 360: one mainnet day is four devnet minutes. Analytic: v2 recovers at 30 (1 - 1/(3x)) days (never at x <= 1/3), v3 at day 30 (the frozen table's expiry), the leave rule after its delay, the decay rule after T + 1/r x (1 - (1 - x) / (2 x)) hours at the latest (the departed weight has to decay until the stayers hold 2/3 of what is left).
| departed weight | rule | first final lock after the departure | provisional tier | stalled checkpoints | stalled after the first lock | conflicting final locks | conflicting provisional locks |
|---|---|---|---|---|---|---|---|
| 34% | v2 | 0.77 to 0.87 d (devnet 3 to 3 min) | | 4057 to 4727 | 1531 to 2489 | 0 | |
| 45% | v2 | 7.85 to 8.03 d (devnet 31 to 32 min) | | 23947 to 25153 | 1369 to 2065 | 0 | |
| 50% | v2 | 10.09 to 10.34 d (devnet 40 to 41 min) | | 30107 to 31264 | 1065 to 1456 | 0 | |
### P. The partition suite under each candidate rule (every side retargets and counts only its own blocks), seeds 7,11,13
Pass line: 0 conflicting FINAL locks in every row. A candidate that removes weight from a denominator on what a view does NOT see (silence, missing blocks) is view-dependent: each side of a partition sees the other side as silent, so both denominators shrink and the two sides lock alone, the active-denominator hazard of `sim/results_v2.md` E in a new coat. A candidate that removes weight on what a view DOES see (a signed leave, equivocation evidence) keeps the 4/3 arithmetic of spec 3.11.2 over the weight both sides count.
P1. Partitions of 360 minutes:
| rule | case | partition min | conflicting final locks | conflicting provisional locks | first conflict, min | first lock per side, min | every pre-heal lock kept | first lock after heal, min | stalls in 2 h after heal |
|---|---|---|---|---|---|---|---|---|---|
| v2 | 50/50 honest, 0% attacker | 360 | 0 | | never | never / never | yes | 0 | 0 |
| v2 | 50/50 + 20% equivocator (sides 60/60) | 360 | 0 | | never | never / never | yes | 0 | 0 |
| v2 | 50/50 + 33% equivocator (sides 66.5/66.5) | 360 | 0 | | never | never / 279 (never in 2 of 3) | yes | 0 to 0 | 0 |
| v2 | 50/50 + 34% equivocator (sides 67/67, the bound) | 360 | 142 to 291 | | 14 to 77 | 11 to 48 / 0 to 76 | yes | 0 to 0 | 0 |
| v2 | 40/40/20 honest | 360 | 0 | | never | never / never / never | yes | 0 to 0 | 0 |
| v2 | 60/40 honest | 360 | 0 | | never | never / never | yes | 0 to 0 | 0 |
| v2 | 70/30 honest | 360 | 0 | | never | 0 to 4 / never | yes | 0 | 0 |
P2. The poisoned eclipse (L3: a 34% attacker feeds a 20% pool a private fork and signs both; the eclipsed side holds 54% of total):
| rule | eclipse h | conflicting final locks | conflicting provisional locks | first conflict, min | locks on the eclipsed side | honest-side stalls during | stalls after heal |
|---|---|---|---|---|---|---|---|
| v2 | 1 | 0 | | never | 0 | 0 | 0 |
| v2 | 2 | 0 | | never | 0 | 0 | 0 |
| v2 | 4 | 0 | | never | 0 | 0 | 0 |
P3. Long honest partitions with view-local weight (L4), 12 days:
| rule | honest split | days | first lock per side, day | conflicting final locks | conflicting provisional locks | first conflict | every pre-heal lock kept | first lock after heal, min |
|---|---|---|---|---|---|---|---|---|
| v2 | 50/50 | 12 | 10.15 to 10.18 / 10.12 to 10.34 | 2890 to 3550 | | 10.22 to 10.35 d | yes | 0 |
| v2 | 60/40 | 12 | 5.15 to 5.27 / never | 0 | | never | yes | 0 to 0 |
### Q. Silent weight that keeps mining (L1) under each candidate, and acquired keys that LEAVE under the leave rule; seeds 7,11,13
Q1. A set holding x of weight stops signing but keeps mining for 6 h, then resumes (its weight never ages out):
| rule | silent weight | silent hours | first lock after the stop, min | stalled while silent | longest gap, min | first lock after resume, min | conflicting locks |
|---|---|---|---|---|---|---|---|
| v2 | 30% | 6 | 0 | 0 to 40 | 1 to 8 | 0 to 0 | 0 |
| v2 | 34% | 6 | never | 714 to 722 | 360 | 0 to 0 | 0 |
| v2 | 45% | 6 | never | 714 to 722 | 360 | 0 to 0 | 0 |

View file

@ -0,0 +1,56 @@
# finality_horizon output (sim/horizon/finality-and-weight/finality_horizon.py, a copy of sim/finality_v2.py at fud-close 1544c63 plus the candidate rules)
seed 7, slot 30 s, 30 blocks per checkpoint, window 720 h, presence 240 checkpoints, dust 100, quorum 2/3, floor factor 1.00 (a lock needs 66.7% of total under the rule as specified), inter-region delay 2.0 s (intra 0.1 s, jitter sigma 0.25), grace 15 s, uptime 0.97 (mean outage 20 slots), uptime 0.995 for keys at or above 1% of hashrate, honest keys 1000 Pareto 1.0, geography 45%/35%/20%
### T. Tonight's departure (6 Oct 2026, 18:40Z): a set holding x of weight stops mining and signing at once; first lock after it under each candidate rule; 31 days, seeds 7,11,13
Mainnet scale (30-day window). The devnet's window is 7,200 DAA s, so divide by 360: one mainnet day is four devnet minutes. Analytic: v2 recovers at 30 (1 - 1/(3x)) days (never at x <= 1/3), v3 at day 30 (the frozen table's expiry), the leave rule after its delay, the decay rule after T + 1/r x (1 - (1 - x) / (2 x)) hours at the latest (the departed weight has to decay until the stayers hold 2/3 of what is left).
| departed weight | rule | first final lock after the departure | provisional tier | stalled checkpoints | stalled after the first lock | conflicting final locks | conflicting provisional locks |
|---|---|---|---|---|---|---|---|
| 34% | v3 | 30.00 d (devnet 120 min) | | 86388 to 86472 | 0 to 31 | 0 | |
| 45% | v3 | 30.00 d (devnet 120 min) | | 86382 to 86420 | 0 to 22 | 0 | |
| 50% | v3 | 30.00 d (devnet 120 min) | | 86387 to 86514 | 0 to 10 | 0 | |
### P. The partition suite under each candidate rule (every side retargets and counts only its own blocks), seeds 7,11,13
Pass line: 0 conflicting FINAL locks in every row. A candidate that removes weight from a denominator on what a view does NOT see (silence, missing blocks) is view-dependent: each side of a partition sees the other side as silent, so both denominators shrink and the two sides lock alone, the active-denominator hazard of `sim/results_v2.md` E in a new coat. A candidate that removes weight on what a view DOES see (a signed leave, equivocation evidence) keeps the 4/3 arithmetic of spec 3.11.2 over the weight both sides count.
P1. Partitions of 360 minutes:
| rule | case | partition min | conflicting final locks | conflicting provisional locks | first conflict, min | first lock per side, min | every pre-heal lock kept | first lock after heal, min | stalls in 2 h after heal |
|---|---|---|---|---|---|---|---|---|---|
| v3 | 50/50 honest, 0% attacker | 360 | 0 | | never | never / never | yes | 0 | 0 |
| v3 | 50/50 + 20% equivocator (sides 60/60) | 360 | 0 | | never | never / never | yes | 0 | 0 |
| v3 | 50/50 + 33% equivocator (sides 66.5/66.5) | 360 | 0 | | never | never / never | yes | 0 to 0 | 0 |
| v3 | 50/50 + 34% equivocator (sides 67/67, the bound) | 360 | 139 to 283 | | 14 to 78 | 12 to 50 / 0 to 77 | yes | 0 to 0 | 0 |
| v3 | 40/40/20 honest | 360 | 0 | | never | never / never / never | yes | 0 to 0 | 0 |
| v3 | 60/40 honest | 360 | 0 | | never | never / never | yes | 0 to 0 | 0 |
| v3 | 70/30 honest | 360 | 0 | | never | 0 to 4 / never | yes | 0 | 0 |
P2. The poisoned eclipse (L3: a 34% attacker feeds a 20% pool a private fork and signs both; the eclipsed side holds 54% of total):
| rule | eclipse h | conflicting final locks | conflicting provisional locks | first conflict, min | locks on the eclipsed side | honest-side stalls during | stalls after heal |
|---|---|---|---|---|---|---|---|
| v3 | 1 | 0 | | never | 0 | 0 | 0 |
| v3 | 2 | 0 | | never | 0 | 0 | 0 |
| v3 | 4 | 0 | | never | 0 | 0 | 0 |
P3. Long honest partitions with view-local weight (L4), 12 days:
| rule | honest split | days | first lock per side, day | conflicting final locks | conflicting provisional locks | first conflict | every pre-heal lock kept | first lock after heal, min |
|---|---|---|---|---|---|---|---|---|
| v3 | 50/50 | 12 | never / never | 0 | | never | yes | 0 |
| v3 | 60/40 | 12 | never / never | 0 | | never | yes | 0 to 0 |
### Q. Silent weight that keeps mining (L1) under each candidate, and acquired keys that LEAVE under the leave rule; seeds 7,11,13
Q1. A set holding x of weight stops signing but keeps mining for 6 h, then resumes (its weight never ages out):
| rule | silent weight | silent hours | first lock after the stop, min | stalled while silent | longest gap, min | first lock after resume, min | conflicting locks |
|---|---|---|---|---|---|---|---|
| v3 | 30% | 6 | 0 | 0 to 40 | 1 to 8 | 0 to 0 | 0 |
| v3 | 34% | 6 | never | 714 to 722 | 360 | 0 to 0 | 0 |
| v3 | 45% | 6 | never | 714 to 722 | 360 | 0 to 0 | 0 |

View file

@ -0,0 +1,81 @@
#!/usr/bin/env python3
"""Weight capture cost (Horizon lane 3, task 3): what it costs in rented hash to hold W of the 30-day window weight
against a network of N GH/s, and what buying old keys is worth. Pure arithmetic from spec 03 W2 (weight = blue blocks over a
flat 30-day window) and the rental price of the fleet's bench entry.
Model (every input labelled):
* Weight share of an attacker who rents A GH/s against a network of N GH/s for t days (t <= 30), spec 3.1 headline,
verified by sim/results_v2.md B to 0.04 points: share(t) = (t / 30) x A / (N + A).
* To hold W at day t: A = N x q / (1 - q) with q = 30 W / t, which needs t > 30 W (q < 1).
* Cost = A x t x 24 x price, price = USD 11.7 per GH/s-hour (MEASURED: docs/bench-log.md "Rental cost of hash,
6 October 2026", 1,748 MH/s for USD 20.44/h on RunPod community pods, USD 0.0117 per MH/s-hour).
* Cost is decreasing in t (cost = 24 x price x N x 30 W / (1 - 30 W / t)), so the cheapest attack takes the whole window:
t = 30, A = N W / (1 - W), cost = 8,424 x N x W / (1 - W) USD per GH/s of network.
* A bought key is worth the blocks it holds and nothing more (spec 3.11.5, sim K): keys worth b of the window are the
position of having rented b / (1 - b) x N for 30 days, so their replacement cost is the same formula at W = b.
Run: python3 weight_capture.py (markdown tables on stdout)
"""
PRICE_GHS_HOUR = 11.7 # USD, measured 6 Oct 2026 (bench-log "Rental cost of hash")
PRICE_LABEL = "USD 11.7 per GH/s-hour, measured 6 Oct 2026 on RunPod community pods (bench-log)"
def rented(N, W, t):
q = 30.0 * W / t
if q >= 1.0:
return None
return N * q / (1.0 - q)
def cost(N, W, t):
A = rented(N, W, t)
return None if A is None else A * t * 24.0 * PRICE_GHS_HOUR
def fmt_usd(x):
if x is None:
return "impossible (needs t > 30 W days)"
if x >= 1e6:
return "USD %.2f M" % (x / 1e6)
if x >= 1e3:
return "USD %.0f k" % (x / 1e3)
return "USD %.0f" % x
def main():
print("## Weight capture by rented hash (price %s)" % PRICE_LABEL)
print()
print("Share of the 30-day window after t days at A GH/s against N GH/s: (t/30) x A/(N + A). The hashrate step the detector sees on day 1: +A/N.")
print()
for W, what in ((0.34, "34%: blocks every lock (the veto)"), (0.51, "51%"), (0.67, "67%: locks alone")):
print("### W = %s" % what)
print()
rows = []
for t in (30, 25, 22, 21):
r = rented(1.0, W, t)
rows.append(["%d days" % t, "impossible" if r is None else "%.2f x N (hash step +%.0f%%)" % (r, 100 * r)] +
[fmt_usd(cost(N, W, t)) for N in (1, 10, 100, 1000)])
print("| held from day | rented hash | N = 1 GH/s | N = 10 GH/s | N = 100 GH/s | N = 1 TH/s |")
print("|---|---|---|---|---|---|")
for r in rows:
print("| " + " | ".join(r) + " |")
print()
print("### What the market could supply tonight (measured): asked for 20 pods of any of 8 card types at 18:59Z to 19:15Z, RunPod gave 0; 38 pods were 1.75 GH/s. "
"So at N = 1 GH/s the 0.52 x N for a veto is rentable for the price of a dinner; at N = 100 GH/s the 52 GH/s does not exist to rent on any one market (approximate: the fleet asked one provider).")
print()
print("## Buying old keys (F19) against renting")
print()
print("| Route | What it buys | Price floor | Decay | Detectable |")
print("|---|---|---|---|---|")
for b in (0.20, 0.34, 0.40):
print("| keys worth %d%% of the window | %d%% of weight on day 0, falling as b (1 - t/30) + r t/30 (sim K, within 0.6 points) | the seller's own 30-day rental equivalent: %s per GH/s of network (same formula at W = b); a pool's key is also its payout identity and reputation, so the price is the pool, not the key | gone in 30 days unless the buyer mines | the key's blocks stop matching its hash (the detector of lane 1); W5 succession is public |"
% (100 * b, 100 * b, fmt_usd(cost(1.0, b, 30))))
print("| renting the same share | the same weight 30 days later, in public on the hashrate chart from day 1 | the table above | the same | day 1: the hashrate step |")
print()
print("What makes weight unbuyable: nothing; what makes it decay: the window (every block leaves 30 days after it was mined whoever holds the key). "
"What keeps the price at the rental cost: a key is one 32-byte scalar, the seller can keep a copy, and one equivocation by either holder strips it for 30 days (spec 3.6), "
"so a buyer pays for weight the seller can destroy. What the header does: it names vote_key_hash, so a sale is invisible until the key's blocks stop matching its old hash profile.")
if __name__ == "__main__":
main()