mirror of
https://github.com/logos-blockchain/research.git
synced 2026-08-07 03:33:33 +00:00
Applied the reconstructed round-4 review to the TSI parameter-selection report set (reports/tsi) and executed the follow-ups. Report (reports/tsi): - Applied the must+should findings across README + parts 1-4: cross-part numeric corrections, figure-caption fixes, spec reconciliation, and cross-file companions (hops-degradation and notch/reward numbers, tip-agreement ordering, density-window timing, VRF -> ZK Proof-of-Leadership, w_u window/reward gloss). - Editorial pass for timeless voice (no "now adopted / merged / coin" narration) and a gentle spec-safety framing (recommendations are thresholds; the protocol's MAX_UNCLES=4 sits safely above them). - Added the fork-rate-vs-scale table (6.10), defined "grinding gain", promoted the clock-skew study to its own paragraph, added the correlated-latency caveat, and moved fig27/fig28 beside their discussion. - Documented the Blend cascade in 2: hops propagate over the shared gossip graph (not direct links), the final broadcast comes from the last relay, relays are blind forwarders. Simulator (tools/simulators/tsi/tsi-sim-pernode): - Docstring/dead-code fixes: theory.block_count_ceiling (legacy framing), measure, reorg (catch-up reading), metrics (removed two dead helpers), config (fixed_point 10^-6; clock_skew_max/lottery_chunks documented inert), stake_vs_delay. - Generator correctness + regenerated figures: figures_pernode.CONFIG_COLS now exhaustive (f no longer pooled); rho_boundary_analysis SEM across replicates + hollow floored markers + de-hardcoded ell_mean (measured from the run's graph); appendix_fluct per-N sigma + ~18x title (figB2); bootstrap_dynamics driving estimate so fig1 epoch-0 matches genesis. - pytest: 186 passed; report links 528/0 dangling. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
102 lines
4.0 KiB
Python
102 lines
4.0 KiB
Python
"""Relative stake estimate vs network delay (fig16).
|
||
|
||
Shows the report's central relationship: the recovered *relative stake* ``D̂/D`` as a function of the
|
||
mean block-visibility delay ``D_vis`` (seconds), one curve per uncle cap ``U``. Accuracy holds at
|
||
the 1.0 exact-recovery bound while the load ``ρ = f·D_vis`` stays below ``⌈U⌉``, then collapses — so
|
||
larger delay needs more uncles. Blend transport, f = 1/30 (30 s blocks); delay swept via the
|
||
per-hop blending budget ``blend_delay_max``.
|
||
|
||
Run: python scripts/stake_vs_delay.py (writes runs/stake_vs_delay.parquet + fig16)
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
from pathlib import Path
|
||
|
||
import numpy as np
|
||
import pandas as pd
|
||
|
||
from tsi_sim import topology
|
||
from tsi_sim.config import SimConfig
|
||
from tsi_sim.engine import run_trajectory
|
||
from tsi_sim.plotting import style
|
||
|
||
HERE = Path(__file__).resolve().parent.parent
|
||
RUNS = HERE / "runs"
|
||
FIGS = HERE / "report-figures"
|
||
RUNS.mkdir(exist_ok=True)
|
||
FIGS.mkdir(exist_ok=True)
|
||
|
||
F = 1.0 / 30.0
|
||
HOPS = 3
|
||
DELAYS = [1.0, 4.0, 8.0, 12.0, 16.0, 20.0, 26.0, 32.0, 40.0] # per-hop blending budget (s)
|
||
UNCLES = [0, 1, 2, 3]
|
||
BASE = dict(n_nodes=800, k=48, epochs=16, stake_dist="uniform", topology="blend", degree=8,
|
||
link_latency_mean=0.3, link_latency_dist="geo", blend_hops=HOPS, uncle_window=300,
|
||
genesis_d_factor=0.5, f=F)
|
||
|
||
|
||
def d_vis(delay: float) -> float:
|
||
"""Mean visibility delay D_vis = hops·δ/2 + (hops+1)·ℓ_mean, ℓ_mean = mean shortest-path."""
|
||
cfg = SimConfig(**BASE, blend_delay_max=delay, max_uncles=1)
|
||
pl = topology.build_path_latency(cfg, np.random.default_rng(np.random.SeedSequence(0)))
|
||
off = pl[~np.eye(pl.shape[0], dtype=bool)]
|
||
l_mean = float(off[np.isfinite(off)].mean())
|
||
return HOPS * delay / 2.0 + (HOPS + 1) * l_mean
|
||
|
||
|
||
def sweep() -> pd.DataFrame:
|
||
rows = []
|
||
for delay in DELAYS:
|
||
dv = d_vis(delay)
|
||
for U in UNCLES:
|
||
reps = []
|
||
for r in range(5):
|
||
df = pd.DataFrame(run_trajectory(SimConfig(
|
||
replicate=r, blend_delay_max=delay, max_uncles=U, **BASE)))
|
||
reps.append(df[df.epoch >= 8].mean_ratio.mean())
|
||
ratio = float(np.mean(reps))
|
||
sem = float(np.std(reps) / np.sqrt(len(reps)))
|
||
rows.append(dict(delay=delay, d_vis=dv, rho=F * dv, U=U, ratio=ratio, sem=sem))
|
||
print(f"delay={delay:4.0f}s D_vis={dv:5.1f} rho={F*dv:4.2f} U={U}: D̂/D={ratio:.3f}")
|
||
out = pd.DataFrame(rows)
|
||
out.to_parquet(RUNS / "stake_vs_delay.parquet")
|
||
return out
|
||
|
||
|
||
def fig16(df: pd.DataFrame) -> None:
|
||
import matplotlib.pyplot as plt
|
||
style.apply_style()
|
||
fig, ax = plt.subplots(figsize=(7.2, 4.4))
|
||
ax.axhline(1.0, color="0.5", lw=0.9, ls="--", label="exact recovery (1.0)")
|
||
for i, U in enumerate(UNCLES):
|
||
s = df[df.U == U].sort_values("d_vis")
|
||
ax.errorbar(s.d_vis, s.ratio, yerr=s["sem"], fmt="-o", ms=4, capsize=2,
|
||
color=style.OKABE_ITO[i], label=f"U = {U}")
|
||
# rho = 1, 2, 3 boundaries: D_vis = k/f <-> rho = f*D_vis = k
|
||
for k in (1, 2, 3):
|
||
dv = k / F
|
||
if dv <= df.d_vis.max() * 1.02:
|
||
ax.axvline(dv, color="0.7", lw=0.7, ls=":")
|
||
ax.text(dv, 0.32, f"ρ={k}", rotation=90, va="bottom", ha="right", fontsize=7,
|
||
color="0.4")
|
||
ax.set_xlabel(r"mean block-visibility delay $D_{\rm vis}$ (s) [load $\rho = f\,D_{\rm vis}$]")
|
||
ax.set_ylabel(r"relative stake estimate $\hat D / D$")
|
||
ax.set_title(r"Recovered relative stake vs delay (blend, $f=1/30$): "
|
||
r"$U$ must grow with $\rho=\lceil f D_{\rm vis}\rceil$")
|
||
ax.set_ylim(0.3, 1.02) # bounded by 1: cap at the exact-recovery bound, no above-1 headroom
|
||
ax.legend(fontsize=8, loc="lower left")
|
||
style.save(fig, FIGS / "fig16_stake_vs_delay", provenance="scripts/stake_vs_delay.py")
|
||
plt.close(fig)
|
||
|
||
|
||
def main() -> None:
|
||
print("=== relative stake estimate vs delay ===")
|
||
df = sweep()
|
||
fig16(df)
|
||
print("wrote fig16_stake_vs_delay")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|