#!/usr/bin/env python3 """ Phase 1.5 battery: griefing, propagation, timestamp attacks under the candidate final design (direct e + monotonic stamps + optional delay floor + asymmetric DAA gains). Sweeps: - delay floor D in {0, 30, 60}s, attacker hardware multiplier hw - DAA: symmetric (50M/10M) vs asymmetric raise-slow/lower-fast (5M/1M up, 50M/10M down) - flood 50x/30min and 1000x/2h, monopoly 90%, future/past stamp attacks, propagation races, multi-seed stability Run: python3 phase15.py """ import os import random import sys sys.path.insert(0, os.path.dirname(__file__)) import mtp_sim as M class Cfg: def __init__(self, scenario="steady", blocks=8000, seed=7, delay=0.0, att_hw=1.0, asym=False, prop_base=0.0, bandwidth=100e6, drop=0.9, ramp_x=10.0, flood_x=50, flood_min=30, attack_share=0.6, stall_frac=0.001, stall_days=4): self.window = 101 self.future = 60 self.kp = 10e6 self.ki = 10e6 self.kd = 0 self.alpha = 0.05 self.lp = 0.15 self.fill = 6600 self.ebasis = "direct" self.monotonic = True self.scenario = scenario self.blocks = blocks self.seed = seed self.delay = delay self.att_hw = att_hw self.prop_base = prop_base self.bandwidth = bandwidth self.drop = drop self.ramp_x = ramp_x self.flood_x = flood_x self.flood_min = flood_min self.attack_share = attack_share self.stall_frac = stall_frac self.stall_days = stall_days if asym: # asymmetric integral: raise-slow / lower-fast self.ki_up = 5e6 self.ki_down = 50e6 def run(cfg): res = M.run(cfg) res["orphans"] = cfg.blocks # placeholder, replaced below return res def metrics(cfg): rng = random.Random(cfg.seed) world = M.scenarios(cfg) chain = M.Chain(cfg) chain.add_block(0.0, False, seed=True) exit_t = M.flood_exit(cfg) for _ in range(cfg.blocks): h, a, mode = world.rates_at(chain.t) total = h + a if total <= 0: break dt = rng.expovariate(total / (M.T * chain.d)) arr = chain.t + dt nxt = world.next_boundary(chain.t) if nxt is not None and arr >= nxt: chain.advance_time(nxt) continue attacker = rng.random() < a / total hw = chain.att_hw if attacker else 1.0 min_arr = chain.last_block_t + chain.delay * hw if arr < min_arr: arr = min_arr if nxt is not None and arr >= nxt: chain.advance_time(nxt) continue if chain.prop_base > 0: est_e = max(1.0, arr - chain.last_block_t) est_size = (M.S_MAX * est_e / M.T) if attacker else min(M.S_MAX * est_e / M.T, chain.backlog) prop = chain.prop_base + est_size / chain.bandwidth hw_other = chain.att_hw if not attacker else 1.0 other_rate = (a if not attacker else h) / (M.T * chain.d) compet_floor = chain.last_block_t + chain.delay * hw_other compet_window = max(0.0, arr + prop - compet_floor) if rng.random() < compet_window * other_rate * 0.5: chain.orphans += 1 continue chain.add_block(arr, attacker, mode=mode if attacker else "honest") ints = sorted(b[2] for b in chain.blocks if b[2] > 0) n = max(1, len(ints)) out = { "blocks": chain.height, "mean_int": sum(ints) / n, "med_int": ints[n // 2], "p90": ints[int(n * 0.9)], "iss": chain.issuance / (M.R_FULL * chain.t / M.T), "d_max": max(b[3] for b in chain.blocks), "d_fin": chain.d, "att": chain.attacker_reward, "honest": chain.honest_reward, "orphans": chain.orphans, "viol": chain.stamp_violations, "maxblk": chain.max_block_size / 1e6, "backlog": chain.max_backlog / 1e6, } if exit_t is not None: post = [b for b in chain.blocks if b[0] >= exit_t and not b[1]] if post: out["first_honest"] = (post[0][0] - exit_t) / 3600 good = 0 out["restore"] = None for b in post: if abs(b[2] - M.T) <= 0.5 * M.T: good += 1 else: good = 0 if good >= 3: out["restore"] = (b[0] - exit_t) / 3600 break return out def header(): print(f"{'scenario':14s} {'D':>4s} {'asym':>4s} {'hw':>4s} {'prop':>4s} " f"{'seed':>4s} {'d_max':>6s} {'d_fin':>6s} {'att':>7s} {'orph':>5s} " f"{'restore':>8s} {'iss':>6s} {'blk':>7s}") def row(label, cfg, r): restore = r.get("restore") restore_s = f"{restore:7.1f}h" if restore is not None else " -" asym = 1 if getattr(cfg, "ki_up", cfg.ki) < cfg.ki_down else 0 print(f"{label:14s} {cfg.delay:4.0f} {asym:4d} " f"{cfg.att_hw:4.1f} {cfg.prop_base:4.0f} {cfg.seed:4d} " f"{r['d_max']:6.2f} {r['d_fin']:6.2f} {r['att']:7.2f} {r['orphans']:5d} " f"{restore_s} {r['iss']:6.3f} {r['maxblk']:7.1f}") def main(): print("== A1/A2: flood matrix (50x/30min) ==") header() for seed in (7, 8): for delay in (0.0, 30.0, 60.0): for asym in (False, True): cfg = Cfg("flood", 8000, seed, delay=delay, asym=asym) row(f"flood50-{delay:g}", cfg, metrics(cfg)) print("\n== A1: flood 1000x/2h ==") header() for seed in (7, 8): for delay in (0.0, 30.0, 60.0): for asym in (False, True): cfg = Cfg("flood", 12000, seed, delay=delay, asym=asym, flood_x=1000, flood_min=120) row(f"flood1k-{delay:g}", cfg, metrics(cfg)) print("\n== A4: monopoly 90% sustained ==") header() for seed in (7, 8): for delay in (0.0, 30.0, 60.0): cfg = Cfg("monopoly", 12000, seed, delay=delay) row(f"mono90-{delay:g}", cfg, metrics(cfg)) print("\n== A5: clock-pin (future 60%) with delay ==") header() for seed in (7, 8): for delay in (0.0, 30.0, 60.0): cfg = Cfg("future_majority", 8000, seed, delay=delay, attack_share=0.6) row(f"future-{delay:g}", cfg, metrics(cfg)) cfg2 = Cfg("past_majority", 8000, seed, delay=delay, attack_share=0.6) row(f"past-{delay:g}", cfg2, metrics(cfg2)) print("\n== B: propagation (12s base, 100MB/s) ==") header() for seed in (7, 8): for delay in (0.0, 60.0): cfg = Cfg("steady", 8000, seed, delay=delay, prop_base=12.0) row(f"prop-{delay:g}", cfg, metrics(cfg)) cfg2 = Cfg("stall", 6000, seed, delay=delay, prop_base=12.0) row(f"stall-{delay:g}", cfg2, metrics(cfg2)) print("\n== D: multi-seed steady + flood, final config ==") header() for seed in (1, 2, 3, 4, 5, 6, 7, 8, 9, 10): cfg = Cfg("steady", 8000, seed, delay=60.0, asym=True) row(f"steady", cfg, metrics(cfg)) for seed in (1, 2, 3, 4, 5): cfg = Cfg("flood", 8000, seed, delay=60.0, asym=True) row(f"flood", cfg, metrics(cfg)) if __name__ == "__main__": main()