Check in the evidence behind the pd report
The simulator gitignores its runs/ directory, so every table and figure in
reports/blend/pd rested on data that existed only on one machine. This adds the
sweep outputs of record under reports/blend/pd/data -- one directory per study,
1 MB total -- so any number can be checked against its source, or challenged,
without re-running hours of compute.
report_numbers.py comes with them: run it and it prints every value the report
quotes together with its across-topology standard error, straight from these
parquets. It reproduces the report tables exactly.
Kept: default (8000 rounds/cell), redundancy (9600), percolation (6400),
correlated-churn (6400), fullscale (192, the deliberately lighter 1e6 check).
Omitted: the smoke runs, and an earlier 144-rounds/cell redundancy grid whose
sampling error produced a non-monotonic delivery curve -- superseded, and the
reason the kept grid samples 9600.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-05 14:50:41 +02:00
|
|
|
|
# Evidence of record
|
|
|
|
|
|
|
|
|
|
|
|
The sweep outputs behind every number in [the report](../README.md). The simulator does not commit
|
|
|
|
|
|
its own `runs/` directory — these are the copies of record, kept so that any figure or table can be
|
|
|
|
|
|
re-derived, or challenged, without re-running hours of compute.
|
|
|
|
|
|
|
|
|
|
|
|
Each run directory holds the three tables the simulator writes: `propagation.parquet`,
|
|
|
|
|
|
`adversary.parquet` and `deanon.parquet`.
|
|
|
|
|
|
|
|
|
|
|
|
| directory | config | sampling | backs |
|
|
|
|
|
|
|---|---|---|---|
|
|
|
|
|
|
| `default/` | `configs/default.yaml` | 1 000 rounds × 8 seeds = **8 000/cell** | §3.1–§3.5 — delay, observation, eclipse, deanonymization, delivery, coverage |
|
|
|
|
|
|
| `redundancy/` | `configs/redundancy.yaml` | 1 200 × 8 = **9 600/cell** | §3.8 — messaging redundancy R = 1…4 |
|
|
|
|
|
|
| `percolation/` | `configs/percolation.yaml` | 800 × 8 = **6 400/cell** | §3.5 — the churn threshold `u_c = 1 − 1/(degree − 1)` |
|
|
|
|
|
|
| `correlated-churn/` | `configs/correlated-churn.yaml` | 800 × 8 = **6 400/cell** | §3.9 — correlated AS/region outages vs uniform churn |
|
|
|
|
|
|
| `fullscale/` | `configs/fullscale.yaml` | 64 × 3 = **192/cell** | §5 — the 10⁶ scaling check (deliberately lighter; not a source of headline numbers) |
|
reports/blend/pd: section 3.10, cover traffic
Written from configs/cover-traffic.yaml: the rate swept over three decades against
three release delays, each timeline paired with the epoch emission budget.
The finding that shapes the section is that mixing never happens. At one message
per second a relay holds 0.0014 messages and never more than two; even 256x the
rate reaches only 0.39, matching Little law exactly. So the anonymity set is
entirely blending -- the broadcasts a relay saw between releases -- which follows
rate*(2M+1)/3, twice the mean hold. Measured within ~1% over most of the range.
That makes delay the cheap lever: both knobs enter linearly, but bandwidth is paid
on every link while delay is paid once per hop. An anonymity set of 100 costs 42.9
msg/s at a 3s delay and 4.9 msg/s at 30s.
The quota ceiling is the hard edge. A node proposals must fit its emission budget,
capping stake at ln(1-q)/ln(1-f) of INFERRED stake -- about 0.1% at the baseline
rate once Poisson fluctuation is allowed for. A 9.5% holder overruns by ~65x and
is distinguishable by emission count alone, before any path is captured.
Two existing sections needed correcting as a result. 3.6-3.7: with cover traffic
running, catching an emission is not catching a block, and the large stakers those
sections analyse sit one to two orders of magnitude above the quota ceiling, so
their binding exposure is the quota rather than the cascade. 5: the timing-
correlation adversary was deferred for want of cover traffic and is now unblocked.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-06 12:14:45 +02:00
|
|
|
|
| `cover-traffic/` | `configs/cover-traffic.yaml` | 900 s timeline × 4 seeds | §3.10 — blending, mixing, and the emission-quota stake ceiling. Carries a fourth table, `traffic.parquet` |
|
Check in the evidence behind the pd report
The simulator gitignores its runs/ directory, so every table and figure in
reports/blend/pd rested on data that existed only on one machine. This adds the
sweep outputs of record under reports/blend/pd/data -- one directory per study,
1 MB total -- so any number can be checked against its source, or challenged,
without re-running hours of compute.
report_numbers.py comes with them: run it and it prints every value the report
quotes together with its across-topology standard error, straight from these
parquets. It reproduces the report tables exactly.
Kept: default (8000 rounds/cell), redundancy (9600), percolation (6400),
correlated-churn (6400), fullscale (192, the deliberately lighter 1e6 check).
Omitted: the smoke runs, and an earlier 144-rounds/cell redundancy grid whose
sampling error produced a non-monotonic delivery curve -- superseded, and the
reason the kept grid samples 9600.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-05 14:50:41 +02:00
|
|
|
|
|
|
|
|
|
|
The linkability results (§3.6–§3.7) and both deanonymization rates are closed forms over these
|
Rename the simulator and report from pd to blend
The study started as a peering-degree question and grew well past it: propagation,
adversary exposure, deanonymization and time-to-link, reliability under uniform
and correlated churn, messaging redundancy, and cover traffic. The pd name no
longer describes it.
tools/simulators/blend/pd/ -> tools/simulators/blend/, package src/pd -> src/blend,
and reports/blend/pd/ -> reports/blend/. Moved with git mv so history follows.
The text substitutions are deliberately narrow. pd is also the conventional pandas
alias, and pandas genuinely has a pd.plotting submodule, so a blanket pd. -> blend.
rewrite would have corrupted four files. Only package-unambiguous forms were
changed: from pd.X, -m pd.X, pd.<our module>, PD_BYTES_BUDGET, src/pd, and the
pyproject name. All four import pandas as pd lines are untouched and verified.
Both READMEs reframed: peering degree is now presented as the primary axis that
ties the others together rather than as the subject, and the relative links, which
lost a directory level in the move, are corrected.
Verified after the move: ruff clean, 101 tests, 45 verify anchors, make targets,
the script shims, an end-to-end smoke run, and data/report_numbers.py still
reproducing the report tables from the checked-in evidence.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-06 12:20:07 +02:00
|
|
|
|
tables rather than separate measurements, so they have no run of their own — `blend.linkability`
|
Check in the evidence behind the pd report
The simulator gitignores its runs/ directory, so every table and figure in
reports/blend/pd rested on data that existed only on one machine. This adds the
sweep outputs of record under reports/blend/pd/data -- one directory per study,
1 MB total -- so any number can be checked against its source, or challenged,
without re-running hours of compute.
report_numbers.py comes with them: run it and it prints every value the report
quotes together with its across-topology standard error, straight from these
parquets. It reproduces the report tables exactly.
Kept: default (8000 rounds/cell), redundancy (9600), percolation (6400),
correlated-churn (6400), fullscale (192, the deliberately lighter 1e6 check).
Omitted: the smoke runs, and an earlier 144-rounds/cell redundancy grid whose
sampling error produced a non-monotonic delivery curve -- superseded, and the
reason the kept grid samples 9600.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-05 14:50:41 +02:00
|
|
|
|
derives them and `make verify` checks them against Monte-Carlo.
|
|
|
|
|
|
|
|
|
|
|
|
## Regenerating the report's numbers
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
python report_numbers.py
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
prints every quoted value with its across-topology standard error, straight from the parquets here.
|
|
|
|
|
|
That is the fastest way to check a table in the report against its evidence. It takes optional
|
|
|
|
|
|
paths (`report_numbers.py <default> <redundancy> <percolation>`) if you want to point it at fresh
|
|
|
|
|
|
runs instead.
|
|
|
|
|
|
|
|
|
|
|
|
## Regenerating the data itself
|
|
|
|
|
|
|
Rename the simulator and report from pd to blend
The study started as a peering-degree question and grew well past it: propagation,
adversary exposure, deanonymization and time-to-link, reliability under uniform
and correlated churn, messaging redundancy, and cover traffic. The pd name no
longer describes it.
tools/simulators/blend/pd/ -> tools/simulators/blend/, package src/pd -> src/blend,
and reports/blend/pd/ -> reports/blend/. Moved with git mv so history follows.
The text substitutions are deliberately narrow. pd is also the conventional pandas
alias, and pandas genuinely has a pd.plotting submodule, so a blanket pd. -> blend.
rewrite would have corrupted four files. Only package-unambiguous forms were
changed: from pd.X, -m pd.X, pd.<our module>, PD_BYTES_BUDGET, src/pd, and the
pyproject name. All four import pandas as pd lines are untouched and verified.
Both READMEs reframed: peering degree is now presented as the primary axis that
ties the others together rather than as the subject, and the relative links, which
lost a directory level in the move, are corrected.
Verified after the move: ruff clean, 101 tests, 45 verify anchors, make targets,
the script shims, an end-to-end smoke run, and data/report_numbers.py still
reproducing the report tables from the checked-in evidence.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-06 12:20:07 +02:00
|
|
|
|
From [`tools/simulators/blend`](../../../tools/simulators/blend): `make sweep`,
|
Check in the evidence behind the pd report
The simulator gitignores its runs/ directory, so every table and figure in
reports/blend/pd rested on data that existed only on one machine. This adds the
sweep outputs of record under reports/blend/pd/data -- one directory per study,
1 MB total -- so any number can be checked against its source, or challenged,
without re-running hours of compute.
report_numbers.py comes with them: run it and it prints every value the report
quotes together with its across-topology standard error, straight from these
parquets. It reproduces the report tables exactly.
Kept: default (8000 rounds/cell), redundancy (9600), percolation (6400),
correlated-churn (6400), fullscale (192, the deliberately lighter 1e6 check).
Omitted: the smoke runs, and an earlier 144-rounds/cell redundancy grid whose
sampling error produced a non-monotonic delivery curve -- superseded, and the
reason the kept grid samples 9600.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-05 14:50:41 +02:00
|
|
|
|
`make redundancy`, `make percolation`, `make correlated-churn`, `make sweep-fullscale`. Results
|
|
|
|
|
|
land in that simulator's `runs/<timestamp>_<label>/`. Note that the seed streams depend on the
|
|
|
|
|
|
configuration, so re-running reproduces the *statistics*, not bit-identical numbers, unless the
|
|
|
|
|
|
config is unchanged — in which case it does reproduce exactly.
|
|
|
|
|
|
|
|
|
|
|
|
Two runs from the same session are deliberately **not** kept: the smoke runs (throwaway, far too
|
|
|
|
|
|
noisy to interpret) and an earlier 144-rounds/cell redundancy grid that was superseded because its
|
|
|
|
|
|
sampling error produced a non-monotonic delivery curve — the reason `redundancy/` samples 9 600.
|