158 lines
7.0 KiB
Python
Raw Permalink Normal View History

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()