mirror of
https://github.com/logos-blockchain/research.git
synced 2026-08-10 21:23:12 +00:00
Make the model carry-over metric-specific instead of wholesale
§9 claimed that studies predating the countable model "carry over unchanged" because no difference is resolvable at ρ < 1. That is true of accuracy and false of the reference rate, and asserting it wholesale is what let the §8.4 capstone sit at p_ref = 1.000 for weeks after the redesign made it 0.944. Measured per metric off the committed paired band (δ_max 1–5, U ≥ 1, common random numbers, so the uncle rule is the only difference): D̂/D 0.9994 vs 1.0000 +0.00055 t = 4.9 carries over fork rate 0.2256 vs 0.2255 -0.00010 t = -2.0 carries over orphan rate 0.2256 vs 0.2255 -0.00009 t = -2.2 carries over tip agreement 0.9943 vs 0.9950 +0.00070 t = 0.9 carries over p_ref 0.9260 vs 0.9998 +0.07381 t = 155.0 RE-MEASURE p_ref fails in kind, not in degree: the unrestricted rule makes every in-window orphan referenceable, so its ≈1.0 restates the model rather than measuring the design. Any pre-countable p_ref is wrong by ~7 points. Also corrects §6.7(1), which still asserted p_ref ≈ 1 for genuine latency orphans — §6.8 had been corrected to the measured 0.944 but the earlier claim was left standing — and drops "no difference is resolvable" from §9, which contradicted §3.2a's pooled t = 5.7. Adds scripts/model_carryover.py so the verdict is reproducible rather than asserted, and a §9 study-table row. deep_ref_share is unaffected (0 by construction under the countable rule); no other reference-structure metric is quoted from a pre-countable run. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
c4577e7062
commit
74ee06047c
@ -660,7 +660,7 @@ At `α` = 0.4 an adversary earning exactly what honest mining would pay it drive
|
||||
|
||||
Everything above measures *blocks*. Whether an attack **pays** depends on the reward schedule, which is a design choice. We add one as a configurable option (`selfish.RewardParams`): a block reward (`= 1`), an **uncle reward** `w_u` (a reward weight — distinct from the uncle *window*, which this report calls `W`) paid to the *producer* of an orphaned block that is referenced as an uncle, and a **nephew reward** `w_n` paid to the block that *references* it. The model has five knobs in total: `w_u`, `w_n`, the honest-orphan reference rate `p_ref` (defined below), the attacker's self-uncling rate `p_ref_adv` (= 1 for a rational attacker), and `adv_nephew` — the fraction of referenced honest orphans whose *nephew* reward the attacker's own canonical blocks capture (0 without a forced-inclusion mandate, since honest blocks do the referencing). Three effects, all adversarially analysed.
|
||||
|
||||
**(1) It compensates orphaned honest producers (fairness).** A block-only schedule pays an orphaned honest block **nothing** — the same "off-chain, uncounted" fate that drives both the [§3.2](#s3-2) latency under-count and the losses a selfish attacker inflicts. Define the **reference rate** `p_ref` = the probability that an orphaned honest block is referenced as an uncle by some canonical block within `W`. An uncle reward `w_u` refunds the loss in proportion to `p_ref`: honest recovery of *mined* value rises with `w_u` toward `1.0` (e.g. in the α = 0.4 selfish race, `0.61 → 0.80 → 1.00` for `w_u = 0 → 0.5 → 1` at `p_ref = 1`; `fig15`, right, plots recovery vs `p_ref`). The same references that restore the TSI *density* ([§6.6](#s6-6), `D̂`) restore the *reward*. **This benefit is robust for the case it is designed for** — a genuine latency orphan is *published*, straddled by *honest* canonical blocks that reference it, and (crucially) the **first block of its fork**, so `p_ref ≈ 1` and the refund is available in full.
|
||||
**(1) It compensates orphaned honest producers (fairness).** A block-only schedule pays an orphaned honest block **nothing** — the same "off-chain, uncounted" fate that drives both the [§3.2](#s3-2) latency under-count and the losses a selfish attacker inflicts. Define the **reference rate** `p_ref` = the probability that an orphaned honest block is referenced as an uncle by some canonical block within `W`. An uncle reward `w_u` refunds the loss in proportion to `p_ref`: honest recovery of *mined* value rises with `w_u` toward `1.0` (e.g. in the α = 0.4 selfish race, `0.61 → 0.80 → 1.00` for `w_u = 0 → 0.5 → 1` at `p_ref = 1`; `fig15`, right, plots recovery vs `p_ref`). The same references that restore the TSI *density* ([§6.6](#s6-6), `D̂`) restore the *reward*. **This benefit is robust for the case it is designed for** — a genuine latency orphan is *published*, straddled by *honest* canonical blocks that reference it, and *usually* the **first block of its fork**, so the refund is available for most of them. "Usually" is the operative word and it is measured, not assumed: the emergent `p_ref` is **0.93–0.94**, not ≈ 1, because the first-fork restriction leaves the rest structurally unreferenceable ([§6.8](#s6-8), [§8.4](#s8-4)). That is far above what the schedule needs (`≳ 0.3`), but it is a ceiling on fairness, not a full refund.
|
||||
|
||||
**But it cannot reach the orphans a private chain makes, at any `w_u`.** The recovery figures above are computed on the SM1 race, where the attacker acts the moment the honest branch reaches length 1 and so every orphan it makes is a first-fork block. A private-chain override discards a *chain*, and only its first block is referenceable ([§2.1](#s2-1)) — measured in the per-node engine, 20–40 % of the honest blocks such an attack destroys can never be referenced by anyone ([§6.8](#s6-8)). An uncle reward refunds `w_u` per *referenced* orphan, so those producers are uncompensatable by construction: not underpaid because `p_ref` is low and could be raised, but unreachable because no valid block may name them. The fairness ceiling is therefore the same first-fork ceiling that caps the density repair, and it binds hardest on exactly the honest miners an attacker targets. This is a limit of the *counting rule*, not of the reward schedule, so no choice of `w_u`/`w_n` addresses it; what a schedule can still do is avoid *compounding* it, which is what the [§6.7](#s6-7)(a) `w_u + w_n < 1` invariant does.
|
||||
|
||||
@ -1040,8 +1040,21 @@ Sweep studies are committed configs, run with `make <name>` (writes a dated `run
|
||||
| **the deployed operating point** (`δ_max` = 4 from the spec's Blend profile; the three `f`-precision arms) | `scripts/spec_point.py` (`f_precision`); `runs/spec_point.parquet` | [§2.2](#s2-2), [§8.5](#s8-5), [Appendix A](#sA) |
|
||||
| **per-recipient delay variance** (jitter 0–8 slots × `U`, both referencing models, exact oracle) | `scripts/spec_jitter.py`, `configs/spec-point-jitter.yaml`; `runs/spec_jitter.parquet` | [§6.1](#s6-1), [§8.3](#s8-3) item 15 |
|
||||
| **the spec point at scale and by window** (`N` = 5000; `W_abs` sweep at `δ_max` = 4) | `configs/spec-point-n5000.yaml` (run twice, `--old`), `configs/spec-point-window.yaml` | [§3.4](#s3-4), [§8.5](#s8-5) |
|
||||
| **which metrics survive the model change** (the per-metric carry-over verdict, off the paired band) | `scripts/model_carryover.py`, from the paired `fine-paired-*` runs | [§9](#s9) below, [§6.8](#s6-8), [§8.4](#s8-4) |
|
||||
|
||||
**Uncle-model convention.** The simulator's default is the **countable** model — first-fork candidates only, derived window `w_u = W/f`, occupied-slot exclusion, per-reference counting rules ([§2.1](#s2-1)). The **unrestricted** baseline is preserved in the code and selected with `--old` on `tsi-sweep`/`tsi-verify`. Its RNG key is byte-identical to the pre-restriction key, so `--old` **bit-reproduces the earlier runs**: a `rho-boundary` cell (δ_max = 8, U = 2, k = 256, N = 1 000) re-run under `--old` matches the committed `2026-07-27_195627_rho-boundary` parquet with `max |Δ| = 0` on every epoch and every metric. Studies in the table above that predate the countable default were produced under the unrestricted model and reproduce exactly under `--old`; the comparison rows quantify where the two models differ, and in the design regime (`ρ < 1`) no difference is resolvable, so those findings carry over unchanged.
|
||||
**Uncle-model convention.** The simulator's default is the **countable** model — first-fork candidates only, derived window `w_u = W/f`, occupied-slot exclusion, per-reference counting rules ([§2.1](#s2-1)). The **unrestricted** baseline is preserved in the code and selected with `--old` on `tsi-sweep`/`tsi-verify`. Its RNG key is byte-identical to the pre-restriction key, so `--old` **bit-reproduces the earlier runs**: a `rho-boundary` cell (δ_max = 8, U = 2, k = 256, N = 1 000) re-run under `--old` matches the committed `2026-07-27_195627_rho-boundary` parquet with `max |Δ| = 0` on every epoch and every metric. Studies in the table above that predate the countable default were produced under the unrestricted model and reproduce exactly under `--old`.
|
||||
|
||||
**What carries over, and what does not.** The carry-over is **metric-specific**, and asserting it wholesale is what let the [§8.4](#s8-4) capstone go stale. Reading every metric off the same 40-replicate paired sweep of the design band (`δ_max` 1–5, `U ≥ 1`, both arms sharing stake, graph and lottery, so the uncle rule is the only difference — `scripts/model_carryover.py`):
|
||||
|
||||
| metric | countable | unrestricted | paired Δ | t |
|
||||
|---|---|---|---|---|
|
||||
| `D̂/D` | 0.9994 | 1.0000 | +0.00055 ± 0.00011 | 4.9 |
|
||||
| fork rate | 0.2256 | 0.2255 | −0.00010 ± 0.00005 | −2.0 |
|
||||
| orphan rate | 0.2256 | 0.2255 | −0.00009 ± 0.00004 | −2.2 |
|
||||
| tip agreement | 0.9943 | 0.9950 | +0.00070 ± 0.00075 | 0.9 |
|
||||
| **`p_ref`** | **0.9260** | **0.9998** | **+0.0738 ± 0.0005** | **155** |
|
||||
|
||||
**Accuracy, fork structure and consensus carry over; the reference rate does not.** The first four differ by at most 0.06 % — resolvable with 40 paired replicates ([§3.2a](#s3-2a)), negligible against the ±0.9 % per-epoch noise floor of [Appendix B](#sB), and in the same direction and magnitude the countable cost is reported at throughout. `p_ref` is different in kind: under the unrestricted rule *every* in-window orphan is referenceable, so `p_ref ≈ 1` is an artefact of the model rather than a finding, and the countable rule's 0.926 is 7.4 points lower at t = 155. **Any pre-countable `p_ref` is therefore wrong by about 7 points and has to be re-measured, not carried over** — which is exactly the [§8.4](#s8-4) correction (1.000 → 0.944), and the reason [§6.8](#s6-8)'s reference rate is quoted from the countable capstone and not from the structural "published orphans are straddled by honest blocks" argument. `deep_ref_share` is not affected because it is 0 by construction under the countable rule ([§6.8](#s6-8)); no other reference-structure metric is quoted from a pre-countable run.
|
||||
|
||||
Because the two models draw independent RNG streams, every countable-vs-unrestricted comparison is **unpaired**, and its resolution is set by the replicate spread rather than by the effect size. Each comparison sweep therefore includes a `U = 0` arm as a **negative control**: with no uncles the models are identical by construction, so the measured `U = 0` gap is a direct reading of the noise floor at that delay and replicate count. At `δ_max = 32` with 5 replicates that floor is ≈ 0.23 in `D̂/D` — larger than several real effects elsewhere in the grid — which is why [§3.2](#s3-2) reports a `t` statistic per cell and why the design regime is measured separately at 40 replicates ([§3.2a](#s3-2a)).
|
||||
|
||||
|
||||
@ -0,0 +1,66 @@
|
||||
"""Which metrics carry over from the unrestricted model to the countable one? (§9)
|
||||
|
||||
Most of the report's studies predate the countable default. They reproduce exactly under `--old`,
|
||||
so the question is not whether they are valid runs — it is whether their *numbers* still describe
|
||||
the countable design. §9 used to answer that wholesale ("no difference is resolvable in the design
|
||||
regime, so those findings carry over"), and that is how the §8.4 capstone went stale: its `p_ref`
|
||||
was quoted at 1.000 for weeks after the redesign made the true value 0.944.
|
||||
|
||||
The wholesale answer is wrong because the carry-over is metric-specific. Accuracy barely moves
|
||||
between the models at `rho < 1` — that is the §3.2a finding. But `p_ref` is not a small difference,
|
||||
it is an artefact: under the unrestricted rule EVERY in-window orphan is referenceable, so
|
||||
`p_ref ~ 1` is a restatement of the model rather than a measurement of the design.
|
||||
|
||||
This prints the per-metric verdict off the committed paired sweep, where both arms share the stake
|
||||
draw, the peering graph and every lottery outcome, so the uncle rule is the only difference and the
|
||||
comparison is a paired one. Any metric that fails here must be re-measured rather than carried over.
|
||||
|
||||
Run: python scripts/model_carryover.py
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import pandas as pd
|
||||
|
||||
HERE = Path(__file__).resolve().parent.parent
|
||||
RUNS = HERE / "runs"
|
||||
|
||||
# The design band: the operating regime the recommendation lives in. U = 0 is excluded because
|
||||
# with no uncle slots the two models are identical by construction and would dilute the contrast.
|
||||
METRICS = ["mean_ratio", "fork_rate", "mean_orphan_rate", "agreement_tip", "p_ref"]
|
||||
CELL = ["blend_delay_max", "max_uncles", "replicate"]
|
||||
|
||||
|
||||
def _load(label: str) -> pd.DataFrame:
|
||||
"""Equilibrium tail of the newest paired run with this label."""
|
||||
src = sorted(RUNS.glob(f"*_{label}/results.parquet"))[-1]
|
||||
df = pd.read_parquet(src)
|
||||
return df[(df.epoch >= df.epochs.iloc[0] // 2) & (df.max_uncles > 0)]
|
||||
|
||||
|
||||
def main() -> None:
|
||||
countable, unrestricted = _load("fine-paired-countable"), _load("fine-paired-old")
|
||||
print("=== countable vs unrestricted ceiling, PAIRED, design band (delta_max 1-5, U >= 1) ===")
|
||||
print(f"{'metric':>18} {'countable':>11} {'ceiling':>11} {'paired diff':>20} {'t':>7} verdict")
|
||||
for m in METRICS:
|
||||
if m not in countable.columns:
|
||||
continue
|
||||
a = countable.groupby(CELL)[m].mean()
|
||||
b = unrestricted.groupby(CELL)[m].mean()
|
||||
i = a.index.intersection(b.index)
|
||||
d = b[i] - a[i]
|
||||
# Paired t over cells. A metric "carries over" if the gap is negligible against the
|
||||
# +-0.9% per-epoch noise floor of Appendix B, NOT if it merely fails to resolve --- with
|
||||
# 40 paired replicates even a 0.06% gap resolves, and that one is still immaterial.
|
||||
t = d.mean() / d.sem() if d.std(ddof=1) > 0 else float("nan")
|
||||
verdict = "carries over" if abs(d.mean()) < 0.009 else "RE-MEASURE"
|
||||
print(f"{m:>18} {a[i].mean():11.4f} {b[i].mean():11.4f} "
|
||||
f"{d.mean():+12.5f}+-{d.sem():.5f} {t:7.1f} {verdict}")
|
||||
print("\np_ref is the one that fails: the unrestricted model makes every in-window orphan\n"
|
||||
"referenceable, so its ~1.0 is a property of the model, not of the design.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Loading…
x
Reference in New Issue
Block a user