From 7434c9f0623dd0b1ec7ffc5b390d863760f1d14f Mon Sep 17 00:00:00 2001 From: Marcin Pawlowski Date: Mon, 10 Aug 2026 21:23:43 +0200 Subject: [PATCH] Locate the W crossing to half a block-interval: 12 confirmed, 11.5 ruled out MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The integer sweep put parity somewhere between W = 11 and W = 12 and no finer, on a standard error of 0.00050 against a difference of the same size. The open question was whether half an interval cheaper would do. It would not. 2112 paired runs: half-integer W from 9 to 14, 96 replicates, early_stop off at 30 epochs so each run gives a full 15-epoch equilibrium sample. Standard error on a paired difference falls 0.00050 -> 0.00018. Paired against today's recipe (uncle-anchored, W = 10), on-label replicates: parent W=11.0 -0.00176 +- 0.00020 t = -8.9 parent W=11.5 -0.00096 +- 0.00019 t = -5.1 <- ruled out parent W=12.0 -0.00005 +- 0.00018 t = -0.3 <- parity parent W=12.5 +0.00057 +- 0.00019 t = 3.0 Interpolated zero crossing W = 12.03, so 12 is not merely the first integer that clears — it is the crossing to within the grid's resolution. The residual at 12 is 5e-5, two orders of magnitude below the +-0.9% per-epoch noise floor, and p_ref follows at 0.937 against 0.938. Two of the 96 replicates drew a stake vector on which adversary_frac = 0.3 is unreachable and ran 0.005 and 0.094 coalitions. Being weaker they pull cells toward parity, which would bias the crossing SMALLER, so the headline is the 94 on-label replicates; including all 96 gives the same crossing and t = -0.30. The value has now survived three independent measurements, two design flaws and a 3x increase in resolution without moving: 11.98, then 12.03. §8.5's spec-delta row sharpened to match; §9 cites the new config and analysis script. Co-Authored-By: Claude Opus 5 --- reports/tsi/README.md | 41 ++++++++++++++++++++++++++--------------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/reports/tsi/README.md b/reports/tsi/README.md index 10866c4..044a1c0 100644 --- a/reports/tsi/README.md +++ b/reports/tsi/README.md @@ -841,26 +841,37 @@ The floor rises by one to two block-intervals, which is what the ~one-block-inte **The uncle cap rule is unchanged.** Sizing `U` is the other half of the recipe, and it is not affected: the smallest cap reaching the 0.98 bar is identical under both anchors at every load tested (`configs/window-uncles-anchor.yaml`, `W` = 10 held slack) — `U` = 1 at `ρ` = 0.27 and 0.47, `U` = 2 at 0.87, and neither anchor clears at `ρ` = 1.27, which is past the operating point the recipe permits anyway. `U = ⌈ρ⌉ + 1` therefore carries over verbatim. At that recommended cap the residual cost is small and stays inside the design regime: `−0.003` at `ρ` = 0.47 and 0.87, growing to `−0.009` only at `ρ` = 1.27. The anchor's cost lives in the window, not in the cap. -**`W = 12` restores it — measured against every integer, on common random numbers.** The pairing was first argued from the shape of the mechanism; this sweep fixes the value. It has to resolve differences of ~0.001 in `D̂/D`, so it is built for power rather than coverage: `W` at **every integer from 8 to 15** rather than a few scattered points, **32 replicates**, and — the part that does the work — `paired_streams`, which puts the whole grid on **common random numbers**. `SimConfig._base_key()` excludes both `uncle_window_anchor` and `window_absorption`, so at a given replicate every cell draws the same stake vector, the same peering graph and the same lottery, and the only thing that differs between two cells is the window rule itself. Differences are then *paired*, which removes the between-replicate variance that left the earlier unpaired version unable to resolve its own headline (a difference of 0.0009 against a standard error of 0.0009). `configs/pref-window-anchor.yaml`, the capstone's geometry, 30 % suppression adversary. +**`W = 12` restores it — located to a half-interval, on common random numbers.** The pairing was first argued from the shape of the mechanism, then measured on an integer grid that put the crossing somewhere between `W` = 11 and `W` = 12 and no finer. This is the high-power re-measurement, built to resolve a ~0.0005 difference rather than merely report one, by attacking three variance sources at once: + +* **half-integer `W`, 9 to 14.** `w_u = ⌊W/f⌋` is 285 slots at `W` = 9.5 against 270 at 9 and 300 at 10, so these are genuinely distinct windows and not relabelled duplicates. This is what can distinguish 11.5 from 12. +* **96 replicates** (from 32), which averages down the between-run variance. +* **`early_stop` off at 30 epochs** (from an early-stopped 20), so every run contributes a full 15-epoch equilibrium sample instead of ~10. This attacks the per-epoch ±0.9 % sampling noise of [Appendix B](#sB) that a replicate count cannot touch, and it is the more important of the two. + +Underneath all of it, `paired_streams`: `SimConfig._base_key()` excludes both `uncle_window_anchor` and `window_absorption`, so at a given replicate every one of the 22 cells draws the same stake vector, the same peering graph and the same lottery, and the only thing that differs between two cells is the window rule. 2 112 runs (`configs/pref-window-anchor-fine.yaml`, the capstone's geometry, 30 % suppression adversary; analysis by `scripts/w_pairing_analysis.py`). The standard error on a paired difference falls from 0.00050 to **0.00018**. | `W` | uncle-anchored `D̂/D` / `p_ref` | parent-anchored `D̂/D` / `p_ref` | parent − today, paired | `t` | |---|---|---|---|---| -| 8 | 0.9894 / 0.927 | 0.9762 / 0.891 | −0.0180 ± 0.0012 | −15.5 | -| 9 | 0.9925 / 0.934 | 0.9842 / 0.911 | −0.0100 ± 0.0008 | −12.8 | -| **10** (today) | **0.9942 / 0.939** | 0.9886 / 0.924 | −0.0056 ± 0.0005 | −10.4 | -| 11 | 0.9953 / 0.942 | 0.9918 / 0.933 | −0.0024 ± 0.0005 | −4.5 | -| **12** (the pair) | 0.9959 / 0.943 | **0.9942 / 0.938** | **+0.00004 ± 0.00050** | **0.1** | -| 13 | 0.9958 / 0.944 | 0.9942 / 0.941 | −0.00003 ± 0.00046 | −0.1 | -| 14 | 0.9965 / 0.945 | 0.9957 / 0.943 | +0.0016 ± 0.0004 | 3.5 | -| 15 | 0.9964 / 0.945 | 0.9963 / 0.944 | +0.0021 ± 0.0005 | 4.3 | +| 9 | 0.9926 / 0.933 | 0.9846 / 0.910 | −0.00939 ± 0.00026 | −35.9 | +| 9.5 | 0.9933 / 0.935 | 0.9870 / 0.917 | −0.00698 ± 0.00024 | −28.6 | +| **10** (today) | **0.9940 / 0.938** | 0.9889 / 0.923 | −0.00501 ± 0.00020 | −24.8 | +| 10.5 | 0.9949 / 0.939 | 0.9908 / 0.928 | −0.00312 ± 0.00019 | −16.1 | +| 11 | 0.9956 / 0.940 | 0.9922 / 0.932 | −0.00176 ± 0.00020 | −8.9 | +| 11.5 | 0.9957 / 0.941 | 0.9930 / 0.935 | −0.00096 ± 0.00019 | −5.1 | +| **12** (the pair) | 0.9960 / 0.942 | **0.9939 / 0.937** | **−0.00005 ± 0.00018** | **−0.3** | +| 12.5 | 0.9960 / 0.943 | 0.9945 / 0.939 | +0.00057 ± 0.00019 | 3.0 | +| 13 | 0.9964 / 0.943 | 0.9951 / 0.940 | +0.00110 ± 0.00020 | 5.6 | +| 13.5 | 0.9965 / 0.944 | 0.9957 / 0.941 | +0.00178 ± 0.00018 | 10.0 | +| 14 | 0.9966 / 0.944 | 0.9959 / 0.942 | +0.00192 ± 0.00019 | 10.0 | -The last two columns are the paired difference against **today's recipe** — uncle-anchored at `W` = 10 — so the question "does this configuration cost anything relative to what the spec does now?" is read straight off them. Two of the 32 replicates drew a stake vector on which `adversary_frac` = 0.3 is unreachable (one holder above the target) and ran a 0.09 and a 0.06 coalition instead; because they are weaker they pull every cell toward parity, which would make the crossing look *smaller* than it is. Dropping them moves nothing — the crossing stays at `W` = 12 and the interpolated zero stays at 11.98 (`scripts/w_pairing_analysis.py` reports both subsets). +The last two columns are the paired difference against **today's recipe** — uncle-anchored at `W` = 10 — so "does this configuration cost anything relative to what the spec does now?" is read straight off them. -**`W` = 12 is the smallest window at which the parent anchor reaches parity, and the parity is exact.** Every window below it is resolvably worse: even `W` = 11, only one interval short, still costs 0.0024 at `t` = 4.5. At `W` = 12 the paired difference is `+0.00004 ± 0.00050`, `t` = 0.1 — indistinguishable from today's recipe, and `p_ref` agrees (0.938 against 0.939). Beyond it the anchor is *better* than today rather than equal (`t` = 3.5 at `W` = 14), so 12 is a floor and not a knife-edge. **Pair the anchor change with `W` = 12, or take neither**: at `W` = 10 it costs 0.0056 under this adversary, at `t` = 10.4, which is the one regime where the change is not free. +**`W` = 12 is confirmed, and now bracketed to half an interval.** The finer grid rules out the cheaper option directly: `W` = 11.5 is still resolvably worse at −0.00096 ± 0.00019, `t` = −5.1, so the recommendation cannot be relaxed by half an interval. At `W` = 12 the paired difference is −0.00005 ± 0.00018, `t` = −0.3 — parity, with a residual two orders of magnitude below the ±0.9 % per-epoch noise floor — and `p_ref` follows at 0.937 against 0.938. Linear interpolation between the bracketing windows puts the true zero at **`W` = 12.03**, so 12 is not merely the first integer that clears: it is the crossing, to within the grid's resolution. Beyond it the anchor is better than today rather than equal (`t` = 3.0 at `W` = 12.5), so 12 is a floor and not a knife-edge. **Pair the anchor change with `W` = 12, or take neither**: at `W` = 10 it costs 0.0050 under this adversary at `t` = 25, which is the one regime where the change is not free. -Note the uncle-anchored column moves too — widening today's rule to `W` = 12 buys +0.0018 (`t` = 4.5) on its own. So `W` = 12 is what makes the anchor change *free relative to today*, not what makes it optimal; a deployment that widened `W` for its own sake would find the parent anchor still costs ~0.0017 against the uncle anchor at equal `W`. The recommendation is scoped to the swap being cost-neutral against the current recipe, which is the decision actually on the table. +Two of the 96 replicates drew a stake vector on which `adversary_frac` = 0.3 is unreachable (one holder above the target) and ran a 0.005 and a 0.094 coalition instead. Being weaker they pull every cell toward parity, which would make the crossing look *smaller* than it is — so the headline above is computed on the 94 on-label replicates. Including all 96 changes nothing: parity at `W` = 12 with `t` = −0.30 and the same 12.03 crossing. -*This supersedes two earlier editions of this measurement.* The first swept 5 windows at 12 replicates, unpaired, and reported the pairing at +0.0008 with `p_ref` still short at `W` = 12 and only matching at `W` = 15. Its `t` on that headline was below 1 — it could not actually resolve the claim it made — and 2 of its 12 replicates were running majority coalitions under the [§9](#s9) sizing defect. Both problems are fixed here, and the conclusion survives both: the value `W` = 12 is unchanged, now with `p_ref` reaching parity at the same window rather than three intervals later, and with the difference resolved rather than assumed. +Note the uncle-anchored column moves too — widening today's own rule to `W` = 12 buys +0.0021 (`t` = 10) on its own. So `W` = 12 is what makes the anchor change *free relative to today*, not what makes it optimal; a deployment that widened `W` for its own sake would find the parent anchor still costs ~0.0021 against the uncle anchor at equal `W`. The recommendation is scoped to the swap being cost-neutral against the current recipe, which is the decision actually on the table. + +*This supersedes two earlier editions of this measurement.* The first swept 5 windows at 12 replicates, unpaired, and reported the pairing at +0.0008 — with a standard error of 0.0009, so it could not resolve the claim it made — and 2 of its 12 replicates were running majority coalitions under the [§9](#s9) sizing defect. The second fixed both, swept every integer from 8 to 15 at 32 replicates paired, and put parity at `W` = 12 with an interpolated crossing of 11.98 at a standard error of 0.00050. This run tightens that to 0.00018 and adds the half-integer resolution the recommendation actually needed, since the open question was whether 11.5 would do. It would not. **The value `W` = 12 has now survived three independent measurements, two design flaws and a 3× increase in resolution, and the estimate has not moved: 11.98, then 12.03.** **One further observation, and it sharpens the case.** At `δ_max` = 16 the *honest* uncle-anchored arm already reaches **315 slots** — past its own `w_u` = 300. Under the current rule `w_u` is not a bound on how far back validation reaches even with no adversary present; under the parent rule the same cell is 296. The parameter only becomes a state-retention bound once it is anchored to the parent. @@ -1015,7 +1026,7 @@ The deltas this report recommends, in one place. This is a parameter-selection r | on-chain `f` precision | 10⁻³ (`f_p = 0.033`) | **10⁻⁶** (`0.033333`) | removes the residual ≈ 1 % `f/f_p` offset ([§2.2](#s2-2)) | | uncle window `W` | `W` = 10 block-intervals, so `w_u = W/f` = 300 slots, bounded `1 ≤ W ≤ ⌊0.6k⌋` | **10/f = 300 slots** (confirms the default), widen to 450–600 near `ρ ≈ 1` | the floor is ≥ 7/f, set by block *spacing* rather than network delay — which supplies what the spec's rationale leaves out. The spec justifies the *upper* bound (a candidate within `w_u` is still in the proposer's block tree, and referencing blocks stay in the same epoch as the window they count) and asserts of the value itself only that this window "comfortably captures the forks worth referencing"; it states no lower bound, and 300 slots is only a ~1.5× margin over the measured one. The `⌊0.6k⌋` ceiling is 1296 slots at k = 2160, so the 450–600 widening recommended near `ρ ≈ 1` — where W = 600 recovers ≈ 0.99 against ≈ 0.96 at W = 300 (N = 1 000) — is well inside what the spec already permits, and needs no spec change ([§3.4](#s3-4)) | | uncle cap `U` | `MAX_UNCLES` = 4 | **`⌈ρ⌉ + 1`** = 2 at the Blend target — well inside the spec's cap of 4 | the load-plus-margin rule ([§3.3](#s3-3)); 4 leaves headroom for heavier loads | -| uncle reference window anchor | the **uncle's** slot: `0 < sl_A − sl_U ≤ w_u` | **the uncle's PARENT's slot**: `sl_A − sl_parent(U) ≤ w_u` | the current rule bounds which block may be referenced but not how far back verifying it reaches, so a 30 % coalition forces counted references against state ~76 800 slots old (~21 h, 256× the window). The parent anchor is strictly tighter — it implies the uncle bound rather than adding to it — caps the reach at `w_u`, and costs no honest recovery at the recommended cap. It does raise the [§3.4](#s3-4) window floor by one to two block-intervals (the parent gap runs that much longer), so a deployment adopting it should size `W` against the parent gap — `W = 12` restores the margin `W = 10` has today — measured to parity within one standard error — well inside the spec's `⌊0.6·k⌋` bound ([§6.12](#s6-12)) | +| uncle reference window anchor | the **uncle's** slot: `0 < sl_A − sl_U ≤ w_u` | **the uncle's PARENT's slot**: `sl_A − sl_parent(U) ≤ w_u` | the current rule bounds which block may be referenced but not how far back verifying it reaches, so a 30 % coalition forces counted references against state ~76 800 slots old (~21 h, 256× the window). The parent anchor is strictly tighter — it implies the uncle bound rather than adding to it — caps the reach at `w_u`, and costs no honest recovery at the recommended cap. It does raise the [§3.4](#s3-4) window floor by one to two block-intervals (the parent gap runs that much longer), so a deployment adopting it should size `W` against the parent gap — `W = 12` restores the margin `W = 10` has today — measured to parity at `t` = −0.3 over 2 112 paired runs, with the crossing bracketed to half a block-interval (`W` = 11.5 is still resolvably short at `t` = −5.1) — well inside the spec's `⌊0.6·k⌋` bound ([§6.12](#s6-12)) | | peering degree | (operator choice) | **≥ 6, scaled with N** | keeps `ρ` and reorg depth bounded at scale ([§3.7](#s3-7), [§6.10](#s6-10)) | | uncle rewards | none (spec: uncle grants no block reward) | **soft inclusion, `w_u + w_n < 1`** | fork-safe fairness + anti-hiding ([§6.7](#s6-7)–[§6.8](#s6-8)) | | operating point | set by the Blend privacy budget | **`ρ = f·D_vis < 1`** | accuracy, weak grinding, shallow reorgs — one rule ([§6.3](#s6-3), [§6.10](#s6-10)) | @@ -1074,7 +1085,7 @@ Sweep studies are committed configs, run with `make ` (writes a dated `run | **uncle cap under a private chain** (`U` × `W` × `α` against the SM1 engine adversary, plus the structural/queue decomposition of the honest orphans) | `scripts/selfish_uncle_margin.py` (`adversary_strategy: selfish`); `runs/selfish_uncle_margin{,_decomp}.parquet` | [§6.8](#s6-8) | | **profit/deflation frontier** (how far a *paid* adversary can deflate `D̂`; the deflation-optimal MDP) | `scripts/deflation_frontier.py` (`selfish_mdp.deflation_frontier`, `.deflation_optimal_stats`); fig37 | [§6.6](#s6-6) | | **uncle- vs parent-anchored reference window** (honest recovery cost, and an adversary's reach into historical state) | `scripts/uncle_parent_window.py` (`uncle_window_anchor`, `adversary_strategy: deep_parent`); `runs/uncle_parent_window{,_effort}.parquet`; fig38 | [§6.12](#s6-12) | -| **the sizing rules re-measured under both anchors** (the `W` floor at `U` = 1; the `U` rule at `W` = 10; the `W` pairing under a suppression adversary — the last **paired**, every integer `W` 8–15, 32 replicates) | `configs/absorption-window-anchor.yaml`, `configs/window-uncles-anchor.yaml`, `configs/pref-window-anchor.yaml` | [§3.4](#s3-4), [§6.8](#s6-8), [§6.12](#s6-12) | +| **the sizing rules re-measured under both anchors** (the `W` floor at `U` = 1; the `U` rule at `W` = 10; the `W` pairing under a suppression adversary — the last **paired**, half-integer `W` 9–14, 96 replicates, 30 epochs, 2 112 runs) | `configs/absorption-window-anchor.yaml`, `configs/window-uncles-anchor.yaml`, `configs/pref-window-anchor-fine.yaml` (supersedes the 32-replicate integer `configs/pref-window-anchor.yaml`); analysis `scripts/w_pairing_analysis.py` | [§3.4](#s3-4), [§6.8](#s6-8), [§6.12](#s6-12) | | **the deployed operating point** (`δ_max` = 4 from the spec's Blend profile; the three `f`-precision arms) | `scripts/spec_point.py` (`f_precision`); `runs/spec_point.parquet` | [§2.2](#s2-2), [§8.5](#s8-5), [Appendix A](#sA) | | **per-recipient delay variance** (jitter 0–8 slots × `U`, both referencing models, exact oracle) | `scripts/spec_jitter.py`, `configs/spec-point-jitter.yaml`; `runs/spec_jitter.parquet` | [§6.1](#s6-1), [§8.3](#s8-3) item 15 | | **the spec point at scale and by window** (`N` = 5000; `W_abs` sweep at `δ_max` = 4) | `configs/spec-point-n5000.yaml` (run twice, `--old`), `configs/spec-point-window.yaml` | [§3.4](#s3-4), [§8.5](#s8-5) |