Item 16: the revenue-optimal adversary is not the estimator's worst case
Both eta ceilings in sec 6.6 come from adversaries optimising something else
(revenue, reorg depth), so they bound eta from above without bounding the
damage from below. Optimising the estimate directly needs no ratio transform:
each transition consumes exactly one block-finding event, so minimising
D-hat = (canonical + p_ref * countable uncles)/events is a plain average-reward
MDP over the transition table that already carries the orphan counts. One
value-iteration pass, no bisection.
Unconstrained, the answer degenerates -- and usefully. The optimum is pure
abstention: publish nothing, adopt when overtaken, D-hat = 1 - alpha exactly,
revenue zero. That is sec 6.4's withholding, which the report already shows is
CORRECT measurement rather than mis-measurement, so the unconstrained objective
asks the wrong question.
The constrained one bites. Sweeping lam * (adversary blocks) - (contribution to
D-hat) enumerates policies; the line of interest is where revenue SHARE reaches
alpha, i.e. where attacking costs nothing versus mining honestly. At alpha=0.4
such a policy drives D-hat to 0.642 where the revenue-maximiser reaches 0.811
-- 17 points of extra deflation bought with the selfish premium alone. At 0.36
and 0.45 the gaps are 0.082 and 0.103. Below the 1/3 threshold nothing
profitable deflates, so the exposure starts exactly where selfish mining does.
This revises two claims that were about revenue but read as though they were
about the adversary in general: sec 6.7's "the adversary frontier is exactly
optimal selfish mining; no compounding lever remains" and sec 8.2's echo of it.
Both now say the PROFIT frontier is bounded and the estimator frontier is not
the same policy. Note the sweep parameter is deliberately non-monotone in
revenue -- selfish mining takes a bigger share of a smaller pie, so raw block
rate is maximised by honesty and large lam returns there; it enumerates
policies rather than tracing a path.
Also closes a fairness loop these findings opened. Sec 6.7(1) credits uncle
rewards with compensating orphaned honest producers, computed on the SM1 race
where every orphan is a first-fork block. Under a private chain 20-40% of the
honest blocks destroyed are unreferenceable by construction, so those producers
are uncompensatable at ANY w_u -- not underpaid because p_ref is low, but
unreachable because no valid block may name them. The fairness guarantee
inherits the same first-fork ceiling as the density repair. Logged as item 19,
flagged as a protocol-design question rather than something a schedule fixes.
_solve_mdp is refactored into _solve_reward/_greedy_policy/_stationary/
_policy_rates so both objectives share one implementation; optimal_policy_stats
reproduces its committed figures exactly (eta 0.4413, D-hat 0.9447/0.8111 at
alpha=0.4). 251 tests pass.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-06 14:54:52 +02:00
|
|
|
"""How far can a PROFITABLE adversary deflate the estimate? — REPORT §8.3 item 16 (fig37).
|
|
|
|
|
|
|
|
|
|
§6.6 measures the countable recovery ceiling under two adversaries, and both optimise something
|
|
|
|
|
other than the estimator: revenue (the SSZ objective) and reorg depth. Item 16 recorded the
|
|
|
|
|
consequence — those are upper bounds on `eta`, not lower bounds on the damage — and asked what an
|
|
|
|
|
adversary optimising deflation directly would do.
|
|
|
|
|
|
|
|
|
|
Two answers, because the obvious question is the wrong one:
|
|
|
|
|
|
|
|
|
|
* **Unconstrained**, the deflation optimum is pure abstention: publish nothing, adopt when
|
|
|
|
|
overtaken, drive `D_hat` to exactly `1 - alpha` and revenue to zero. §6.4 already covers this
|
|
|
|
|
and shows it is *correct* measurement — a coalition that publishes nothing is not
|
|
|
|
|
participating, so `1 - alpha` is the right answer for the stake that is.
|
|
|
|
|
* **Constrained to stay paid**, the question bites. Sweeping the mixed objective
|
|
|
|
|
`lam * (adversary blocks) - (contribution to D_hat)` traces the profit/deflation frontier;
|
|
|
|
|
the point of interest is where the revenue *share* reaches `alpha`, i.e. where the adversary
|
|
|
|
|
does no worse than mining honestly. Everything below that line is self-punishing griefing,
|
|
|
|
|
already bounded by §6.5.
|
|
|
|
|
|
|
|
|
|
The headline: at `alpha = 0.4` an adversary content with break-even revenue pushes `D_hat` to
|
|
|
|
|
~0.64, against the revenue-optimal policy's 0.81. The revenue-optimal adversary is not the
|
|
|
|
|
estimator's worst case, and the gap is free — it costs only the selfish premium.
|
|
|
|
|
|
|
|
|
|
Note the sweep parameter is not monotone in revenue: selfish mining wins a larger share of a
|
|
|
|
|
*smaller* pie, so raw adversary block rate is maximised by honest mining and large `lam` returns
|
|
|
|
|
there. That is fine — the sweep is used to enumerate candidate policies and their measured
|
|
|
|
|
(share, `D_hat`) pairs, not as a monotone path, and any point on it is a legitimate strategy.
|
|
|
|
|
|
|
|
|
|
Run: python scripts/deflation_frontier.py (writes runs/deflation_frontier.parquet + fig37)
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
|
|
|
|
import numpy as np
|
|
|
|
|
import pandas as pd
|
|
|
|
|
|
|
|
|
|
from tsi_sim.plotting import style
|
|
|
|
|
from tsi_sim.selfish_mdp import deflation_frontier, deflation_optimal_stats, optimal_policy_stats
|
|
|
|
|
|
|
|
|
|
HERE = Path(__file__).resolve().parent.parent
|
|
|
|
|
RUNS = HERE / "runs"
|
|
|
|
|
FIGS = HERE / "report-figures"
|
|
|
|
|
RUNS.mkdir(exist_ok=True)
|
|
|
|
|
FIGS.mkdir(exist_ok=True)
|
|
|
|
|
|
|
|
|
|
CAP = 64
|
|
|
|
|
GAMMA = 0.0
|
|
|
|
|
ALPHAS = [0.30, 1 / 3, 0.36, 0.38, 0.40, 0.42, 0.45]
|
|
|
|
|
LAMS = [round(x, 2) for x in np.arange(0.0, 3.05, 0.1)]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def sweep() -> pd.DataFrame:
|
|
|
|
|
rows = []
|
|
|
|
|
for a in ALPHAS:
|
|
|
|
|
ro = optimal_policy_stats(a, GAMMA, cap=CAP)
|
|
|
|
|
do = deflation_optimal_stats(a, GAMMA, cap=CAP)
|
|
|
|
|
for lam in LAMS:
|
|
|
|
|
r = deflation_frontier(a, GAMMA, lam, cap=CAP)
|
|
|
|
|
r |= dict(revenue_optimal=ro.revenue,
|
|
|
|
|
dhat_revenue_optimal=ro.dhat_ratio(1.0, True) if ro.deviates else 1.0,
|
|
|
|
|
dhat_abstention=do.dhat_ratio(1.0, True))
|
|
|
|
|
rows.append(r)
|
|
|
|
|
df = pd.DataFrame(rows)
|
|
|
|
|
df.to_parquet(RUNS / "deflation_frontier.parquet", index=False)
|
|
|
|
|
return df
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def best_profitable(df: pd.DataFrame) -> pd.DataFrame:
|
Score the deflation frontier on absolute pay, not revenue share
Verification of my own item-16 result, which I had flagged as resting on a
step I had not checked: "break-even" was measured as revenue SHARE >= stake
share, and share-to-pay equivalence rests on sec 6.6's issuance argument. It
does not hold as stated.
A revenue share is measured against the canonical block rate, but pay is set by
the estimator. TSI drives COUNTED density -- canonical plus recovered uncles --
to f per slot, so a run yields f/D-hat block-finding events per slot and a
deflating attack makes the lottery easier for everyone. Pay per slot is
adv_rate * f / D-hat against an honest miner's alpha * f, so the correction on
the share ratio is density/D-hat, which is below 1 whenever any uncle is
recovered. Scoring on share credits the attacker with pay it never receives.
The headline survives; one row does not. At alpha = 0.4 the true break-even
point deflates to 0.649 rather than 0.642 (against the revenue-optimal 0.811),
so 16 points of free deflation instead of 17. But at alpha = 0.36 the policy I
reported as paying 1.033x stake actually pays 0.95x, and NO policy in the sweep
both pays and deflates there -- that row is withdrawn. The exposure therefore
opens near alpha ~ 0.37-0.38, above the 1/3 selfish threshold, not at it. That
is a better result for the protocol than the one I published yesterday, which
is the direction these corrections usually do not go.
pay_vs_honest is now computed in deflation_frontier() and is the criterion
best_profitable() selects on, so the claim is reproducible rather than resting
on a hand check. The conversion neglects the multi-winner factor c(f) ~ 1.017
between events and occupied slots (sec 2.1), noted inline and small against a
16-point effect. Downstream citations in sec 1, sec 8.2 and item 16 updated.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-06 17:54:37 +02:00
|
|
|
"""Per alpha, the frontier point minimising D_hat among those that actually pay.
|
|
|
|
|
|
|
|
|
|
"Pays" means ABSOLUTE reward per unit time at least matching honest mining
|
|
|
|
|
(``pay_vs_honest >= 1``), not merely a revenue *share* at least matching stake. The two
|
|
|
|
|
differ by ``density / dhat``: the share's denominator is the canonical block rate, but the
|
|
|
|
|
pay rate is set by the estimator, which holds counted density at ``f`` per slot. Using the
|
|
|
|
|
share alone credits the attacker with pay it does not receive — at alpha = 0.36 it marks
|
|
|
|
|
points as break-even that are ~5 % short.
|
|
|
|
|
"""
|
Item 16: the revenue-optimal adversary is not the estimator's worst case
Both eta ceilings in sec 6.6 come from adversaries optimising something else
(revenue, reorg depth), so they bound eta from above without bounding the
damage from below. Optimising the estimate directly needs no ratio transform:
each transition consumes exactly one block-finding event, so minimising
D-hat = (canonical + p_ref * countable uncles)/events is a plain average-reward
MDP over the transition table that already carries the orphan counts. One
value-iteration pass, no bisection.
Unconstrained, the answer degenerates -- and usefully. The optimum is pure
abstention: publish nothing, adopt when overtaken, D-hat = 1 - alpha exactly,
revenue zero. That is sec 6.4's withholding, which the report already shows is
CORRECT measurement rather than mis-measurement, so the unconstrained objective
asks the wrong question.
The constrained one bites. Sweeping lam * (adversary blocks) - (contribution to
D-hat) enumerates policies; the line of interest is where revenue SHARE reaches
alpha, i.e. where attacking costs nothing versus mining honestly. At alpha=0.4
such a policy drives D-hat to 0.642 where the revenue-maximiser reaches 0.811
-- 17 points of extra deflation bought with the selfish premium alone. At 0.36
and 0.45 the gaps are 0.082 and 0.103. Below the 1/3 threshold nothing
profitable deflates, so the exposure starts exactly where selfish mining does.
This revises two claims that were about revenue but read as though they were
about the adversary in general: sec 6.7's "the adversary frontier is exactly
optimal selfish mining; no compounding lever remains" and sec 8.2's echo of it.
Both now say the PROFIT frontier is bounded and the estimator frontier is not
the same policy. Note the sweep parameter is deliberately non-monotone in
revenue -- selfish mining takes a bigger share of a smaller pie, so raw block
rate is maximised by honesty and large lam returns there; it enumerates
policies rather than tracing a path.
Also closes a fairness loop these findings opened. Sec 6.7(1) credits uncle
rewards with compensating orphaned honest producers, computed on the SM1 race
where every orphan is a first-fork block. Under a private chain 20-40% of the
honest blocks destroyed are unreferenceable by construction, so those producers
are uncompensatable at ANY w_u -- not underpaid because p_ref is low, but
unreachable because no valid block may name them. The fairness guarantee
inherits the same first-fork ceiling as the density repair. Logged as item 19,
flagged as a protocol-design question rather than something a schedule fixes.
_solve_mdp is refactored into _solve_reward/_greedy_policy/_stationary/
_policy_rates so both objectives share one implementation; optimal_policy_stats
reproduces its committed figures exactly (eta 0.4413, D-hat 0.9447/0.8111 at
alpha=0.4). 251 tests pass.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-06 14:54:52 +02:00
|
|
|
out = []
|
|
|
|
|
for _a, g in df.groupby("alpha"):
|
Score the deflation frontier on absolute pay, not revenue share
Verification of my own item-16 result, which I had flagged as resting on a
step I had not checked: "break-even" was measured as revenue SHARE >= stake
share, and share-to-pay equivalence rests on sec 6.6's issuance argument. It
does not hold as stated.
A revenue share is measured against the canonical block rate, but pay is set by
the estimator. TSI drives COUNTED density -- canonical plus recovered uncles --
to f per slot, so a run yields f/D-hat block-finding events per slot and a
deflating attack makes the lottery easier for everyone. Pay per slot is
adv_rate * f / D-hat against an honest miner's alpha * f, so the correction on
the share ratio is density/D-hat, which is below 1 whenever any uncle is
recovered. Scoring on share credits the attacker with pay it never receives.
The headline survives; one row does not. At alpha = 0.4 the true break-even
point deflates to 0.649 rather than 0.642 (against the revenue-optimal 0.811),
so 16 points of free deflation instead of 17. But at alpha = 0.36 the policy I
reported as paying 1.033x stake actually pays 0.95x, and NO policy in the sweep
both pays and deflates there -- that row is withdrawn. The exposure therefore
opens near alpha ~ 0.37-0.38, above the 1/3 selfish threshold, not at it. That
is a better result for the protocol than the one I published yesterday, which
is the direction these corrections usually do not go.
pay_vs_honest is now computed in deflation_frontier() and is the criterion
best_profitable() selects on, so the claim is reproducible rather than resting
on a hand check. The conversion neglects the multi-winner factor c(f) ~ 1.017
between events and occupied slots (sec 2.1), noted inline and small against a
16-point effect. Downstream citations in sec 1, sec 8.2 and item 16 updated.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-06 17:54:37 +02:00
|
|
|
paid = g[g.pay_vs_honest >= 1.0 - 1e-9]
|
Item 16: the revenue-optimal adversary is not the estimator's worst case
Both eta ceilings in sec 6.6 come from adversaries optimising something else
(revenue, reorg depth), so they bound eta from above without bounding the
damage from below. Optimising the estimate directly needs no ratio transform:
each transition consumes exactly one block-finding event, so minimising
D-hat = (canonical + p_ref * countable uncles)/events is a plain average-reward
MDP over the transition table that already carries the orphan counts. One
value-iteration pass, no bisection.
Unconstrained, the answer degenerates -- and usefully. The optimum is pure
abstention: publish nothing, adopt when overtaken, D-hat = 1 - alpha exactly,
revenue zero. That is sec 6.4's withholding, which the report already shows is
CORRECT measurement rather than mis-measurement, so the unconstrained objective
asks the wrong question.
The constrained one bites. Sweeping lam * (adversary blocks) - (contribution to
D-hat) enumerates policies; the line of interest is where revenue SHARE reaches
alpha, i.e. where attacking costs nothing versus mining honestly. At alpha=0.4
such a policy drives D-hat to 0.642 where the revenue-maximiser reaches 0.811
-- 17 points of extra deflation bought with the selfish premium alone. At 0.36
and 0.45 the gaps are 0.082 and 0.103. Below the 1/3 threshold nothing
profitable deflates, so the exposure starts exactly where selfish mining does.
This revises two claims that were about revenue but read as though they were
about the adversary in general: sec 6.7's "the adversary frontier is exactly
optimal selfish mining; no compounding lever remains" and sec 8.2's echo of it.
Both now say the PROFIT frontier is bounded and the estimator frontier is not
the same policy. Note the sweep parameter is deliberately non-monotone in
revenue -- selfish mining takes a bigger share of a smaller pie, so raw block
rate is maximised by honesty and large lam returns there; it enumerates
policies rather than tracing a path.
Also closes a fairness loop these findings opened. Sec 6.7(1) credits uncle
rewards with compensating orphaned honest producers, computed on the SM1 race
where every orphan is a first-fork block. Under a private chain 20-40% of the
honest blocks destroyed are unreferenceable by construction, so those producers
are uncompensatable at ANY w_u -- not underpaid because p_ref is low, but
unreachable because no valid block may name them. The fairness guarantee
inherits the same first-fork ceiling as the density repair. Logged as item 19,
flagged as a protocol-design question rather than something a schedule fixes.
_solve_mdp is refactored into _solve_reward/_greedy_policy/_stationary/
_policy_rates so both objectives share one implementation; optimal_policy_stats
reproduces its committed figures exactly (eta 0.4413, D-hat 0.9447/0.8111 at
alpha=0.4). 251 tests pass.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-06 14:54:52 +02:00
|
|
|
if paid.empty:
|
|
|
|
|
continue
|
|
|
|
|
out.append(paid.loc[paid.dhat_countable.idxmin()])
|
|
|
|
|
return pd.DataFrame(out)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def report(df: pd.DataFrame) -> None:
|
|
|
|
|
best = best_profitable(df)
|
|
|
|
|
print(f"{'alpha':>6} {'rev-opt rev':>12} {'rev-opt D':>10} | "
|
Score the deflation frontier on absolute pay, not revenue share
Verification of my own item-16 result, which I had flagged as resting on a
step I had not checked: "break-even" was measured as revenue SHARE >= stake
share, and share-to-pay equivalence rests on sec 6.6's issuance argument. It
does not hold as stated.
A revenue share is measured against the canonical block rate, but pay is set by
the estimator. TSI drives COUNTED density -- canonical plus recovered uncles --
to f per slot, so a run yields f/D-hat block-finding events per slot and a
deflating attack makes the lottery easier for everyone. Pay per slot is
adv_rate * f / D-hat against an honest miner's alpha * f, so the correction on
the share ratio is density/D-hat, which is below 1 whenever any uncle is
recovered. Scoring on share credits the attacker with pay it never receives.
The headline survives; one row does not. At alpha = 0.4 the true break-even
point deflates to 0.649 rather than 0.642 (against the revenue-optimal 0.811),
so 16 points of free deflation instead of 17. But at alpha = 0.36 the policy I
reported as paying 1.033x stake actually pays 0.95x, and NO policy in the sweep
both pays and deflates there -- that row is withdrawn. The exposure therefore
opens near alpha ~ 0.37-0.38, above the 1/3 selfish threshold, not at it. That
is a better result for the protocol than the one I published yesterday, which
is the direction these corrections usually do not go.
pay_vs_honest is now computed in deflation_frontier() and is the criterion
best_profitable() selects on, so the claim is reproducible rather than resting
on a hand check. The conversion neglects the multi-winner factor c(f) ~ 1.017
between events and occupied slots (sec 2.1), noted inline and small against a
16-point effect. Downstream citations in sec 1, sec 8.2 and item 16 updated.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-06 17:54:37 +02:00
|
|
|
f"{'best paid rev':>14} {'pay/hon':>8} {'D':>7} {'extra deflation':>16}")
|
Item 16: the revenue-optimal adversary is not the estimator's worst case
Both eta ceilings in sec 6.6 come from adversaries optimising something else
(revenue, reorg depth), so they bound eta from above without bounding the
damage from below. Optimising the estimate directly needs no ratio transform:
each transition consumes exactly one block-finding event, so minimising
D-hat = (canonical + p_ref * countable uncles)/events is a plain average-reward
MDP over the transition table that already carries the orphan counts. One
value-iteration pass, no bisection.
Unconstrained, the answer degenerates -- and usefully. The optimum is pure
abstention: publish nothing, adopt when overtaken, D-hat = 1 - alpha exactly,
revenue zero. That is sec 6.4's withholding, which the report already shows is
CORRECT measurement rather than mis-measurement, so the unconstrained objective
asks the wrong question.
The constrained one bites. Sweeping lam * (adversary blocks) - (contribution to
D-hat) enumerates policies; the line of interest is where revenue SHARE reaches
alpha, i.e. where attacking costs nothing versus mining honestly. At alpha=0.4
such a policy drives D-hat to 0.642 where the revenue-maximiser reaches 0.811
-- 17 points of extra deflation bought with the selfish premium alone. At 0.36
and 0.45 the gaps are 0.082 and 0.103. Below the 1/3 threshold nothing
profitable deflates, so the exposure starts exactly where selfish mining does.
This revises two claims that were about revenue but read as though they were
about the adversary in general: sec 6.7's "the adversary frontier is exactly
optimal selfish mining; no compounding lever remains" and sec 8.2's echo of it.
Both now say the PROFIT frontier is bounded and the estimator frontier is not
the same policy. Note the sweep parameter is deliberately non-monotone in
revenue -- selfish mining takes a bigger share of a smaller pie, so raw block
rate is maximised by honesty and large lam returns there; it enumerates
policies rather than tracing a path.
Also closes a fairness loop these findings opened. Sec 6.7(1) credits uncle
rewards with compensating orphaned honest producers, computed on the SM1 race
where every orphan is a first-fork block. Under a private chain 20-40% of the
honest blocks destroyed are unreferenceable by construction, so those producers
are uncompensatable at ANY w_u -- not underpaid because p_ref is low, but
unreachable because no valid block may name them. The fairness guarantee
inherits the same first-fork ceiling as the density repair. Logged as item 19,
flagged as a protocol-design question rather than something a schedule fixes.
_solve_mdp is refactored into _solve_reward/_greedy_policy/_stationary/
_policy_rates so both objectives share one implementation; optimal_policy_stats
reproduces its committed figures exactly (eta 0.4413, D-hat 0.9447/0.8111 at
alpha=0.4). 251 tests pass.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-06 14:54:52 +02:00
|
|
|
for a, g in df.groupby("alpha"):
|
|
|
|
|
ro_r, ro_d = g.revenue_optimal.iloc[0], g.dhat_revenue_optimal.iloc[0]
|
|
|
|
|
b = best[best.alpha == a]
|
|
|
|
|
if b.empty:
|
Score the deflation frontier on absolute pay, not revenue share
Verification of my own item-16 result, which I had flagged as resting on a
step I had not checked: "break-even" was measured as revenue SHARE >= stake
share, and share-to-pay equivalence rests on sec 6.6's issuance argument. It
does not hold as stated.
A revenue share is measured against the canonical block rate, but pay is set by
the estimator. TSI drives COUNTED density -- canonical plus recovered uncles --
to f per slot, so a run yields f/D-hat block-finding events per slot and a
deflating attack makes the lottery easier for everyone. Pay per slot is
adv_rate * f / D-hat against an honest miner's alpha * f, so the correction on
the share ratio is density/D-hat, which is below 1 whenever any uncle is
recovered. Scoring on share credits the attacker with pay it never receives.
The headline survives; one row does not. At alpha = 0.4 the true break-even
point deflates to 0.649 rather than 0.642 (against the revenue-optimal 0.811),
so 16 points of free deflation instead of 17. But at alpha = 0.36 the policy I
reported as paying 1.033x stake actually pays 0.95x, and NO policy in the sweep
both pays and deflates there -- that row is withdrawn. The exposure therefore
opens near alpha ~ 0.37-0.38, above the 1/3 selfish threshold, not at it. That
is a better result for the protocol than the one I published yesterday, which
is the direction these corrections usually do not go.
pay_vs_honest is now computed in deflation_frontier() and is the criterion
best_profitable() selects on, so the claim is reproducible rather than resting
on a hand check. The conversion neglects the multi-winner factor c(f) ~ 1.017
between events and occupied slots (sec 2.1), noted inline and small against a
16-point effect. Downstream citations in sec 1, sec 8.2 and item 16 updated.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-06 17:54:37 +02:00
|
|
|
print(f"{a:6.3f} {ro_r:12.4f} {ro_d:10.4f} | (no policy in the sweep both pays "
|
|
|
|
|
f"and deflates)")
|
Item 16: the revenue-optimal adversary is not the estimator's worst case
Both eta ceilings in sec 6.6 come from adversaries optimising something else
(revenue, reorg depth), so they bound eta from above without bounding the
damage from below. Optimising the estimate directly needs no ratio transform:
each transition consumes exactly one block-finding event, so minimising
D-hat = (canonical + p_ref * countable uncles)/events is a plain average-reward
MDP over the transition table that already carries the orphan counts. One
value-iteration pass, no bisection.
Unconstrained, the answer degenerates -- and usefully. The optimum is pure
abstention: publish nothing, adopt when overtaken, D-hat = 1 - alpha exactly,
revenue zero. That is sec 6.4's withholding, which the report already shows is
CORRECT measurement rather than mis-measurement, so the unconstrained objective
asks the wrong question.
The constrained one bites. Sweeping lam * (adversary blocks) - (contribution to
D-hat) enumerates policies; the line of interest is where revenue SHARE reaches
alpha, i.e. where attacking costs nothing versus mining honestly. At alpha=0.4
such a policy drives D-hat to 0.642 where the revenue-maximiser reaches 0.811
-- 17 points of extra deflation bought with the selfish premium alone. At 0.36
and 0.45 the gaps are 0.082 and 0.103. Below the 1/3 threshold nothing
profitable deflates, so the exposure starts exactly where selfish mining does.
This revises two claims that were about revenue but read as though they were
about the adversary in general: sec 6.7's "the adversary frontier is exactly
optimal selfish mining; no compounding lever remains" and sec 8.2's echo of it.
Both now say the PROFIT frontier is bounded and the estimator frontier is not
the same policy. Note the sweep parameter is deliberately non-monotone in
revenue -- selfish mining takes a bigger share of a smaller pie, so raw block
rate is maximised by honesty and large lam returns there; it enumerates
policies rather than tracing a path.
Also closes a fairness loop these findings opened. Sec 6.7(1) credits uncle
rewards with compensating orphaned honest producers, computed on the SM1 race
where every orphan is a first-fork block. Under a private chain 20-40% of the
honest blocks destroyed are unreferenceable by construction, so those producers
are uncompensatable at ANY w_u -- not underpaid because p_ref is low, but
unreachable because no valid block may name them. The fairness guarantee
inherits the same first-fork ceiling as the density repair. Logged as item 19,
flagged as a protocol-design question rather than something a schedule fixes.
_solve_mdp is refactored into _solve_reward/_greedy_policy/_stationary/
_policy_rates so both objectives share one implementation; optimal_policy_stats
reproduces its committed figures exactly (eta 0.4413, D-hat 0.9447/0.8111 at
alpha=0.4). 251 tests pass.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-06 14:54:52 +02:00
|
|
|
continue
|
|
|
|
|
b = b.iloc[0]
|
|
|
|
|
print(f"{a:6.3f} {ro_r:12.4f} {ro_d:10.4f} | {b.revenue:14.4f} "
|
Score the deflation frontier on absolute pay, not revenue share
Verification of my own item-16 result, which I had flagged as resting on a
step I had not checked: "break-even" was measured as revenue SHARE >= stake
share, and share-to-pay equivalence rests on sec 6.6's issuance argument. It
does not hold as stated.
A revenue share is measured against the canonical block rate, but pay is set by
the estimator. TSI drives COUNTED density -- canonical plus recovered uncles --
to f per slot, so a run yields f/D-hat block-finding events per slot and a
deflating attack makes the lottery easier for everyone. Pay per slot is
adv_rate * f / D-hat against an honest miner's alpha * f, so the correction on
the share ratio is density/D-hat, which is below 1 whenever any uncle is
recovered. Scoring on share credits the attacker with pay it never receives.
The headline survives; one row does not. At alpha = 0.4 the true break-even
point deflates to 0.649 rather than 0.642 (against the revenue-optimal 0.811),
so 16 points of free deflation instead of 17. But at alpha = 0.36 the policy I
reported as paying 1.033x stake actually pays 0.95x, and NO policy in the sweep
both pays and deflates there -- that row is withdrawn. The exposure therefore
opens near alpha ~ 0.37-0.38, above the 1/3 selfish threshold, not at it. That
is a better result for the protocol than the one I published yesterday, which
is the direction these corrections usually do not go.
pay_vs_honest is now computed in deflation_frontier() and is the criterion
best_profitable() selects on, so the claim is reproducible rather than resting
on a hand check. The conversion neglects the multi-winner factor c(f) ~ 1.017
between events and occupied slots (sec 2.1), noted inline and small against a
16-point effect. Downstream citations in sec 1, sec 8.2 and item 16 updated.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-06 17:54:37 +02:00
|
|
|
f"{b.pay_vs_honest:8.3f} {b.dhat_countable:7.4f} {ro_d - b.dhat_countable:+16.4f}")
|
|
|
|
|
print("\n(extra deflation > 0 means the paying-but-deflating policy beats the revenue-optimal "
|
|
|
|
|
"one at damaging the estimate, at no cost versus honest mining; 'pay/hon' is ABSOLUTE "
|
|
|
|
|
"reward per unit time, not revenue share)")
|
Item 16: the revenue-optimal adversary is not the estimator's worst case
Both eta ceilings in sec 6.6 come from adversaries optimising something else
(revenue, reorg depth), so they bound eta from above without bounding the
damage from below. Optimising the estimate directly needs no ratio transform:
each transition consumes exactly one block-finding event, so minimising
D-hat = (canonical + p_ref * countable uncles)/events is a plain average-reward
MDP over the transition table that already carries the orphan counts. One
value-iteration pass, no bisection.
Unconstrained, the answer degenerates -- and usefully. The optimum is pure
abstention: publish nothing, adopt when overtaken, D-hat = 1 - alpha exactly,
revenue zero. That is sec 6.4's withholding, which the report already shows is
CORRECT measurement rather than mis-measurement, so the unconstrained objective
asks the wrong question.
The constrained one bites. Sweeping lam * (adversary blocks) - (contribution to
D-hat) enumerates policies; the line of interest is where revenue SHARE reaches
alpha, i.e. where attacking costs nothing versus mining honestly. At alpha=0.4
such a policy drives D-hat to 0.642 where the revenue-maximiser reaches 0.811
-- 17 points of extra deflation bought with the selfish premium alone. At 0.36
and 0.45 the gaps are 0.082 and 0.103. Below the 1/3 threshold nothing
profitable deflates, so the exposure starts exactly where selfish mining does.
This revises two claims that were about revenue but read as though they were
about the adversary in general: sec 6.7's "the adversary frontier is exactly
optimal selfish mining; no compounding lever remains" and sec 8.2's echo of it.
Both now say the PROFIT frontier is bounded and the estimator frontier is not
the same policy. Note the sweep parameter is deliberately non-monotone in
revenue -- selfish mining takes a bigger share of a smaller pie, so raw block
rate is maximised by honesty and large lam returns there; it enumerates
policies rather than tracing a path.
Also closes a fairness loop these findings opened. Sec 6.7(1) credits uncle
rewards with compensating orphaned honest producers, computed on the SM1 race
where every orphan is a first-fork block. Under a private chain 20-40% of the
honest blocks destroyed are unreferenceable by construction, so those producers
are uncompensatable at ANY w_u -- not underpaid because p_ref is low, but
unreachable because no valid block may name them. The fairness guarantee
inherits the same first-fork ceiling as the density repair. Logged as item 19,
flagged as a protocol-design question rather than something a schedule fixes.
_solve_mdp is refactored into _solve_reward/_greedy_policy/_stationary/
_policy_rates so both objectives share one implementation; optimal_policy_stats
reproduces its committed figures exactly (eta 0.4413, D-hat 0.9447/0.8111 at
alpha=0.4). 251 tests pass.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-06 14:54:52 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def fig37(df: pd.DataFrame) -> None:
|
|
|
|
|
import matplotlib.pyplot as plt
|
|
|
|
|
style.apply_style()
|
|
|
|
|
fig, axes = plt.subplots(1, 2, figsize=(9.6, 3.8))
|
|
|
|
|
|
|
|
|
|
# LEFT: the frontier itself for a few alphas — revenue share against the estimate
|
|
|
|
|
ax = axes[0]
|
|
|
|
|
for i, a in enumerate([0.36, 0.40, 0.45]):
|
|
|
|
|
g = df[np.isclose(df.alpha, a)].sort_values("lam")
|
|
|
|
|
c = style.OKABE_ITO[i + 1]
|
|
|
|
|
ax.plot(g.revenue, g.dhat_countable, "-o", ms=3, color=c, label=rf"$\alpha={a}$")
|
|
|
|
|
ax.axvline(a, color=c, lw=0.7, ls=":")
|
|
|
|
|
ro = g.dhat_revenue_optimal.iloc[0]
|
|
|
|
|
ax.plot([g.revenue_optimal.iloc[0]], [ro], "*", ms=11, color=c)
|
|
|
|
|
ax.set_xlabel("adversary revenue share (dotted line = its stake, i.e. break-even)")
|
|
|
|
|
ax.set_ylabel(r"$\hat D / D^*$")
|
|
|
|
|
ax.set_title("Profit/deflation frontier (★ = revenue-optimal)")
|
|
|
|
|
ax.legend(fontsize=7, loc="lower right")
|
|
|
|
|
|
|
|
|
|
# RIGHT: how much worse the estimator gets once the adversary stops maximising revenue
|
|
|
|
|
ax = axes[1]
|
|
|
|
|
best = best_profitable(df).sort_values("alpha")
|
|
|
|
|
ref = df.groupby("alpha").agg(d_ro=("dhat_revenue_optimal", "first"),
|
|
|
|
|
d_ab=("dhat_abstention", "first")).reset_index()
|
|
|
|
|
ax.plot(ref.alpha, ref.d_ro, "-*", ms=9, color=style.OKABE_ITO[2],
|
|
|
|
|
label="revenue-optimal (§6.6)")
|
|
|
|
|
ax.plot(best.alpha, best.dhat_countable, "-o", ms=4, color=style.OKABE_ITO[3],
|
|
|
|
|
label="best deflation at break-even pay")
|
|
|
|
|
ax.plot(ref.alpha, ref.d_ab, "--v", ms=4, color=style.OKABE_ITO[1],
|
|
|
|
|
label=r"abstention ($1-\alpha$, unpaid)")
|
|
|
|
|
ax.axhline(1.0, color="0.5", lw=0.9, ls="--")
|
|
|
|
|
ax.set_xlabel(r"adversary stake $\alpha$")
|
|
|
|
|
ax.set_ylabel(r"$\hat D / D^*$")
|
|
|
|
|
ax.set_title("The revenue-optimal adversary is not the worst case")
|
|
|
|
|
ax.legend(fontsize=7, loc="lower left")
|
|
|
|
|
|
|
|
|
|
style.save(fig, FIGS / "fig37_deflation_frontier", provenance="scripts/deflation_frontier.py")
|
|
|
|
|
plt.close(fig)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def main() -> None:
|
|
|
|
|
print(f"=== profit/deflation frontier (MDP cap={CAP}, gamma={GAMMA}) ===")
|
|
|
|
|
df = sweep()
|
|
|
|
|
report(df)
|
|
|
|
|
fig37(df)
|
|
|
|
|
print("wrote fig37_deflation_frontier")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
main()
|