From 4689ee23f2b69607d0a8c8efaff0c77f8fd0db11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Castro-Castilla?= Date: Wed, 4 Jun 2025 15:40:34 +0200 Subject: [PATCH] Cryptarchia v2 simulations --- cryptarchia-v2/.gitignore | 1 + cryptarchia-v2/cryptarchia-v2.4.ipynb | 1356 +++++++++++++++++++++++++ cryptarchia-v2/requirements.txt | 4 + 3 files changed, 1361 insertions(+) create mode 100644 cryptarchia-v2/.gitignore create mode 100644 cryptarchia-v2/cryptarchia-v2.4.ipynb create mode 100644 cryptarchia-v2/requirements.txt diff --git a/cryptarchia-v2/.gitignore b/cryptarchia-v2/.gitignore new file mode 100644 index 0000000..1d17dae --- /dev/null +++ b/cryptarchia-v2/.gitignore @@ -0,0 +1 @@ +.venv diff --git a/cryptarchia-v2/cryptarchia-v2.4.ipynb b/cryptarchia-v2/cryptarchia-v2.4.ipynb new file mode 100644 index 0000000..a78803b --- /dev/null +++ b/cryptarchia-v2/cryptarchia-v2.4.ipynb @@ -0,0 +1,1356 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1146, + "id": "ad657d5a-bd36-4329-b134-6745daff7ae9", + "metadata": { + "jupyter": { + "source_hidden": true + } + }, + "outputs": [], + "source": [ + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "from dataclasses import dataclass, replace\n", + "from pyvis.network import Network\n", + "from pyvis.options import Layout\n", + "import time\n", + "import collections\n", + "from collections import deque, defaultdict\n", + "import copy\n", + "import random\n", + "from functools import lru_cache\n", + "from joblib import Parallel, delayed" + ] + }, + { + "cell_type": "markdown", + "id": "71b7ae0d-bb4d-4ec8-b498-2482e254cf5b", + "metadata": { + "jp-MarkdownHeadingCollapsed": true + }, + "source": [ + "# Network model" + ] + }, + { + "cell_type": "code", + "execution_count": 1147, + "id": "a538cf45-d551-4603-b484-dbbc3f3d0a73", + "metadata": {}, + "outputs": [], + "source": [ + "@dataclass\n", + "class NetworkParams:\n", + " broadcast_delay_mean: int # second\n", + " pol_proof_time: int # seconds\n", + " # ---- blend network -- \n", + " blending_delay: int\n", + " dissemination_delay_mean: float\n", + " # desimenation_delay_var: float\n", + " blend_hops: int\n", + " no_network_delay: bool = False\n", + "\n", + " def sample_blending_delay(self):\n", + " return np.random.uniform(0, self.blending_delay)\n", + "\n", + " def sample_dissemination_delay(self):\n", + " return np.random.exponential(self.dissemination_delay_mean)\n", + "\n", + " def sample_blend_network_delay(self):\n", + " return sum(self.sample_blending_delay() + self.sample_dissemination_delay() for _ in range(self.blend_hops))\n", + " \n", + " def sample_broadcast_delay(self, blocks):\n", + " return np.random.exponential(self.broadcast_delay_mean, size=blocks.shape)\n", + "\n", + " def block_arrival_slot(self, block_slot):\n", + " if self.no_network_delay:\n", + " return block_slot\n", + " return self.pol_proof_time + self.sample_blend_network_delay() + self.sample_broadcast_delay(block_slot) + block_slot\n", + "\n", + " def empirical_network_delay(self, N=10000, M=1000):\n", + " return np.array([self.block_arrival_slot(np.zeros(M)) for _ in range(N)]).reshape(N*M)" + ] + }, + { + "cell_type": "code", + "execution_count": 1148, + "id": "17ef82f8-968c-48b0-bee7-f2642c8b3f3e", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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Y8aM0W7Nbdu2tfYg6tq1qwwbNkzy5MmjAUz//v0lU6ZM0qBBg+g+XyIiIooPAUuTJk3k+vXrOtAbimJRm7JhwwZr0WxQUJD2HDLcvn1bu0Fj3VSpUmmGZseOHdol2tCzZ08Netq3b69BzRtvvKH7dBxgjoiIiOInPwsGQvFyaEJCbyEU4MZEPYvtXEDPi3MJEREReX7/5lxCREREZHoMWIiIiMj0GLAQERGR6TFgiWWoh4nOmhgiIqL4gAELERERmR4DFiIiIjI9BixERERkegxYiIiIyPQYsBAREZHpMWAhIiIi02PAQkRERKbHgIWIiIhMjwELERERmR4DFiIiIjI9BixERERkegxYiIiIyPQYsBAREZHpJYzrA4ivbGdsPjuqTpweCxERkdkxw0JERESmx4CFiIiITI8BCxEREZkeAxYiIiIyPQYsREREZHoMWIiIiMj0GLAQERGR6TFgISIiItNjwEJERESmx4CFiIiITI8BCxEREZkeAxaTzCtkO7cQERER2WPAQkRERKbHgIWIiIhMjwELERERmR4DFiIiIjI9BixERERkegxYiIiIyPQYsBAREZFvBizTpk2T7NmzS5IkSaRMmTKyZ88el+vOnj1bKlSoIKlSpdKlevXq4dZv1aqV+Pn52S21atWKyqERERGRD/I4YFm2bJl069ZNBg4cKPv375eiRYtKzZo15dq1a07X37p1qzRr1kx+/fVX2blzp2TJkkVq1KghFy9etFsPAcrly5ety5IlSyS+4QByRERE0RSwjB8/Xtq1ayetW7eWggULysyZMyVZsmQyb948p+t/88030rFjRylWrJjkz59f5syZI2FhYbJlyxa79RInTiwZMmSwLsjGEBEREXkcsISEhEhgYKA26xgSJEigj5E9iYzg4GB5+vSppE6dOlwmJl26dJIvXz7p0KGD3Lx50+U+njx5Ivfu3bNbiIiIyHd5FLDcuHFDQkNDJX369HbP4/GVK1citY9evXpJpkyZ7IIeNActWrRIsy5ffvmlbNu2TWrXrq3v5czIkSMlZcqU1gXNTHElLCRMzn15XBf87O0ePgwRP7+xuuDn+PLevuBZcLB8W6iQLviZfIu3/H695TjJ+ySMzTcbNWqULF26VLMpKNg1NG3a1Ppz4cKFpUiRIpIrVy5dr1q1auH206dPH62jMSDDEpdBCxEREZko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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "blend_net = NetworkParams(\n", + " broadcast_delay_mean=0.5,\n", + " pol_proof_time=1,\n", + " blending_delay=3,\n", + " dissemination_delay_mean=0.5,\n", + " blend_hops=3,\n", + ")\n", + "no_blend_net = replace(blend_net, blend_hops=0)\n", + "\n", + "N = 100\n", + "M = 10000\n", + "no_blend_samples = no_blend_net.empirical_network_delay()\n", + "no_blend_mean = no_blend_samples.mean()\n", + "blend_samples = blend_net.empirical_network_delay()\n", + "blend_mean = blend_samples.mean()\n", + "\n", + "_ = plt.hist(no_blend_samples, bins=100, density=True, label=\"no-blend\")\n", + "_ = plt.hist(blend_samples, bins=100, density=True, label=\"blend\")\n", + "\n", + "for p in [50, 99, 99.9]:\n", + " no_blend_pct = np.percentile(no_blend_samples, p)\n", + " _ = plt.vlines(no_blend_pct, ymin=0, ymax=0.25, color='darkblue', label=f\"no-blend {p}p={no_blend_pct:.1f}s\")\n", + "\n", + "for p in [50, 99, 99.9]:\n", + " blend_pct = np.percentile(blend_samples, p)\n", + " _ = plt.vlines(blend_pct, ymin=0, ymax=0.25, color='brown', label=f\"blend {p}p={blend_pct:.1f}s\")\n", + "# _ = plt.vlines(blend_mean, ymin=0, ymax=1, color='brown', label=f\"blend 50p={blend_mean:.1f}s\")\n", + "# _ = plt.hist(blend_net.block_arrival_slot(np.zeros(1000)), bins=100, density=True, label=\"blend\")\n", + "_ = plt.legend()\n", + "_ = plt.xlabel(\"block delay\")" + ] + }, + { + "cell_type": "markdown", + "id": "51db3605-c164-44fe-aefa-c7bf2aad587b", + "metadata": { + "jp-MarkdownHeadingCollapsed": true + }, + "source": [ + "# Transaction dependencies (probabilistic models)" + ] + }, + { + "cell_type": "code", + "execution_count": 1149, + "id": "38b1e549-4f83-4f37-a563-8ba724d6d845", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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XJxaz5NPPim5ubk+fjbkjldffVVNuvn444+br+O+D22HDx+Wfv36qf1brFgxqV+/vmzatMl8O0ohX3zxRSlfvry6vXPnzrJnzx6rdZw+fVr69u2retyXKlVKHnzwQTl//rwfng05Ky8vT1544QWpXr262q81atSQ8ePHq/2t4b4PHT/99JPccsstapI5fMYvWrTI6nZP7evff/9d2rZtq44RMane66+/7pPnZ/lEKIjMmzfPFB0dbZo5c6bpzz//NA0aNMhUqlQp07Fjx/y9aeSGrl27mj766CPT9u3bTSkpKabu3bubqlSpYjp//rx5mSFDhpgqV65sWrlypWnTpk2mFi1amFq1amW+PTc311SvXj1T586dTVu3bjV9++23pqSkJNOoUaP89KzIVRs3bjRVq1bN1KBBA9Pw4cPN13Pfh67Tp0+bqlatanrggQdMGzZsMO3bt8/0/fffm/7++2/zMq+++qopISHBtGjRItO2bdtMt956q6l69eqmixcvmpfp1q2bqWHDhqZff/3V9PPPP5tq1qxp6tOnj5+eFTnj5ZdfNpUpU8b0zTffmFJTU00LFiwwlShRwjRlyhTzMtz3oePbb781Pffcc6aFCxcicjR99dVXVrd7Yl+fPXvWVK5cOVPfvn3V8cTcuXNNxYoVM7333ns+e54MLIJMs2bNTI888oj597y8PFOFChVMEyZM8Ot2kWcdP35cffCsWbNG/Z6enm6KiopSXzyav/76Sy2zfv1684dWeHi4KS0tzbzMtGnTTPHx8aasrCw/PAtyxblz50y1atUyrVixwtS+fXtzYMF9H9qefvppU5s2bRzenp+fb0pOTjZNnDjRfB3eEzExMeqgAXbs2KHeD7/99pt5me+++84UFhZmOnz4sJefARVVjx49TP/5z3+srrv99tvVQSFw34cusQksPLWv3333XVNiYqLV5z4+Y2rXru2jZ2YyMRUqiGRnZ8vmzZvV8JgmPDxc/b5+/Xq/bht51tmzZ9XP0qVLq5/Y7zk5OVb7vk6dOlKlShXzvsdPpFCUK1fOvEzXrl0lIyND/vzzT58/B3INUp2QymS5j4H7PrQtWbJEmjZtKnfddZdKYWvcuLG8//775ttTU1MlLS3Nav8nJCSoNFjL/Y+0CKxHg+Xx/bBhwwYfPyNyVqtWrWTlypWye/du9fu2bdvkl19+kZtv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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAxYAAAGGCAYAAADmRxfNAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjMsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvZiW1igAAAAlwSFlzAAAPYQAAD2EBqD+naQAAbwNJREFUeJzt3Qd8VFX2wPGTnlBCh9CkL4g0qYIKutItIBZwVZB1ccXFRbHiCogNK6L+EVZX7ApWVFSKKCqKoCAqVUSaQKhCSEKSSTL/z7nhjTOTmTCTTMvk9/18hsy8efPem5mb8M6759wbY7fb7QIAAAAAZRBblhcDAAAAgCKwAAAAAFBmBBYAAAAAyozAAgAAAECZEVgAAAAAKDMCCwAAAABlRmABAAAAoMwILAAAAACUGYEFAAAAgDIjsACiWExMjIwbN+6k67344otm3e3bt7ssf/TRR6V58+YSFxcnnTp1CuKRitxzzz3mGIBgtJFrrrlGmjZt6tO6hYWF0q5dO3nggQei/vcO4XPo0CGpXLmyfPzxx+E+FCBgCCyAcujnn3+WSy+9VJo0aSLJycnSsGFD6devnzz99NMB28fixYvl9ttvlzPPPFNeeOEFefDBB2XPnj3m5G7t2rUSLnqCqCdjnm4LFy4M6L6eeeYZc/LnK+tE0brpd9OgQQMZMGCAPPXUU3Ls2DGJBnqCru+vb9++Hp9/7rnnHJ/B999/L+XNG2+8Ibt27SoWHFTE37tNmzaZ49EAp2rVqlK/fn05//zzvX6vu3fvlssvv1yqV68uqampMmTIEPntt9+kvNP2MHXqVOnevbvUqFFDateuLeecc458+umnHtc/cuSIXHfddVKnTh0TPJx77rmyZs0al3Vq1aol//jHP2TSpEkhehdA8MWHYB8AAuibb74x/0mdcsopMmbMGElLSzP/6X377bfy5JNPyo033uj3Nq+++moZMWKEJCUlOZZ99tlnEhsbK88//7wkJiaaZXoyof+56ollOK+k6nH+73//K7a8Y8eOAQ8s9ARCgxl/3HvvvdKsWTOx2WySnp4uy5Ytk5tuukmmT58uH3zwgXTo0EHKOz2x/vzzz8370zbo7LXXXjPP5+TkSHmkPQb6+1CtWjWp6L93+numx3LJJZfIDTfcIEePHpX//ve/csYZZ5hA3jm4zMzMNJ+RrnPXXXdJQkKCPPHEE9KnTx8TFOmJdHn1/vvvy8MPPyxDhw6VUaNGSX5+vrz88ssmsJwzZ46MHj3apcdLg68ff/xRbrvtNvM3RP+WaCCyevVqadWqlWPd66+/3lx00O/9r3/9a5jeHRBAdgDlyuDBg+116tSx//HHH8We27dvn8tj/RX/17/+Var9jB492l65cmWXZd99953Z5gsvvGAPtClTpphtn8yoUaOKHVewnHbaafY+ffr4vL5+Lvoe9HNyt3TpUntKSoq9SZMm9uzsbHt5pu/hvPPOs6emptpnzJjh8tyuXbvssbGx9ksuucTrZxHsNuKt3ehxn8yaNWvMPj799NMK8Xt3Mt9//7392LFjLssOHjxoPoszzzzTZfnDDz9sjnPVqlWOZRs3brTHxcXZJ06caC/P1q1bZz9w4IDLspycHHubNm3sjRo1clk+b9488zm89dZbjmX79++3V69e3X7FFVcU23a7du3sV199dRCPHggdUqGAcmbr1q1y2mmnmVQDd3Xr1vX4mvnz55uccb0yqq91Txlyz/XW+5qGkZWV5Uhp0XW6detmnterc87LLStXrpSBAweaK72VKlUyVyq//vrrYsezfPlysy29qt2iRQtzBTRQvvrqK7nsssvMlWV9v40bN5abb75Zjh8/7rKeXmnX99GoUSOznqZ4aNqG9Rno1eH169fLF1984XivesWxtPRqpKY87NixQ1599dVi6SaaYlOzZk3zmXTt2tX0bHhKr9D3osemx6zHPnLkSDl48KB5Pi8vTyZPnixdunQx34GmYJx99tmmZ8Gi5736en2v7rSHQV/3z3/+86TvR49z2LBh8vrrrxdLI9JUEU3/8kSvzOox6bFpG9bj2LhxY5naiH6e+p5TUlLMZ6i9ANqbUBr6u6I9Bb179y43v3fBpJ9rlSpVXJZpz4N+h+7f29tvv22O1Tpe1aZNGznvvPPkzTff9Hvf7qmFnm6hot+f9jw40+918ODB8vvvv7ukOernUK9ePfP7YdGUKE0R056P3Nxcl+1or8eHH35ofjeB8o5UKKCc0fzuFStWyLp168xJy8noCdq7775r0hg0R1q73TWtYefOnV5TE1555RV59tlnZdWqVY6UI+2+1xQfPXHV3GE9sVC9evVynDAOGjTInIhMmTLFpHPoSZKeUOvJvuYmW3nq/fv3N//Rat64phTo+vofsT+sk2mL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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "def weights_from_ages(\n", + " ages: np.ndarray,\n", + " peak_age: float = 360.0,\n", + " shape: float = 5.0\n", + ") -> np.ndarray:\n", + " # Compute scale parameter so mode = peak_age\n", + " theta = peak_age / (shape - 1)\n", + "\n", + " # Ensure ages >= 0\n", + " a = np.maximum(ages, 0.0)\n", + "\n", + " # Compute unnormalized weights: a^(shape-1) * exp(-a/theta)\n", + " w = (a ** (shape - 1)) * np.exp(-a / theta)\n", + "\n", + " # Zero out negative ages (already clamped, but enforce)\n", + " w[ages < 0] = 0.0\n", + "\n", + " # Normalize to sum = 1\n", + " total = w.sum()\n", + " if total > 0:\n", + " w /= total\n", + " return w\n", + "\n", + "ages = np.arange(0, 1001, 10)\n", + "\n", + "weights_360 = weights_from_ages(ages, peak_age=360.0, shape=5.0)\n", + "weights_500 = weights_from_ages(ages, peak_age=500.0, shape=5.0)\n", + "\n", + "plt.figure(figsize=(8, 4))\n", + "plt.plot(ages, weights_360, marker='o', linestyle='-', label='Peak at 360')\n", + "plt.plot(ages, weights_500, marker='s', linestyle='--', label='Peak at 500')\n", + "plt.xlabel('Age (slots)')\n", + "plt.ylabel('Normalized Weight')\n", + "plt.title('Dependency probability model (pessimistic)')\n", + "plt.grid(True)\n", + "plt.legend()\n", + "plt.tight_layout()\n", + "plt.show()\n", + "\n", + "def weights_shifted_fast_decay(\n", + " ages: np.ndarray,\n", + " shift: float = 20.0,\n", + " tau: float = 20.0\n", + ") -> np.ndarray:\n", + " w = np.zeros_like(ages, dtype=float)\n", + " # clamp shift so it’s never above ages.max()\n", + " shift_eff = min(shift, ages.max())\n", + " valid = ages >= shift_eff\n", + " a = ages[valid]\n", + " w[valid] = np.exp(-(a - shift_eff) / tau)\n", + " total = w.sum()\n", + " if total > 0:\n", + " w /= total\n", + " return w\n", + "\n", + "# Define ages from 0 to 1000 in steps of 10\n", + "ages = np.arange(0, 1001, 10)\n", + "weights = weights_shifted_fast_decay(ages, shift=20.0, tau=20.0)\n", + "\n", + "# Plotting\n", + "plt.figure(figsize=(8, 4))\n", + "plt.plot(ages, weights, marker='o', linestyle='-')\n", + "plt.axvline(20, color='red', linestyle='--', label='Shift = 20')\n", + "plt.xlabel('Age (slots)')\n", + "plt.ylabel('Normalized Weight')\n", + "plt.title('Shifted Fast-Decay Model (Shift = 20, τ = 20)')\n", + "plt.grid(True)\n", + "plt.legend()\n", + "plt.tight_layout()\n", + "plt.show()\n" + ] + }, + { + "cell_type": "markdown", + "id": "cb9828c4-7511-4bef-a125-a300fc2885b0", + "metadata": {}, + "source": [ + "# Cryptarchia v2.2" + ] + }, + { + "cell_type": "code", + "execution_count": 1150, + "id": "24779de7-284f-4200-9e4a-d2aa6e1b823b", + "metadata": { + "jupyter": { + "source_hidden": true + } + }, + "outputs": [], + "source": [ + "def phi(f, alpha):\n", + " return 1 - (1-f)**alpha\n", + "\n", + "@dataclass\n", + "class Params:\n", + " SLOTS: int\n", + " f: float\n", + " honest_stake: np.array\n", + " adversary_control: float\n", + " window_size: int\n", + " use_deps: bool\n", + "\n", + " @property\n", + " def N(self):\n", + " return len(self.honest_stake) + 1\n", + "\n", + " @property\n", + " def stake(self):\n", + " return np.append(self.honest_stake, self.honest_stake.sum() / (1/self.adversary_control - 1))\n", + " \n", + " @property\n", + " def relative_stake(self):\n", + " return self.stake / self.stake.sum()\n", + "\n", + " def slot_prob(self):\n", + " return phi(self.f, self.relative_stake)\n", + "\n", + "@dataclass\n", + "class Block:\n", + " id: int\n", + " slot: int\n", + " refs: list[int]\n", + " deps: list[int]\n", + " leader: int\n", + " adversarial: bool" + ] + }, + { + "cell_type": "code", + "execution_count": 1151, + "id": "055ed35f-b142-4d80-ae4a-b951381cdcd3", + "metadata": { + "jupyter": { + "source_hidden": true + } + }, + "outputs": [], + "source": [ + "def visualize_chain(sim):\n", + " layout = Layout()\n", + " layout.hierachical = True\n", + "\n", + " tooltip_css = \"\"\"\n", + " \n", + " \"\"\"\n", + " G = Network(width=1600, height=800, notebook=True, directed=True, layout=layout, cdn_resources='in_line', heading=tooltip_css)\n", + " options_str = \"\"\"\n", + " {\n", + " \"layout\": {\n", + " \"hierarchical\": {\n", + " \"enabled\": true,\n", + " \"levelSeparation\": 200,\n", + " \"nodeSpacing\": 150,\n", + " \"treeSpacing\": 250,\n", + " \"direction\": \"UD\",\n", + " \"sortMethod\": \"directed\"\n", + " }\n", + " },\n", + " \"physics\": {\n", + " \"enabled\": true,\n", + " \"solver\": \"hierarchicalRepulsion\",\n", + " \"hierarchicalRepulsion\": {\n", + " \"centralGravity\": 0.0,\n", + " \"springLength\": 150,\n", + " \"springConstant\": 0.05,\n", + " \"nodeDistance\": 150,\n", + " \"damping\": 0.15\n", + " },\n", + " \"stabilization\": {\n", + " \"enabled\": true,\n", + " \"iterations\": 200000,\n", + " \"updateInterval\": 2500,\n", + " \"onlyDynamicEdges\": false,\n", + " \"fit\": true\n", + " }\n", + " },\n", + " \"interaction\": {\n", + " \"tooltipDelay\": 200,\n", + " \"hover\": true,\n", + " \"dragNodes\": true,\n", + " \"dragView\": true,\n", + " \"zoomView\": true\n", + " },\n", + " \"nodes\": {\n", + " \"font\": {\n", + " \"size\": 14\n", + " },\n", + " \"shape\": \"box\",\n", + " \"margin\": 10\n", + " },\n", + " \"edges\": {\n", + " \"smooth\": {\n", + " \"enabled\": true,\n", + " \"type\": \"cubicBezier\",\n", + " \"roundness\": 0.5\n", + " },\n", + " \"arrows\": {\n", + " \"to\": { \"enabled\": true, \"scaleFactor\": 0.7 }\n", + " }\n", + " }\n", + " }\n", + " \"\"\"\n", + " G.set_options(options_str)\n", + " for block in sim.blocks:\n", + " level = block.slot/20 # This puts all the blocks that happen within 20s in the same level (just for visual clarity)\n", + " color = \"darkgrey\"\n", + " #if block.id in honest_chain_set:\n", + " # color = \"orange\"\n", + "\n", + " if block.adversarial: color = \"red\"\n", + " G.add_node(int(block.id), level=level, color=color, label=f\"(id:{block.id})\\ns: {block.slot}\\nrefs: {len(block.refs)}\")\n", + " # if block.parent >= 0:\n", + " # G.add_edge(int(block.id), int(block.parent), width=2, color=color)\n", + " # Draw deps first so they are in the background\n", + " # for dep in block.deps:\n", + " # G.add_edge(int(block.id), int(dep), width=1, color=\"#dddddd\")\n", + " for ref in block.refs:\n", + " G.add_edge(int(block.id), int(ref), width=1, color=\"blue\")\n", + "\n", + " \n", + " return G.show(\"chain.html\")" + ] + }, + { + "cell_type": "code", + "execution_count": 1152, + "id": "3d4896b2-b1b6-4b6c-8519-be1f65747246", + "metadata": { + "jupyter": { + "source_hidden": true + } + }, + "outputs": [], + "source": [ + "def normalize_from_slot(arr: np.ndarray, slot: int) -> np.ndarray:\n", + " \"\"\"\n", + " Subtract arr[slot] from every element of arr, then clip negatives to zero.\n", + " \"\"\"\n", + " base = arr[slot]\n", + " adjusted = arr - base\n", + " adjusted[adjusted < 0] = 0\n", + " return adjusted\n", + "\n", + "def longest_advantage_run(a: np.ndarray, b: np.ndarray) -> int:\n", + " \"\"\"\n", + " Return the length of the longest consecutive run where b >= a,\n", + " using purely NumPy operations.\n", + " \"\"\"\n", + " mask = b >= a\n", + " # Pad with False at both ends to catch runs at boundaries\n", + " padded = np.concatenate(([False], mask, [False]))\n", + " diff = np.diff(padded.astype(np.int8))\n", + " starts = np.where(diff == 1)[0]\n", + " ends = np.where(diff == -1)[0]\n", + " if starts.size == 0:\n", + " return 0\n", + " lengths = ends - starts\n", + " return int(lengths.max())\n", + "\n", + "def highest_advantage_index(a: np.ndarray, b: np.ndarray) -> int:\n", + " \"\"\"\n", + " Return the largest index i where b[i] >= a[i], or -1 if none exist.\n", + " \"\"\"\n", + " mask = b >= a\n", + " idxs = np.nonzero(mask)[0]\n", + " return int(idxs[-1]) if idxs.size > 0 else -1\n", + "\n", + "def highest_advantage_index_nonzero(a: np.ndarray, b: np.ndarray) -> int:\n", + " \"\"\"\n", + " Return the largest index i where b[i] >= a[i] and not (a[i] == 0 and b[i] == 0), \n", + " or -1 if none exist.\n", + " \"\"\"\n", + " mask = (b >= a) & ~((a == 0) & (b == 0))\n", + " idxs = np.nonzero(mask)[0]\n", + " return int(idxs[-1]) if idxs.size > 0 else -1\n" + ] + }, + { + "cell_type": "code", + "execution_count": 1153, + "id": "a90495a8-fcda-4e47-92b4-cc5ceaa9ff9c", + "metadata": {}, + "outputs": [], + "source": [ + "class Sim:\n", + " def __init__(self, params: Params, network: NetworkParams):\n", + " self.params = params\n", + " self.network = network\n", + "\n", + " # leaders: fixed-size (N × SLOTS)\n", + " self.leaders = np.zeros((params.N, params.SLOTS), dtype=np.int32)\n", + "\n", + " # Preallocate capacity for blocks: at most N * SLOTS blocks\n", + " max_blocks = params.N * params.SLOTS\n", + " self.block_slots = np.empty(max_blocks, dtype=np.int32)\n", + " self.block_arrivals = np.empty((params.N, max_blocks), dtype=np.int32)\n", + " self.num_blocks = 0\n", + "\n", + " self.blocks: list[Block] = []\n", + "\n", + " # Emit genesis block (id = 0)\n", + " self.emit_block(leader=0, slot=0, refs=[], deps=[])\n", + " # Set arrival times of genesis to 0\n", + " self.block_arrivals[:, 0] = 0\n", + "\n", + " def clone_for_attack(self):\n", + " new = object.__new__(Sim)\n", + " new.params = self.params\n", + " new.network = self.network\n", + "\n", + " # Copy leaders array\n", + " new.leaders = self.leaders.copy()\n", + "\n", + " # Preallocate same sizes\n", + " max_blocks = self.params.N * self.params.SLOTS\n", + " new.block_slots = np.empty_like(self.block_slots)\n", + " new.block_arrivals = np.empty_like(self.block_arrivals)\n", + " new.num_blocks = self.num_blocks\n", + "\n", + " # Copy blocks list (shallow copy of each Block)\n", + " new.blocks = [\n", + " Block(\n", + " id=b.id,\n", + " leader=b.leader,\n", + " slot=b.slot,\n", + " refs=b.refs.copy(),\n", + " deps=b.deps.copy(),\n", + " adversarial=b.adversarial\n", + " ) for b in self.blocks\n", + " ]\n", + "\n", + " # Copy underlying arrays\n", + " new.block_slots[: self.num_blocks] = self.block_slots[: self.num_blocks]\n", + " new.block_arrivals[:, : self.num_blocks] = self.block_arrivals[:, : self.num_blocks]\n", + "\n", + " return new\n", + "\n", + " def get_seen_blocks_in_window_for_node(self, node_id: int, current_slot: int, window_size: int) -> list[int]:\n", + " if not (0 <= node_id < self.params.N):\n", + " raise ValueError(f\"Invalid node_id: {node_id}. Must be between 0 and {self.params.N - 1}.\")\n", + " if window_size <= 0:\n", + " raise ValueError(f\"window_size must be positive. Got {window_size}.\")\n", + " if self.num_blocks == 0:\n", + " return []\n", + "\n", + " min_slot = current_slot - window_size + 1\n", + " max_slot = current_slot\n", + "\n", + " arrivals = self.block_arrivals[node_id, : self.num_blocks]\n", + " slots = self.block_slots[: self.num_blocks]\n", + "\n", + " mask = (\n", + " (arrivals >= min_slot) & (arrivals <= max_slot) &\n", + " (slots >= min_slot) & (slots <= max_slot)\n", + " )\n", + " return np.nonzero(mask)[0].tolist()\n", + "\n", + " def get_all_blocks_in_window_for_node(self, node_id: int, current_slot: int, window_size: int) -> list[int]:\n", + " if not (0 <= node_id < self.params.N):\n", + " raise ValueError(f\"Invalid node_id: {node_id}. Must be between 0 and {self.params.N - 1}.\")\n", + " if window_size <= 0:\n", + " raise ValueError(f\"window_size must be positive. Got {window_size}.\")\n", + " if self.num_blocks == 0:\n", + " return []\n", + "\n", + " min_slot = current_slot - window_size + 1\n", + " max_slot = current_slot\n", + "\n", + " slots = self.block_slots[: self.num_blocks]\n", + " mask = (slots >= min_slot) & (slots <= max_slot)\n", + " return np.nonzero(mask)[0].tolist()\n", + "\n", + " def get_unreachable_blocks(self, node_id: int, current_slot: int) -> list[int]:\n", + " if not (0 <= node_id < self.params.N):\n", + " raise ValueError(f\"Invalid node_id: {node_id}. Must be between 0 and {self.params.N - 1}.\")\n", + "\n", + " arrivals = self.block_arrivals[node_id, : self.num_blocks]\n", + " seen_ids = set(np.nonzero(arrivals <= current_slot)[0].tolist())\n", + "\n", + " has_incoming = set()\n", + " for b in seen_ids:\n", + " for parent in self.blocks[b].refs:\n", + " if parent in seen_ids:\n", + " has_incoming.add(parent)\n", + "\n", + " return [b for b in seen_ids if b not in has_incoming]\n", + "\n", + " def get_max_cardinality_antichain(self, node_id: int, current_slot: int, window: int = None, forbidden: set[int] = None) -> list[int]:\n", + " arrivals = self.block_arrivals[node_id, : self.num_blocks]\n", + " slots_arr = self.block_slots[: self.num_blocks]\n", + "\n", + " if window is not None:\n", + " min_slot = current_slot - window + 1\n", + " mask = (\n", + " (arrivals <= current_slot) &\n", + " (slots_arr >= min_slot) &\n", + " (slots_arr <= current_slot)\n", + " )\n", + " seen_ids = np.nonzero(mask)[0].tolist()\n", + " else:\n", + " seen_ids = np.nonzero(arrivals <= current_slot)[0].tolist()\n", + "\n", + " # Filter out any forbidden blocks right away\n", + " if forbidden is not None:\n", + " seen_ids = [i for i in seen_ids if i not in forbidden]\n", + " \n", + " if not seen_ids:\n", + " return []\n", + "\n", + " idx = {blk_id: i for i, blk_id in enumerate(seen_ids)}\n", + " n = len(seen_ids)\n", + "\n", + " adj_children = {b: [] for b in seen_ids}\n", + " for b in seen_ids:\n", + " for parent in self.blocks[b].refs:\n", + " if parent in idx:\n", + " adj_children[parent].append(b)\n", + "\n", + " graph: list[list[int]] = [[] for _ in range(n)]\n", + " for u in seen_ids:\n", + " u_idx = idx[u]\n", + " visited = set()\n", + " stack = adj_children[u].copy()\n", + " while stack:\n", + " x = stack.pop()\n", + " if x not in visited:\n", + " visited.add(x)\n", + " stack.extend(adj_children.get(x, []))\n", + " graph[u_idx] = [idx[v] for v in visited]\n", + "\n", + " pair_u = [-1] * n\n", + " pair_v = [-1] * n\n", + " dist = [0] * n\n", + "\n", + " def bfs():\n", + " queue = deque()\n", + " found_augment = False\n", + " for u in range(n):\n", + " if pair_u[u] == -1:\n", + " dist[u] = 0\n", + " queue.append(u)\n", + " else:\n", + " dist[u] = float(\"inf\")\n", + " while queue:\n", + " u = queue.popleft()\n", + " for v_idx in graph[u]:\n", + " pu = pair_v[v_idx]\n", + " if pu != -1 and dist[pu] == float(\"inf\"):\n", + " dist[pu] = dist[u] + 1\n", + " queue.append(pu)\n", + " if pu == -1:\n", + " found_augment = True\n", + " return found_augment\n", + "\n", + " def dfs(u):\n", + " for v_idx in graph[u]:\n", + " pu = pair_v[v_idx]\n", + " if pu == -1 or (dist[pu] == dist[u] + 1 and dfs(pu)):\n", + " pair_u[u] = v_idx\n", + " pair_v[v_idx] = u\n", + " return True\n", + " dist[u] = float(\"inf\")\n", + " return False\n", + "\n", + " while bfs():\n", + " for u in range(n):\n", + " if pair_u[u] == -1 and dfs(u):\n", + " pass\n", + "\n", + " visited_u = [False] * n\n", + " visited_v = [False] * n\n", + " queue = deque(u for u in range(n) if pair_u[u] == -1)\n", + " while queue:\n", + " u = queue.popleft()\n", + " if visited_u[u]:\n", + " continue\n", + " visited_u[u] = True\n", + " for v_idx in graph[u]:\n", + " if not visited_v[v_idx]:\n", + " visited_v[v_idx] = True\n", + " pu = pair_v[v_idx]\n", + " if pu != -1 and not visited_u[pu]:\n", + " queue.append(pu)\n", + "\n", + " return [\n", + " blk_id\n", + " for blk_id, u_idx in idx.items()\n", + " if visited_u[u_idx] and not visited_v[u_idx]\n", + " ]\n", + "\n", + " def emit_block(self, leader, slot, refs, deps, adversarial=False):\n", + " assert isinstance(leader, (int, np.int64))\n", + " assert isinstance(slot, (int, np.int64))\n", + " assert all(isinstance(r, (int, np.int64)) for r in refs)\n", + "\n", + " block = Block(\n", + " id=self.num_blocks,\n", + " leader=leader,\n", + " slot=slot,\n", + " refs=refs.copy(),\n", + " deps=deps.copy(),\n", + " adversarial=adversarial\n", + " )\n", + " self.blocks.append(block)\n", + "\n", + " self.block_slots[self.num_blocks] = slot\n", + "\n", + " if not adversarial:\n", + " base = np.repeat(slot, self.params.N)\n", + " arrival = self.network.block_arrival_slot(base)\n", + " else:\n", + " arrival = np.full((self.params.N,), self.params.SLOTS - 1, dtype=np.int64)\n", + " arrival[self.params.N - 1] = slot\n", + "\n", + " self.block_arrivals[:, self.num_blocks] = arrival\n", + "\n", + " bid = self.num_blocks\n", + " self.num_blocks += 1\n", + " return bid\n", + "\n", + " def emit_leader_block(self, leader, slot):\n", + " assert isinstance(leader, (int, np.int64))\n", + " assert isinstance(slot, int)\n", + "\n", + " arrivals = self.block_arrivals[leader, : self.num_blocks]\n", + " seen_ids = np.nonzero(arrivals <= slot)[0].tolist()\n", + "\n", + " deps = []\n", + " if seen_ids:\n", + " seen_slots = [self.block_slots[i] for i in seen_ids]\n", + " ages = [slot - s for s in seen_slots]\n", + " weights = weights_from_ages(np.array(ages), peak_age=60.0, shape=5.0)\n", + " dep = random.choices(seen_ids, weights=weights, k=1)[0]\n", + " deps = [int(dep)]\n", + "\n", + " refs = self.get_max_cardinality_antichain(leader, slot, window=self.params.window_size)\n", + "\n", + " emitted = self.emit_block(leader, slot, refs=refs, deps=deps)\n", + "\n", + " unreachable = self.get_unreachable_blocks(leader, slot)\n", + " unreachable = [b for b in unreachable if b != emitted]\n", + " if unreachable:\n", + " self.blocks[emitted].refs.append(random.choice(unreachable))\n", + "\n", + " return emitted\n", + "\n", + " def run(self):\n", + " for s in range(1, self.params.SLOTS):\n", + " self.leaders[:, s] = np.random.random(size=self.params.N) < self.params.slot_prob()\n", + " for leader in np.nonzero(self.leaders[:, s])[0]:\n", + " if self.params.adversary_control is not None and leader == self.params.N - 1:\n", + " continue\n", + " self.emit_leader_block(leader, s)\n", + "\n", + " def compute_descendants(self, start_block):\n", + " start_id = start_block.id if hasattr(start_block, \"id\") else start_block\n", + "\n", + " children = {i: [] for i in range(len(self.blocks))}\n", + " for i, blk in enumerate(self.blocks):\n", + " parents = blk.refs if self.params.use_deps else blk.refs\n", + " for r in parents:\n", + " children[r].append(i)\n", + "\n", + " desc = {start_id}\n", + " queue = collections.deque([start_id])\n", + " while queue:\n", + " cur = queue.popleft()\n", + " for child in children[cur]:\n", + " if child not in desc:\n", + " desc.add(child)\n", + " queue.append(child)\n", + " desc.remove(start_id)\n", + " return desc\n", + "\n", + " def block_ref_weights_by_slot(self, start_id: int) -> np.ndarray:\n", + " descendants = self.compute_descendants(start_id)\n", + " weights_by_slot = np.zeros(self.params.SLOTS, dtype=np.int64)\n", + " window = self.params.window_size\n", + " for did in descendants:\n", + " did_slot = self.blocks[did].slot\n", + " refs = self.blocks[did].refs\n", + " count = sum(\n", + " 1\n", + " for r in refs\n", + " if did_slot - self.blocks[r].slot < window\n", + " )\n", + " weights_by_slot[did_slot] += count\n", + " return np.cumsum(weights_by_slot)\n", + "\n", + " def adversarial_ref_weights_by_slot(self, start_id: int) -> np.ndarray:\n", + " descendants = self.compute_descendants(start_id)\n", + " adv_desc = [d for d in descendants if self.blocks[d].adversarial]\n", + " weights_by_slot = np.zeros(self.params.SLOTS, dtype=np.int64)\n", + " window = self.params.window_size\n", + " for did in adv_desc:\n", + " did_slot = self.blocks[did].slot\n", + " refs = self.blocks[did].refs\n", + " count = sum(\n", + " 1\n", + " for r in refs\n", + " if did_slot - self.blocks[r].slot < window\n", + " )\n", + " weights_by_slot[did_slot] += count\n", + " return np.cumsum(weights_by_slot)\n", + "\n", + " def attack_on_block(self, target_block: Block):\n", + " \"\"\"\n", + " Attack on a specific block. The provided block is the closest ancestor of a honest block on which\n", + " the attacker wants to introduce a conflict and win. Since the conflict resolution rules operate on\n", + " the closest common ancestor of those two conflicting blocks, we perform the attack by exhaustively\n", + " exploring every possible starting block.\n", + "\n", + " We are returning the reorg measured as from the attacked block (common ancestor) up to the last\n", + " adversarial block. This makes it imprecise, but in a DAG there is no perfect measure of \"reorg \n", + " length\". In any case, this is only relevant for \"full reorg\" counting, which is fine to compute\n", + " approximately giving some tolearance value to the definition of \"full reorg\".\n", + " \"\"\"\n", + " adv = self.params.N - 1\n", + " fid = target_block.id\n", + " fslot = target_block.slot\n", + "\n", + " # 1. Static forbidden = all descendants of fid (and fid itself)\n", + " forbidden = set(self.compute_descendants(fid)) | {fid}\n", + "\n", + " # 2. Precompute adversarial slots after fslot\n", + " adv_slots = list(np.flatnonzero(self.leaders[adv, fslot + 1 :]) + fslot + 1)\n", + " if not adv_slots:\n", + " return -1\n", + "\n", + " # 3. Pre‐emit every adversarial block at its slot, chaining deps,\n", + " # but with empty refs for now\n", + " adversarial_block_ids = []\n", + " prev_bid = None\n", + " for slot in adv_slots:\n", + " deps = [] if prev_bid is None else [prev_bid]\n", + " bid = self.emit_block(adv, slot, refs=[], deps=deps, adversarial=True)\n", + " adversarial_block_ids.append(bid)\n", + " prev_bid = bid\n", + "\n", + " # 4. Cache best references given a slot (forbidden is constant)\n", + " @lru_cache(maxsize=None)\n", + " def best_refs_at(slot: int, idx: int):\n", + " local_forbidden = forbidden | {adversarial_block_ids[idx]}\n", + " return tuple(\n", + " self.get_max_cardinality_antichain(\n", + " node_id=adv,\n", + " current_slot=slot,\n", + " window=self.params.window_size,\n", + " forbidden=local_forbidden,\n", + " )\n", + " )\n", + "\n", + " # 5. Iterative DP over adversarial slots\n", + " n = len(adv_slots)\n", + " dp_count = [0] * (n + 1)\n", + " dp_seq = [()] * (n + 1)\n", + "\n", + " # Base case: dp_count[n] = 0, dp_seq[n] = ()\n", + " for i in range(n - 1, -1, -1):\n", + " slot = adv_slots[i]\n", + " refs = list(best_refs_at(slot, i))\n", + " count_here = len(refs)\n", + "\n", + " total_with = count_here + dp_count[i + 1]\n", + " skip_count = dp_count[i + 1]\n", + "\n", + " if total_with >= skip_count:\n", + " dp_count[i] = total_with\n", + " dp_seq[i] = (i,) + dp_seq[i + 1]\n", + " else:\n", + " dp_count[i] = skip_count\n", + " dp_seq[i] = dp_seq[i + 1]\n", + "\n", + " chosen_indices = dp_seq[0] # tuple of indices into adversarial_block_ids\n", + "\n", + " # 6. Assign refs to each chosen adversarial block\n", + " for idx in chosen_indices:\n", + " bid = adversarial_block_ids[idx]\n", + " slot = self.blocks[bid].slot\n", + " refs = list(best_refs_at(slot, idx))\n", + " self.blocks[bid].refs = refs\n", + "\n", + " # 8. Post‐processing: for every adversarial block, try to add one extra reference\n", + " for i, a in reversed(list(enumerate(adversarial_block_ids))):\n", + " desc_set = self.compute_descendants(a)\n", + " for b in adversarial_block_ids[i + 1:]:\n", + " if b not in desc_set and not self.blocks[b].refs:\n", + " self.blocks[b].refs.append(a)\n", + " break\n", + " \n", + " # 9. Find the slot of the closest direct honest descendant of fid\n", + " # This is the block with the conflict that will be replaced by the adversary\n", + " ref_slot = None\n", + " for b in self.blocks[target_block.id+1:]:\n", + " if b.adversarial: # all adversarial blocks are at the end\n", + " break\n", + " if target_block.id in b.refs:\n", + " ref_slot = b.slot\n", + " break\n", + " if ref_slot is None:\n", + " return -1\n", + "\n", + " # 10. Compute reorg length, capping at last adversarial slot\n", + " last_adv_slot = self.blocks[adversarial_block_ids[-1]].slot\n", + " first_adv_bid = adversarial_block_ids[0]\n", + " honest_weights = self.block_ref_weights_by_slot(fid)\n", + " adv_weights = self.adversarial_ref_weights_by_slot(first_adv_bid)\n", + "\n", + " # We are returning the reorg measured as from the attacked block (common ancestor)\n", + " # up to the last adversarial block. This makes it imprecise, but in a DAG there is no\n", + " # perfect measure of \"reorg length\".\n", + " hi_uncapped = highest_advantage_index_nonzero(honest_weights, adv_weights)\n", + " hi = min(hi_uncapped, last_adv_slot)\n", + " return int(hi - ref_slot) if hi - ref_slot >= 0 else -1\n", + " \n", + " # # Compute reorg length relative to ref_slot\n", + " # first_adv_bid = adversarial_block_ids[chosen_indices[0]]\n", + " # honest_weights = self.block_ref_weights_by_slot(fid)\n", + " # adv_weights = self.adversarial_ref_weights_by_slot(first_adv_bid)\n", + " # hi = highest_advantage_index_nonzero(honest_weights, adv_weights)\n", + " # return int(hi - ref_slot) if hi >= 0 else -1" + ] + }, + { + "cell_type": "markdown", + "id": "911b38c8-8f8b-4ca5-b875-ea84e8161a79", + "metadata": { + "jp-MarkdownHeadingCollapsed": true + }, + "source": [ + "## Single Run and Visualization" + ] + }, + { + "cell_type": "code", + "execution_count": 1154, + "id": "a0123dab-cf0d-4721-81c7-bb881a27c13c", + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "# %%time\n", + "# random.seed(0)\n", + "# np.random.seed(0)\n", + "\n", + "# sim = Sim(\n", + "# params=Params(\n", + "# SLOTS=2000,\n", + "# f=0.25,\n", + "# window_size=30,\n", + "# use_deps=True,\n", + "# adversary_control = 0.3,\n", + "# honest_stake = np.random.pareto(10, 1000)\n", + "# ),\n", + "# network=blend_net\n", + "# )\n", + "# sim.run()\n", + "\n", + "# n_blocks_per_slot = len(sim.blocks) / sim.params.SLOTS\n", + "# print(\"avg blocks per slot\", n_blocks_per_slot)\n", + "# print(\"Number of blocks\", len(sim.blocks))\n", + "\n", + "# total_refs = sum([len(b.refs) for b in sim.blocks])\n", + "# print(\"Total number of refs created\", total_refs)" + ] + }, + { + "cell_type": "code", + "execution_count": 1155, + "id": "a252486b-8c25-4d79-9dae-085a879a3112", + "metadata": {}, + "outputs": [], + "source": [ + "# max_reorg = sim.attack_on_block(sim.blocks[303])\n", + "# print(\"reorg:\", max_reorg)\n", + "\n", + "# visualize_chain(sim)" + ] + }, + { + "cell_type": "markdown", + "id": "81d29c1d-98cb-4ab3-8f66-ea032be30eb1", + "metadata": { + "jp-MarkdownHeadingCollapsed": true + }, + "source": [ + "## Attack all blocks (single-threaded)" + ] + }, + { + "cell_type": "code", + "execution_count": 1156, + "id": "f9d62ff7-e03a-4ef0-9521-00466019feb5", + "metadata": {}, + "outputs": [], + "source": [ + "# def attack_all_blocks(sim) -> dict[int, any]:\n", + "# sim.run()\n", + "# baseline = sim.clone_for_attack()\n", + "\n", + "# results: dict[int, any] = {}\n", + "# adv_id = baseline.params.N - 1\n", + "# for blk in baseline.blocks:\n", + "# if blk.leader == adv_id:\n", + "# continue\n", + "# sim_copy = baseline.clone_for_attack()\n", + "# results[blk.id] = sim_copy.attack_on_block(sim_copy.blocks[blk.id])\n", + "# return results\n", + "\n", + "# random.seed(0)\n", + "# np.random.seed(0)\n", + "\n", + "# sim = Sim(\n", + "# params=Params(\n", + "# SLOTS=2000,\n", + "# f=0.25,\n", + "# window_size=30,\n", + "# use_deps=True,\n", + "# adversary_control = 0.3,\n", + "# honest_stake = np.random.pareto(10, 1000)\n", + "# ),\n", + "# network=blend_net\n", + "# )\n", + "# results = attack_all_blocks(sim)\n", + "# print(\"(ID, reorg length) -> \", max(results.items(), key=lambda x: x[1]))" + ] + }, + { + "cell_type": "markdown", + "id": "ed750bd2-c083-4768-a317-c4f8aa487cd7", + "metadata": { + "jp-MarkdownHeadingCollapsed": true + }, + "source": [ + "## Attack all blocks (parallelized)" + ] + }, + { + "cell_type": "code", + "execution_count": 1157, + "id": "d5f0a0b9-732e-4120-a0ec-da4e867994d9", + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "import platform\n", + "\n", + "home_dir = os.path.expanduser(\"~\")\n", + "joblib_temp = os.path.join(home_dir, \"joblib_tmp\")\n", + "\n", + "def parallel_attack_all_blocks(sim, skip_last: int = 0):\n", + " sim.run()\n", + "\n", + " def _attack_block_copy(orig_sim, block_id):\n", + " sim_copy = orig_sim.clone_for_attack()\n", + " result = sim_copy.attack_on_block(sim_copy.blocks[block_id])\n", + " return block_id, result\n", + "\n", + " # Configuration for the Nomos experiments server\n", + " n_jobs = 26 if platform.system() == \"Linux\" else 8\n", + " \n", + " blocks = sim.blocks if skip_last == 0 else sim.blocks[:-skip_last]\n", + " block_ids = [b.id for b in blocks]\n", + " attacked_results = Parallel(\n", + " n_jobs=8,\n", + " backend=\"loky\",\n", + " temp_folder=joblib_temp\n", + " )(\n", + " delayed(_attack_block_copy)(sim, bid)\n", + " for bid in block_ids\n", + " )\n", + " return (attacked_results, sim.blocks)\n", + "\n", + "def plot_attack_histogram_binned(attacked_results, bin_size=30, figsize=(12, 6), label_fontsize=8):\n", + " indices = [result for _, result in attacked_results]\n", + " max_idx = max(indices)\n", + " \n", + " bin_start = 0\n", + " bin_end = ((max_idx // bin_size) + 1) * bin_size\n", + " bins = list(range(bin_start, bin_end + bin_size, bin_size))\n", + " \n", + " labels = [f\"{b // bin_size} ({b})\" for b in bins]\n", + " \n", + " plt.figure(figsize=figsize)\n", + " plt.hist(indices, bins=bins, edgecolor='black')\n", + " plt.xlabel('Reorg in virtual blocks and slots')\n", + " plt.ylabel('Frequency')\n", + " plt.title(f'Histogram of Attack Results (bins of {bin_size})')\n", + " plt.xticks(bins, labels, rotation='vertical', fontsize=label_fontsize)\n", + " plt.tight_layout()\n", + " plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 1158, + "id": "3050fdf6-c22a-44de-b1f0-77af5b42d96d", + "metadata": {}, + "outputs": [], + "source": [ + "# %%time\n", + "# random.seed(0)\n", + "# np.random.seed(0)\n", + "\n", + "# sim = Sim(\n", + "# params=Params(\n", + "# SLOTS=15000,\n", + "# f=0.25,\n", + "# window_size=30,\n", + "# use_deps=True,\n", + "# adversary_control = 0.3,\n", + "# honest_stake = np.random.pareto(10, 1000)\n", + "# ),\n", + "# network=blend_net\n", + "# )\n", + "# attack_result = parallel_attack_all_blocks(sim)\n", + "# plot_attack_histogram_binned(attack_result, bin_size=30)\n", + "\n", + "# print(max(attack_result, key=lambda x: x[1]))" + ] + }, + { + "cell_type": "markdown", + "id": "1b5f62a7-d59d-4162-9a5a-5220c061a6b5", + "metadata": {}, + "source": [ + "## Multiple Experiments and frequency analysis" + ] + }, + { + "cell_type": "code", + "execution_count": 1159, + "id": "0953f236-7176-4787-8a21-77450835f728", + "metadata": { + "jupyter": { + "source_hidden": true + } + }, + "outputs": [], + "source": [ + "def plot_attack_histogram_frequency(\n", + " attacked_results: list[tuple[int, int]],\n", + " total_honest_blocks: int,\n", + " adversary_stake: int,\n", + " bin_size: int = 30,\n", + " figsize: tuple[int, int] = (12, 6),\n", + " label_fontsize: int = 8\n", + "):\n", + " # Extract reorg lengths from (block_id, length) tuples\n", + " all_reorgs = [length for _, length in attacked_results]\n", + " \n", + " # Compute bins\n", + " max_idx = max(all_reorgs)\n", + " bin_end = ((max_idx // bin_size) + 1) * bin_size\n", + " bins = np.arange(0, bin_end + bin_size, bin_size)\n", + "\n", + " # Compute weights (relative frequencies) for histogram\n", + " weights = np.ones_like(all_reorgs, dtype=float) / total_honest_blocks\n", + " counts, edges = np.histogram(all_reorgs, bins=bins, weights=weights)\n", + "\n", + " plt.figure(figsize=figsize)\n", + "\n", + " # Bar plot with a muted blue and slight transparency\n", + " bars = plt.bar(\n", + " edges[:-1],\n", + " counts,\n", + " width=bin_size,\n", + " align='edge',\n", + " edgecolor='#555555',\n", + " color='#4C72B0',\n", + " alpha=0.8,\n", + " label='Grouped relative frequency (virtual blocks)'\n", + " )\n", + "\n", + " # Compute exact relative frequency (no bins)\n", + " unique_vals, raw_counts = np.unique(all_reorgs, return_counts=True)\n", + " exact_rel_freq = raw_counts / total_honest_blocks\n", + "\n", + " # Line plot of exact relative frequencies with a contrasting orange and thinner line\n", + " plt.plot(\n", + " unique_vals,\n", + " exact_rel_freq,\n", + " marker='o',\n", + " markersize=4,\n", + " linestyle='-',\n", + " color='#DD8452',\n", + " linewidth=1.0,\n", + " label='Relative frequency per slot'\n", + " )\n", + "\n", + " plt.yscale('log')\n", + " plt.xlabel('Reorg in virtual blocks and slots')\n", + " plt.ylabel('Relative frequency (log scale)')\n", + " plt.title(f'Log‐Scaled Histogram over {total_honest_blocks} Honest Blocks ({ADVERSARY_STAKE * 100}% adversarial stake)')\n", + "\n", + " # Annotate bars\n", + " for bar, height in zip(bars, counts):\n", + " if height > 0:\n", + " x = bar.get_x() + bar.get_width() / 2\n", + " y = height\n", + " plt.text(\n", + " x,\n", + " y,\n", + " f'{height:.2e}',\n", + " ha='center',\n", + " va='bottom',\n", + " fontsize=label_fontsize\n", + " )\n", + "\n", + " plt.grid(True, which='both', axis='y', linestyle='--', linewidth=0.3)\n", + " plt.xticks(\n", + " edges,\n", + " [f\"{int(edge // bin_size)} ({int(edge)})\" for edge in edges],\n", + " rotation='vertical',\n", + " fontsize=label_fontsize\n", + " )\n", + " plt.legend(fontsize=label_fontsize)\n", + " plt.tight_layout()\n", + " plt.savefig('simulation_results_histogram.png', dpi=300, bbox_inches='tight')\n", + " plt.show()\n", + "\n", + "def fully_successful_attacks(\n", + " attacked_results: list[tuple[int, int]],\n", + " sim: Sim,\n", + " tolerance: int = 0\n", + ") -> int:\n", + " \"\"\"\n", + " Count how many attacks were “fully successful,” meaning\n", + " reorg_length ≥ ( (S−1) − fslot ) − tolerance,\n", + " where fslot is the honest block’s slot.\n", + "\n", + " Read the attack_on_block function for more details\n", + " \"\"\"\n", + " S = sim.params.SLOTS\n", + " count = 0\n", + "\n", + " successful = []\n", + " for honest_id, length in attacked_results:\n", + " if length < 0:\n", + " continue\n", + "\n", + " fslot = sim.blocks[honest_id].slot\n", + " max_len = (S - 1) - fslot\n", + " if length >= max_len - tolerance:\n", + " successful.append([honest_id, fslot, length])\n", + " count += 1\n", + "\n", + " return count, successful" + ] + }, + { + "cell_type": "code", + "execution_count": 1160, + "id": "35f3346b-0ffa-4aa4-8be4-237a25a0f4a8", + "metadata": { + "jupyter": { + "source_hidden": true + } + }, + "outputs": [], + "source": [ + "def run_multiple_attacks(\n", + " n_runs: int,\n", + " slots: int,\n", + " f: float,\n", + " window_size: int,\n", + " use_deps: bool,\n", + " adversary_control: float,\n", + " network,\n", + " base_seed: int = 0,\n", + " skip_last: int = 100,\n", + ") -> tuple[list[int], int]:\n", + " \"\"\"\n", + " Runs `parallel_attack_all_blocks` over `n_runs` independent seeds.\n", + " Returns:\n", + " - all_attacks: flattened list of (block_id, reorg_length) tuples\n", + " - total_honest_blocks: count of honest blocks across all runs\n", + " - last_sim: the Sim instance from the final run\n", + " \"\"\"\n", + " assert slots > 500, \"Must simulate more than 500 slots\"\n", + " all_attacks: list[tuple[int, int]] = []\n", + " total_honest_blocks = 0\n", + " last_sim = None\n", + "\n", + " for i in range(n_runs):\n", + " print(f\"Executing run {i + 1}/{n_runs}\")\n", + " seed = base_seed + i\n", + " random.seed(seed)\n", + " np.random.seed(seed)\n", + "\n", + " params = Params(\n", + " SLOTS=slots,\n", + " f=f,\n", + " window_size=window_size,\n", + " use_deps=use_deps,\n", + " adversary_control=adversary_control,\n", + " honest_stake=np.random.pareto(10, 1000)\n", + " )\n", + " sim = Sim(params=params, network=network)\n", + " last_sim = sim # save for return\n", + "\n", + " attacked, blocks = parallel_attack_all_blocks(sim, skip_last=skip_last)\n", + " all_attacks.extend(attacked)\n", + "\n", + " total_honest_blocks += len(blocks)-skip_last\n", + "\n", + " return all_attacks, total_honest_blocks, last_sim" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "86554739-64cd-4a8b-9187-de5991fc9fce", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Executing run 1/1\n" + ] + } + ], + "source": [ + "%%time\n", + "\n", + "ADVERSARY_STAKE = 0.30\n", + "# Run multiple experiments\n", + "attacked_results, total_blocks, sim = run_multiple_attacks(\n", + " n_runs=1,\n", + " slots=15000,\n", + " f=0.25,\n", + " window_size=30,\n", + " use_deps=True,\n", + " adversary_control=ADVERSARY_STAKE,\n", + " network=blend_net,\n", + " base_seed=0,\n", + " # these are honest blocks that won't be attacked and will not count in the stats, but give room for the attack.\n", + " skip_last=1000\n", + ")\n", + "\n", + "# Plot normalized histogram:\n", + "plot_attack_histogram_frequency(attacked_results, total_blocks, ADVERSARY_STAKE, bin_size=30)\n", + "\n", + "# Print the most extreme reorg seen:\n", + "reorg_lengths = [length for _, length in attacked_results]\n", + "max_length = max(reorg_lengths) if reorg_lengths else 0\n", + "print(f\"Largest reorg length: {max_length} (~{(max_length // 30)} virtual blocks of 30-seconds)\")\n", + "\n", + "fully = fully_successful_attacks(attacked_results, sim, tolerance=500) # Very generous tolerance\n", + "print(f\"Successful attacks: {fully[0]}\")\n", + "print(f\"Successful attacks list [target block, target slot, reorg length]): {fully[1]}\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "myenv", + "language": "python", + "name": "myenv" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/cryptarchia-v2/requirements.txt b/cryptarchia-v2/requirements.txt new file mode 100644 index 0000000..e395c02 --- /dev/null +++ b/cryptarchia-v2/requirements.txt @@ -0,0 +1,4 @@ +numpy +matplotlib +pyvis +joblib