Marcin Pawlowski e5a89004f2
Answer the fork-loss handoff: the section's residual is ~17x overstated, and it misses the real bias
Settles E1-E4 and E6 of handoff-fork-loss-validation.md against spec d6fd7648.

E1 costs nothing and reframes everything: analysis-block-times-blend-network.md
sets blending_delay as a FIXED per-hop dwell (the 3d+5 max-delay arithmetic
gives 14 s at d=3 and 11 s at d=2, matching its prose), not a mean or a bound.
The simulator's Uniform(0, delta_max) matches a 2 s dwell in the mean at
delta_max = 4, so D_vis = 8 s and rho = 0.27 -- inside the committed 40-replicate
paired design band, which answers C1-C3 from data of record.

C1 refuted: every U>=1 cell sits at 0.9985-0.9997, not 0.986. C2's mechanism is
right but its size is ~17x over: the paired first-fork cost is 0.08 pp pooled
(95% CI [0.03, 0.13], t = 3.08), resolved only because the arms share streams --
the U=0 negative control is exactly 0.00000 +- 0.00000. C3 is refuted in the
UNFAVOURABLE direction: the no-uncle loss is 33% at N=1000 and 34.6% at N=5000
(42% / 49.5% at delta_max = 8), so the section understates what uncles buy by
about half. C4 stands with ~7x margin (U=3 still recovers at rho = 1.87). C5 is
right in effect, wrong in wording -- the knee is at W_abs ~ 5, so the spec's 10
is ~2x above it, which is "has margin", not "never binds".

C6 is the section's real omission. The deployed estimator quantises the target
rate at PRECISION = 1e3, and measured in the full dynamics that reads
1.01026 +- 0.00056 against a closed form of 1.0101 -- a 1.0% bias ~13x the
first-fork cost the section is concerned with, opposite in sign, removed by a
one-constant change. It could not be measured before because PRECISION was a
module constant pinned at the RECOMMENDED 1e6; f_precision is now a config
field, appended to the RNG key only when non-default so no committed run moves.

Also from the guide: uncle_window_slots now floors rather than rounds, matching
w_u := floor(W/f) (identical at the defaults; matters only for the W and f
sweeps).

Reviewed sec 4.3's argument as sec 6 asks, and it holds -- inclusion stayed soft
("may reference fewer uncles than it could ... and its block remains valid"), so
row 10, the anti-mandate argument and the suppress adversary are all unaffected;
only the CONTENT of a reference became validity-gated. One correction: the
"no incentive to deviate" clause does still exist, so sec 8.5's implication (ii)
is live, not moot.

E5 -- the jitter diagnostic, and the only experiment that could invalidate the
report rather than the section -- is not run and is flagged as such.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-07 11:59:37 +02:00

84 lines
4.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Protocol constants and epoch/window geometry.
All slot geometry derives from the pair ``(k, f)`` so a scaled-down ``k`` (used for
parameter sweeps) automatically shrinks the epoch and measurement window. See
``cryptarchia-v1-protocol.md`` and ``cryptarchia-total-stake-inference.md``.
"""
from __future__ import annotations
# --- True protocol values (full scale) -------------------------------------
K_TRUE = 2160 # security parameter (blocks)
F = 1.0 / 30.0 # slot activation coefficient (default; configurable per run)
W_DEFAULT = 300 # old model: uncle reference window w_u (slots), set directly (--old)
BETA_DEFAULT = 1.0 # TSI learning rate
SLOT_SECONDS = 1 # slot length (seconds) — so 1 slot == 1 s
# --- Countable uncle model (cryptarchia-v1-protocol.md, uncle references) ---
# The spec derives the uncle reference window from the *window absorption parameter* W:
# w_u = W * f^-1 slots, i.e. W expected block-intervals. W is bounded by 1 <= W <= 0.6*k,
# equivalently w_u <= 0.6*k/f = s/5, keeping the window strictly inside the finalization
# window. The default W = 10 reproduces w_u = 300 slots at f = 1/30.
W_ABS_DEFAULT = 10.0 # window absorption parameter W (expected block-intervals)
W_ABS_MAX_FACTOR = 0.6 # bound: W <= W_ABS_MAX_FACTOR * k
def uncle_window_slots(w_abs: float, f: float = F) -> int:
"""Derived uncle reference window ``w_u = floor(W / f)`` in slots (countable model).
The spec writes this as a **floor** (``w_u := ⌊W·f⁻¹⌋``, cryptarchia-v1-protocol.md
Constants). At the defaults (W = 10, f = 1/30) the quotient is exactly 300 either way, so no
committed result moves — but ``configs/absorption-window.yaml`` sweeps ``W`` and
``configs/block-rate.yaml`` sweeps ``f``, and at a non-integer ``W/f`` rounding would put the
derived window a slot above the spec's.
"""
return max(1, int(w_abs / f))
# --- Real-world inter-node network latency (per gossip link) ---------------
# A slot is SLOT_SECONDS = 1 s, so measured internet latencies (tenshundreds of ms) are
# FRACTIONS of a slot. The values below are one-way, application-level latencies between two
# directly-peered nodes, bucketed by the geographic relationship of the peers — in a globally
# distributed node set a random peer is usually on another continent. (≈ RTT/2 from public
# latency measurements plus a little gossip processing/serialization overhead.) A block
# gossip-floods over the peering graph, so its end-to-end delay to a far node is the sum of
# a few such per-link latencies along the fastest path (Dijkstra) — see topology.py.
GEO_LATENCY_BANDS_SLOTS = (
0.015, # metro / same country (~15 ms one-way)
0.040, # same continent, e.g. EU↔EU (~40 ms)
0.090, # transatlantic, e.g. EU↔US-East (~90 ms)
0.200, # antipodal, e.g. EU↔AU / EU↔JP (~200 ms)
)
# Share of random peer links falling in each band for a globally distributed node set
# (NA/EU/Asia-weighted). Most peer pairs are cross-continent, hence the long-latency mass.
GEO_LATENCY_WEIGHTS = (0.15, 0.35, 0.35, 0.15)
# Mean one-way latency of a random global peer link under the mixture above (~0.078 slot,
# i.e. ~78 ms). Used to rescale the "geo" link-latency distribution to a requested mean.
GEO_LATENCY_MEAN_SLOTS = sum(
b * w for b, w in zip(GEO_LATENCY_BANDS_SLOTS, GEO_LATENCY_WEIGHTS, strict=True)
)
def floor_k_over_f(k: int, f: float = F) -> int:
"""``floor(k / f)`` — the base quantum of the epoch schedule."""
return int(k / f)
def epoch_len(k: int, f: float = F) -> int:
"""Epoch length in slots: ``10 * floor(k/f)``."""
return 10 * floor_k_over_f(k, f)
def period_T(k: int, f: float = F) -> int:
"""TSI measurement window length ``T`` in slots: ``6 * floor(k/f)``.
This is the first ``6*floor(k/f)`` slots of the (previous) epoch over which the
block density is measured.
"""
return 6 * floor_k_over_f(k, f)
def expected_blocks_in_window(k: int, f: float = F) -> float:
"""Expected honest-chain block count in the measurement window at equilibrium."""
return period_T(k, f) * f