mirror of
https://github.com/logos-blockchain/research.git
synced 2026-08-07 19:53:10 +00:00
120 lines
5.3 KiB
Python
120 lines
5.3 KiB
Python
"""Soft (reward-weighted) uncle inclusion — REPORT §6.7 / §6.8 (fig15).
|
|
|
|
Inclusion is a SOFT rule (omission forfeits the nephew reward; it is NOT a block-validity rule) —
|
|
because no node can prove which forks a producer could see within the window, so a *validity* rule
|
|
cannot be encoded fork-safely (§6.8). Under a soft rule the reference rate ``p_ref`` is EMERGENT: an
|
|
honest orphan was published, so any honest canonical block that sees it within ``W`` references it
|
|
for the nephew reward — the attacker only suppresses on *its own* canonical blocks. So ``p_ref`` is
|
|
high in practice (honest referencers), driven toward 1 by a larger ``W`` and toward 0 only by deep
|
|
reorgs whose orphans age out of the window before an honest block references them.
|
|
|
|
fig15 sweeps that emergent ``p_ref`` for the SM1 selfish attacker (gamma=0, self-uncle on):
|
|
LEFT — reward share vs ``p_ref``: the ``p_ref → 0`` end (attacker suppresses all / tiny ``W``) is
|
|
the *backfire* (share above block-only); the honest-referencer / large-``W`` end
|
|
(``p_ref → 1``) reaches ~stake. The crossover below block-only is near ``p_ref ≈ 0.3``.
|
|
RIGHT — honest-orphan reward recovery vs ``p_ref`` (the fairness metric).
|
|
|
|
So the soft rule delivers the fairness + selfish-mitigation of a hard mandate *without* the fork
|
|
risk, to the extent ``W``/visibility keep ``p_ref`` high; the residual is exactly the "can't
|
|
guarantee a node sees every fork in the window" gap. Also prints the bribery bound (§6.9).
|
|
|
|
Run: python scripts/reward_mandate.py (writes runs/reward_mandate.parquet + fig15)
|
|
"""
|
|
|
|
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 import RewardParams, honest_reward_recovery, race_from_alpha, reward_shares
|
|
|
|
HERE = Path(__file__).resolve().parent.parent
|
|
RUNS = HERE / "runs"
|
|
FIGS = HERE / "report-figures"
|
|
RUNS.mkdir(exist_ok=True)
|
|
FIGS.mkdir(exist_ok=True)
|
|
|
|
N_EVENTS = 4_000_000
|
|
ALPHAS = [0.35, 0.40, 0.46] # near / above the selfish threshold, where uncle rewards matter
|
|
P_REFS = [0.0, 0.2, 0.3, 0.5, 0.7, 0.85, 1.0]
|
|
W_U, W_N = 0.875, 0.03125 # Ethereum-like: w_u + w_n = 0.906 < 1 (farming-safe)
|
|
|
|
|
|
def sweep() -> pd.DataFrame:
|
|
rng = np.random.default_rng(11)
|
|
rows = []
|
|
for alpha in ALPHAS:
|
|
r = race_from_alpha(alpha, N_EVENTS, 0.0, rng)
|
|
for p in P_REFS:
|
|
# soft rule: honest referencers take the nephew (adv_nephew=0), attacker self-uncles
|
|
rp = RewardParams(w_uncle=W_U, w_nephew=W_N, p_ref=p, p_ref_adv=1.0, adv_nephew=0.0)
|
|
rec = honest_reward_recovery(r, RewardParams(w_uncle=W_U, p_ref=p))
|
|
rows.append(dict(alpha=alpha, p_ref=p, block=r.revenue_share,
|
|
reward_share=reward_shares(r, rp).adv_reward_share, recovery=rec))
|
|
out = pd.DataFrame(rows)
|
|
out.to_parquet(RUNS / "reward_mandate.parquet")
|
|
return out
|
|
|
|
|
|
def report(df: pd.DataFrame) -> None:
|
|
print(f"Soft rule, Ethereum-like w_u={W_U}, w_n={W_N} (sum {W_U+W_N:.3f} < 1, farming-safe):")
|
|
for alpha in ALPHAS:
|
|
s = df[df.alpha == alpha].sort_values("p_ref")
|
|
block = s.block.iloc[0]
|
|
print(f"alpha={alpha} block_share={block:.3f} stake={alpha}: reward_share by p_ref")
|
|
for _, row in s.iterrows():
|
|
tag = " BACKFIRE" if row.reward_share > block + 1e-3 else ""
|
|
print(f" p_ref={row.p_ref:.2f}: share={row.reward_share:.3f} "
|
|
f"rec={row.recovery:.2f}{tag}")
|
|
print("\nbribery to suppress a reference (§6.9): a soft rule costs the briber only w_nephew =",
|
|
W_N, "(cheap) but never forks; a hard rule would cost a full block but cannot be encoded "
|
|
"fork-safely.")
|
|
|
|
|
|
def fig15(df: pd.DataFrame) -> None:
|
|
import matplotlib.pyplot as plt
|
|
style.apply_style()
|
|
fig, axes = plt.subplots(1, 2, figsize=(9.6, 3.8))
|
|
|
|
ax = axes[0]
|
|
for i, alpha in enumerate(ALPHAS):
|
|
s = df[df.alpha == alpha].sort_values("p_ref")
|
|
c = style.OKABE_ITO[i]
|
|
ax.plot(s.p_ref, s.reward_share, "-o", ms=4, color=c, label=rf"$\alpha={alpha}$")
|
|
ax.axhline(s.block.iloc[0], color=c, lw=0.8, ls=":", alpha=0.7) # block-only reference
|
|
ax.axvspan(0, 0.3, color="0.9", label="attacker-suppressed / small W")
|
|
ax.set_xlabel(r"emergent reference rate $p_{\rm ref}$ (grows with $W$)")
|
|
ax.set_ylabel("attacker reward share")
|
|
ax.set_title("Soft rule: high $p_{\\rm ref}$ (honest refs) → ~stake")
|
|
ax.legend(fontsize=7, loc="upper right")
|
|
|
|
ax = axes[1]
|
|
for i, alpha in enumerate(ALPHAS):
|
|
s = df[df.alpha == alpha].sort_values("p_ref")
|
|
ax.plot(s.p_ref, s.recovery, "-o", ms=4, color=style.OKABE_ITO[i],
|
|
label=rf"$\alpha={alpha}$")
|
|
ax.axhline(1.0, color="0.5", lw=0.8, ls="--")
|
|
ax.set_xlabel(r"emergent reference rate $p_{\rm ref}$")
|
|
ax.set_ylabel("honest reward recovery")
|
|
ax.set_title(r"Honest-orphan compensation ($w_u=0.875$)")
|
|
ax.set_ylim(0, 1.05)
|
|
ax.legend(fontsize=7, loc="lower right")
|
|
|
|
style.save(fig, FIGS / "fig15_mandate", provenance="scripts/reward_mandate.py")
|
|
plt.close(fig)
|
|
|
|
|
|
def main() -> None:
|
|
print("=== soft (reward-weighted) uncle inclusion: reward share vs emergent p_ref ===")
|
|
df = sweep()
|
|
report(df)
|
|
fig15(df)
|
|
print("wrote fig15_mandate")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|