phase15.py raw

   1  #!/usr/bin/env python3
   2  """
   3  Phase 1.5 battery: griefing, propagation, timestamp attacks under the
   4  candidate final design (direct e + monotonic stamps + optional delay
   5  floor + asymmetric DAA gains).
   6  
   7  Sweeps:
   8    - delay floor D in {0, 30, 60}s, attacker hardware multiplier hw
   9    - DAA: symmetric (50M/10M) vs asymmetric raise-slow/lower-fast
  10      (5M/1M up, 50M/10M down)
  11    - flood 50x/30min and 1000x/2h, monopoly 90%, future/past stamp
  12      attacks, propagation races, multi-seed stability
  13  
  14  Run:  python3 phase15.py
  15  """
  16  import os
  17  import random
  18  import sys
  19  
  20  sys.path.insert(0, os.path.dirname(__file__))
  21  import mtp_sim as M
  22  
  23  
  24  class Cfg:
  25      def __init__(self, scenario="steady", blocks=8000, seed=7, delay=0.0,
  26                   att_hw=1.0, asym=False, prop_base=0.0, bandwidth=100e6,
  27                   drop=0.9, ramp_x=10.0, flood_x=50, flood_min=30,
  28                   attack_share=0.6, stall_frac=0.001, stall_days=4):
  29          self.window = 101
  30          self.future = 60
  31          self.kp = 10e6
  32          self.ki = 10e6
  33          self.kd = 0
  34          self.alpha = 0.05
  35          self.lp = 0.15
  36          self.fill = 6600
  37          self.ebasis = "direct"
  38          self.monotonic = True
  39          self.scenario = scenario
  40          self.blocks = blocks
  41          self.seed = seed
  42          self.delay = delay
  43          self.att_hw = att_hw
  44          self.prop_base = prop_base
  45          self.bandwidth = bandwidth
  46          self.drop = drop
  47          self.ramp_x = ramp_x
  48          self.flood_x = flood_x
  49          self.flood_min = flood_min
  50          self.attack_share = attack_share
  51          self.stall_frac = stall_frac
  52          self.stall_days = stall_days
  53          if asym:
  54              # asymmetric integral: raise-slow / lower-fast
  55              self.ki_up = 5e6
  56              self.ki_down = 50e6
  57  
  58  
  59  def run(cfg):
  60      res = M.run(cfg)
  61      res["orphans"] = cfg.blocks  # placeholder, replaced below
  62      return res
  63  
  64  
  65  def metrics(cfg):
  66      rng = random.Random(cfg.seed)
  67      world = M.scenarios(cfg)
  68      chain = M.Chain(cfg)
  69      chain.add_block(0.0, False, seed=True)
  70      exit_t = M.flood_exit(cfg)
  71      for _ in range(cfg.blocks):
  72          h, a, mode = world.rates_at(chain.t)
  73          total = h + a
  74          if total <= 0:
  75              break
  76          dt = rng.expovariate(total / (M.T * chain.d))
  77          arr = chain.t + dt
  78          nxt = world.next_boundary(chain.t)
  79          if nxt is not None and arr >= nxt:
  80              chain.advance_time(nxt)
  81              continue
  82          attacker = rng.random() < a / total
  83          hw = chain.att_hw if attacker else 1.0
  84          min_arr = chain.last_block_t + chain.delay * hw
  85          if arr < min_arr:
  86              arr = min_arr
  87          if nxt is not None and arr >= nxt:
  88              chain.advance_time(nxt)
  89              continue
  90          if chain.prop_base > 0:
  91              est_e = max(1.0, arr - chain.last_block_t)
  92              est_size = (M.S_MAX * est_e / M.T) if attacker else min(M.S_MAX * est_e / M.T, chain.backlog)
  93              prop = chain.prop_base + est_size / chain.bandwidth
  94              hw_other = chain.att_hw if not attacker else 1.0
  95              other_rate = (a if not attacker else h) / (M.T * chain.d)
  96              compet_floor = chain.last_block_t + chain.delay * hw_other
  97              compet_window = max(0.0, arr + prop - compet_floor)
  98              if rng.random() < compet_window * other_rate * 0.5:
  99                  chain.orphans += 1
 100                  continue
 101          chain.add_block(arr, attacker, mode=mode if attacker else "honest")
 102  
 103      ints = sorted(b[2] for b in chain.blocks if b[2] > 0)
 104      n = max(1, len(ints))
 105      out = {
 106          "blocks": chain.height,
 107          "mean_int": sum(ints) / n,
 108          "med_int": ints[n // 2],
 109          "p90": ints[int(n * 0.9)],
 110          "iss": chain.issuance / (M.R_FULL * chain.t / M.T),
 111          "d_max": max(b[3] for b in chain.blocks),
 112          "d_fin": chain.d,
 113          "att": chain.attacker_reward,
 114          "honest": chain.honest_reward,
 115          "orphans": chain.orphans,
 116          "viol": chain.stamp_violations,
 117          "maxblk": chain.max_block_size / 1e6,
 118          "backlog": chain.max_backlog / 1e6,
 119      }
 120      if exit_t is not None:
 121          post = [b for b in chain.blocks if b[0] >= exit_t and not b[1]]
 122          if post:
 123              out["first_honest"] = (post[0][0] - exit_t) / 3600
 124              good = 0
 125              out["restore"] = None
 126              for b in post:
 127                  if abs(b[2] - M.T) <= 0.5 * M.T:
 128                      good += 1
 129                  else:
 130                      good = 0
 131                  if good >= 3:
 132                      out["restore"] = (b[0] - exit_t) / 3600
 133                      break
 134      return out
 135  
 136  
 137  def header():
 138      print(f"{'scenario':14s} {'D':>4s} {'asym':>4s} {'hw':>4s} {'prop':>4s} "
 139            f"{'seed':>4s} {'d_max':>6s} {'d_fin':>6s} {'att':>7s} {'orph':>5s} "
 140            f"{'restore':>8s} {'iss':>6s} {'blk':>7s}")
 141  
 142  
 143  def row(label, cfg, r):
 144      restore = r.get("restore")
 145      restore_s = f"{restore:7.1f}h" if restore is not None else "     -"
 146      asym = 1 if getattr(cfg, "ki_up", cfg.ki) < cfg.ki_down else 0
 147      print(f"{label:14s} {cfg.delay:4.0f} {asym:4d} "
 148            f"{cfg.att_hw:4.1f} {cfg.prop_base:4.0f} {cfg.seed:4d} "
 149            f"{r['d_max']:6.2f} {r['d_fin']:6.2f} {r['att']:7.2f} {r['orphans']:5d} "
 150            f"{restore_s} {r['iss']:6.3f} {r['maxblk']:7.1f}")
 151  
 152  
 153  def main():
 154      print("== A1/A2: flood matrix (50x/30min) ==")
 155      header()
 156      for seed in (7, 8):
 157          for delay in (0.0, 30.0, 60.0):
 158              for asym in (False, True):
 159                  cfg = Cfg("flood", 8000, seed, delay=delay, asym=asym)
 160                  row(f"flood50-{delay:g}", cfg, metrics(cfg))
 161  
 162      print("\n== A1: flood 1000x/2h ==")
 163      header()
 164      for seed in (7, 8):
 165          for delay in (0.0, 30.0, 60.0):
 166              for asym in (False, True):
 167                  cfg = Cfg("flood", 12000, seed, delay=delay, asym=asym,
 168                            flood_x=1000, flood_min=120)
 169                  row(f"flood1k-{delay:g}", cfg, metrics(cfg))
 170  
 171      print("\n== A4: monopoly 90% sustained ==")
 172      header()
 173      for seed in (7, 8):
 174          for delay in (0.0, 30.0, 60.0):
 175              cfg = Cfg("monopoly", 12000, seed, delay=delay)
 176              row(f"mono90-{delay:g}", cfg, metrics(cfg))
 177  
 178      print("\n== A5: clock-pin (future 60%) with delay ==")
 179      header()
 180      for seed in (7, 8):
 181          for delay in (0.0, 30.0, 60.0):
 182              cfg = Cfg("future_majority", 8000, seed, delay=delay,
 183                        attack_share=0.6)
 184              row(f"future-{delay:g}", cfg, metrics(cfg))
 185              cfg2 = Cfg("past_majority", 8000, seed, delay=delay,
 186                         attack_share=0.6)
 187              row(f"past-{delay:g}", cfg2, metrics(cfg2))
 188  
 189      print("\n== B: propagation (12s base, 100MB/s) ==")
 190      header()
 191      for seed in (7, 8):
 192          for delay in (0.0, 60.0):
 193              cfg = Cfg("steady", 8000, seed, delay=delay, prop_base=12.0)
 194              row(f"prop-{delay:g}", cfg, metrics(cfg))
 195              cfg2 = Cfg("stall", 6000, seed, delay=delay, prop_base=12.0)
 196              row(f"stall-{delay:g}", cfg2, metrics(cfg2))
 197  
 198      print("\n== D: multi-seed steady + flood, final config ==")
 199      header()
 200      for seed in (1, 2, 3, 4, 5, 6, 7, 8, 9, 10):
 201          cfg = Cfg("steady", 8000, seed, delay=60.0, asym=True)
 202          row(f"steady", cfg, metrics(cfg))
 203      for seed in (1, 2, 3, 4, 5):
 204          cfg = Cfg("flood", 8000, seed, delay=60.0, asym=True)
 205          row(f"flood", cfg, metrics(cfg))
 206  
 207  
 208  if __name__ == "__main__":
 209      main()
 210