mirror of
https://github.com/logos-blockchain/research.git
synced 2026-08-11 05:33:32 +00:00
Re-running the capstone under both anchors turned up something worse than the anchor question. The uncle-anchored arm should have bit-reproduced the committed capstone, since its key is unchanged -- and it did not. Bisecting against the pre-session source showed my changes are clean (identical trajectory, epochs=21, tail 0.994358 either way); the committed parquet is simply stale. It is dated 24 July and has no uncle_model column at all, so it predates the countable redesign. sec 8.4's headline table has been reporting UNRESTRICTED-model numbers ever since that landed. sec 9's "all studies re-run" note covers the slot-counting fix of 23/24 July, not the countable redesign of 4 August, and the capstone was missed. Corrected, countable model, delta_max = 8, U = 2, W = 10, k = 2160, 8 reps: honest D-hat/D 0.996 (was 1.001) p_ref 0.944 (was 1.000) 30% suppress 0.994 (was 0.998) p_ref 0.936 (was 0.990) Lower, and p_ref materially lower, because the first-fork restriction makes some orphans unreferenceable that the earlier model counted. No recommendation changes: accuracy sits inside the +-0.9% per-epoch noise floor below the hard ceiling of 1, consensus is exact, p_ref stays far above the ~0.3 the soft rule needs. sec 6.8's p_ref quotes came from the same stale run and are corrected too. The anchor result the re-run was for: parent-anchored gives 0.996 honest -- unchanged -- but 0.974 under the 30% suppression adversary, with p_ref 0.875. The effects compound: a tighter effective window leaves an orphan fewer chances to be referenced before its PARENT ages out, and a suppressing adversary removes some of those chances. Neither isolated sweep shows it, because sec 6.12's honest arms hold the adversary at zero and its adversarial arms use a slack cap. That is what the capstone is for, and it is the strongest argument for pairing the anchor change with W = 12 rather than adopting it at W = 10. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
65 lines
3.1 KiB
Python
65 lines
3.1 KiB
Python
#!/usr/bin/env python
|
||
"""Capstone: the recommended configuration end-to-end at true k=2160 (report §8).
|
||
|
||
One config — f=1/30, W=300, U=2, β=1, degree 6, Blend 3 hops × 8 s, Pareto stake — run honest
|
||
and under a 30 % uncle-suppression adversary, confirming accuracy, consensus, fork rate, reorg
|
||
depth, and the emergent reference rate p_ref ALL hold together. Writes runs/capstone.parquet.
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
import sys
|
||
from pathlib import Path
|
||
|
||
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))
|
||
|
||
import pandas as pd
|
||
from joblib import Parallel, delayed
|
||
|
||
from tsi_sim.config import SimConfig
|
||
from tsi_sim.engine import run_trajectory
|
||
|
||
REC = dict(n_nodes=1000, stake_dist="pareto", topology="blend", degree=6,
|
||
link_latency_mean=0.5, link_latency_dist="geo", blend_hops=3, blend_delay_max=8.0,
|
||
max_uncles=2, uncle_window=300, uncle_strategy="oldest", k=2160, epochs=40,
|
||
genesis_d_factor=0.5, early_stop=True)
|
||
|
||
|
||
def _one(adv: float, rep: int, anchor: str = "uncle") -> list[dict]:
|
||
cfg = SimConfig(**REC, adversary_frac=adv, adversary_strategy="suppress",
|
||
uncle_window_anchor=anchor, replicate=rep)
|
||
rows = run_trajectory(cfg)
|
||
for r in rows:
|
||
r["adv"] = adv
|
||
r["anchor"] = anchor
|
||
return rows
|
||
|
||
|
||
def main() -> None:
|
||
out = Path(__file__).resolve().parents[1] / "runs"
|
||
# Both window anchors: the spec's uncle-anchored rule, and the §6.12 proposal. The capstone
|
||
# is the "whole recipe together" check, so a change to any rule in the recipe has to be run
|
||
# through it rather than argued from the isolated sweeps.
|
||
jobs = [(a, r, w) for w in ("uncle", "parent") for a in (0.0, 0.3) for r in range(8)]
|
||
res = Parallel(n_jobs=4, backend="loky", inner_max_num_threads=1)(
|
||
delayed(_one)(a, r, w) for a, r, w in jobs)
|
||
df = pd.DataFrame([row for traj in res for row in traj])
|
||
df.to_parquet(out / "capstone.parquet", index=False)
|
||
print("=== Capstone: recommended config, all metrics together (equilibrium tail) ===")
|
||
for (anchor, adv), g in df.groupby(["anchor", "adv"]):
|
||
# Per-REPLICATE tail: early_stop ends replicates at different epochs, so a per-arm cut
|
||
# (epoch >= arm_max//2) would silently drop any replicate that stopped before the cut
|
||
# and skew the tail toward the slow-converging ones. The report's §8.4 numbers are the
|
||
# per-replicate aggregation; keep this printout matching them.
|
||
t = pd.concat([r[r.epoch >= r.epoch.max() // 2] for _, r in g.groupby("replicate")])
|
||
per_rep = t.groupby("replicate").fork_rate.mean()
|
||
sem = per_rep.std(ddof=1) / (len(per_rep) ** 0.5)
|
||
print(f"{anchor:>6}-anchored, adversary {adv:.0%}: D̂/D {t.mean_ratio.mean():.4f} "
|
||
f"range_ratio {t.range_ratio.max():.4f} agreement {t.agreement_window.min():.4f} "
|
||
f"fork_rate {per_rep.mean():.3f}+-{sem:.3f}(SEM over {len(per_rep)} reps) "
|
||
f"max_reorg_depth {t.max_reorg_depth.max()} p_ref {t.p_ref.mean():.3f}")
|
||
print(f"wrote {out/'capstone.parquet'} ({len(df)} rows)")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|