pd: cover traffic -- emission quota and the blending timeline
First half of the cover-traffic work: the two new modules and their tests.
quota.py -- the emission budget. Cover traffic gives every node the same number
of emissions per epoch, which only holds while a node block proposals fit inside
its quota. The bind is exact: alpha_max = ln(1-q)/ln(1-f), where alpha is stake
relative to the INFERRED total D_hat, since that is the denominator the lottery
threshold is derived from. In true stake the ceiling carries the estimator ratio,
s_max = (D_hat/D)*alpha_max, with D_hat/D an input rather than an assumption. The
familiar q/f is a small-q approximation that runs 1.7% high and so overstates the
tolerable stake. Sitting on the mean bind overruns the quota half the time, so
max_alpha_for_confidence gives the ceiling that holds with stated probability.
traffic.py -- the timeline. The rest of the simulator samples independent rounds
and draws each hold from the stationary residual, which has no notion of time and
so can never let two messages meet at a relay. Here every node owns one
free-running clock shared by all messages through it, extended lazily so only the
relays actually visited grow one. A clock sampled once still reproduces
mixclock.mix_wait, so single-message statistics are unchanged.
It separates two quantities that are easy to conflate: mixing (messages a relay
holds at once) and blending (messages it has SEEN between consecutive releases).
Blending is the anonymity set -- every broadcast reaches every node, so an
observer cannot tell which of them the relay forwarded. Gaps sampled at a release
are size-biased, so blending is rate*(2M+1)/3, twice the mean hold, not
rate*M/2 as a naive reading gives. Measured within 1-4% of that at M = 3, 10, 30
and linear in the cover rate.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-05 16:56:08 +02:00
|
|
|
"""The emission-quota stake ceiling: exact bind, the D_hat/D normalisation, and epoch compliance."""
|
|
|
|
|
|
|
|
|
|
import math
|
|
|
|
|
|
|
|
|
|
import numpy as np
|
|
|
|
|
|
|
|
|
|
from pd.quota import (
|
|
|
|
|
alpha_max,
|
pd: stake distribution, so the emission ceiling is measured not asserted
The quota ceiling was closed-form only. This adds per-node stake so a run can
show nodes actually breaking it.
- assign_stake: uniform, or heavy-tailed zipf (s ~ 1/rank^a), which is what makes
the ceiling bite -- the head sits orders of magnitude above it, the tail far below;
- inferred_alpha: converts true relative stake to the sigma/D_hat the lottery
actually weighs, so a low estimate inflates every node alpha;
- simulate_epoch_emissions: measures the budget over a full epoch. Overrun happens
at epoch scale and needs no graph, so this is cheap: proposals are Binomial over
the epoch slots, a proposal cancels the next cover, and a node stays at exactly
its quota until its wins no longer fit -- at which point it emits more often than
everyone else, which is the signal cover traffic exists to suppress.
Measured against the closed form at N=20,000, zipf stake, over an epoch: the
predicted ceiling falls inside the transition band every time, and at D_hat/D = 1
the smallest overrunning node sits at 0.1468% against a predicted 0.1475%. The
D_hat/D normalisation is confirmed empirically -- deflating the estimate to 0.64
pulls the measured ceiling down with it, as the (D_hat/D)*alpha_max form requires.
With heavy-tailed stake 99.7% of nodes comply and only the head breaks; the
largest holder at 9.5% stake is some 65x over its allowance.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-05 17:21:36 +02:00
|
|
|
assign_stake,
|
pd: cover traffic -- emission quota and the blending timeline
First half of the cover-traffic work: the two new modules and their tests.
quota.py -- the emission budget. Cover traffic gives every node the same number
of emissions per epoch, which only holds while a node block proposals fit inside
its quota. The bind is exact: alpha_max = ln(1-q)/ln(1-f), where alpha is stake
relative to the INFERRED total D_hat, since that is the denominator the lottery
threshold is derived from. In true stake the ceiling carries the estimator ratio,
s_max = (D_hat/D)*alpha_max, with D_hat/D an input rather than an assumption. The
familiar q/f is a small-q approximation that runs 1.7% high and so overstates the
tolerable stake. Sitting on the mean bind overruns the quota half the time, so
max_alpha_for_confidence gives the ceiling that holds with stated probability.
traffic.py -- the timeline. The rest of the simulator samples independent rounds
and draws each hold from the stationary residual, which has no notion of time and
so can never let two messages meet at a relay. Here every node owns one
free-running clock shared by all messages through it, extended lazily so only the
relays actually visited grow one. A clock sampled once still reproduces
mixclock.mix_wait, so single-message statistics are unchanged.
It separates two quantities that are easy to conflate: mixing (messages a relay
holds at once) and blending (messages it has SEEN between consecutive releases).
Blending is the anonymity set -- every broadcast reaches every node, so an
observer cannot tell which of them the relay forwarded. Gaps sampled at a release
are size-biased, so blending is rate*(2M+1)/3, twice the mean hold, not
rate*M/2 as a naive reading gives. Measured within 1-4% of that at M = 3, 10, 30
and linear in the cover rate.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-05 16:56:08 +02:00
|
|
|
emission_quota_per_slot,
|
|
|
|
|
expected_blocks_per_epoch,
|
pd: stake distribution, so the emission ceiling is measured not asserted
The quota ceiling was closed-form only. This adds per-node stake so a run can
show nodes actually breaking it.
- assign_stake: uniform, or heavy-tailed zipf (s ~ 1/rank^a), which is what makes
the ceiling bite -- the head sits orders of magnitude above it, the tail far below;
- inferred_alpha: converts true relative stake to the sigma/D_hat the lottery
actually weighs, so a low estimate inflates every node alpha;
- simulate_epoch_emissions: measures the budget over a full epoch. Overrun happens
at epoch scale and needs no graph, so this is cheap: proposals are Binomial over
the epoch slots, a proposal cancels the next cover, and a node stays at exactly
its quota until its wins no longer fit -- at which point it emits more often than
everyone else, which is the signal cover traffic exists to suppress.
Measured against the closed form at N=20,000, zipf stake, over an epoch: the
predicted ceiling falls inside the transition band every time, and at D_hat/D = 1
the smallest overrunning node sits at 0.1468% against a predicted 0.1475%. The
D_hat/D normalisation is confirmed empirically -- deflating the estimate to 0.64
pulls the measured ceiling down with it, as the (D_hat/D)*alpha_max form requires.
With heavy-tailed stake 99.7% of nodes comply and only the head breaks; the
largest holder at 9.5% stake is some 65x over its allowance.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-05 17:21:36 +02:00
|
|
|
inferred_alpha,
|
pd: cover traffic -- emission quota and the blending timeline
First half of the cover-traffic work: the two new modules and their tests.
quota.py -- the emission budget. Cover traffic gives every node the same number
of emissions per epoch, which only holds while a node block proposals fit inside
its quota. The bind is exact: alpha_max = ln(1-q)/ln(1-f), where alpha is stake
relative to the INFERRED total D_hat, since that is the denominator the lottery
threshold is derived from. In true stake the ceiling carries the estimator ratio,
s_max = (D_hat/D)*alpha_max, with D_hat/D an input rather than an assumption. The
familiar q/f is a small-q approximation that runs 1.7% high and so overstates the
tolerable stake. Sitting on the mean bind overruns the quota half the time, so
max_alpha_for_confidence gives the ceiling that holds with stated probability.
traffic.py -- the timeline. The rest of the simulator samples independent rounds
and draws each hold from the stationary residual, which has no notion of time and
so can never let two messages meet at a relay. Here every node owns one
free-running clock shared by all messages through it, extended lazily so only the
relays actually visited grow one. A clock sampled once still reproduces
mixclock.mix_wait, so single-message statistics are unchanged.
It separates two quantities that are easy to conflate: mixing (messages a relay
holds at once) and blending (messages it has SEEN between consecutive releases).
Blending is the anonymity set -- every broadcast reaches every node, so an
observer cannot tell which of them the relay forwarded. Gaps sampled at a release
are size-biased, so blending is rate*(2M+1)/3, twice the mean hold, not
rate*M/2 as a naive reading gives. Measured within 1-4% of that at M = 3, 10, 30
and linear in the cover rate.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-05 16:56:08 +02:00
|
|
|
max_alpha_for_confidence,
|
|
|
|
|
quota_exceedance_prob,
|
|
|
|
|
quota_per_epoch,
|
|
|
|
|
s_max_true,
|
pd: stake distribution, so the emission ceiling is measured not asserted
The quota ceiling was closed-form only. This adds per-node stake so a run can
show nodes actually breaking it.
- assign_stake: uniform, or heavy-tailed zipf (s ~ 1/rank^a), which is what makes
the ceiling bite -- the head sits orders of magnitude above it, the tail far below;
- inferred_alpha: converts true relative stake to the sigma/D_hat the lottery
actually weighs, so a low estimate inflates every node alpha;
- simulate_epoch_emissions: measures the budget over a full epoch. Overrun happens
at epoch scale and needs no graph, so this is cheap: proposals are Binomial over
the epoch slots, a proposal cancels the next cover, and a node stays at exactly
its quota until its wins no longer fit -- at which point it emits more often than
everyone else, which is the signal cover traffic exists to suppress.
Measured against the closed form at N=20,000, zipf stake, over an epoch: the
predicted ceiling falls inside the transition band every time, and at D_hat/D = 1
the smallest overrunning node sits at 0.1468% against a predicted 0.1475%. The
D_hat/D normalisation is confirmed empirically -- deflating the estimate to 0.64
pulls the measured ceiling down with it, as the (D_hat/D)*alpha_max form requires.
With heavy-tailed stake 99.7% of nodes comply and only the head breaks; the
largest holder at 9.5% stake is some 65x over its allowance.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-05 17:21:36 +02:00
|
|
|
simulate_epoch_emissions,
|
pd: cover traffic -- emission quota and the blending timeline
First half of the cover-traffic work: the two new modules and their tests.
quota.py -- the emission budget. Cover traffic gives every node the same number
of emissions per epoch, which only holds while a node block proposals fit inside
its quota. The bind is exact: alpha_max = ln(1-q)/ln(1-f), where alpha is stake
relative to the INFERRED total D_hat, since that is the denominator the lottery
threshold is derived from. In true stake the ceiling carries the estimator ratio,
s_max = (D_hat/D)*alpha_max, with D_hat/D an input rather than an assumption. The
familiar q/f is a small-q approximation that runs 1.7% high and so overstates the
tolerable stake. Sitting on the mean bind overruns the quota half the time, so
max_alpha_for_confidence gives the ceiling that holds with stated probability.
traffic.py -- the timeline. The rest of the simulator samples independent rounds
and draws each hold from the stationary residual, which has no notion of time and
so can never let two messages meet at a relay. Here every node owns one
free-running clock shared by all messages through it, extended lazily so only the
relays actually visited grow one. A clock sampled once still reproduces
mixclock.mix_wait, so single-message statistics are unchanged.
It separates two quantities that are easy to conflate: mixing (messages a relay
holds at once) and blending (messages it has SEEN between consecutive releases).
Blending is the anonymity set -- every broadcast reaches every node, so an
observer cannot tell which of them the relay forwarded. Gaps sampled at a release
are size-biased, so blending is rate*(2M+1)/3, twice the mean hold, not
rate*M/2 as a naive reading gives. Measured within 1-4% of that at M = 3, 10, 30
and linear in the cover rate.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-05 16:56:08 +02:00
|
|
|
win_prob,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
F = 1.0 / 30.0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_quota_is_one_emission_per_slot_network_wide():
|
|
|
|
|
n = 20_000
|
|
|
|
|
assert emission_quota_per_slot(n) * n == 1.0 # whole network emits once per slot
|
|
|
|
|
assert emission_quota_per_slot(n, 4.0) * n == 4.0 # the multiplier scales it
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_alpha_max_is_where_the_win_rate_equals_the_quota():
|
|
|
|
|
n = 20_000
|
|
|
|
|
a = alpha_max(n, F)
|
|
|
|
|
assert abs(win_prob(a, F) - emission_quota_per_slot(n)) < 1e-15
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_alpha_max_is_below_the_q_over_f_approximation():
|
|
|
|
|
"""q/f is a small-q expansion and errs optimistic, so the exact bind must be lower."""
|
|
|
|
|
for n in (1_000, 20_000, 10**6):
|
|
|
|
|
exact = alpha_max(n, F)
|
|
|
|
|
approx = emission_quota_per_slot(n) / F
|
|
|
|
|
assert exact < approx
|
|
|
|
|
assert abs(approx / exact - 1) < 0.02 # ~1.7% at f = 1/30
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_alpha_max_scales_inversely_with_network_size_and_with_cover_rate():
|
|
|
|
|
assert abs(alpha_max(20_000, F) / alpha_max(200_000, F) - 10.0) < 0.01
|
|
|
|
|
assert abs(alpha_max(20_000, F, 8.0) / alpha_max(20_000, F, 1.0) - 8.0) < 0.01
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_true_stake_ceiling_is_scaled_by_the_inference_ratio():
|
|
|
|
|
"""The lottery uses sigma/D_hat, so the ceiling in TRUE stake carries the D_hat/D factor."""
|
|
|
|
|
n = 20_000
|
|
|
|
|
a = alpha_max(n, F)
|
|
|
|
|
assert s_max_true(n, F, 1.0) == a # accurate estimator: no correction
|
|
|
|
|
assert abs(s_max_true(n, F, 0.74) - 0.74 * a) < 1e-15 # deflated estimate tightens it
|
|
|
|
|
assert s_max_true(n, F, 0.64) < s_max_true(n, F, 0.74) < a
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_expected_blocks_equal_the_quota_at_alpha_max():
|
|
|
|
|
n, S = 20_000, 648_000
|
|
|
|
|
a = alpha_max(n, F)
|
|
|
|
|
assert abs(expected_blocks_per_epoch(a, F, S) - quota_per_epoch(n, S)) < 1e-6
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_a_node_at_the_mean_bind_overruns_about_half_the_time():
|
|
|
|
|
n, S = 20_000, 648_000
|
|
|
|
|
p = quota_exceedance_prob(alpha_max(n, F), F, n, S)
|
|
|
|
|
assert 0.35 < p < 0.65 # mean bind is a coin flip, as expected
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_confidence_ceiling_is_stricter_than_the_mean_bind():
|
|
|
|
|
n, S = 20_000, 648_000
|
|
|
|
|
safe = max_alpha_for_confidence(F, n, S, confidence=0.99)
|
|
|
|
|
assert safe < alpha_max(n, F)
|
|
|
|
|
assert quota_exceedance_prob(safe, F, n, S) <= 0.01 + 1e-9
|
|
|
|
|
assert 0.5 < safe / alpha_max(n, F) < 0.9 # Poisson noise eats real headroom
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_exceedance_matches_a_direct_simulation():
|
|
|
|
|
"""Closed-form exceedance vs drawing epochs of block wins."""
|
|
|
|
|
n, S = 2_000, 20_000
|
|
|
|
|
a = alpha_max(n, F) * 0.8
|
|
|
|
|
closed = quota_exceedance_prob(a, F, n, S)
|
|
|
|
|
rng = np.random.default_rng(0)
|
|
|
|
|
quota = quota_per_epoch(n, S)
|
|
|
|
|
wins = rng.binomial(S, win_prob(a, F), size=20_000)
|
|
|
|
|
emp = float(np.mean(wins > math.floor(quota)))
|
|
|
|
|
assert abs(closed - emp) < max(0.01, 0.1 * closed)
|
pd: stake distribution, so the emission ceiling is measured not asserted
The quota ceiling was closed-form only. This adds per-node stake so a run can
show nodes actually breaking it.
- assign_stake: uniform, or heavy-tailed zipf (s ~ 1/rank^a), which is what makes
the ceiling bite -- the head sits orders of magnitude above it, the tail far below;
- inferred_alpha: converts true relative stake to the sigma/D_hat the lottery
actually weighs, so a low estimate inflates every node alpha;
- simulate_epoch_emissions: measures the budget over a full epoch. Overrun happens
at epoch scale and needs no graph, so this is cheap: proposals are Binomial over
the epoch slots, a proposal cancels the next cover, and a node stays at exactly
its quota until its wins no longer fit -- at which point it emits more often than
everyone else, which is the signal cover traffic exists to suppress.
Measured against the closed form at N=20,000, zipf stake, over an epoch: the
predicted ceiling falls inside the transition band every time, and at D_hat/D = 1
the smallest overrunning node sits at 0.1468% against a predicted 0.1475%. The
D_hat/D normalisation is confirmed empirically -- deflating the estimate to 0.64
pulls the measured ceiling down with it, as the (D_hat/D)*alpha_max form requires.
With heavy-tailed stake 99.7% of nodes comply and only the head breaks; the
largest holder at 9.5% stake is some 65x over its allowance.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-05 17:21:36 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
# --- stake distribution and the measured ceiling --------------------------------------------------
|
|
|
|
|
|
|
|
|
|
def test_stake_distributions_normalise_and_zipf_is_heavy_tailed():
|
|
|
|
|
n = 5_000
|
|
|
|
|
rng = np.random.default_rng(0)
|
|
|
|
|
uni = assign_stake(n, "uniform", rng)
|
|
|
|
|
zipf = assign_stake(n, "zipf", rng, zipf_a=1.0)
|
|
|
|
|
for s in (uni, zipf):
|
|
|
|
|
assert abs(s.sum() - 1.0) < 1e-12
|
|
|
|
|
assert (s > 0).all()
|
|
|
|
|
assert np.allclose(uni, 1.0 / n)
|
|
|
|
|
assert zipf.max() > 50 * uni.max() # a real head, unlike the flat case
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_inferred_alpha_divides_by_the_estimator_ratio():
|
|
|
|
|
"""The lottery weighs sigma/D_hat, so a low estimate inflates every node's alpha."""
|
|
|
|
|
s = np.array([0.001, 0.01])
|
|
|
|
|
assert np.allclose(inferred_alpha(s, 1.0), s)
|
|
|
|
|
assert np.allclose(inferred_alpha(s, 0.5), s * 2.0)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_uniform_stake_stays_inside_the_quota_at_scale():
|
|
|
|
|
"""At 1/N each, every node's block rate is f/N -- far under a 1/N emission budget."""
|
|
|
|
|
n, S = 20_000, 648_000
|
|
|
|
|
s = assign_stake(n, "uniform", np.random.default_rng(1))
|
|
|
|
|
r = simulate_epoch_emissions(s, F, n, S, np.random.default_rng(2))
|
|
|
|
|
assert r["compliant_frac"] == 1.0
|
|
|
|
|
assert r["overrun"].sum() == 0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_heavy_tailed_stake_makes_the_head_overrun_its_quota():
|
|
|
|
|
n, S = 20_000, 648_000
|
|
|
|
|
s = assign_stake(n, "zipf", np.random.default_rng(3), zipf_a=1.0)
|
|
|
|
|
r = simulate_epoch_emissions(s, F, n, S, np.random.default_rng(4))
|
|
|
|
|
assert 0.0 < r["compliant_frac"] < 1.0 # the head breaks, the tail does not
|
|
|
|
|
assert r["min_overrun_stake"] > r["stake"].min() # it is the large holders that break
|
|
|
|
|
assert r["overrun"][np.argmax(s)] > 0 # the biggest staker certainly does
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_the_measured_ceiling_matches_the_closed_form():
|
|
|
|
|
"""Where compliance actually breaks must bracket the analytic alpha_max."""
|
|
|
|
|
n, S = 20_000, 648_000
|
|
|
|
|
s = assign_stake(n, "zipf", np.random.default_rng(5), zipf_a=0.8)
|
|
|
|
|
r = simulate_epoch_emissions(s, F, n, S, np.random.default_rng(6))
|
|
|
|
|
predicted = alpha_max(n, F)
|
|
|
|
|
assert r["max_compliant_stake"] < 3.0 * predicted
|
|
|
|
|
assert r["min_overrun_stake"] > 0.3 * predicted
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_a_low_stake_estimate_tightens_the_measured_ceiling():
|
|
|
|
|
"""D_hat/D is an input, and lowering it must push more nodes over their quota."""
|
|
|
|
|
n, S = 20_000, 648_000
|
|
|
|
|
s = assign_stake(n, "zipf", np.random.default_rng(7), zipf_a=1.0)
|
|
|
|
|
accurate = simulate_epoch_emissions(s, F, n, S, np.random.default_rng(8),
|
|
|
|
|
stake_inference_ratio=1.0)
|
|
|
|
|
deflated = simulate_epoch_emissions(s, F, n, S, np.random.default_rng(8),
|
|
|
|
|
stake_inference_ratio=0.64)
|
|
|
|
|
assert deflated["compliant_frac"] < accurate["compliant_frac"]
|
|
|
|
|
assert deflated["max_compliant_stake"] <= accurate["max_compliant_stake"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_more_cover_traffic_raises_the_ceiling():
|
|
|
|
|
"""The quota is the budget, so paying more cover traffic admits more concentrated stake."""
|
|
|
|
|
n, S = 20_000, 648_000
|
|
|
|
|
s = assign_stake(n, "zipf", np.random.default_rng(9), zipf_a=1.0)
|
|
|
|
|
lean = simulate_epoch_emissions(s, F, n, S, np.random.default_rng(10), cover_rate_mult=1.0)
|
|
|
|
|
rich = simulate_epoch_emissions(s, F, n, S, np.random.default_rng(10), cover_rate_mult=32.0)
|
|
|
|
|
assert rich["compliant_frac"] > lean["compliant_frac"]
|
|
|
|
|
assert rich["max_compliant_stake"] > lean["max_compliant_stake"]
|