Correct sec 6.12: the parent anchor does move the window floor

My sec 6.12 claim was "costs no honest recovery at any tested delay". That
measurement held max_uncles = 4 -- a slack cap, which hides the window entirely,
since with four uncle slots the queue drains faster than the window can bind.
Re-running the sec 3.4 absorption sweep at U = 1, where the window IS the binding
constraint, shows the cost is real:

  delta=4   knee W 5 -> 6    at W=10: 0.9982 -> 0.9981  (free)
  delta=8   knee W 6 -> 8    at W=10: 0.9963 -> 0.9930  (-0.0033)
  delta=16  knee W 20 -> 20  at W=10: 0.9658 -> 0.9326  (-0.0332)

The floor rises by one to two block-intervals, exactly what a parent gap running
one block-interval longer than the uncle gap predicts -- the prediction I wrote
into the plan and then failed to test, because the first study was configured
with a cap that masked it.

Both statements are true and the report now carries both: at the recommended
U = ceil(rho)+1 the anchor is free, and at U = 1 it costs measurably from
delta = 8 up. The consequence for sizing is that W should be measured against
the parent gap under the new rule -- W = 12 restores the margin W = 10 has
today, inside the spec's floor(0.6k) bound, so it needs no further spec change.
The delta = 16 row is U-limited rather than window-limited (rho ~ 0.87 against
U = 1, below the recommended cap), and is labelled as such.

The sec 8.5 spec-delta row and the sec 6 arc bullet carried the same overclaim and
are corrected too.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Marcin Pawlowski 2026-08-07 18:27:08 +02:00
parent 525d081a3c
commit 99ce8b76d0
No known key found for this signature in database
2 changed files with 45 additions and 3 deletions

View File

@ -490,7 +490,7 @@ Read this before using the numbers: the regime they hold in, and the margins tha
- **[§6.8](#s6-8)** a *soft* (reward-weighted) inclusion rule — the fork-safe choice (a validity rule cannot prove which forks a producer saw). The emergent reference rate stays high (honest blocks reference the published orphans), so the backfire mostly vanishes; the residual is set by the uncle window `W` (how far back a block may reach to reference an orphan; recommended 300 slots = 10/f, [§3.4](#s3-4)) and visibility.
- **[§6.9](#s6-9)** multiple coalitions and bribery — the abstention commons is bounded and structure-independent; multi-coalition *selfish* mining is flagged, not solved.
- **[§6.10](#s6-10)** fork rate and reorg depth — uncles and `ρ < 1` keep reorganisations shallow and keep even a 30 % coalition below the effective-majority cliff.
- **[§6.12](#s6-12)** the reference window bounds the uncle but not the historical state a validator must reach — a 30 % coalition drives it to the epoch boundary, and anchoring the window to the uncle's *parent* caps it at no honest cost.
- **[§6.12](#s6-12)** the reference window bounds the uncle but not the historical state a validator must reach — a 30 % coalition drives it to the epoch boundary, and anchoring the window to the uncle's *parent* caps it, free at the recommended cap but raising the window floor by one to two block-intervals.
- **[§6.11](#s6-11)** organic stake churn against the ~7.5-day epoch cadence — tracked with a one-epoch lag; a bounded rate wobble, never a consensus or accuracy failure.
The recurring theme: the same uncle references that fix the honest under-count ([§3.2](#s3-2)) are also the adversarial safeguard — and *rewarding* them (softly) makes the safeguard incentive-compatible without a fork risk.
@ -801,7 +801,17 @@ A 30 % coalition moves the *median* referenced parent from 54 slots back to 20 1
so bounding the parent bounds the uncle *for free*. A rule imposing both windows would be identical to the parent rule alone, which is why only two arms are simulated. Under it the same 30 % coalition reaches 292 / 300 / 300 slots at the three delays — capped at `w_u` by construction, a bound that now actually holds.
**It costs no honest recovery.** A latency orphan's parent is recent by construction: it forked off the chain a block or two ago. Measured honestly, `D̂/D` is **0.9993 → 0.9999** at `δ_max` = 4, **0.9969 → 0.9986** at 8 and **0.9791 → 0.9858** at 16 (uncle- → parent-anchored) — no loss anywhere, within one to two standard errors at every delay. The honest parent gap does run about one block-interval longer than the uncle gap (median 54 vs ~30), so the same numeric `w_u` is a slightly tighter window; at `W = 10` block-intervals that costs nothing measurable, but a deployment choosing `W` close to the [§3.4](#s3-4) floor of 7 should size against the parent gap rather than the uncle gap.
**What it costs: nothing at the recommended cap, but the window floor moves.** A latency orphan's parent is recent by construction — it forked off the chain a block or two ago — so at the recommended `U = ⌈ρ⌉ + 1` the anchor is free: `D̂/D` reads **0.9993 → 0.9999** at `δ_max` = 4, **0.9969 → 0.9986** at 8 and **0.9791 → 0.9858** at 16 (uncle- → parent-anchored, `U` = 4), no loss anywhere within one to two standard errors.
That measurement holds the cap slack, though, which hides the window. Isolating it at `U = 1` (`configs/absorption-window-anchor.yaml`) shows the honest cost is real where the window is the binding constraint, and moves the [§3.4](#s3-4) floor:
| `δ_max` | knee `W` (uncle-anchored) | knee `W` (parent-anchored) | `D̂/D` at the recommended `W = 10` |
|---|---|---|---|
| 4 | 5 | 6 | 0.9982 → 0.9981 |
| 8 | 6 | **8** | 0.9963 → 0.9930 |
| 16 | 20 | 20 | 0.9658 → 0.9326 |
The floor rises by one to two block-intervals, which is what the ~one-block-interval-longer parent gap predicts. Two consequences for the sizing rules. At the deployed operating point nothing changes: the knee moves 5 → 6 and `W = 10` still clears with margin. At the heavier design point the margin behind `W = 10` narrows from 1.67× to 1.25×, so **a deployment adopting the parent anchor should size `W` against the parent gap**`W = 12` restores the old margin, and the spec's `⌊0.6·k⌋` bound permits it without further change. The `δ_max` = 16 row is `U`-limited rather than window-limited (`ρ ≈ 0.87` against `U = 1`, below the recommended cap), so it measures the interaction of an under-provisioned cap with a tighter window rather than the window alone.
**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.
@ -949,7 +959,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 450600 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 450600 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 any tested delay ([§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, 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)) |

View File

@ -0,0 +1,32 @@
# Does the parent anchor change the uncle cap rule U = ceil(rho) + 1? (§3.3, §3.5, §6.12)
#
# The cap is sized by the LOAD: one uncle slot drains one concurrent block per block interval,
# so U must cover rho with a margin. Anchoring the window to the uncle's parent does not change
# how fast the queue drains, but it does shrink the candidate pool — an orphan whose parent has
# aged past the window is no longer eligible — so the queue it drains is a slightly different
# one. This checks whether the smallest U reaching the 0.98 recovery bar moves.
#
# W is held at the recommended 10 block-intervals throughout, since the window's own floor is
# measured separately by absorption-window-anchor.yaml; here the window is meant to be slack so
# that U is the binding constraint.
# Latency is in SLOTS (1 slot = 1 s).
n_nodes: [1000] # network size
stake_dist: [pareto] # heavy-tailed (realistic) stake distribution
topology: [blend] # Blend mixnet — the deployment transport
degree: [6] # peering degree of the d-regular graph
link_latency_mean: [0.5] # natural geographic transport (sub-slot)
link_latency_dist: [geo] # real-world geographic band mixture
blend_hops: [3] # fixed hop count; delay is the swept knob
blend_delay_max: [4.0, 8.0, 16.0, 24.0] # rho ~ 0.27 / 0.47 / 0.87 / 1.27
window_absorption: [10] # the recommended W; slack by design here
uncle_window_anchor: [uncle, parent] # spec rule vs the §6.12 proposal
max_uncles: [0, 1, 2, 3, 4] # 0 = baseline; find the smallest U clearing 0.98
uncle_strategy: [oldest] # spec Uncle Selection
init_dest: [common] # per-node initial D_est from agreement
replicates: 12
base: # per-run settings shared by every cell
k: 2160 # true security parameter
epochs: 20 # equilibrium within ~2 epochs; burn 50%
f: 0.03333333333333333 # slot activation coefficient (1/30)
genesis_d_factor: 0.5 # start near true stake (cheap epoch 0)
early_stop: true