reorg_sim.py raw

   1  #!/usr/bin/env python3
   2  """
   3  Reorg race simulation - private sidechain vs public chain.
   4  
   5  Two chains race from a common tip:
   6    honest   : PoW rate 1/600s, PI-controlled delay D(e)
   7    attacker : PoW rate X/600s, delay floor D*hw per block
   8  
   9  Key properties:
  10    - minority attackers (X < 1) never outrun
  11    - majority attackers outrun but speed capped at 1/D
  12    - reorg depth quantized by delay: k confirmations costs k*D wall time
  13    - asymmetric PI: fast difficulty drop (ki_down=50M), slow rise (ki_up=5M)
  14  
  15  Run:  python3 reorg_sim.py
  16  """
  17  import argparse
  18  import random
  19  import sys
  20  
  21  T = 600.0       # target cadence
  22  D_MIN = 60.0    # delay floor
  23  KP = 10e6       # proportional gain
  24  KI_UP = 5e6     # integral gain (difficulty drop - fast)
  25  KI_DOWN = 50e6  # integral gain (difficulty rise - slow)
  26  ALPHA = 0.05    # EMA smoothing
  27  SETPOINT = 617.0  # target interval (setpoint shift)
  28  
  29  
  30  def delay_from_e(e):
  31      """Compute delay from error e (seconds deviation from target)."""
  32      # PI controller: delay = kp * e + ki * integral
  33      # For simplicity, use proportional only for the delay floor
  34      # The actual delay is max(D_MIN, kp * abs(e) / 1e9)
  35      delay = D_MIN + max(0, KP * abs(e) / 1e9)
  36      return delay
  37  
  38  
  39  def race(X, hw, days, seed=7):
  40      """Race the honest chain against a private attacker chain."""
  41      rng = random.Random(seed)
  42      t = 0.0
  43      honest = 0
  44      attacker = 0
  45      prev_honest_t = 0.0
  46      prev_att_t = 0.0
  47      honest_e = 0.0  # error accumulator for honest chain
  48      horizon = days * 86400.0
  49      outrun = None
  50      
  51      while t < horizon:
  52          # Honest chain delay
  53          dt_h = rng.expovariate(1.0 / T)
  54          h_delay = delay_from_e(honest_e)
  55          h_arr = t + max(dt_h, (prev_honest_t + h_delay) - t) if honest > 0 else t + dt_h
  56          
  57          # Attacker chain delay (constant hw multiplier)
  58          dt_a = rng.expovariate(X / T)
  59          a_delay = D_MIN * hw
  60          a_arr = t + max(dt_a, (prev_att_t + a_delay) - t) if attacker > 0 else t + dt_a
  61          
  62          # Process next block
  63          if h_arr <= a_arr:
  64              if honest == 0 or h_arr <= t:
  65                  h_arr = t + dt_h
  66              t = h_arr
  67              honest += 1
  68              prev_honest_t = t
  69              # Update error (actual interval vs target)
  70              if honest > 1:
  71                  actual_interval = t - (prev_honest_t - h_delay)
  72                  honest_e = honest_e * (1 - ALPHA) + (actual_interval - SETPOINT) * ALPHA
  73              else:
  74                  honest_e = 0.0
  75          else:
  76              if attacker == 0 or a_arr <= t:
  77                  a_arr = t + dt_a
  78              t = a_arr
  79              attacker += 1
  80              prev_att_t = t
  81          
  82          if outrun is None and attacker >= honest and honest > 0 and attacker > 0:
  83              outrun = t / 3600.0
  84      
  85      return honest, attacker, outrun
  86  
  87  
  88  def main():
  89      ap = argparse.ArgumentParser()
  90      ap.add_argument("--days", type=float, default=30.0)
  91      ap.add_argument("--seed", type=int, default=7)
  92      args = ap.parse_args()
  93  
  94      print(f"reorg race, {args.days} days, D_MIN={D_MIN}s, KP={KP/1e6}M, KI_UP={KI_UP/1e6}M, KI_DOWN={KI_DOWN/1e6}M")
  95      print(f"{'X':>7s} {'hw':>4s} | {'honest':>7s} {'attacker':>9s} {'ratio':>7s} | {'outrun_h':>9s}")
  96      for X in (0.5, 1.0, 2.0, 10.0, 100.0, 1e6):
  97          for hw in (1.0, 0.5):
  98              honest, attacker, outrun = race(X, hw, args.days, args.seed)
  99              ratio = attacker / max(1, honest)
 100              out_s = f"{outrun:8.1f}h" if outrun is not None else "   never"
 101              print(f"{X:7.1f} {hw:4.1f} | {honest:7d} {attacker:9d} {ratio:7.2f} | {out_s}")
 102  
 103      print()
 104      print("catch-up cost: blocks to reorg k honest confirmations (D_MIN=60s each)")
 105      for k in (6, 144, 2016):
 106          print(f"  {k} confirmations: minimum {k * D_MIN / 3600:.1f}h of attacker wall time")
 107  
 108  
 109  if __name__ == "__main__":
 110      main()
 111