3 Commits

Author SHA1 Message Date
Marcin Pawlowski
097543a5f9
Re-measure the W pairing paired, and correct my own severity numbers
The W = 12 pairing is now measured the way a ~0.001 claim has to be: every
integer W from 8 to 15, 32 replicates, and paired_streams so the whole grid runs
on common random numbers (_base_key excludes both uncle_window_anchor and
window_absorption, so a replicate draws one stake vector, one graph and one
lottery for every cell). The earlier unpaired sweep reported +0.0008 against a
standard error of 0.0009 — it could not resolve its own headline.

Paired against today's recipe (uncle-anchored, W = 10):

  parent W=10   -0.0056 +- 0.0005   t = -10.4
  parent W=11   -0.0024 +- 0.0005   t =  -4.5
  parent W=12   +0.00004 +- 0.00050 t =   0.1   <- parity
  parent W=14   +0.0016 +- 0.0004   t =   3.5

W = 12 is the smallest window reaching parity, and the parity is exact rather
than marginal: W = 11, one interval short, is still resolvably worse. p_ref
agrees at the same window (0.938 vs 0.939) instead of lagging to W = 15 as the
unpaired edition had it. Also states what the sweep makes visible: widening
today's uncle-anchored rule buys +0.0018 on its own, so W = 12 makes the swap
cost-neutral against the CURRENT recipe rather than optimal in absolute terms.

CORRECTIONS to the previous commit, which measured contamination on the wrong
RNG stream. The engine draws stake from seedseq_for(config).spawn(...)[0]; I
used rng_for(config), the root. Both are valid stake draws, neither is the same
vector. Redone properly:

  - The capstone draw was NOT contaminated: 0 of 8 replicates over 1.25x its
    label, worst 0.369 against 0.30, no majority. My "2 of 8, one a 61%
    majority" was wrong and is withdrawn from §8.4 and §9.
  - The finding that survives is sharper: on that same mild overshoot the spec's
    rule moved 0.001 and the parent-anchored variant moved 0.016. A rule leaning
    harder on the reference window is far more sensitive to an oversized
    suppressing coalition.
  - Genuinely contaminated: §6.12's 12-replicate W sweep (2 majorities, worst
    0.720) and §6.8's selfish margin at a=0.3 and a=0.4 (2 and 1 majorities).
    §6.5's variants and §6.8's a=0.2 arm are clean; §8.3 item 20 narrowed to the
    one sweep that still needs re-running.
  - The general severity is worse than first stated, not better: at the report's
    geometry a nominal 0.3 realised a majority in 12% of replicates.

Two more defects found on the way:

  - stake_for(config) added, because scripts used rng_for and the engine uses
    the spawned child — so every script that rebuilt a tree was analysing a
    different network than the trajectory it was compared against. All scripts
    and tests now use it.
  - A coalition member could receive a private block BEFORE its parent: the
    arrival was clamped against the PRODUCER's view of the parent and applied to
    the whole coalition, so a member still awaiting a public parent got the child
    first. Now clamped per member. Caught by the existing arrival-order test once
    the stake derivation was corrected.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 12:37:21 +02:00
Marcin Pawlowski
c202ad9c17
Add deep_orphan_share: the structural observable behind the first-fork cost
E5 needs to watch whether per-recipient delay variance manufactures the
depth->=2 forks the countable rule cannot reach, and no recorded metric measured
that. p_ref conflates "unreachable by construction" with "eligible but never
picked up" -- the distinction that turned out to be the whole answer to item 5 --
and deep_ref_share is 0 by construction under the countable model, since the
proposer's candidate filter drops deep-fork blocks before any reference to one
is proposed. deep_orphan_share is the fraction of in-window orphans sitting
below the first block of their fork, computed from the depth array fork_stats
already builds.

Also fixes a splat-unpack in test_selfish_engine that silently re-bound to the
wrong quantities when fork_stats grew this field (it read deep_orphan_share as
p_ref_honest). fork_stats has now gained a field twice; both call sites unpack
by position explicitly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-07 11:59:37 +02:00
Marcin Pawlowski
3708d03e22
Private-chain (SM1) adversary in the per-node engine
Sec 6.8 recorded that "the per-node engine has no private-chain strategy", which is
why every selfish result came from the global race model with uncle recovery as
a free knob eta -- and why open item 5 (does the uncle cap need margin under
attack-inflated orphaning?) could not be sized: a knob has no queue to overflow.

adversary_strategy="selfish" adds it. The coalition mines one shared private
chain and releases under the classic SM1 rules in (a, h) form: adopt when the
public chain wins, match at equal length, override at a one-block lead, else
wait. Only VISIBILITY is modelled -- the coalition's mining needs no special
case, because a member's fork choice already builds on the private tip whenever
it leads (that tip has the greatest height among blocks the member can see) and
falls back to the public chain exactly when the public chain overtakes, which
is the adopt branch. So the private chain forms, extends and is abandoned
emergently, and the code that had to be written is the arrival matrix.

Design notes worth keeping:
- Private blocks reuse the sentinel `withhold` already had (never-arrives), so
  the existing exclusions from canonical-tip selection apply unchanged; release
  flips it back and gossips DIRECTLY from the producer, bypassing Blend, since
  an adversary has no privacy budget to respect and wants the race won.
- A private chain breaks the windowed horizon's premise (a hidden block is old
  enough to look fully-propagated while no honest node has it, and it becomes
  visible LATER, which the one-way frontier pointer cannot revisit), so selfish
  forces the exact full scan and full matrix.
- Blocks still hidden at epoch end are abandoned and hidden from the coalition
  too, or the canonical-tip search would crown a chain no honest node saw.

Validated against Eyal-Sirer at sub-slot latency: revenue share 0.0356 vs an
exact 0.0356 at alpha = 0.1, and above the closed form at higher alpha by just
the margin the alpha_eff fork-amplification correction predicts (0.498 vs 0.484
at alpha = 0.4, with fork rate 0.38).

Adds p_ref_honest: the reference rate over orphans produced OUTSIDE the
coalition. Under a private-chain attack this diverges sharply from p_ref, and
only the honest one measures the repair the report credits to uncle counting --
an attacker's own discarded blocks are its loss to bear.

test_fork unpacks fork_stats positionally, so its three call sites take the new
fifth value. 247 tests pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-06 12:46:57 +02:00