2026-07-30 18:57:10 +02:00
|
|
|
|
"""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)
|
Countable uncle model: spec counting rules, sweeps, figures
Implement the countable uncle model from the Cryptarchia spec's
counting-only reference rules, and make it the simulator default.
Counting rules (uncles.py, measure.py):
- Only the first block of a fork (parent on the producer's chain) is
referenceable and countable, which makes every reference verifiable
from chain data alone.
- The reference window is derived from a window-absorption parameter,
w_u = W_abs/f slots (W_abs in expected block-intervals, default 10,
bounded W_abs <= 0.6*k), replacing the free-standing uncle_window.
- Selection skips slots already occupied on the producer's chain and
takes at most one uncle per slot.
- The measurement pass re-checks every rule per reference and tallies
rejections as deep_ref_share.
The pre-redesign model is preserved behind --old on tsi-sweep and
tsi-verify. Its RNG key is byte-identical to the pre-uncle_model key,
so --old bit-reproduces the historical runs.
Supporting changes: uncle_model and window_absorption config surface
with validation (config.py, constants.py); accuracy closed form over
the effective q_u (theory.py); plumbing through tsi.py, epoch.py,
sweep.py, blocktree.py, metrics.py, verify.py, figures_pernode.py.
Studies and figures:
- configs/countable-vs-old.yaml -- delay x U grid, run under both
models on the same grid.
- configs/absorption-window.yaml -- accuracy vs W_abs at U=1.
- scripts/plot_countable_vs_old.py renders fig30-fig33 into
reports/tsi/report-figures/.
Tests: tests/test_countable_counting.py (7 cases) covering first-fork
eligibility, derived-window bounds, occupied-slot exclusion, and
per-reference re-checking; extensions to test_uncles.py,
test_config.py, test_slot_counting.py. Full fast suite: 202 passed.
Also adds CLAUDE.md (graphify project instructions) and ignores
editor/local-agent state plus the vendored Equi-X benchmark clone.
The reports/tsi/ prose describing this model is held back for a
separate editorial pass.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-04 18:48:46 +02:00
|
|
|
|
W_DEFAULT = 300 # old model: uncle reference window w_u (slots), set directly (--old)
|
2026-07-30 18:57:10 +02:00
|
|
|
|
BETA_DEFAULT = 1.0 # TSI learning rate
|
|
|
|
|
|
SLOT_SECONDS = 1 # slot length (seconds) — so 1 slot == 1 s
|
|
|
|
|
|
|
Countable uncle model: spec counting rules, sweeps, figures
Implement the countable uncle model from the Cryptarchia spec's
counting-only reference rules, and make it the simulator default.
Counting rules (uncles.py, measure.py):
- Only the first block of a fork (parent on the producer's chain) is
referenceable and countable, which makes every reference verifiable
from chain data alone.
- The reference window is derived from a window-absorption parameter,
w_u = W_abs/f slots (W_abs in expected block-intervals, default 10,
bounded W_abs <= 0.6*k), replacing the free-standing uncle_window.
- Selection skips slots already occupied on the producer's chain and
takes at most one uncle per slot.
- The measurement pass re-checks every rule per reference and tallies
rejections as deep_ref_share.
The pre-redesign model is preserved behind --old on tsi-sweep and
tsi-verify. Its RNG key is byte-identical to the pre-uncle_model key,
so --old bit-reproduces the historical runs.
Supporting changes: uncle_model and window_absorption config surface
with validation (config.py, constants.py); accuracy closed form over
the effective q_u (theory.py); plumbing through tsi.py, epoch.py,
sweep.py, blocktree.py, metrics.py, verify.py, figures_pernode.py.
Studies and figures:
- configs/countable-vs-old.yaml -- delay x U grid, run under both
models on the same grid.
- configs/absorption-window.yaml -- accuracy vs W_abs at U=1.
- scripts/plot_countable_vs_old.py renders fig30-fig33 into
reports/tsi/report-figures/.
Tests: tests/test_countable_counting.py (7 cases) covering first-fork
eligibility, derived-window bounds, occupied-slot exclusion, and
per-reference re-checking; extensions to test_uncles.py,
test_config.py, test_slot_counting.py. Full fast suite: 202 passed.
Also adds CLAUDE.md (graphify project instructions) and ignores
editor/local-agent state plus the vendored Equi-X benchmark clone.
The reports/tsi/ prose describing this model is held back for a
separate editorial pass.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-04 18:48:46 +02:00
|
|
|
|
# --- 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:
|
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-06 18:45:00 +02:00
|
|
|
|
"""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))
|
Countable uncle model: spec counting rules, sweeps, figures
Implement the countable uncle model from the Cryptarchia spec's
counting-only reference rules, and make it the simulator default.
Counting rules (uncles.py, measure.py):
- Only the first block of a fork (parent on the producer's chain) is
referenceable and countable, which makes every reference verifiable
from chain data alone.
- The reference window is derived from a window-absorption parameter,
w_u = W_abs/f slots (W_abs in expected block-intervals, default 10,
bounded W_abs <= 0.6*k), replacing the free-standing uncle_window.
- Selection skips slots already occupied on the producer's chain and
takes at most one uncle per slot.
- The measurement pass re-checks every rule per reference and tallies
rejections as deep_ref_share.
The pre-redesign model is preserved behind --old on tsi-sweep and
tsi-verify. Its RNG key is byte-identical to the pre-uncle_model key,
so --old bit-reproduces the historical runs.
Supporting changes: uncle_model and window_absorption config surface
with validation (config.py, constants.py); accuracy closed form over
the effective q_u (theory.py); plumbing through tsi.py, epoch.py,
sweep.py, blocktree.py, metrics.py, verify.py, figures_pernode.py.
Studies and figures:
- configs/countable-vs-old.yaml -- delay x U grid, run under both
models on the same grid.
- configs/absorption-window.yaml -- accuracy vs W_abs at U=1.
- scripts/plot_countable_vs_old.py renders fig30-fig33 into
reports/tsi/report-figures/.
Tests: tests/test_countable_counting.py (7 cases) covering first-fork
eligibility, derived-window bounds, occupied-slot exclusion, and
per-reference re-checking; extensions to test_uncles.py,
test_config.py, test_slot_counting.py. Full fast suite: 202 passed.
Also adds CLAUDE.md (graphify project instructions) and ignores
editor/local-agent state plus the vendored Equi-X benchmark clone.
The reports/tsi/ prose describing this model is held back for a
separate editorial pass.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-04 18:48:46 +02:00
|
|
|
|
|
2026-07-30 18:57:10 +02:00
|
|
|
|
|
|
|
|
|
|
# --- Real-world inter-node network latency (per gossip link) ---------------
|
|
|
|
|
|
# A slot is SLOT_SECONDS = 1 s, so measured internet latencies (tens–hundreds 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
|