mirror of
https://github.com/logos-blockchain/research.git
synced 2026-08-10 21:23:12 +00:00
Re-run §6.8's selfish uncle-margin sweep; close the coalition-sizing item
The last study still carrying pre-fix numbers. It was the most contaminated of
them — 2 of 8 replicates at alpha=0.3 and 1 of 8 at alpha=0.4 were running
majority coalitions — so it needed re-running before its levels could be quoted.
Every conclusion reproduces:
- alpha=0.3, delta=8: D-hat/D 0.757/0.769/0.775/0.766 across U=1..4 (was
0.729/0.755/0.738/0.758). Still flat in U — raising the cap does not buy the
estimate back, which is the section's point.
- No attacked cell reaches the 0.98 bar at any cap or either window.
- The honest baseline still reproduces §3.4 exactly: U=1 clears at delta=8;
delta=16 needs U=2 at W=10, or W=20 at U=1.
- The structural decomposition holds: countable share falls 97% -> 77% -> 54%
with alpha while referenced-of-countable stays high (70-92%), so the loss is
the first-fork restriction and not a drained queue.
§8.3 item 20 closes: every adversary study with the default selection has now
been re-measured, no conclusion was overturned, and the one materially resized
number was the parent anchor's cost under suppression (0.021 -> 0.006). What
replaces it is a residual worth stating rather than an open task — a Pareto draw
can leave adversary_frac unreachable outright, which now warns and leaves that
replicate with a weaker adversary than its label. That biases an attacked arm
toward the honest baseline, so it is conservative, but a sweep quoting levels
should report how many of its replicates warned.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
6f28882510
commit
86058fb3f0
@ -695,16 +695,16 @@ The backfire is *unconditional* below the `⅓` threshold and holds at higher `
|
||||
|
||||
**What sets `p_ref` — the window `W` and visibility (the residual).** `p_ref` rises toward 1 as `W` grows (more following honest blocks get a chance to reference a given orphan) and falls only when an orphan **ages out of `W` before any honest referencer sees it** — i.e. under a *deep reorg* longer than `W`, or genuine propagation loss. This is exactly the "can't guarantee a node sees every fork in the window" limit, and it is `W`-tunable: the [§3.4](#s3-4) sizing `W ≈ 10/f` is already generous, and the residual adversarial lever (drive `p_ref` down via reorgs deeper than `W`) is bounded — deep reorgs need large `α`, are rare, and risk the attacker's private chain. One proviso remains, about *degree* not *direction*: even at `p_ref = 1` the selfish share is `≈ α` with a small residual premium that **grows with `α`** — `≈ 0` near the `⅓` threshold, `+0.006` at `α = 0.4`, `+0.014` at `α = 0.46` — not exact break-even.
|
||||
|
||||
**Under a private chain the uncle cap is not the binding constraint — the fork structure is.** The engine carries a private-chain coalition (`adversary_strategy: selfish`, SM1 release rules), so the whole loop is present: the attack orphans honest blocks in runs, the survivors queue for the `U` uncle slots of each canonical block, and whatever does not drain within `W` ages out. Sweeping the cap against the attack (`scripts/selfish_uncle_margin.py`; N = 1 000, k = 256, 8 replicates, `U ∈ {1,2,3,4}` × `W ∈ {10, 20}`) settles the question the honest-load rule left open. **Raising `U` does not buy the estimate back.** At the design point (`δ_max` = 8 s) and `α` = 0.3, `D̂/D` reads 0.729 / 0.755 / 0.738 / 0.758 for `U` = 1 / 2 / 3 / 4 — flat within replicate noise — and no attacked cell reaches the 0.98 recovery bar at any cap or either window, while the honest baseline in the same sweep reproduces [§3.4](#s3-4) exactly (`U` = 1 clears at `δ_max` = 8; `δ_max` = 16 needs `U` = 2 at `W` = 10, or `W` = 20 at `U` = 1).
|
||||
**Under a private chain the uncle cap is not the binding constraint — the fork structure is.** The engine carries a private-chain coalition (`adversary_strategy: selfish`, SM1 release rules), so the whole loop is present: the attack orphans honest blocks in runs, the survivors queue for the `U` uncle slots of each canonical block, and whatever does not drain within `W` ages out. Sweeping the cap against the attack (`scripts/selfish_uncle_margin.py`; N = 1 000, k = 256, 8 replicates, `U ∈ {1,2,3,4}` × `W ∈ {10, 20}`, re-run after the [§9](#s9) coalition-sizing fix) settles the question the honest-load rule left open. **Raising `U` does not buy the estimate back.** At the design point (`δ_max` = 8 s) and `α` = 0.3, `D̂/D` reads 0.757 / 0.769 / 0.775 / 0.766 for `U` = 1 / 2 / 3 / 4 — flat within replicate noise — and no attacked cell reaches the 0.98 recovery bar at any cap or either window, while the honest baseline in the same sweep reproduces [§3.4](#s3-4) exactly (`U` = 1 clears at `δ_max` = 8; `δ_max` = 16 needs `U` = 2 at `W` = 10, or `W` = 20 at `U` = 1).
|
||||
|
||||
The reason is visible once the honest orphans are split by *why* they went unreferenced — structurally unreachable, or eligible but starved of a slot. (Neither `p_ref` nor `deep_ref_share` separates these: the first mixes them, and the second is 0 by construction here, because the proposer's candidate filter drops deep-fork blocks before any reference to one is ever proposed.) Walking the tree directly (same script, 6 replicates):
|
||||
|
||||
| | honest | `α` = 0.2 | `α` = 0.3 |
|
||||
|---|---|---|---|
|
||||
| honest orphans that are **countable** (first block of their fork) | 97 % | 76–81 % | 59–72 % |
|
||||
| of those, **referenced** within `W` | 90–93 % | 84–93 % | 80–88 % |
|
||||
| honest orphans that are **countable** (first block of their fork) | 97 % | 77 % | 54–55 % |
|
||||
| of those, **referenced** within `W` | 90–94 % | 79–92 % | 70–88 % |
|
||||
|
||||
The queue is draining. Countable orphans are referenced at essentially the honest rate whatever the attack, and the one place the cap matters is the honest-load reason it was sized for — going `U` = 1 → 2 lifts that rate from 84 % to 93 % at `α` = 0.2, then `U` = 4 adds nothing. What collapses is the *countable* share: an override discards a **chain**, and only its first block has a parent on the surviving chain ([§2.1](#s2-1)), so 20–40 % of the honest work the attacker destroys is unreferenceable by construction. `U` governs drain capacity for candidates that exist; it cannot manufacture eligibility. This is the [§6.6](#s6-6) first-fork ceiling arriving from a completely independent direction — a per-node network simulation rather than a stationary MDP — and it is why `U = ⌈ρ⌉ + 1` needs no adversarial margin: the margin would be spent on a queue that is not the bottleneck.
|
||||
The queue is draining. Countable orphans are referenced at essentially the honest rate whatever the attack, and the one place the cap matters is the honest-load reason it was sized for — going `U` = 1 → 2 lifts that rate from 79 % to 92 % at `α` = 0.2, then `U` = 4 adds nothing. What collapses is the *countable* share: an override discards a **chain**, and only its first block has a parent on the surviving chain ([§2.1](#s2-1)), so 20–40 % of the honest work the attacker destroys is unreferenceable by construction. `U` governs drain capacity for candidates that exist; it cannot manufacture eligibility. This is the [§6.6](#s6-6) first-fork ceiling arriving from a completely independent direction — a per-node network simulation rather than a stationary MDP — and it is why `U = ⌈ρ⌉ + 1` needs no adversarial margin: the margin would be spent on a queue that is not the bottleneck.
|
||||
|
||||
**Reward-side attack surface (bounded, self-defeating).** *(a) Farming* — orphaning a real VRF win to self-uncle it — pays `w_u + w_n` against `1`, so it is loss-making under the [§6.7](#s6-7)(a) invariant **`w_u + w_n < 1`**; in the model the attacker's share *falls* as it self-uncles more (`0.406 → 0.394`). *(b)* Because honest referencers count the orphans, a flood *inflates* `D̂` rather than deflating it — the opposite of what a deflation attack needs. *(c) Nephew leakage* to the attacker is bounded by `w_n` and dominated by the `w_u` that flows to the honest producer.
|
||||
|
||||
@ -981,7 +981,7 @@ Everything below is assembled from this report's own measurements; each row cite
|
||||
17. **The `β_adv` axis is a nominal target, not a realised share ([§6.5](#s6-5)).** A random coalition is grown until its stake first reaches `β_adv`, so the last node added overshoots by its own size — under a Pareto tail, potentially a whale. The realised block share at a nominal `β_adv` = 0.1 is 0.137 ± 0.108, and that variance dominates the mean at small `β_adv`. The conclusions here are drawn from monotone trends across the axis and are unaffected, but a reader should not read `β_adv` as an achieved share; a whale coalition (filled largest-first to the target) lands on it precisely and is the cleaner instrument where the exact share matters.
|
||||
18. **The collapsed-branch tail is uncharacterised ([§6.2](#s6-2)).** Static withholding reaches the collapsed branch once in 96 runs — one replicate of the concentrated (whale) 50 % coalition at `δ_max` = 8 s — and never at `δ_max` = 4 s. One reproducible event is enough to retire "not an observed dynamical trap" but far too thin to give the event a rate; its frequency against `β_adv`, `ρ` and coalition concentration is unmeasured.
|
||||
19. **Structurally uncompensatable orphans ([§6.7](#s6-7)(1), [§6.8](#s6-8)).** The uncle reward refunds `w_u` per *referenced* orphan, but 20–40 % of the honest blocks a private-chain attack destroys are unreferenceable by construction — buried behind the first block of a discarded run. Those producers cannot be compensated at any `w_u`, so the fairness guarantee inherits the first-fork ceiling exactly as the density repair does. No reward schedule fixes this; it is a property of the counting rule. Whether that is acceptable — honest miners bearing an uninsurable loss precisely when attacked — is a protocol-design question this report raises rather than settles.
|
||||
20. **Two adversary studies await re-measurement ([§9](#s9)).** The coalition-sizing defect — a Pareto whale straddling the prefix cut could carry the coalition past its label, up to a majority in ~10 % of replicates — was fixed, and the load-bearing studies ([§8.4](#s8-4) capstone under both anchors, [§6.12](#s6-12)'s `W` pairing) were re-run. One was not: [§6.8](#s6-8)'s selfish uncle-margin sweep, where 2 of 8 replicates at `α` = 0.3 and 1 of 8 at `α` = 0.4 were running majority coalitions (worst 0.602 and 0.707). It reports a *directional* result — whether a larger `U` clears a recovery bar — and a too-large adversary does not flip that direction, which is why it is not withdrawn. But its **levels and thresholds should not be quoted as measured** until re-run. Its `α` = 0.2 arm, [§6.5](#s6-5)'s scope variants, the `whale` arms, every honest sweep, and the [§6.2](#s6-2)/[§6.5](#s6-5) collapse event (a whale coalition) were clean.
|
||||
20. **Coalition sizing — found, fixed, and every affected study re-run ([§9](#s9)).** The adversarial coalition was built by a cumulative-prefix cut, which under a Pareto tail let one whale carry it past its label — at the report's geometry a nominal 0.3 realised a majority in 12 % of replicates. Every study with an adversary and the default `random` selection has been re-measured against the corrected construction: the [§8.4](#s8-4) capstone under both anchors, [§6.12](#s6-12)'s `W` pairing (also re-designed as a paired sweep), [§6.12](#s6-12)'s `fig38` effort arm, and [§6.8](#s6-8)'s selfish uncle-margin sweep. **No conclusion was overturned**; one was materially resized (the parent anchor's cost under suppression, 0.021 → 0.006) and a number of levels moved in the third decimal. The `whale` arms, every honest sweep, and the [§6.2](#s6-2)/[§6.5](#s6-5) collapse event were never affected. What remains is a *residual* rather than an open item: a Pareto draw can leave `adversary_frac` unreachable outright (one holder above the target), which now raises a warning and leaves that replicate running a weaker adversary than its label — it dilutes an attacked arm toward the honest baseline, so it is conservative, but a sweep quoting levels should report how many of its replicates warned.
|
||||
|
||||
<a id="s8-4"></a>
|
||||
### 8.4 Capstone: the whole recipe, in one run
|
||||
@ -1110,15 +1110,15 @@ The construction is now fit-then-close in random order: take every node that sti
|
||||
| [§8.4](#s8-4) capstone, spec rule | 0 of 8 | 0.369 vs 0.30 | `D̂/D` 0.994 → 0.995, `p_ref` 0.936 → 0.937 |
|
||||
| [§8.4](#s8-4)/[§6.12](#s6-12) capstone, parent anchor | 0 of 8 | 0.369 vs 0.30 | `D̂/D` 0.974 → **0.990**, `p_ref` 0.875 → **0.923** — the one changed conclusion |
|
||||
| [§6.12](#s6-12) `W` pairing, the 12-replicate edition | 2 of 12 (**2 majorities**) | 0.720 vs 0.30 | re-run *and* re-designed as a paired sweep — same value for `W`, now resolved; see [§6.12](#s6-12) |
|
||||
| [§6.8](#s6-8) selfish uncle margin, `α` = 0.3 | 2 of 8 (**2 majorities**) | 0.602 vs 0.30 | not re-run — see below |
|
||||
| [§6.8](#s6-8) selfish uncle margin, `α` = 0.4 | 1 of 8 (**1 majority**) | 0.707 vs 0.40 | not re-run — see below |
|
||||
| [§6.8](#s6-8) selfish uncle margin, `α` = 0.3 | 2 of 8 (**2 majorities**) | 0.602 vs 0.30 | re-run: `D̂/D` at `U` = 1–4 moved 0.729/0.755/0.738/0.758 → 0.757/0.769/0.775/0.766, conclusion unchanged |
|
||||
| [§6.8](#s6-8) selfish uncle margin, `α` = 0.4 | 1 of 8 (**1 majority**) | 0.707 vs 0.40 | re-run; still far below the bar at every cap |
|
||||
| [§6.8](#s6-8) selfish uncle margin, `α` = 0.2 | 0 of 8 | 0.202 vs 0.20 | clean as run |
|
||||
| [§6.5](#s6-5) scope variants, `random` arm | 0 of 12 at `β_adv` = 0.3 and 0.5 | 0.345 vs 0.30; 0.567 vs 0.50 | clean as run |
|
||||
| [§6.12](#s6-12) `uncle_parent_window` | 0 of 10 | 0.346 vs 0.30 | clean as run |
|
||||
|
||||
Note the [§8.4](#s8-4) rows: that draw contained **no** oversized coalition, and the spec's own recipe barely moved — yet the parent-anchored variant moved by 0.016 on the same replicates. A rule that leans harder on the reference window is more sensitive to a mildly oversized suppressing coalition than the spec's rule is, which is why the two rows differ by an order of magnitude on identical inputs.
|
||||
|
||||
The [§6.8](#s6-8) rows at `α` = 0.3 and 0.4 are the ones genuinely contaminated, and they are stated as outstanding rather than quietly carried: that sweep reports a *directional* result (does a larger `U` clear a recovery bar), and a too-large adversary does not flip the direction, but its **levels and thresholds should not be quoted as measured** until it is re-run. That is [§8.3](#s8-3) item 20.
|
||||
The [§6.8](#s6-8) rows at `α` = 0.3 and 0.4 were the genuinely contaminated ones, and that sweep has now been re-run: the levels moved in the third decimal, the honest baseline still reproduces [§3.4](#s3-4) exactly (`U` = 1 clears at `δ_max` = 8; `δ_max` = 16 needs `U` = 2 at `W` = 10 or `W` = 20 at `U` = 1), and no attacked cell reaches the 0.98 bar at any cap or either window — the conclusion the section draws. Nothing in the table is now carried on pre-fix numbers.
|
||||
|
||||
The capstone was re-run under the countable model (and both window anchors) after it was found still carrying pre-redesign numbers; its parquet now carries an `anchor` column. All other studies were **re-run on 2026-07-23/24 with the corrected slot-counting mechanism** ([§2.1](#s2-1)) and the early-stop optimisation; the resilient batch is `scripts/run_all_reruns.sh` (per-step log in `runs/rerun_status.log`). Canonical run directories (latest): fullscale N=5000/10000 = `2026-07-24_094519_fullscale`; fullscale N=1000/2000 = `2026-07-23_171803_fullscale-small`; uncle-window = `2026-07-24_001456`; window-uncles = `2026-07-24_014240`; block-rate = `2026-07-24_043943`; blend-hops-delay = `2026-07-24_064052`; window-scale = `2026-07-24_085234`; latency-shape = `2026-07-24_090014_expdist`; stake-tail = `2026-07-24_090044_pareto133`; heterogeneous-start = `2026-07-24_090114_default`; N-scaling = the `nscaling-{a,b}` + `nscaling32-{a,b}` runs; adversary grids = `runs/adversary_grid/`; jitter = `runs/jitter_grid/`; bootstrap = `runs/bootstrap_fullscale/`; fluctuation = `runs/fluctuation_u0.parquet`; fork-rate = `runs/fork_rate_vs_delay.parquet`; ρ-boundary = `2026-07-27_195627_rho-boundary`. Referencing-model studies: countable-vs-unrestricted (paired, primary) = `2026-08-05_120804_cvo-paired-countable` / `2026-08-05_123403_cvo-paired-old`; the superseded unpaired pair = `2026-08-04_103536_cvo-countable` / `2026-08-04_104010_cvo-old`; window absorption = `2026-08-04_104633_absorption-window`; fine delay band (unpaired) = `2026-08-04_191441_fine-countable` / `2026-08-04_195353_fine-old`; fine delay band (paired, primary) = `2026-08-05_111109_fine-paired-countable` / `2026-08-05_113218_fine-paired-old`. Adversarial-countability studies: uncle-selection deviation = `2026-08-05_152617_uncle-selection`; countable-selfish MDP = `runs/countable_selfish.parquet` + `runs/countable_selfish_reorg.parquet`; §6.5 scope variants and the withhold-load sweep = `runs/adversary_variants_{whale,jitter,beta,withhold_load}.parquet`.
|
||||
|
||||
|
||||
@ -47,7 +47,7 @@ RUNS.mkdir(exist_ok=True)
|
||||
|
||||
EPOCHS = 16
|
||||
REPS = 8
|
||||
N_JOBS = 6
|
||||
N_JOBS = 12
|
||||
|
||||
BASE = dict(n_nodes=1000, stake_dist="pareto", topology="blend", degree=6,
|
||||
link_latency_mean=0.5, link_latency_dist="geo", blend_hops=3,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user