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