From 086caebc015a4dd6c2f1c7a37e58126735483237 Mon Sep 17 00:00:00 2001 From: Cody Littley Date: Wed, 4 Mar 2026 12:42:19 -0600 Subject: [PATCH 01/20] Show time spent per block. --- .../dashboards/cryptosim-dashboard.json | 506 ++++++++++++------ 1 file changed, 332 insertions(+), 174 deletions(-) diff --git a/docker/monitornode/dashboards/cryptosim-dashboard.json b/docker/monitornode/dashboards/cryptosim-dashboard.json index 5ffc37c4c8..12d6542a09 100644 --- a/docker/monitornode/dashboards/cryptosim-dashboard.json +++ b/docker/monitornode/dashboards/cryptosim-dashboard.json @@ -97,6 +97,69 @@ "title": "Time Spent", "type": "piechart" }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + } + }, + "mappings": [], + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 16, + "w": 12, + "x": 12, + "y": 1 + }, + "id": 275, + "options": { + "legend": { + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "pieType": "pie", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "sort": "desc", + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.4.0", + "targets": [ + { + "editorMode": "code", + "expr": "rate(seidb_main_thread_phase_duration_seconds_total[$__rate_interval])\n/ on() group_left()\nrate(cryptosim_blocks_finalized_total[$__rate_interval])", + "legendFormat": "{{phase}}", + "range": true, + "refId": "A" + } + ], + "title": "Time Spent Per Block (amortized)", + "type": "piechart" + }, { "datasource": { "type": "prometheus", @@ -162,8 +225,8 @@ "gridPos": { "h": 8, "w": 12, - "x": 12, - "y": 1 + "x": 0, + "y": 17 }, "id": 15, "options": { @@ -192,6 +255,101 @@ "title": "Time Spent", "type": "timeseries" }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 100, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 17 + }, + "id": 276, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.4.0", + "targets": [ + { + "editorMode": "code", + "expr": "rate(seidb_main_thread_phase_duration_seconds_total[$__rate_interval])\n/ on() group_left()\nrate(cryptosim_blocks_finalized_total[$__rate_interval])", + "legendFormat": "{{phase}}", + "range": true, + "refId": "A" + } + ], + "title": "Time Spent Per Block (amortized)", + "type": "timeseries" + }, { "datasource": { "type": "prometheus", @@ -257,8 +415,8 @@ "gridPos": { "h": 8, "w": 12, - "x": 12, - "y": 9 + "x": 0, + "y": 25 }, "id": 16, "options": { @@ -293,7 +451,7 @@ "h": 1, "w": 24, "x": 0, - "y": 17 + "y": 33 }, "id": 9, "panels": [ @@ -363,7 +521,7 @@ "h": 8, "w": 12, "x": 0, - "y": 4103 + "y": 18 }, "id": 6, "options": { @@ -493,7 +651,7 @@ "h": 8, "w": 12, "x": 12, - "y": 4103 + "y": 18 }, "id": 7, "options": { @@ -588,7 +746,7 @@ "h": 8, "w": 12, "x": 0, - "y": 4111 + "y": 3370 }, "id": 10, "options": { @@ -682,7 +840,7 @@ "h": 8, "w": 12, "x": 12, - "y": 4111 + "y": 3370 }, "id": 11, "options": { @@ -721,7 +879,7 @@ "h": 1, "w": 24, "x": 0, - "y": 18 + "y": 34 }, "id": 13, "panels": [ @@ -751,7 +909,7 @@ "h": 16, "w": 12, "x": 0, - "y": 3900 + "y": 19 }, "id": 20, "options": { @@ -854,7 +1012,7 @@ "h": 8, "w": 12, "x": 12, - "y": 3900 + "y": 19 }, "id": 19, "options": { @@ -985,7 +1143,7 @@ "h": 8, "w": 12, "x": 12, - "y": 3948 + "y": 3243 }, "id": 21, "options": { @@ -1080,7 +1238,7 @@ "h": 8, "w": 12, "x": 0, - "y": 3956 + "y": 3291 }, "id": 22, "options": { @@ -1211,7 +1369,7 @@ "h": 8, "w": 12, "x": 12, - "y": 3956 + "y": 3291 }, "id": 23, "options": { @@ -1342,7 +1500,7 @@ "h": 8, "w": 12, "x": 0, - "y": 3964 + "y": 3299 }, "id": 24, "options": { @@ -1473,7 +1631,7 @@ "h": 8, "w": 12, "x": 12, - "y": 3964 + "y": 3299 }, "id": 25, "options": { @@ -1604,7 +1762,7 @@ "h": 8, "w": 12, "x": 0, - "y": 3972 + "y": 3307 }, "id": 26, "options": { @@ -1735,7 +1893,7 @@ "h": 8, "w": 12, "x": 12, - "y": 3972 + "y": 3307 }, "id": 27, "options": { @@ -1866,7 +2024,7 @@ "h": 8, "w": 12, "x": 0, - "y": 3980 + "y": 3315 }, "id": 28, "options": { @@ -1941,7 +2099,7 @@ "h": 1, "w": 24, "x": 0, - "y": 19 + "y": 35 }, "id": 8, "panels": [ @@ -2010,7 +2168,7 @@ "h": 8, "w": 12, "x": 0, - "y": 4101 + "y": 44 }, "id": 1, "options": { @@ -2034,7 +2192,7 @@ "uid": "PBFA97CFB590B2093" }, "editorMode": "code", - "expr": "rate(cryptosim_blocks_finalized_total[$__rate_interval ])", + "expr": "rate(cryptosim_blocks_finalized_total[$__rate_interval])", "legendFormat": "Blocks / Second", "range": true, "refId": "A" @@ -2109,7 +2267,7 @@ "h": 8, "w": 12, "x": 12, - "y": 4101 + "y": 44 }, "id": 18, "options": { @@ -2184,7 +2342,7 @@ "h": 1, "w": 24, "x": 0, - "y": 20 + "y": 36 }, "id": 12, "panels": [ @@ -2253,7 +2411,7 @@ "h": 8, "w": 12, "x": 0, - "y": 4102 + "y": 7478 }, "id": 3, "options": { @@ -2352,7 +2510,7 @@ "h": 8, "w": 12, "x": 12, - "y": 4102 + "y": 7478 }, "id": 4, "options": { @@ -2427,7 +2585,7 @@ "h": 1, "w": 24, "x": 0, - "y": 21 + "y": 37 }, "id": 29, "panels": [ @@ -2497,7 +2655,7 @@ "h": 8, "w": 12, "x": 0, - "y": 4104 + "y": 3382 }, "id": 31, "options": { @@ -2592,7 +2750,7 @@ "h": 8, "w": 12, "x": 12, - "y": 4104 + "y": 3382 }, "id": 36, "options": { @@ -2687,7 +2845,7 @@ "h": 8, "w": 12, "x": 0, - "y": 4112 + "y": 3390 }, "id": 38, "options": { @@ -2726,7 +2884,7 @@ "h": 1, "w": 24, "x": 0, - "y": 22 + "y": 38 }, "id": 35, "panels": [ @@ -2795,7 +2953,7 @@ "h": 8, "w": 12, "x": 0, - "y": 4713 + "y": 8089 }, "id": 30, "options": { @@ -2890,7 +3048,7 @@ "h": 8, "w": 12, "x": 12, - "y": 4713 + "y": 8089 }, "id": 33, "options": { @@ -2985,7 +3143,7 @@ "h": 8, "w": 12, "x": 0, - "y": 4753 + "y": 8129 }, "id": 34, "options": { @@ -3024,7 +3182,7 @@ "h": 1, "w": 24, "x": 0, - "y": 23 + "y": 39 }, "id": 37, "panels": [ @@ -3094,7 +3252,7 @@ "h": 8, "w": 12, "x": 0, - "y": 4106 + "y": 7482 }, "id": 39, "options": { @@ -3189,7 +3347,7 @@ "h": 8, "w": 12, "x": 12, - "y": 4106 + "y": 7482 }, "id": 40, "options": { @@ -3284,7 +3442,7 @@ "h": 8, "w": 12, "x": 0, - "y": 4114 + "y": 7490 }, "id": 41, "options": { @@ -3379,7 +3537,7 @@ "h": 8, "w": 12, "x": 12, - "y": 4114 + "y": 7490 }, "id": 42, "options": { @@ -3474,7 +3632,7 @@ "h": 8, "w": 12, "x": 0, - "y": 4122 + "y": 7498 }, "id": 32, "options": { @@ -3513,7 +3671,7 @@ "h": 1, "w": 24, "x": 0, - "y": 24 + "y": 40 }, "id": 44, "panels": [ @@ -3583,7 +3741,7 @@ "h": 8, "w": 12, "x": 0, - "y": 3907 + "y": 7283 }, "id": 43, "options": { @@ -3622,7 +3780,7 @@ "h": 1, "w": 24, "x": 0, - "y": 25 + "y": 41 }, "id": 117, "panels": [ @@ -3882,7 +4040,7 @@ "h": 8, "w": 12, "x": 0, - "y": 530 + "y": 218 }, "id": 261, "options": { @@ -3977,7 +4135,7 @@ "h": 8, "w": 12, "x": 12, - "y": 530 + "y": 218 }, "id": 263, "options": { @@ -4072,7 +4230,7 @@ "h": 8, "w": 12, "x": 0, - "y": 586 + "y": 226 }, "id": 262, "options": { @@ -4167,7 +4325,7 @@ "h": 8, "w": 12, "x": 12, - "y": 586 + "y": 226 }, "id": 264, "options": { @@ -4206,7 +4364,7 @@ "h": 1, "w": 24, "x": 0, - "y": 26 + "y": 42 }, "id": 191, "panels": [ @@ -4275,7 +4433,7 @@ "h": 8, "w": 12, "x": 0, - "y": 27 + "y": 3403 }, "id": 155, "options": { @@ -4370,7 +4528,7 @@ "h": 8, "w": 12, "x": 12, - "y": 27 + "y": 3403 }, "id": 111, "options": { @@ -4465,7 +4623,7 @@ "h": 8, "w": 12, "x": 0, - "y": 91 + "y": 3467 }, "id": 175, "options": { @@ -4560,7 +4718,7 @@ "h": 8, "w": 12, "x": 12, - "y": 91 + "y": 3467 }, "id": 173, "options": { @@ -4654,7 +4812,7 @@ "h": 8, "w": 12, "x": 0, - "y": 99 + "y": 3475 }, "id": 138, "options": { @@ -4749,7 +4907,7 @@ "h": 8, "w": 12, "x": 12, - "y": 99 + "y": 3475 }, "id": 172, "options": { @@ -4844,7 +5002,7 @@ "h": 8, "w": 12, "x": 0, - "y": 107 + "y": 3483 }, "id": 236, "options": { @@ -4883,7 +5041,7 @@ "h": 1, "w": 24, "x": 0, - "y": 27 + "y": 43 }, "id": 118, "panels": [ @@ -4952,7 +5110,7 @@ "h": 8, "w": 12, "x": 0, - "y": 28 + "y": 1556 }, "id": 127, "options": { @@ -5047,7 +5205,7 @@ "h": 8, "w": 12, "x": 12, - "y": 28 + "y": 1556 }, "id": 120, "options": { @@ -5141,7 +5299,7 @@ "h": 8, "w": 12, "x": 0, - "y": 36 + "y": 1564 }, "id": 128, "options": { @@ -5236,7 +5394,7 @@ "h": 8, "w": 12, "x": 12, - "y": 36 + "y": 1564 }, "id": 121, "options": { @@ -5330,7 +5488,7 @@ "h": 8, "w": 12, "x": 0, - "y": 44 + "y": 1572 }, "id": 129, "options": { @@ -5425,7 +5583,7 @@ "h": 8, "w": 12, "x": 12, - "y": 44 + "y": 1572 }, "id": 122, "options": { @@ -5519,7 +5677,7 @@ "h": 8, "w": 12, "x": 0, - "y": 52 + "y": 1580 }, "id": 130, "options": { @@ -5614,7 +5772,7 @@ "h": 8, "w": 12, "x": 12, - "y": 52 + "y": 1580 }, "id": 123, "options": { @@ -5708,7 +5866,7 @@ "h": 8, "w": 12, "x": 0, - "y": 60 + "y": 1588 }, "id": 131, "options": { @@ -5803,7 +5961,7 @@ "h": 8, "w": 12, "x": 12, - "y": 60 + "y": 1588 }, "id": 124, "options": { @@ -5897,7 +6055,7 @@ "h": 8, "w": 12, "x": 0, - "y": 68 + "y": 1596 }, "id": 132, "options": { @@ -5992,7 +6150,7 @@ "h": 8, "w": 12, "x": 12, - "y": 68 + "y": 1596 }, "id": 125, "options": { @@ -6086,7 +6244,7 @@ "h": 8, "w": 12, "x": 0, - "y": 76 + "y": 1604 }, "id": 119, "options": { @@ -6181,7 +6339,7 @@ "h": 8, "w": 12, "x": 12, - "y": 76 + "y": 1604 }, "id": 126, "options": { @@ -6220,7 +6378,7 @@ "h": 1, "w": 24, "x": 0, - "y": 28 + "y": 44 }, "id": 115, "panels": [ @@ -6289,7 +6447,7 @@ "h": 8, "w": 12, "x": 0, - "y": 1705 + "y": 53 }, "id": 101, "options": { @@ -6383,7 +6541,7 @@ "h": 8, "w": 12, "x": 12, - "y": 1705 + "y": 53 }, "id": 187, "options": { @@ -6478,7 +6636,7 @@ "h": 8, "w": 12, "x": 0, - "y": 1713 + "y": 61 }, "id": 113, "options": { @@ -6572,7 +6730,7 @@ "h": 8, "w": 12, "x": 12, - "y": 1713 + "y": 61 }, "id": 103, "options": { @@ -6667,7 +6825,7 @@ "h": 8, "w": 12, "x": 0, - "y": 1721 + "y": 69 }, "id": 102, "options": { @@ -6762,7 +6920,7 @@ "h": 8, "w": 12, "x": 12, - "y": 1721 + "y": 69 }, "id": 116, "options": { @@ -6856,7 +7014,7 @@ "h": 8, "w": 12, "x": 0, - "y": 1729 + "y": 77 }, "id": 135, "options": { @@ -6951,7 +7109,7 @@ "h": 8, "w": 12, "x": 12, - "y": 1729 + "y": 77 }, "id": 134, "options": { @@ -7045,7 +7203,7 @@ "h": 8, "w": 12, "x": 0, - "y": 1737 + "y": 85 }, "id": 136, "options": { @@ -7140,7 +7298,7 @@ "h": 8, "w": 12, "x": 12, - "y": 1737 + "y": 85 }, "id": 159, "options": { @@ -7179,7 +7337,7 @@ "h": 1, "w": 24, "x": 0, - "y": 29 + "y": 45 }, "id": 193, "panels": [ @@ -7248,7 +7406,7 @@ "h": 8, "w": 12, "x": 0, - "y": 1754 + "y": 5130 }, "id": 141, "options": { @@ -7342,7 +7500,7 @@ "h": 8, "w": 12, "x": 12, - "y": 1754 + "y": 5130 }, "id": 148, "options": { @@ -7436,7 +7594,7 @@ "h": 8, "w": 12, "x": 0, - "y": 1762 + "y": 5138 }, "id": 142, "options": { @@ -7530,7 +7688,7 @@ "h": 8, "w": 12, "x": 12, - "y": 1762 + "y": 5138 }, "id": 149, "options": { @@ -7624,7 +7782,7 @@ "h": 8, "w": 12, "x": 0, - "y": 1770 + "y": 5146 }, "id": 143, "options": { @@ -7718,7 +7876,7 @@ "h": 8, "w": 12, "x": 12, - "y": 1770 + "y": 5146 }, "id": 150, "options": { @@ -7812,7 +7970,7 @@ "h": 8, "w": 12, "x": 0, - "y": 1778 + "y": 5154 }, "id": 144, "options": { @@ -7906,7 +8064,7 @@ "h": 8, "w": 12, "x": 12, - "y": 1778 + "y": 5154 }, "id": 151, "options": { @@ -8000,7 +8158,7 @@ "h": 8, "w": 12, "x": 0, - "y": 1786 + "y": 5162 }, "id": 145, "options": { @@ -8094,7 +8252,7 @@ "h": 8, "w": 12, "x": 12, - "y": 1786 + "y": 5162 }, "id": 152, "options": { @@ -8188,7 +8346,7 @@ "h": 8, "w": 12, "x": 0, - "y": 1794 + "y": 5170 }, "id": 146, "options": { @@ -8282,7 +8440,7 @@ "h": 8, "w": 12, "x": 12, - "y": 1794 + "y": 5170 }, "id": 153, "options": { @@ -8376,7 +8534,7 @@ "h": 8, "w": 12, "x": 0, - "y": 1802 + "y": 5178 }, "id": 147, "options": { @@ -8470,7 +8628,7 @@ "h": 8, "w": 12, "x": 12, - "y": 1802 + "y": 5178 }, "id": 154, "options": { @@ -8509,7 +8667,7 @@ "h": 1, "w": 24, "x": 0, - "y": 30 + "y": 46 }, "id": 192, "panels": [ @@ -8578,7 +8736,7 @@ "h": 8, "w": 12, "x": 0, - "y": 1530 + "y": 4906 }, "id": 190, "options": { @@ -8673,7 +8831,7 @@ "h": 8, "w": 12, "x": 12, - "y": 1530 + "y": 4906 }, "id": 184, "options": { @@ -8767,7 +8925,7 @@ "h": 8, "w": 12, "x": 0, - "y": 1538 + "y": 4914 }, "id": 188, "options": { @@ -8862,7 +9020,7 @@ "h": 8, "w": 12, "x": 12, - "y": 1538 + "y": 4914 }, "id": 186, "options": { @@ -8957,7 +9115,7 @@ "h": 8, "w": 12, "x": 0, - "y": 1546 + "y": 4922 }, "id": 185, "options": { @@ -9051,7 +9209,7 @@ "h": 8, "w": 12, "x": 12, - "y": 1546 + "y": 4922 }, "id": 189, "options": { @@ -9145,7 +9303,7 @@ "h": 8, "w": 12, "x": 0, - "y": 1554 + "y": 4930 }, "id": 181, "options": { @@ -9240,7 +9398,7 @@ "h": 8, "w": 12, "x": 12, - "y": 1554 + "y": 4930 }, "id": 182, "options": { @@ -9279,7 +9437,7 @@ "h": 1, "w": 24, "x": 0, - "y": 31 + "y": 47 }, "id": 194, "panels": [ @@ -9348,7 +9506,7 @@ "h": 8, "w": 12, "x": 0, - "y": 112 + "y": 856 }, "id": 170, "options": { @@ -9443,7 +9601,7 @@ "h": 8, "w": 12, "x": 12, - "y": 112 + "y": 856 }, "id": 171, "options": { @@ -9537,7 +9695,7 @@ "h": 8, "w": 12, "x": 0, - "y": 488 + "y": 1256 }, "id": 162, "options": { @@ -9632,7 +9790,7 @@ "h": 8, "w": 12, "x": 12, - "y": 488 + "y": 1256 }, "id": 108, "options": { @@ -9727,7 +9885,7 @@ "h": 8, "w": 12, "x": 0, - "y": 496 + "y": 1264 }, "id": 169, "options": { @@ -9821,7 +9979,7 @@ "h": 8, "w": 12, "x": 12, - "y": 496 + "y": 1264 }, "id": 166, "options": { @@ -9915,7 +10073,7 @@ "h": 8, "w": 12, "x": 0, - "y": 504 + "y": 1272 }, "id": 157, "options": { @@ -10010,7 +10168,7 @@ "h": 8, "w": 12, "x": 12, - "y": 504 + "y": 1272 }, "id": 158, "options": { @@ -10104,7 +10262,7 @@ "h": 8, "w": 12, "x": 0, - "y": 512 + "y": 1280 }, "id": 167, "options": { @@ -10199,7 +10357,7 @@ "h": 8, "w": 12, "x": 12, - "y": 512 + "y": 1280 }, "id": 168, "options": { @@ -10293,7 +10451,7 @@ "h": 8, "w": 12, "x": 0, - "y": 520 + "y": 1288 }, "id": 137, "options": { @@ -10388,7 +10546,7 @@ "h": 8, "w": 12, "x": 12, - "y": 520 + "y": 1288 }, "id": 183, "options": { @@ -10483,7 +10641,7 @@ "h": 8, "w": 12, "x": 0, - "y": 528 + "y": 1296 }, "id": 241, "options": { @@ -10578,7 +10736,7 @@ "h": 8, "w": 12, "x": 12, - "y": 528 + "y": 1296 }, "id": 242, "options": { @@ -10673,7 +10831,7 @@ "h": 8, "w": 12, "x": 0, - "y": 536 + "y": 1304 }, "id": 243, "options": { @@ -10768,7 +10926,7 @@ "h": 8, "w": 12, "x": 12, - "y": 536 + "y": 1304 }, "id": 244, "options": { @@ -10863,7 +11021,7 @@ "h": 8, "w": 12, "x": 0, - "y": 544 + "y": 1312 }, "id": 245, "options": { @@ -10958,7 +11116,7 @@ "h": 8, "w": 12, "x": 12, - "y": 544 + "y": 1312 }, "id": 246, "options": { @@ -11053,7 +11211,7 @@ "h": 8, "w": 12, "x": 0, - "y": 552 + "y": 1320 }, "id": 247, "options": { @@ -11148,7 +11306,7 @@ "h": 8, "w": 12, "x": 12, - "y": 552 + "y": 1320 }, "id": 248, "options": { @@ -11187,7 +11345,7 @@ "h": 1, "w": 24, "x": 0, - "y": 32 + "y": 48 }, "id": 195, "panels": [ @@ -11256,7 +11414,7 @@ "h": 8, "w": 12, "x": 0, - "y": 113 + "y": 81 }, "id": 161, "options": { @@ -11350,7 +11508,7 @@ "h": 8, "w": 12, "x": 12, - "y": 113 + "y": 81 }, "id": 104, "options": { @@ -11445,7 +11603,7 @@ "h": 8, "w": 12, "x": 0, - "y": 377 + "y": 89 }, "id": 105, "options": { @@ -11539,7 +11697,7 @@ "h": 8, "w": 12, "x": 12, - "y": 377 + "y": 89 }, "id": 164, "options": { @@ -11633,7 +11791,7 @@ "h": 8, "w": 12, "x": 0, - "y": 385 + "y": 97 }, "id": 163, "options": { @@ -11728,7 +11886,7 @@ "h": 8, "w": 12, "x": 12, - "y": 385 + "y": 97 }, "id": 165, "options": { @@ -11823,7 +11981,7 @@ "h": 8, "w": 12, "x": 0, - "y": 393 + "y": 105 }, "id": 223, "options": { @@ -11862,7 +12020,7 @@ "h": 1, "w": 24, "x": 0, - "y": 33 + "y": 49 }, "id": 210, "panels": [ @@ -11932,7 +12090,7 @@ "h": 8, "w": 12, "x": 0, - "y": 587 + "y": 858 }, "id": 211, "options": { @@ -12027,7 +12185,7 @@ "h": 8, "w": 12, "x": 12, - "y": 587 + "y": 858 }, "id": 212, "options": { @@ -12122,7 +12280,7 @@ "h": 8, "w": 12, "x": 0, - "y": 595 + "y": 938 }, "id": 213, "options": { @@ -12217,7 +12375,7 @@ "h": 8, "w": 12, "x": 12, - "y": 595 + "y": 938 }, "id": 214, "options": { @@ -12312,7 +12470,7 @@ "h": 8, "w": 12, "x": 0, - "y": 603 + "y": 946 }, "id": 215, "options": { @@ -12407,7 +12565,7 @@ "h": 8, "w": 12, "x": 12, - "y": 603 + "y": 946 }, "id": 216, "options": { @@ -12502,7 +12660,7 @@ "h": 8, "w": 12, "x": 0, - "y": 611 + "y": 954 }, "id": 217, "options": { @@ -12597,7 +12755,7 @@ "h": 8, "w": 12, "x": 12, - "y": 611 + "y": 954 }, "id": 218, "options": { @@ -12636,7 +12794,7 @@ "h": 1, "w": 24, "x": 0, - "y": 34 + "y": 50 }, "id": 230, "panels": [ @@ -12706,7 +12864,7 @@ "h": 8, "w": 12, "x": 0, - "y": 324 + "y": 3700 }, "id": 231, "options": { @@ -12800,7 +12958,7 @@ "h": 8, "w": 12, "x": 12, - "y": 324 + "y": 3700 }, "id": 178, "options": { @@ -12895,7 +13053,7 @@ "h": 8, "w": 12, "x": 0, - "y": 332 + "y": 3708 }, "id": 179, "options": { @@ -12989,7 +13147,7 @@ "h": 8, "w": 12, "x": 12, - "y": 332 + "y": 3708 }, "id": 156, "options": { @@ -13028,7 +13186,7 @@ "h": 1, "w": 24, "x": 0, - "y": 35 + "y": 51 }, "id": 250, "panels": [ @@ -13098,7 +13256,7 @@ "h": 8, "w": 12, "x": 0, - "y": 116 + "y": 3492 }, "id": 251, "options": { @@ -13193,7 +13351,7 @@ "h": 8, "w": 12, "x": 12, - "y": 116 + "y": 3492 }, "id": 252, "options": { @@ -13288,7 +13446,7 @@ "h": 8, "w": 12, "x": 0, - "y": 228 + "y": 3604 }, "id": 253, "options": { @@ -13383,7 +13541,7 @@ "h": 8, "w": 12, "x": 12, - "y": 228 + "y": 3604 }, "id": 254, "options": { @@ -13478,7 +13636,7 @@ "h": 8, "w": 12, "x": 0, - "y": 236 + "y": 3612 }, "id": 273, "options": { @@ -13517,7 +13675,7 @@ "h": 1, "w": 24, "x": 0, - "y": 36 + "y": 52 }, "id": 100, "panels": [ @@ -13586,7 +13744,7 @@ "h": 8, "w": 12, "x": 0, - "y": 37 + "y": 861 }, "id": 107, "options": { @@ -13681,7 +13839,7 @@ "h": 8, "w": 12, "x": 12, - "y": 37 + "y": 861 }, "id": 110, "options": { @@ -13776,7 +13934,7 @@ "h": 8, "w": 12, "x": 0, - "y": 45 + "y": 869 }, "id": 180, "options": { @@ -13870,7 +14028,7 @@ "h": 8, "w": 12, "x": 12, - "y": 45 + "y": 869 }, "id": 160, "options": { @@ -13964,7 +14122,7 @@ "h": 8, "w": 12, "x": 0, - "y": 53 + "y": 877 }, "id": 139, "options": { @@ -14058,7 +14216,7 @@ "h": 8, "w": 12, "x": 12, - "y": 53 + "y": 877 }, "id": 176, "options": { @@ -14152,7 +14310,7 @@ "h": 8, "w": 12, "x": 0, - "y": 61 + "y": 885 }, "id": 133, "options": { @@ -14247,7 +14405,7 @@ "h": 8, "w": 12, "x": 12, - "y": 61 + "y": 885 }, "id": 221, "options": { @@ -14341,7 +14499,7 @@ "h": 8, "w": 12, "x": 0, - "y": 69 + "y": 893 }, "id": 177, "options": { @@ -14436,7 +14594,7 @@ "h": 8, "w": 12, "x": 12, - "y": 69 + "y": 893 }, "id": 271, "options": { @@ -14531,7 +14689,7 @@ "h": 8, "w": 12, "x": 0, - "y": 77 + "y": 901 }, "id": 274, "options": { @@ -14626,7 +14784,7 @@ "h": 8, "w": 12, "x": 12, - "y": 77 + "y": 901 }, "id": 272, "options": { @@ -14721,7 +14879,7 @@ "h": 8, "w": 12, "x": 0, - "y": 85 + "y": 909 }, "id": 232, "options": { @@ -14816,7 +14974,7 @@ "h": 8, "w": 12, "x": 12, - "y": 85 + "y": 909 }, "id": 233, "options": { @@ -14911,7 +15069,7 @@ "h": 8, "w": 12, "x": 0, - "y": 93 + "y": 917 }, "id": 234, "options": { @@ -15006,7 +15164,7 @@ "h": 8, "w": 12, "x": 12, - "y": 93 + "y": 917 }, "id": 235, "options": { @@ -15101,7 +15259,7 @@ "h": 8, "w": 12, "x": 0, - "y": 101 + "y": 925 }, "id": 222, "options": { @@ -15150,6 +15308,6 @@ "timezone": "browser", "title": "CryptoSim", "uid": "adnqfm4", - "version": 25, + "version": 29, "weekStart": "" } \ No newline at end of file From 13865432478b406629c5226fafe316666612c21f Mon Sep 17 00:00:00 2001 From: Cody Littley Date: Wed, 4 Mar 2026 14:47:56 -0600 Subject: [PATCH 02/20] Handle block creation on background goroutine. --- .../bench/cryptosim/config/basic-config.json | 3 +- sei-db/state_db/bench/cryptosim/cryptosim.go | 77 ++++++++++--------- .../bench/cryptosim/cryptosim_config.go | 4 + .../bench/cryptosim/cryptosim_metrics.go | 11 +-- .../bench/cryptosim/data_generator.go | 6 +- sei-db/state_db/bench/cryptosim/database.go | 10 ++- 6 files changed, 56 insertions(+), 55 deletions(-) diff --git a/sei-db/state_db/bench/cryptosim/config/basic-config.json b/sei-db/state_db/bench/cryptosim/config/basic-config.json index dea1b2a229..26001cd944 100644 --- a/sei-db/state_db/bench/cryptosim/config/basic-config.json +++ b/sei-db/state_db/bench/cryptosim/config/basic-config.json @@ -29,5 +29,6 @@ "TransactionsPerBlock": 1024, "MaxRuntimeSeconds": 0, "TransactionMetricsSampleRate": 0.001, - "BackgroundMetricsScrapeInterval": 60 + "BackgroundMetricsScrapeInterval": 60, + "BlockChannelCapacity": 8 } diff --git a/sei-db/state_db/bench/cryptosim/cryptosim.go b/sei-db/state_db/bench/cryptosim/cryptosim.go index 04fc441b1a..1468332aa9 100644 --- a/sei-db/state_db/bench/cryptosim/cryptosim.go +++ b/sei-db/state_db/bench/cryptosim/cryptosim.go @@ -48,6 +48,9 @@ type CryptoSim struct { // The data generator for the benchmark. dataGenerator *DataGenerator + // Builds blocks of transactions. + blockBuilder *blockBuilder + // The database for the benchmark. database *Database @@ -142,6 +145,8 @@ func NewCryptoSim( ctx, cancel, database, dataGenerator.FeeCollectionAddress(), config.ExecutorQueueSize, metrics) } + blockBuilder := NewBlockBuilder(ctx, config, metrics, dataGenerator) + c := &CryptoSim{ ctx: ctx, cancel: cancel, @@ -151,6 +156,7 @@ func NewCryptoSim( lastConsoleUpdateTransactionCount: 0, closeChan: make(chan struct{}, 1), dataGenerator: dataGenerator, + blockBuilder: blockBuilder, database: database, executors: executors, metrics: metrics, @@ -166,7 +172,10 @@ func NewCryptoSim( c.database.ResetTransactionCount() c.startTimestamp = time.Now() - c.metrics.StartBackgroundSampling(c.startTimestamp) + + // Now that we are done generating initial data, it is thread safe to start the block builder. + // (dataGenerator is not thread safe, and is used both for initial setup and for transaction generation) + c.blockBuilder.Start() go c.run() return c, nil @@ -210,7 +219,7 @@ func (c *CryptoSim) setupAccounts() error { if err != nil { return fmt.Errorf("failed to create new account: %w", err) } - c.database.IncrementTransactionCount() + c.database.IncrementTransactionCount(1) finalized, err := c.database.MaybeFinalizeBlock( c.dataGenerator.NextAccountID(), c.dataGenerator.NextErc20ContractID()) if err != nil { @@ -218,7 +227,7 @@ func (c *CryptoSim) setupAccounts() error { } if finalized { c.dataGenerator.ReportAccountCounts() - c.dataGenerator.ReportFinalizeBlock() + c.dataGenerator.ReportEndOfBlock() } if c.dataGenerator.NextAccountID()%c.config.SetupUpdateIntervalCount == 0 { @@ -237,7 +246,7 @@ func (c *CryptoSim) setupAccounts() error { return fmt.Errorf("failed to finalize block: %w", err) } c.dataGenerator.ReportAccountCounts() - c.dataGenerator.ReportFinalizeBlock() + c.dataGenerator.ReportEndOfBlock() fmt.Printf("There are now %s accounts in the database.\n", int64Commas(c.dataGenerator.NextAccountID())) @@ -267,7 +276,7 @@ func (c *CryptoSim) setupErc20Contracts() error { break } - c.database.IncrementTransactionCount() + c.database.IncrementTransactionCount(1) _, _, err := c.dataGenerator.CreateNewErc20Contract(c.config.Erc20ContractSize, true) if err != nil { @@ -279,7 +288,7 @@ func (c *CryptoSim) setupErc20Contracts() error { return fmt.Errorf("failed to maybe commit batch: %w", err) } if finalized { - c.dataGenerator.ReportFinalizeBlock() + c.dataGenerator.ReportEndOfBlock() c.metrics.SetTotalNumberOfERC20Contracts(c.dataGenerator.NextErc20ContractID()) } @@ -301,7 +310,7 @@ func (c *CryptoSim) setupErc20Contracts() error { if err != nil { return fmt.Errorf("failed to finalize block: %w", err) } - c.dataGenerator.ReportFinalizeBlock() + c.dataGenerator.ReportEndOfBlock() c.metrics.SetTotalNumberOfERC20Contracts(c.dataGenerator.NextErc20ContractID()) fmt.Printf("There are now %s simulated ERC20 contracts in the database.\n", @@ -316,10 +325,14 @@ func (c *CryptoSim) run() { defer c.teardown() haltTime := time.Now().Add(time.Duration(c.config.MaxRuntimeSeconds) * time.Second) - - c.metrics.SetMainThreadPhase("executing") + var timeoutChan <-chan time.Time + if c.config.MaxRuntimeSeconds > 0 { + timeoutChan = time.After(time.Until(haltTime)) + } for { + c.metrics.SetMainThreadPhase("get_block") + select { case <-c.ctx.Done(): if c.database.TransactionCount() > 0 { @@ -331,42 +344,31 @@ func (c *CryptoSim) run() { if isSuspended { c.suspend() } - default: - c.handleNextCycle(haltTime) + case <-timeoutChan: + fmt.Printf("\nBenchmark timed out after %s.\n", formatDuration(time.Since(c.startTimestamp), 1)) + c.cancel() + return + case blk := <-c.blockBuilder.blocksChan: + c.handleNextBlock(blk) } - } -} -// Process the next benchmark cycle, creating a new transaction and executing it. -func (c *CryptoSim) handleNextCycle(haltTime time.Time) { - txn, err := BuildTransaction(c.dataGenerator) - if err != nil { - fmt.Printf("\nfailed to build transaction: %v\n", err) - c.cancel() - return + c.generateConsoleReport(false) } +} - c.executors[c.nextExecutorIndex].ScheduleForExecution(txn) - c.nextExecutorIndex = (c.nextExecutorIndex + 1) % len(c.executors) +// Execute and finalize the next block. +func (c *CryptoSim) handleNextBlock(blk *block) { + c.metrics.SetMainThreadPhase("send_to_executors") - finalized, err := c.database.MaybeFinalizeBlock( - c.dataGenerator.NextAccountID(), c.dataGenerator.NextErc20ContractID()) - if err != nil { - fmt.Printf("error finalizing block: %v\n", err) - c.cancel() - return - } - if finalized { - c.dataGenerator.ReportAccountCounts() - c.dataGenerator.ReportFinalizeBlock() + c.database.IncrementTransactionCount(blk.TransactionCount()) - if c.config.MaxRuntimeSeconds > 0 && time.Now().After(haltTime) { - c.cancel() - } + for _, txn := range blk.transactions { + c.executors[c.nextExecutorIndex].ScheduleForExecution(txn) + c.nextExecutorIndex = (c.nextExecutorIndex + 1) % len(c.executors) } - c.database.IncrementTransactionCount() - c.generateConsoleReport(false) + c.database.FinalizeBlock(blk.NextAccountID(), blk.NextErc20ContractID(), false) + blk.ReportBlockMetrcs() } // Suspends the benchmark. This method blocks until the benchmark is resumed or shut down. @@ -380,6 +382,7 @@ func (c *CryptoSim) suspend() { } fmt.Printf("Benchmark suspended.\n") + c.metrics.SetMainThreadPhase("suspended") for { select { diff --git a/sei-db/state_db/bench/cryptosim/cryptosim_config.go b/sei-db/state_db/bench/cryptosim/cryptosim_config.go index a33e5a42ff..c6e368b0cb 100644 --- a/sei-db/state_db/bench/cryptosim/cryptosim_config.go +++ b/sei-db/state_db/bench/cryptosim/cryptosim_config.go @@ -132,6 +132,9 @@ type CryptoSimConfig struct { // If true, pressing Enter in the terminal will toggle suspend/resume of the benchmark. // If false, Enter has no effect. EnableSuspension bool + + // The capacity of the channel that holds blocks awaiting execution. + BlockChannelCapacity int } // Returns the default configuration for the cryptosim benchmark. @@ -170,6 +173,7 @@ func DefaultCryptoSimConfig() *CryptoSimConfig { TransactionMetricsSampleRate: 0.001, BackgroundMetricsScrapeInterval: 60, EnableSuspension: true, + BlockChannelCapacity: 8, } } diff --git a/sei-db/state_db/bench/cryptosim/cryptosim_metrics.go b/sei-db/state_db/bench/cryptosim/cryptosim_metrics.go index a25ac9ebc6..c5b16408b8 100644 --- a/sei-db/state_db/bench/cryptosim/cryptosim_metrics.go +++ b/sei-db/state_db/bench/cryptosim/cryptosim_metrics.go @@ -172,20 +172,11 @@ func NewCryptosimMetrics( m.startDataDirSizeSampling(dataDir, config.BackgroundMetricsScrapeInterval) } m.startProcessIOSampling(config.BackgroundMetricsScrapeInterval) + m.startUptimeSampling(time.Now()) } return m } -// StartBackgroundSampling starts goroutines that periodically update gauges -// (uptime, etc.). Call this when the benchmark is about to run, after initial -// state is loaded. Does not start any HTTP server; the caller configures export. -func (m *CryptosimMetrics) StartBackgroundSampling(startTime time.Time) { - if m == nil { - return - } - m.startUptimeSampling(startTime) -} - func (m *CryptosimMetrics) startUptimeSampling(startTime time.Time) { if m == nil || m.uptimeSeconds == nil { return diff --git a/sei-db/state_db/bench/cryptosim/data_generator.go b/sei-db/state_db/bench/cryptosim/data_generator.go index 5920366814..8347d6bd2d 100644 --- a/sei-db/state_db/bench/cryptosim/data_generator.go +++ b/sei-db/state_db/bench/cryptosim/data_generator.go @@ -138,7 +138,6 @@ func (d *DataGenerator) CreateNewAccount( accountID := d.nextAccountID d.nextAccountID++ - // Use EVMKeyCode for account data (balance+padding); EVMKeyNonce only accepts 8-byte values. addr := d.rand.Address(accountPrefix, accountID, AddressLen) address = evm.BuildMemIAVLEVMKey(evm.EVMKeyCode, addr) @@ -289,7 +288,8 @@ func (d *DataGenerator) FeeCollectionAddress() []byte { return d.feeCollectionAddress } -// This method should be called after a block is finalized. -func (d *DataGenerator) ReportFinalizeBlock() { +// Call this to signal that we have reached the end of a block. This is a signal that it is now safe to use +// recently created accounts as read/write targets. +func (d *DataGenerator) ReportEndOfBlock() { d.highestSafeAccountIDInBlock = d.nextAccountID - 1 } diff --git a/sei-db/state_db/bench/cryptosim/database.go b/sei-db/state_db/bench/cryptosim/database.go index 2b723fbdbc..25b3888dda 100644 --- a/sei-db/state_db/bench/cryptosim/database.go +++ b/sei-db/state_db/bench/cryptosim/database.go @@ -80,10 +80,10 @@ func (d *Database) Get(key []byte) ([]byte, bool, error) { return nil, false, nil } -// Signal that a transaction has been executed. -func (d *Database) IncrementTransactionCount() { - d.transactionCount++ - d.transactionsInCurrentBlock++ +// Signal that transactions have been added to the current block. +func (d *Database) IncrementTransactionCount(count int64) { + d.transactionCount += count + d.transactionsInCurrentBlock += count } // Reset the transaction count. Useful for when changing test phases. @@ -120,6 +120,8 @@ func (d *Database) FinalizeBlock( forceCommit bool, ) error { + d.metrics.SetMainThreadPhase("execute_block") + // Wait for all transactions in the current block to be executed. if d.flushFunc != nil { d.flushFunc() From 8ecfba52ac466118524ef0e4280ebb8ff11213a5 Mon Sep 17 00:00:00 2001 From: Cody Littley Date: Wed, 4 Mar 2026 15:10:19 -0600 Subject: [PATCH 03/20] added missing files --- sei-db/state_db/bench/cryptosim/block.go | 94 +++++++++++++++++++ .../state_db/bench/cryptosim/block_builder.go | 82 ++++++++++++++++ 2 files changed, 176 insertions(+) create mode 100644 sei-db/state_db/bench/cryptosim/block.go create mode 100644 sei-db/state_db/bench/cryptosim/block_builder.go diff --git a/sei-db/state_db/bench/cryptosim/block.go b/sei-db/state_db/bench/cryptosim/block.go new file mode 100644 index 0000000000..2963389db2 --- /dev/null +++ b/sei-db/state_db/bench/cryptosim/block.go @@ -0,0 +1,94 @@ +package cryptosim + +import "iter" + +// A simulated block of transactions. +type block struct { + config *CryptoSimConfig + + // The transactions in the block. + transactions []*transaction + + // The block number. This is not currently preserved across benchmark restarts, but otherwise monotonically + // increases as you'd expect. + blockNumber int64 + + // The next account ID to be used when creating a new account, as of the end of this block. + nextAccountID int64 + + // The number of cold accounts, as of the end of this block. + numberOfColdAccounts int64 + + // The next ERC20 contract ID to be used when creating a new ERC20 contract, as of the end of this block. + nextErc20ContractID int64 + + metrics *CryptosimMetrics +} + +// Creates a new block with the given capacity. +func NewBlock( + config *CryptoSimConfig, + metrics *CryptosimMetrics, + blockNumber int64, + capacity int, +) *block { + return &block{ + config: config, + blockNumber: blockNumber, + transactions: make([]*transaction, 0, capacity), + metrics: metrics, + } +} + +// Returns an iterator over the transactions in the block. +func (b *block) Iterator() iter.Seq[*transaction] { + return func(yield func(*transaction) bool) { + for _, txn := range b.transactions { + if !yield(txn) { + return + } + } + } +} + +// Adds a transaction to the block. +func (b *block) AddTransaction(txn *transaction) { + b.transactions = append(b.transactions, txn) +} + +// Returns the block number. +func (b *block) BlockNumber() int64 { + return b.blockNumber +} + +// Sets information about account state as of the end of this block. +func (b *block) SetBlockAccountStats( + nextAccountID int64, + numberOfColdAccounts int64, + nextErc20ContractID int64, +) { + b.nextAccountID = nextAccountID + b.numberOfColdAccounts = numberOfColdAccounts + b.nextErc20ContractID = nextErc20ContractID +} + +// This method should be called after a block is finished executing and finalized. +// Reports metrics about the block. +func (b *block) ReportBlockMetrcs() { + b.metrics.SetTotalNumberOfAccounts(b.nextAccountID, int64(b.config.NumberOfHotAccounts), b.numberOfColdAccounts) +} + +// Returns the next account ID to be used when creating a new account, as of the end of this block. +func (b *block) NextAccountID() int64 { + return b.nextAccountID +} + +// Returns the next ERC20 contract ID to be used when creating a new ERC20 contract, as of the end of this block. +func (b *block) NextErc20ContractID() int64 { + return b.nextErc20ContractID +} + +// Returns the number of transactions in the block. +func (b *block) TransactionCount() int64 { + return int64(len(b.transactions)) +} diff --git a/sei-db/state_db/bench/cryptosim/block_builder.go b/sei-db/state_db/bench/cryptosim/block_builder.go new file mode 100644 index 0000000000..eb36b5d281 --- /dev/null +++ b/sei-db/state_db/bench/cryptosim/block_builder.go @@ -0,0 +1,82 @@ +package cryptosim + +import ( + "context" + "fmt" +) + +// A builder for blocks of transactions. +type blockBuilder struct { + ctx context.Context + + config *CryptoSimConfig + + // Metrics for the benchmark. + metrics *CryptosimMetrics + + // Produces random data. + dataGenerator *DataGenerator + + // Blocks are sent to this channel. + blocksChan chan *block + + // The next block number to be used. + nextBlockNumber int64 +} + +// Asyncronously produces blocks of transactions. +func NewBlockBuilder( + ctx context.Context, + config *CryptoSimConfig, + metrics *CryptosimMetrics, + dataGenerator *DataGenerator, +) *blockBuilder { + return &blockBuilder{ + ctx: ctx, + config: config, + metrics: metrics, + dataGenerator: dataGenerator, + blocksChan: make(chan *block, config.BlockChannelCapacity), + } +} + +// Starts the block builder. This should not be called until all other threads are done using the data generator, +// as the data generator is not thread-safe. +func (b *blockBuilder) Start() { + go b.mainLoop() +} + +// Builds blocks and sends them to the blocks channel. +func (b *blockBuilder) mainLoop() { + for { + block := b.buildBlock() + select { + case <-b.ctx.Done(): + return + case b.blocksChan <- block: + } + } +} + +func (b *blockBuilder) buildBlock() *block { + blk := NewBlock(b.config, b.metrics, b.nextBlockNumber, b.config.TransactionsPerBlock) + b.nextBlockNumber++ + + for i := 0; i < b.config.TransactionsPerBlock; i++ { + txn, err := BuildTransaction(b.dataGenerator) + if err != nil { + fmt.Printf("failed to build transaction: %v\n", err) + continue + } + blk.AddTransaction(txn) + } + + blk.SetBlockAccountStats( + b.dataGenerator.NextAccountID(), + b.dataGenerator.NumberOfColdAccounts(), + b.dataGenerator.NextErc20ContractID()) + + b.dataGenerator.ReportEndOfBlock() + + return blk +} From 8938c6de599494283aa7ec563182c6924bd98431 Mon Sep 17 00:00:00 2001 From: Cody Littley Date: Wed, 4 Mar 2026 15:14:43 -0600 Subject: [PATCH 04/20] adjust executor queue size Signed-off-by: Cody Littley --- sei-db/state_db/bench/cryptosim/config/basic-config.json | 2 +- sei-db/state_db/bench/cryptosim/cryptosim_config.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sei-db/state_db/bench/cryptosim/config/basic-config.json b/sei-db/state_db/bench/cryptosim/config/basic-config.json index 26001cd944..53cdf98724 100644 --- a/sei-db/state_db/bench/cryptosim/config/basic-config.json +++ b/sei-db/state_db/bench/cryptosim/config/basic-config.json @@ -11,7 +11,7 @@ "Erc20ContractSize": 2048, "Erc20InteractionsPerAccount": 10, "Erc20StorageSlotSize": 32, - "ExecutorQueueSize": 64, + "ExecutorQueueSize": 1024, "HotAccountProbability": 0.1, "HotErc20ContractProbability": 0.5, "HotErc20ContractSetSize": 100, diff --git a/sei-db/state_db/bench/cryptosim/cryptosim_config.go b/sei-db/state_db/bench/cryptosim/cryptosim_config.go index c6e368b0cb..cb2451f0ec 100644 --- a/sei-db/state_db/bench/cryptosim/cryptosim_config.go +++ b/sei-db/state_db/bench/cryptosim/cryptosim_config.go @@ -167,7 +167,7 @@ func DefaultCryptoSimConfig() *CryptoSimConfig { SetupUpdateIntervalCount: 100_000, ThreadsPerCore: 2.0, ConstantThreadCount: 0, - ExecutorQueueSize: 64, + ExecutorQueueSize: 1024, MaxRuntimeSeconds: 0, MetricsAddr: ":9090", TransactionMetricsSampleRate: 0.001, From a7d0c5419a7c62eb36e99dac2521772ca06170dd Mon Sep 17 00:00:00 2001 From: Cody Littley Date: Thu, 5 Mar 2026 11:33:07 -0600 Subject: [PATCH 05/20] lint --- sei-db/state_db/bench/cryptosim/cryptosim.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/sei-db/state_db/bench/cryptosim/cryptosim.go b/sei-db/state_db/bench/cryptosim/cryptosim.go index 1468332aa9..6e9cb41018 100644 --- a/sei-db/state_db/bench/cryptosim/cryptosim.go +++ b/sei-db/state_db/bench/cryptosim/cryptosim.go @@ -367,7 +367,11 @@ func (c *CryptoSim) handleNextBlock(blk *block) { c.nextExecutorIndex = (c.nextExecutorIndex + 1) % len(c.executors) } - c.database.FinalizeBlock(blk.NextAccountID(), blk.NextErc20ContractID(), false) + if err := c.database.FinalizeBlock(blk.NextAccountID(), blk.NextErc20ContractID(), false); err != nil { + fmt.Printf("failed to finalize block: %v\n", err) + c.cancel() + return + } blk.ReportBlockMetrcs() } From 86743304bbe748bffa2e1dd1c3d75f51a026507d Mon Sep 17 00:00:00 2001 From: Cody Littley Date: Fri, 6 Mar 2026 08:58:10 -0600 Subject: [PATCH 06/20] made suggested changes --- sei-db/state_db/bench/cryptosim/block.go | 2 +- sei-db/state_db/bench/cryptosim/cryptosim.go | 40 ++++++++++++++----- .../bench/cryptosim/cryptosim_config.go | 3 ++ sei-db/state_db/bench/cryptosim/database.go | 11 +++++ 4 files changed, 45 insertions(+), 11 deletions(-) diff --git a/sei-db/state_db/bench/cryptosim/block.go b/sei-db/state_db/bench/cryptosim/block.go index 2963389db2..a7833f957d 100644 --- a/sei-db/state_db/bench/cryptosim/block.go +++ b/sei-db/state_db/bench/cryptosim/block.go @@ -74,7 +74,7 @@ func (b *block) SetBlockAccountStats( // This method should be called after a block is finished executing and finalized. // Reports metrics about the block. -func (b *block) ReportBlockMetrcs() { +func (b *block) ReportBlockMetrics() { b.metrics.SetTotalNumberOfAccounts(b.nextAccountID, int64(b.config.NumberOfHotAccounts), b.numberOfColdAccounts) } diff --git a/sei-db/state_db/bench/cryptosim/cryptosim.go b/sei-db/state_db/bench/cryptosim/cryptosim.go index 6e9cb41018..94fdd02d3a 100644 --- a/sei-db/state_db/bench/cryptosim/cryptosim.go +++ b/sei-db/state_db/bench/cryptosim/cryptosim.go @@ -67,6 +67,14 @@ type CryptoSim struct { // benchmark, sending "false" will resume it. Suspending an already suspended benchmark will have no effect, // and resuming an already resumed benchmark will likewise have no effect. suspendChan chan bool + + // The most recent block that has been processed. + mostRecentBlock *block + + // The next ERC20 contract ID to be used when creating a new ERC20 contract. + // This is fixed after initial setup is complete, since we don't currently simulate + // the creation of new ERC20 contracts during the benchmark. + nextERC20ContractID int64 } // Creates a new cryptosim benchmark runner. @@ -316,6 +324,8 @@ func (c *CryptoSim) setupErc20Contracts() error { fmt.Printf("There are now %s simulated ERC20 contracts in the database.\n", int64Commas(c.dataGenerator.NextErc20ContractID())) + c.nextERC20ContractID = c.dataGenerator.NextErc20ContractID() + return nil } @@ -358,11 +368,12 @@ func (c *CryptoSim) run() { // Execute and finalize the next block. func (c *CryptoSim) handleNextBlock(blk *block) { + c.mostRecentBlock = blk c.metrics.SetMainThreadPhase("send_to_executors") c.database.IncrementTransactionCount(blk.TransactionCount()) - for _, txn := range blk.transactions { + for txn := range blk.Iterator() { c.executors[c.nextExecutorIndex].ScheduleForExecution(txn) c.nextExecutorIndex = (c.nextExecutorIndex + 1) % len(c.executors) } @@ -372,17 +383,19 @@ func (c *CryptoSim) handleNextBlock(blk *block) { c.cancel() return } - blk.ReportBlockMetrcs() + blk.ReportBlockMetrics() } // Suspends the benchmark. This method blocks until the benchmark is resumed or shut down. func (c *CryptoSim) suspend() { - err := c.database.FinalizeBlock(c.dataGenerator.NextAccountID(), c.dataGenerator.NextErc20ContractID(), true) - if err != nil { - fmt.Printf("failed to finalize block: %v\n", err) - c.cancel() - return + if c.mostRecentBlock != nil { + err := c.database.FinalizeBlock(c.mostRecentBlock.nextAccountID, c.nextERC20ContractID, true) + if err != nil { + fmt.Printf("failed to finalize block: %v\n", err) + c.cancel() + return + } } fmt.Printf("Benchmark suspended.\n") @@ -410,9 +423,16 @@ func (c *CryptoSim) suspend() { // Clean up the benchmark and release any resources. func (c *CryptoSim) teardown() { - err := c.database.Close(c.dataGenerator.NextAccountID(), c.dataGenerator.NextErc20ContractID()) - if err != nil { - fmt.Printf("failed to close database: %v\n", err) + if c.mostRecentBlock == nil { + err := c.database.CloseWithoutFinalizing() + if err != nil { + fmt.Printf("failed to close database: %v\n", err) + } + } else { + err := c.database.Close(c.mostRecentBlock.nextAccountID, c.nextERC20ContractID) + if err != nil { + fmt.Printf("failed to close database: %v\n", err) + } } c.dataGenerator.Close() diff --git a/sei-db/state_db/bench/cryptosim/cryptosim_config.go b/sei-db/state_db/bench/cryptosim/cryptosim_config.go index cb2451f0ec..a6434c269e 100644 --- a/sei-db/state_db/bench/cryptosim/cryptosim_config.go +++ b/sei-db/state_db/bench/cryptosim/cryptosim_config.go @@ -246,6 +246,9 @@ func (c *CryptoSimConfig) Validate() error { if c.BackgroundMetricsScrapeInterval < 0 { return fmt.Errorf("BackgroundMetricsScrapeInterval must be non-negative (got %d)", c.BackgroundMetricsScrapeInterval) } + if c.BlockChannelCapacity < 1 { + return fmt.Errorf("BlockChannelCapacity must be at least 1 (got %d)", c.BlockChannelCapacity) + } return nil } diff --git a/sei-db/state_db/bench/cryptosim/database.go b/sei-db/state_db/bench/cryptosim/database.go index 25b3888dda..21d9a48937 100644 --- a/sei-db/state_db/bench/cryptosim/database.go +++ b/sei-db/state_db/bench/cryptosim/database.go @@ -206,6 +206,17 @@ func (d *Database) Close(nextAccountID int64, nextErc20ContractID int64) error { return nil } +// Close the database and release any resources without finalizing the last batch. +func (d *Database) CloseWithoutFinalizing() error { + fmt.Printf("Closing database.\n") + err := d.db.Close() + if err != nil { + return fmt.Errorf("failed to close database: %w", err) + } + + return nil +} + // Set the function that flushes the executors. This setter is required to break a circular dependency. func (d *Database) SetFlushFunc(flushFunc func()) { d.flushFunc = flushFunc From e70db1fb4d18e150cf3b351fcb1c838254470594 Mon Sep 17 00:00:00 2001 From: Jeremy Wei Date: Tue, 10 Mar 2026 12:56:40 -0400 Subject: [PATCH 07/20] add receipt generation to cryptosim --- sei-db/state_db/bench/cryptosim/receipt.go | 170 ++++++++++++++++++ .../state_db/bench/cryptosim/receipt_test.go | 57 ++++++ 2 files changed, 227 insertions(+) create mode 100644 sei-db/state_db/bench/cryptosim/receipt.go create mode 100644 sei-db/state_db/bench/cryptosim/receipt_test.go diff --git a/sei-db/state_db/bench/cryptosim/receipt.go b/sei-db/state_db/bench/cryptosim/receipt.go new file mode 100644 index 0000000000..d2263155ec --- /dev/null +++ b/sei-db/state_db/bench/cryptosim/receipt.go @@ -0,0 +1,170 @@ +package cryptosim + +import ( + "encoding/binary" + "errors" + "fmt" + "hash" + + ethtypes "github.com/ethereum/go-ethereum/core/types" + evmtypes "github.com/sei-protocol/sei-chain/x/evm/types" + "golang.org/x/crypto/sha3" +) + +const ( + erc20TransferEventSignatureHex = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef" + + // These mirror immutable memiavl EVM key prefixes and are duplicated here to keep the hot path minimal. + evmCodeKeyPrefixByte = 0x07 + evmStorageKeyPrefixByte = 0x03 + + hashLen = 32 + indexedAddressBase = hashLen - AddressLen + + syntheticReceiptMinBlockNumber uint64 = 1_000_000 + + syntheticReceiptGasUsedBase uint64 = 52_000 + syntheticReceiptGasUsedSpan uint64 = 18_000 + syntheticReceiptPreviousGasBase uint64 = 21_000 + syntheticReceiptPreviousGasSpan uint64 = 35_000 + syntheticReceiptGasPriceBase uint64 = 1_000_000_000 + syntheticReceiptGasPriceSpan uint64 = 9_000_000_000 + syntheticReceiptTransferBase uint64 = 1_000_000 + syntheticReceiptTransferSpan uint64 = 10_000_000_000 +) + +var erc20TransferEventSignatureBytes = [hashLen]byte{ + 0xdd, 0xf2, 0x52, 0xad, 0x1b, 0xe2, 0xc8, 0x9b, + 0x69, 0xc2, 0xb0, 0x68, 0xfc, 0x37, 0x8d, 0xaa, + 0x95, 0x2b, 0xa7, 0xf1, 0x63, 0xc4, 0xa1, 0x16, + 0x28, 0xf5, 0x5a, 0x4d, 0xf5, 0x23, 0xb3, 0xef, +} + +// BuildERC20TransferReceipt produces a plausible successful ERC20 transfer receipt. +// +// The sender and receiver are derived from the address portion of the supplied storage keys, since cryptosim tracks +// ERC20 balances as storage slots rather than separate account references. The caller supplies the block number and tx +// index so the resulting receipt can line up with the simulated block being benchmarked. +func BuildERC20TransferReceipt( + crand *CannedRandom, + feeCollectionAccount []byte, + srcAccount []byte, + dstAccount []byte, + senderSlot []byte, + receiverSlot []byte, + erc20ContractCode []byte, + blockNumber uint64, + txIndex uint32, +) (*evmtypes.Receipt, error) { + if crand == nil { + return nil, errors.New("canned random is required") + } + + if err := validateCodeKey("fee collection account", feeCollectionAccount); err != nil { + return nil, err + } + srcAddressBytes, err := extractCodeKeyBytes("src account", srcAccount) + if err != nil { + return nil, err + } + if err := validateCodeKey("dst account", dstAccount); err != nil { + return nil, err + } + senderAddressBytes, err := extractStorageKeyAddressBytes("sender slot", senderSlot) + if err != nil { + return nil, err + } + receiverAddressBytes, err := extractStorageKeyAddressBytes("receiver slot", receiverSlot) + if err != nil { + return nil, err + } + contractAddressBytes, err := extractCodeKeyBytes("erc20 contract code", erc20ContractCode) + if err != nil { + return nil, err + } + txType := uint32(ethtypes.DynamicFeeTxType) + if crand.Int64Range(0, 5) == 0 { + txType = uint32(ethtypes.LegacyTxType) + } + + gasUsed := syntheticReceiptGasUsedBase + uint64(crand.Int64Range(0, int64(syntheticReceiptGasUsedSpan))) + previousGas := syntheticReceiptPreviousGasBase + uint64(crand.Int64Range(0, int64(syntheticReceiptPreviousGasSpan))) + cumulativeGasUsed := gasUsed + uint64(txIndex)*previousGas + effectiveGasPrice := syntheticReceiptGasPriceBase + uint64(crand.Int64Range(0, int64(syntheticReceiptGasPriceSpan))) + transferAmount := syntheticReceiptTransferBase + uint64(crand.Int64Range(0, int64(syntheticReceiptTransferSpan))) + + var senderTopic [hashLen]byte + copy(senderTopic[indexedAddressBase:], senderAddressBytes) + var receiverTopic [hashLen]byte + copy(receiverTopic[indexedAddressBase:], receiverAddressBytes) + + contractAddressHex := BytesToHex(contractAddressBytes) + amountData := encodeUint256FromUint64(transferAmount) + var bloom ethtypes.Bloom + hasher := sha3.NewLegacyKeccak256() + var bloomDigest [hashLen]byte + addToBloom(hasher, &bloomDigest, &bloom, contractAddressBytes) + addToBloom(hasher, &bloomDigest, &bloom, erc20TransferEventSignatureBytes[:]) + addToBloom(hasher, &bloomDigest, &bloom, senderTopic[:]) + addToBloom(hasher, &bloomDigest, &bloom, receiverTopic[:]) + + return &evmtypes.Receipt{ + TxType: txType, + CumulativeGasUsed: cumulativeGasUsed, + ContractAddress: contractAddressHex, + TxHashHex: BytesToHex(crand.Bytes(hashLen)), + GasUsed: gasUsed, + EffectiveGasPrice: effectiveGasPrice, + BlockNumber: blockNumber, + TransactionIndex: txIndex, + Status: uint32(ethtypes.ReceiptStatusSuccessful), + From: BytesToHex(srcAddressBytes), + To: contractAddressHex, + Logs: []*evmtypes.Log{{ + Address: contractAddressHex, + Topics: []string{ + erc20TransferEventSignatureHex, + BytesToHex(senderTopic[:]), + BytesToHex(receiverTopic[:]), + }, + Data: amountData, + Index: 0, + }}, + LogsBloom: bloom[:], + }, nil +} + +func validateCodeKey(name string, key []byte) error { + _, err := extractCodeKeyBytes(name, key) + return err +} + +func extractCodeKeyBytes(name string, key []byte) ([]byte, error) { + if len(key) != 1+AddressLen || key[0] != evmCodeKeyPrefixByte { + return nil, fmt.Errorf("%s must be an EVM code key with %d address bytes", name, AddressLen) + } + return key[1:], nil +} + +func extractStorageKeyAddressBytes(name string, key []byte) ([]byte, error) { + if len(key) != 1+StorageKeyLen || key[0] != evmStorageKeyPrefixByte { + return nil, fmt.Errorf("%s must be an EVM storage key with %d address+slot bytes", name, StorageKeyLen) + } + return key[1 : 1+AddressLen], nil +} + +func addToBloom(hasher hash.Hash, digest *[hashLen]byte, bloom *ethtypes.Bloom, value []byte) { + hasher.Reset() + _, _ = hasher.Write(value) + hash := hasher.Sum(digest[:0]) + for i := 0; i < 6; i += 2 { + bit := (uint(hash[i])<<8)&2047 + uint(hash[i+1]) + bloom[ethtypes.BloomByteLength-1-bit/8] |= byte(1 << (bit % 8)) + } +} + +func encodeUint256FromUint64(value uint64) []byte { + encoded := make([]byte, hashLen) + binary.BigEndian.PutUint64(encoded[hashLen-8:], value) + return encoded +} diff --git a/sei-db/state_db/bench/cryptosim/receipt_test.go b/sei-db/state_db/bench/cryptosim/receipt_test.go new file mode 100644 index 0000000000..3147721d47 --- /dev/null +++ b/sei-db/state_db/bench/cryptosim/receipt_test.go @@ -0,0 +1,57 @@ +package cryptosim + +import ( + "testing" + + "github.com/sei-protocol/sei-chain/sei-db/common/evm" +) + +func BenchmarkBuildERC20TransferReceipt(b *testing.B) { + keyRand := NewCannedRandom(4096, 1) + receiptRand := NewCannedRandom(1<<20, 2) + + feeAccount := evm.BuildMemIAVLEVMKey(evm.EVMKeyCode, keyRand.Address(accountPrefix, 0, AddressLen)) + + srcAccountAddress := keyRand.Address(accountPrefix, 1, AddressLen) + srcAccount := evm.BuildMemIAVLEVMKey(evm.EVMKeyCode, srcAccountAddress) + + dstAccountAddress := keyRand.Address(accountPrefix, 2, AddressLen) + dstAccount := evm.BuildMemIAVLEVMKey(evm.EVMKeyCode, dstAccountAddress) + + senderSlotBytes := make([]byte, StorageKeyLen) + copy(senderSlotBytes[:AddressLen], srcAccountAddress) + copy(senderSlotBytes[AddressLen:], keyRand.SeededBytes(SlotLen, 11)) + senderSlot := evm.BuildMemIAVLEVMKey(evm.EVMKeyStorage, senderSlotBytes) + + receiverSlotBytes := make([]byte, StorageKeyLen) + copy(receiverSlotBytes[:AddressLen], dstAccountAddress) + copy(receiverSlotBytes[AddressLen:], keyRand.SeededBytes(SlotLen, 12)) + receiverSlot := evm.BuildMemIAVLEVMKey(evm.EVMKeyStorage, receiverSlotBytes) + + erc20Contract := evm.BuildMemIAVLEVMKey(evm.EVMKeyCode, keyRand.Address(contractPrefix, 0, AddressLen)) + blockNumber := syntheticReceiptMinBlockNumber + txIndex := uint32(0) + + b.ReportAllocs() + b.ResetTimer() + + for i := 0; i < b.N; i++ { + receipt, err := BuildERC20TransferReceipt( + receiptRand, + feeAccount, + srcAccount, + dstAccount, + senderSlot, + receiverSlot, + erc20Contract, + blockNumber, + txIndex, + ) + if err != nil { + b.Fatalf("BuildERC20TransferReceipt failed: %v", err) + } + if len(receipt.Logs) != 1 { + b.Fatalf("expected 1 log, got %d", len(receipt.Logs)) + } + } +} From c8d278acd8e1c51b240434c43164945fbdeac369 Mon Sep 17 00:00:00 2001 From: Cody Littley Date: Tue, 10 Mar 2026 13:36:10 -0500 Subject: [PATCH 08/20] persist block number across restarts --- sei-db/state_db/bench/cryptosim/block.go | 30 ++++++++++++---- .../state_db/bench/cryptosim/block_builder.go | 29 +++++++++++---- .../bench/cryptosim/config/basic-config.json | 3 +- sei-db/state_db/bench/cryptosim/cryptosim.go | 35 ++++++++++++++----- .../bench/cryptosim/cryptosim_config.go | 5 +++ .../bench/cryptosim/data_generator.go | 29 +++++++++++++++ sei-db/state_db/bench/cryptosim/database.go | 20 ++++++++--- sei-db/state_db/bench/cryptosim/receipt.go | 20 +++++++++++ sei-db/state_db/bench/cryptosim/util.go | 5 +++ 9 files changed, 150 insertions(+), 26 deletions(-) diff --git a/sei-db/state_db/bench/cryptosim/block.go b/sei-db/state_db/bench/cryptosim/block.go index a7833f957d..c066d3b183 100644 --- a/sei-db/state_db/bench/cryptosim/block.go +++ b/sei-db/state_db/bench/cryptosim/block.go @@ -1,6 +1,10 @@ package cryptosim -import "iter" +import ( + "iter" + + evmtypes "github.com/sei-protocol/sei-chain/x/evm/types" +) // A simulated block of transactions. type block struct { @@ -9,9 +13,11 @@ type block struct { // The transactions in the block. transactions []*transaction - // The block number. This is not currently preserved across benchmark restarts, but otherwise monotonically - // increases as you'd expect. - blockNumber int64 + // If reciept generation is enabled, this will contain the receipts for each transaction in the block. + reciepts []*evmtypes.Receipt + + // The block number. + blockNumber uint64 // The next account ID to be used when creating a new account, as of the end of this block. nextAccountID int64 @@ -29,14 +35,21 @@ type block struct { func NewBlock( config *CryptoSimConfig, metrics *CryptosimMetrics, - blockNumber int64, + blockNumber uint64, capacity int, ) *block { + + var reciepts []*evmtypes.Receipt + if config.GenerateReceipts { + reciepts = make([]*evmtypes.Receipt, 0, capacity) + } + return &block{ config: config, blockNumber: blockNumber, transactions: make([]*transaction, 0, capacity), metrics: metrics, + reciepts: reciepts, } } @@ -56,8 +69,13 @@ func (b *block) AddTransaction(txn *transaction) { b.transactions = append(b.transactions, txn) } +// Adds a receipt to the block. +func (b *block) AddReceipt(receipt *evmtypes.Receipt) { + b.reciepts = append(b.reciepts, receipt) +} + // Returns the block number. -func (b *block) BlockNumber() int64 { +func (b *block) BlockNumber() uint64 { return b.blockNumber } diff --git a/sei-db/state_db/bench/cryptosim/block_builder.go b/sei-db/state_db/bench/cryptosim/block_builder.go index eb36b5d281..bd549ef2ff 100644 --- a/sei-db/state_db/bench/cryptosim/block_builder.go +++ b/sei-db/state_db/bench/cryptosim/block_builder.go @@ -21,7 +21,7 @@ type blockBuilder struct { blocksChan chan *block // The next block number to be used. - nextBlockNumber int64 + nextBlockNumber uint64 } // Asyncronously produces blocks of transactions. @@ -30,13 +30,15 @@ func NewBlockBuilder( config *CryptoSimConfig, metrics *CryptosimMetrics, dataGenerator *DataGenerator, + nextBlockNumber uint64, ) *blockBuilder { return &blockBuilder{ - ctx: ctx, - config: config, - metrics: metrics, - dataGenerator: dataGenerator, - blocksChan: make(chan *block, config.BlockChannelCapacity), + ctx: ctx, + config: config, + metrics: metrics, + dataGenerator: dataGenerator, + blocksChan: make(chan *block, config.BlockChannelCapacity), + nextBlockNumber: nextBlockNumber, } } @@ -69,6 +71,21 @@ func (b *blockBuilder) buildBlock() *block { continue } blk.AddTransaction(txn) + + if b.config.GenerateReceipts { + receipt, err := BuildERC20TransferReceiptFromTxn( + b.dataGenerator.Rand(), + b.dataGenerator.FeeCollectionAddress(), + blk.BlockNumber(), + 0, // TODO + txn, + ) + if err != nil { + fmt.Printf("failed to build receipt: %v\n", err) + continue + } + blk.AddReceipt(receipt) + } } blk.SetBlockAccountStats( diff --git a/sei-db/state_db/bench/cryptosim/config/basic-config.json b/sei-db/state_db/bench/cryptosim/config/basic-config.json index 53cdf98724..9d708815b8 100644 --- a/sei-db/state_db/bench/cryptosim/config/basic-config.json +++ b/sei-db/state_db/bench/cryptosim/config/basic-config.json @@ -30,5 +30,6 @@ "MaxRuntimeSeconds": 0, "TransactionMetricsSampleRate": 0.001, "BackgroundMetricsScrapeInterval": 60, - "BlockChannelCapacity": 8 + "BlockChannelCapacity": 8, + "GenerateReceipts": false } diff --git a/sei-db/state_db/bench/cryptosim/cryptosim.go b/sei-db/state_db/bench/cryptosim/cryptosim.go index 94fdd02d3a..8f3bf10e7b 100644 --- a/sei-db/state_db/bench/cryptosim/cryptosim.go +++ b/sei-db/state_db/bench/cryptosim/cryptosim.go @@ -153,7 +153,7 @@ func NewCryptoSim( ctx, cancel, database, dataGenerator.FeeCollectionAddress(), config.ExecutorQueueSize, metrics) } - blockBuilder := NewBlockBuilder(ctx, config, metrics, dataGenerator) + blockBuilder := NewBlockBuilder(ctx, config, metrics, dataGenerator, dataGenerator.InitialNextBlockNumber()) c := &CryptoSim{ ctx: ctx, @@ -229,7 +229,8 @@ func (c *CryptoSim) setupAccounts() error { } c.database.IncrementTransactionCount(1) finalized, err := c.database.MaybeFinalizeBlock( - c.dataGenerator.NextAccountID(), c.dataGenerator.NextErc20ContractID()) + c.dataGenerator.NextAccountID(), c.dataGenerator.NextErc20ContractID(), + c.dataGenerator.InitialNextBlockNumber()) if err != nil { return fmt.Errorf("failed to maybe commit batch: %w", err) } @@ -249,7 +250,9 @@ func (c *CryptoSim) setupAccounts() error { fmt.Printf("Created %s of %s accounts. \n", int64Commas(c.dataGenerator.NextAccountID()), int64Commas(int64(requiredNumberOfAccounts))) - err := c.database.FinalizeBlock(c.dataGenerator.NextAccountID(), c.dataGenerator.NextErc20ContractID(), true) + err := c.database.FinalizeBlock( + c.dataGenerator.NextAccountID(), c.dataGenerator.NextErc20ContractID(), + c.dataGenerator.InitialNextBlockNumber(), true) if err != nil { return fmt.Errorf("failed to finalize block: %w", err) } @@ -291,7 +294,8 @@ func (c *CryptoSim) setupErc20Contracts() error { return fmt.Errorf("failed to create new ERC20 contract: %w", err) } finalized, err := c.database.MaybeFinalizeBlock( - c.dataGenerator.NextAccountID(), c.dataGenerator.NextErc20ContractID()) + c.dataGenerator.NextAccountID(), c.dataGenerator.NextErc20ContractID(), + c.dataGenerator.InitialNextBlockNumber()) if err != nil { return fmt.Errorf("failed to maybe commit batch: %w", err) } @@ -314,7 +318,11 @@ func (c *CryptoSim) setupErc20Contracts() error { fmt.Printf("Created %s of %s simulated ERC20 contracts. \n", int64Commas(c.dataGenerator.NextErc20ContractID()), int64Commas(int64(c.config.MinimumNumberOfErc20Contracts))) - err := c.database.FinalizeBlock(c.dataGenerator.NextAccountID(), c.dataGenerator.NextErc20ContractID(), true) + err := c.database.FinalizeBlock( + c.dataGenerator.NextAccountID(), + c.dataGenerator.NextErc20ContractID(), + c.dataGenerator.InitialNextBlockNumber(), + true) if err != nil { return fmt.Errorf("failed to finalize block: %w", err) } @@ -378,8 +386,9 @@ func (c *CryptoSim) handleNextBlock(blk *block) { c.nextExecutorIndex = (c.nextExecutorIndex + 1) % len(c.executors) } - if err := c.database.FinalizeBlock(blk.NextAccountID(), blk.NextErc20ContractID(), false); err != nil { - fmt.Printf("failed to finalize block: %v\n", err) + _, err := c.database.MaybeFinalizeBlock(blk.NextAccountID(), blk.NextErc20ContractID(), blk.BlockNumber()+1) + if err != nil { + fmt.Printf("failed to maybe finalize block: %v\n", err) c.cancel() return } @@ -390,7 +399,11 @@ func (c *CryptoSim) handleNextBlock(blk *block) { func (c *CryptoSim) suspend() { if c.mostRecentBlock != nil { - err := c.database.FinalizeBlock(c.mostRecentBlock.nextAccountID, c.nextERC20ContractID, true) + err := c.database.FinalizeBlock( + c.mostRecentBlock.nextAccountID, + c.nextERC20ContractID, + c.mostRecentBlock.BlockNumber()+1, + true) if err != nil { fmt.Printf("failed to finalize block: %v\n", err) c.cancel() @@ -429,7 +442,11 @@ func (c *CryptoSim) teardown() { fmt.Printf("failed to close database: %v\n", err) } } else { - err := c.database.Close(c.mostRecentBlock.nextAccountID, c.nextERC20ContractID) + err := c.database.Close( + c.mostRecentBlock.nextAccountID, + c.nextERC20ContractID, + c.mostRecentBlock.BlockNumber()+1, + ) if err != nil { fmt.Printf("failed to close database: %v\n", err) } diff --git a/sei-db/state_db/bench/cryptosim/cryptosim_config.go b/sei-db/state_db/bench/cryptosim/cryptosim_config.go index a6434c269e..e99ccf8fbe 100644 --- a/sei-db/state_db/bench/cryptosim/cryptosim_config.go +++ b/sei-db/state_db/bench/cryptosim/cryptosim_config.go @@ -135,6 +135,10 @@ type CryptoSimConfig struct { // The capacity of the channel that holds blocks awaiting execution. BlockChannelCapacity int + + // If true, the benchmark will generate receipts for each transaction in each block and + // feed those reciepts into the reciept store. + GenerateReceipts bool } // Returns the default configuration for the cryptosim benchmark. @@ -174,6 +178,7 @@ func DefaultCryptoSimConfig() *CryptoSimConfig { BackgroundMetricsScrapeInterval: 60, EnableSuspension: true, BlockChannelCapacity: 8, + GenerateReceipts: false, } } diff --git a/sei-db/state_db/bench/cryptosim/data_generator.go b/sei-db/state_db/bench/cryptosim/data_generator.go index 8347d6bd2d..9eefd0ff8e 100644 --- a/sei-db/state_db/bench/cryptosim/data_generator.go +++ b/sei-db/state_db/bench/cryptosim/data_generator.go @@ -12,6 +12,8 @@ const ( accountIdCounterKey = "accountIdCounterKey" // Used to store the next ERC20 contract ID in the database. erc20IdCounterKey = "erc20IdCounterKey" + // Used to store the next block number in the database. + blockNumberCounterKey = "blockNumberCounterKey" ) // Generates random data for the benchmark. This is not a thread safe utility. @@ -24,6 +26,10 @@ type DataGenerator struct { // The next ERC20 contract ID to be used when creating a new ERC20 contract. nextErc20ContractID int64 + // The next block number at startup time. Not updated after initialization; + // the block builder tracks the ongoing value. + initialNextBlockNumber uint64 + // The random number generator. rand *CannedRandom @@ -85,6 +91,17 @@ func NewDataGenerator( fmt.Printf("There are currently %s ERC20 contracts in the database.\n", int64Commas(nextErc20ContractID)) metrics.SetTotalNumberOfERC20Contracts(nextErc20ContractID) + nextBlockNumberBinary, found, err := database.Get(BlockNumberCounterKey()) + if err != nil { + return nil, fmt.Errorf("failed to read block number counter: %w", err) + } + var nextBlockNumber uint64 + if found { + nextBlockNumber = binary.BigEndian.Uint64(nextBlockNumberBinary) + } + + fmt.Printf("Next block number: %s.\n", int64Commas(int64(nextBlockNumber))) + // Use EVMKeyCode for account data; EVMKeyNonce only accepts 8-byte values. feeCollectionAddress := evm.BuildMemIAVLEVMKey( evm.EVMKeyCode, @@ -95,6 +112,7 @@ func NewDataGenerator( config: config, nextAccountID: nextAccountID, nextErc20ContractID: nextErc20ContractID, + initialNextBlockNumber: nextBlockNumber, rand: rand, feeCollectionAddress: feeCollectionAddress, database: database, @@ -126,6 +144,11 @@ func (d *DataGenerator) NextErc20ContractID() int64 { return d.nextErc20ContractID } +// Get the next block number as it was at startup time. +func (d *DataGenerator) InitialNextBlockNumber() uint64 { + return d.initialNextBlockNumber +} + // Creates a new account and optionally writes it to the database. Returns the address of the new // account and whether it is a cold account (vs dormant). func (d *DataGenerator) CreateNewAccount( @@ -293,3 +316,9 @@ func (d *DataGenerator) FeeCollectionAddress() []byte { func (d *DataGenerator) ReportEndOfBlock() { d.highestSafeAccountIDInBlock = d.nextAccountID - 1 } + +// Get the random number generator. Note that the random number generator is not thread safe, and +// so the caller is responsible for ensuring that it is not used concurrently with other calls to the data generator. +func (d *DataGenerator) Rand() *CannedRandom { + return d.rand +} diff --git a/sei-db/state_db/bench/cryptosim/database.go b/sei-db/state_db/bench/cryptosim/database.go index 21d9a48937..5066965321 100644 --- a/sei-db/state_db/bench/cryptosim/database.go +++ b/sei-db/state_db/bench/cryptosim/database.go @@ -102,9 +102,10 @@ func (d *Database) TransactionCount() int64 { func (d *Database) MaybeFinalizeBlock( nextAccountID int64, nextErc20ContractID int64, + nextBlockNumber uint64, ) (bool, error) { if d.transactionsInCurrentBlock >= int64(d.config.TransactionsPerBlock) { - err := d.FinalizeBlock(nextAccountID, nextErc20ContractID, false) + err := d.FinalizeBlock(nextAccountID, nextErc20ContractID, nextBlockNumber, false) if err != nil { return false, fmt.Errorf("failed to finalize block: %w", err) } @@ -117,6 +118,7 @@ func (d *Database) MaybeFinalizeBlock( func (d *Database) FinalizeBlock( nextAccountID int64, nextErc20ContractID int64, + nextBlockNumber uint64, forceCommit bool, ) error { @@ -133,7 +135,7 @@ func (d *Database) FinalizeBlock( d.metrics.SetMainThreadPhase("finalizing") - changeSets := make([]*proto.NamedChangeSet, 0, d.transactionsInCurrentBlock+2) + changeSets := make([]*proto.NamedChangeSet, 0, d.transactionsInCurrentBlock+3) for key, value := range d.batch.Iterator() { changeSets = append(changeSets, &proto.NamedChangeSet{ Name: wrappers.EVMStoreName, @@ -164,6 +166,16 @@ func (d *Database) FinalizeBlock( }}, }) + // Persist the block number counter in every batch. + blockNumberValue := make([]byte, 8) + binary.BigEndian.PutUint64(blockNumberValue, nextBlockNumber) + changeSets = append(changeSets, &proto.NamedChangeSet{ + Name: wrappers.EVMStoreName, + Changeset: iavl.ChangeSet{Pairs: []*iavl.KVPair{ + {Key: BlockNumberCounterKey(), Value: blockNumberValue}, + }}, + }) + err := d.db.ApplyChangeSets(changeSets) if err != nil { return fmt.Errorf("failed to apply change sets: %w", err) @@ -190,10 +202,10 @@ func (d *Database) FinalizeBlock( } // Close the database and release any resources. -func (d *Database) Close(nextAccountID int64, nextErc20ContractID int64) error { +func (d *Database) Close(nextAccountID int64, nextErc20ContractID int64, nextBlockNumber uint64) error { fmt.Printf("Committing final batch.\n") - if err := d.FinalizeBlock(nextAccountID, nextErc20ContractID, true); err != nil { + if err := d.FinalizeBlock(nextAccountID, nextErc20ContractID, nextBlockNumber, true); err != nil { return fmt.Errorf("failed to commit batch: %w", err) } diff --git a/sei-db/state_db/bench/cryptosim/receipt.go b/sei-db/state_db/bench/cryptosim/receipt.go index d2263155ec..d1b76c95ff 100644 --- a/sei-db/state_db/bench/cryptosim/receipt.go +++ b/sei-db/state_db/bench/cryptosim/receipt.go @@ -40,6 +40,26 @@ var erc20TransferEventSignatureBytes = [hashLen]byte{ 0x28, 0xf5, 0x5a, 0x4d, 0xf5, 0x23, 0xb3, 0xef, } +// BuildERC20TransferReceiptFromTxn produces a plausible successful ERC20 transfer receipt from a transaction. +func BuildERC20TransferReceiptFromTxn( + crand *CannedRandom, + feeCollectionAccount []byte, + blockNumber uint64, + txIndex uint32, + txn *transaction, +) (*evmtypes.Receipt, error) { + return BuildERC20TransferReceipt( + crand, + feeCollectionAccount, + txn.srcAccount, + txn.dstAccount, + txn.srcAccountSlot, + txn.dstAccountSlot, + txn.erc20Contract, + blockNumber, + txIndex) +} + // BuildERC20TransferReceipt produces a plausible successful ERC20 transfer receipt. // // The sender and receiver are derived from the address portion of the supplied storage keys, since cryptosim tracks diff --git a/sei-db/state_db/bench/cryptosim/util.go b/sei-db/state_db/bench/cryptosim/util.go index 0cc4453e82..b1444225cf 100644 --- a/sei-db/state_db/bench/cryptosim/util.go +++ b/sei-db/state_db/bench/cryptosim/util.go @@ -30,6 +30,11 @@ func Erc20IDCounterKey() []byte { return evm.BuildMemIAVLEVMKey(evm.EVMKeyCode, paddedCounterKey(erc20IdCounterKey)) } +// Get the key for the block number counter in the database. +func BlockNumberCounterKey() []byte { + return evm.BuildMemIAVLEVMKey(evm.EVMKeyCode, paddedCounterKey(blockNumberCounterKey)) +} + // paddedCounterKey pads the string to AddressLen bytes for use with EVM key builders. func paddedCounterKey(s string) []byte { b := make([]byte, AddressLen) From 3e89a7289320e3960ce68bcc23ec0c475c99bc50 Mon Sep 17 00:00:00 2001 From: Cody Littley Date: Tue, 10 Mar 2026 14:08:32 -0500 Subject: [PATCH 09/20] added reciept store simulator --- .../state_db/bench/cryptosim/block_builder.go | 2 +- .../bench/cryptosim/config/reciept-store.json | 8 +++ sei-db/state_db/bench/cryptosim/cryptosim.go | 23 ++++++++ .../bench/cryptosim/cryptosim_config.go | 7 +++ .../cryptosim/reciept_store_simulator.go | 53 +++++++++++++++++++ 5 files changed, 92 insertions(+), 1 deletion(-) create mode 100644 sei-db/state_db/bench/cryptosim/config/reciept-store.json create mode 100644 sei-db/state_db/bench/cryptosim/reciept_store_simulator.go diff --git a/sei-db/state_db/bench/cryptosim/block_builder.go b/sei-db/state_db/bench/cryptosim/block_builder.go index bd549ef2ff..cf2e9f2630 100644 --- a/sei-db/state_db/bench/cryptosim/block_builder.go +++ b/sei-db/state_db/bench/cryptosim/block_builder.go @@ -77,7 +77,7 @@ func (b *blockBuilder) buildBlock() *block { b.dataGenerator.Rand(), b.dataGenerator.FeeCollectionAddress(), blk.BlockNumber(), - 0, // TODO + uint32(i), txn, ) if err != nil { diff --git a/sei-db/state_db/bench/cryptosim/config/reciept-store.json b/sei-db/state_db/bench/cryptosim/config/reciept-store.json new file mode 100644 index 0000000000..dbb621e8ae --- /dev/null +++ b/sei-db/state_db/bench/cryptosim/config/reciept-store.json @@ -0,0 +1,8 @@ +{ + "Comment": "For testing with the state store and reciept store both enabled.", + "DataDir": "data", + "MinimumNumberOfColdAccounts": 1000000, + "MinimumNumberOfDormantAccounts": 1000000, + "GenerateReceipts": true +} + diff --git a/sei-db/state_db/bench/cryptosim/cryptosim.go b/sei-db/state_db/bench/cryptosim/cryptosim.go index 8f3bf10e7b..53d35f83a7 100644 --- a/sei-db/state_db/bench/cryptosim/cryptosim.go +++ b/sei-db/state_db/bench/cryptosim/cryptosim.go @@ -75,6 +75,9 @@ type CryptoSim struct { // This is fixed after initial setup is complete, since we don't currently simulate // the creation of new ERC20 contracts during the benchmark. nextERC20ContractID int64 + + // The channel that holds blocks sent to the reciept store. + recieptsChan chan *block } // Creates a new cryptosim benchmark runner. @@ -155,6 +158,16 @@ func NewCryptoSim( blockBuilder := NewBlockBuilder(ctx, config, metrics, dataGenerator, dataGenerator.InitialNextBlockNumber()) + var recieptsChan chan *block + if config.GenerateReceipts { + recieptsChan = make(chan *block, config.RecieptChannelCapacity) + _, err := NewRecieptStoreSimulator(ctx, config, recieptsChan) + if err != nil { + cancel() + return nil, fmt.Errorf("failed to create receipt store simulator: %w", err) + } + } + c := &CryptoSim{ ctx: ctx, cancel: cancel, @@ -169,6 +182,7 @@ func NewCryptoSim( executors: executors, metrics: metrics, suspendChan: make(chan bool, 1), + recieptsChan: recieptsChan, } database.SetFlushFunc(c.flushExecutors) @@ -392,6 +406,15 @@ func (c *CryptoSim) handleNextBlock(blk *block) { c.cancel() return } + + if c.config.GenerateReceipts { + select { + case <-c.ctx.Done(): + return + case c.recieptsChan <- blk: + } + } + blk.ReportBlockMetrics() } diff --git a/sei-db/state_db/bench/cryptosim/cryptosim_config.go b/sei-db/state_db/bench/cryptosim/cryptosim_config.go index e99ccf8fbe..56ca095c11 100644 --- a/sei-db/state_db/bench/cryptosim/cryptosim_config.go +++ b/sei-db/state_db/bench/cryptosim/cryptosim_config.go @@ -139,6 +139,9 @@ type CryptoSimConfig struct { // If true, the benchmark will generate receipts for each transaction in each block and // feed those reciepts into the reciept store. GenerateReceipts bool + + // The capacity of the channel that holds blocks sent to the reciept store. + RecieptChannelCapacity int } // Returns the default configuration for the cryptosim benchmark. @@ -179,6 +182,7 @@ func DefaultCryptoSimConfig() *CryptoSimConfig { EnableSuspension: true, BlockChannelCapacity: 8, GenerateReceipts: false, + RecieptChannelCapacity: 32, } } @@ -254,6 +258,9 @@ func (c *CryptoSimConfig) Validate() error { if c.BlockChannelCapacity < 1 { return fmt.Errorf("BlockChannelCapacity must be at least 1 (got %d)", c.BlockChannelCapacity) } + if c.RecieptChannelCapacity < 1 { + return fmt.Errorf("RecieptChannelCapacity must be at least 1 (got %d)", c.RecieptChannelCapacity) + } return nil } diff --git a/sei-db/state_db/bench/cryptosim/reciept_store_simulator.go b/sei-db/state_db/bench/cryptosim/reciept_store_simulator.go new file mode 100644 index 0000000000..953ae2c4bb --- /dev/null +++ b/sei-db/state_db/bench/cryptosim/reciept_store_simulator.go @@ -0,0 +1,53 @@ +package cryptosim + +import ( + "context" + "fmt" +) + +// A simulated reciept store. +type RecieptStoreSimulator struct { + ctx context.Context + cancel context.CancelFunc + + config *CryptoSimConfig + + recieptsChan chan *block +} + +// Creates a new reciept store simulator. +func NewRecieptStoreSimulator( + ctx context.Context, + config *CryptoSimConfig, + recieptsChan chan *block, +) (*RecieptStoreSimulator, error) { + r := &RecieptStoreSimulator{ + ctx: ctx, + config: config, + recieptsChan: recieptsChan, + } + go r.mainLoop() + return r, nil +} + +func (r *RecieptStoreSimulator) mainLoop() { + for { + select { + case <-r.ctx.Done(): + // TODO add shutdown logic if needed + return + case blk := <-r.recieptsChan: + r.processBlock(blk) + } + } +} + +// Processes a block of reciepts. +func (r *RecieptStoreSimulator) processBlock(blk *block) { + // TODO implement + fmt.Printf("processing block %d with %d reciepts\n", blk.BlockNumber(), len(blk.reciepts)) // TODO remove print + + // for _, reciept := range blk.reciepts { + // // TODO + // } +} From 61cd8da064c4d0fe9d62f111be1044f9e2486289 Mon Sep 17 00:00:00 2001 From: Jeremy Wei Date: Wed, 11 Mar 2026 13:38:40 -0400 Subject: [PATCH 10/20] fill out RecieptStoreSimulator --- sei-db/ledger_db/parquet/store.go | 4 +- sei-db/ledger_db/receipt/parquet_store.go | 10 +- .../cryptosim/reciept_store_simulator.go | 113 ++++++++++++++++-- 3 files changed, 113 insertions(+), 14 deletions(-) diff --git a/sei-db/ledger_db/parquet/store.go b/sei-db/ledger_db/parquet/store.go index 28b1941947..78693f6bb4 100644 --- a/sei-db/ledger_db/parquet/store.go +++ b/sei-db/ledger_db/parquet/store.go @@ -32,8 +32,8 @@ type StoreConfig struct { DBDirectory string KeepRecent int64 PruneIntervalSeconds int64 - BlockFlushInterval uint64 - MaxBlocksPerFile uint64 + BlockFlushInterval uint64 + MaxBlocksPerFile uint64 } // DefaultStoreConfig returns the default store configuration. diff --git a/sei-db/ledger_db/receipt/parquet_store.go b/sei-db/ledger_db/receipt/parquet_store.go index 4d17f74f1c..4b63a6a4ad 100644 --- a/sei-db/ledger_db/receipt/parquet_store.go +++ b/sei-db/ledger_db/receipt/parquet_store.go @@ -178,7 +178,7 @@ func (s *parquetReceiptStore) SetReceipts(ctx sdk.Context, receipts []ReceiptRec BlockNumber: blockNumber, ReceiptBytes: parquet.CopyBytesOrEmpty(receiptBytes), }, - Logs: buildParquetLogRecords(txLogs, blockHash), + Logs: BuildParquetLogRecords(txLogs, blockHash), ReceiptBytes: parquet.CopyBytesOrEmpty(receiptBytes), }) } @@ -309,7 +309,7 @@ func (s *parquetReceiptStore) replayWAL() error { BlockNumber: blockNumber, ReceiptBytes: parquet.CopyBytesOrEmpty(receiptBytes), }, - Logs: buildParquetLogRecords(txLogs, blockHash), + Logs: BuildParquetLogRecords(txLogs, blockHash), } if err := s.store.ApplyReceiptFromReplay(input); err != nil { @@ -349,14 +349,14 @@ func truncateReplayWAL(w interface{ TruncateBefore(offset uint64) error }, dropO return nil } -func buildParquetLogRecords(logs []*ethtypes.Log, blockHash common.Hash) []parquet.LogRecord { +func BuildParquetLogRecords(logs []*ethtypes.Log, blockHash common.Hash) []parquet.LogRecord { if len(logs) == 0 { return nil } records := make([]parquet.LogRecord, 0, len(logs)) for _, lg := range logs { - topic0, topic1, topic2, topic3 := extractLogTopics(lg.Topics) + topic0, topic1, topic2, topic3 := ExtractLogTopics(lg.Topics) rec := parquet.LogRecord{ BlockNumber: lg.BlockNumber, TxHash: lg.TxHash[:], @@ -394,7 +394,7 @@ func buildTopicsFromParquetLogResult(lr parquet.LogResult) []common.Hash { return topicList } -func extractLogTopics(topics []common.Hash) ([]byte, []byte, []byte, []byte) { +func ExtractLogTopics(topics []common.Hash) ([]byte, []byte, []byte, []byte) { t0 := make([]byte, 0) t1 := make([]byte, 0) t2 := make([]byte, 0) diff --git a/sei-db/state_db/bench/cryptosim/reciept_store_simulator.go b/sei-db/state_db/bench/cryptosim/reciept_store_simulator.go index 953ae2c4bb..aeb2bbdb5c 100644 --- a/sei-db/state_db/bench/cryptosim/reciept_store_simulator.go +++ b/sei-db/state_db/bench/cryptosim/reciept_store_simulator.go @@ -3,6 +3,14 @@ package cryptosim import ( "context" "fmt" + "path/filepath" + + "github.com/ethereum/go-ethereum/common" + ethtypes "github.com/ethereum/go-ethereum/core/types" + dbLogger "github.com/sei-protocol/sei-chain/sei-db/common/logger" + "github.com/sei-protocol/sei-chain/sei-db/ledger_db/parquet" + receiptpkg "github.com/sei-protocol/sei-chain/sei-db/ledger_db/receipt" + evmtypes "github.com/sei-protocol/sei-chain/x/evm/types" ) // A simulated reciept store. @@ -13,6 +21,8 @@ type RecieptStoreSimulator struct { config *CryptoSimConfig recieptsChan chan *block + + store *parquet.Store } // Creates a new reciept store simulator. @@ -21,20 +31,37 @@ func NewRecieptStoreSimulator( config *CryptoSimConfig, recieptsChan chan *block, ) (*RecieptStoreSimulator, error) { + derivedCtx, cancel := context.WithCancel(ctx) + + storeCfg := parquet.StoreConfig{ + DBDirectory: filepath.Join(config.DataDir, "receipts"), + BlockFlushInterval: 25, + MaxBlocksPerFile: 500, + KeepRecent: 0, + PruneIntervalSeconds: 0, + } + store, err := parquet.NewStore(dbLogger.NewNopLogger(), storeCfg) + if err != nil { + cancel() + return nil, fmt.Errorf("failed to create parquet receipt store: %w", err) + } + r := &RecieptStoreSimulator{ - ctx: ctx, + ctx: derivedCtx, + cancel: cancel, config: config, recieptsChan: recieptsChan, + store: store, } go r.mainLoop() return r, nil } func (r *RecieptStoreSimulator) mainLoop() { + defer r.store.Close() for { select { case <-r.ctx.Done(): - // TODO add shutdown logic if needed return case blk := <-r.recieptsChan: r.processBlock(blk) @@ -44,10 +71,82 @@ func (r *RecieptStoreSimulator) mainLoop() { // Processes a block of reciepts. func (r *RecieptStoreSimulator) processBlock(blk *block) { - // TODO implement - fmt.Printf("processing block %d with %d reciepts\n", blk.BlockNumber(), len(blk.reciepts)) // TODO remove print + blockNumber := blk.BlockNumber() + blockHash := common.Hash{} + + inputs := make([]parquet.ReceiptInput, 0, len(blk.reciepts)) + + var logStartIndex uint - // for _, reciept := range blk.reciepts { - // // TODO - // } + for _, receipt := range blk.reciepts { + if receipt == nil { + continue + } + + receiptBytes, err := receipt.Marshal() + if err != nil { + fmt.Printf("failed to marshal receipt: %v\n", err) + continue + } + + txHash := common.HexToHash(receipt.TxHashHex) + txLogs := convertLogsForTx(receipt, logStartIndex) + logStartIndex += uint(len(txLogs)) + for _, lg := range txLogs { + lg.BlockHash = blockHash + } + + inputs = append(inputs, parquet.ReceiptInput{ + BlockNumber: blockNumber, + Receipt: parquet.ReceiptRecord{ + TxHash: parquet.CopyBytes(txHash[:]), + BlockNumber: blockNumber, + ReceiptBytes: parquet.CopyBytesOrEmpty(receiptBytes), + }, + Logs: receiptpkg.BuildParquetLogRecords(txLogs, blockHash), + ReceiptBytes: parquet.CopyBytesOrEmpty(receiptBytes), + }) + } + + if len(inputs) > 0 { + if err := r.store.WriteReceipts(inputs); err != nil { + fmt.Printf("failed to write receipts for block %d: %v\n", blockNumber, err) + return + } + } + r.store.UpdateLatestVersion(int64(blockNumber)) //nolint:gosec // block numbers won't exceed int64 max +} + +// convertLogsForTx converts evmtypes.Log entries to ethtypes.Log entries. +// Mirrors receipt.getLogsForTx. +func convertLogsForTx(receipt *evmtypes.Receipt, logStartIndex uint) []*ethtypes.Log { + logs := make([]*ethtypes.Log, 0, len(receipt.Logs)) + for _, l := range receipt.Logs { + logs = append(logs, convertLogEntry(l, receipt, logStartIndex)) + } + return logs } + +// convertLogEntry converts a single evmtypes.Log to an ethtypes.Log. +// Mirrors receipt.convertLog. +func convertLogEntry(l *evmtypes.Log, receipt *evmtypes.Receipt, logStartIndex uint) *ethtypes.Log { + return ðtypes.Log{ + Address: common.HexToAddress(l.Address), + Topics: mapTopics(l.Topics), + Data: l.Data, + BlockNumber: receipt.BlockNumber, + TxHash: common.HexToHash(receipt.TxHashHex), + TxIndex: uint(receipt.TransactionIndex), + Index: uint(l.Index) + logStartIndex, + } +} + +// mapTopics converts hex-encoded topic strings to common.Hash values. +func mapTopics(topics []string) []common.Hash { + result := make([]common.Hash, len(topics)) + for i, t := range topics { + result[i] = common.HexToHash(t) + } + return result +} + From d10374ebae139b8cc12b6b8d1d086804fb14a745 Mon Sep 17 00:00:00 2001 From: Jeremy Wei Date: Mon, 16 Mar 2026 10:01:18 -0400 Subject: [PATCH 11/20] add metrics to cryptosim --- sei-db/state_db/bench/cryptosim/cryptosim.go | 3 +- .../bench/cryptosim/cryptosim_metrics.go | 73 +++++++++++++++++++ .../cryptosim/reciept_store_simulator.go | 17 ++++- 3 files changed, 90 insertions(+), 3 deletions(-) diff --git a/sei-db/state_db/bench/cryptosim/cryptosim.go b/sei-db/state_db/bench/cryptosim/cryptosim.go index 53d35f83a7..64130b263a 100644 --- a/sei-db/state_db/bench/cryptosim/cryptosim.go +++ b/sei-db/state_db/bench/cryptosim/cryptosim.go @@ -161,11 +161,12 @@ func NewCryptoSim( var recieptsChan chan *block if config.GenerateReceipts { recieptsChan = make(chan *block, config.RecieptChannelCapacity) - _, err := NewRecieptStoreSimulator(ctx, config, recieptsChan) + _, err := NewRecieptStoreSimulator(ctx, config, recieptsChan, metrics) if err != nil { cancel() return nil, fmt.Errorf("failed to create receipt store simulator: %w", err) } + metrics.startReceiptChannelDepthSampling(recieptsChan, config.BackgroundMetricsScrapeInterval) } c := &CryptoSim{ diff --git a/sei-db/state_db/bench/cryptosim/cryptosim_metrics.go b/sei-db/state_db/bench/cryptosim/cryptosim_metrics.go index c5b16408b8..08454b3cb2 100644 --- a/sei-db/state_db/bench/cryptosim/cryptosim_metrics.go +++ b/sei-db/state_db/bench/cryptosim/cryptosim_metrics.go @@ -41,6 +41,12 @@ type CryptosimMetrics struct { processWriteCountTotal metric.Int64Counter uptimeSeconds metric.Float64Gauge + // Receipt metrics + receiptBlockWriteDuration metric.Float64Histogram + receiptChannelDepth metric.Int64Gauge + receiptsWrittenTotal metric.Int64Counter + receiptErrorsTotal metric.Int64Counter + mainThreadPhase *metrics.PhaseTimer transactionPhaseTimerFactory *metrics.PhaseTimerFactory } @@ -140,6 +146,27 @@ func NewCryptosimMetrics( metric.WithUnit("s"), ) + receiptBlockWriteDuration, _ := meter.Float64Histogram( + "cryptosim_receipt_block_write_duration_seconds", + metric.WithDescription("Time to write a block of receipts to the parquet store"), + metric.WithUnit("s"), + ) + receiptChannelDepth, _ := meter.Int64Gauge( + "cryptosim_receipt_channel_depth", + metric.WithDescription("Current number of blocks queued for receipt writing"), + metric.WithUnit("{count}"), + ) + receiptsWrittenTotal, _ := meter.Int64Counter( + "cryptosim_receipts_written_total", + metric.WithDescription("Total number of receipts written to the parquet store"), + metric.WithUnit("{count}"), + ) + receiptErrorsTotal, _ := meter.Int64Counter( + "cryptosim_receipt_errors_total", + metric.WithDescription("Total receipt processing errors (marshal or write failures)"), + metric.WithUnit("{count}"), + ) + mainThreadPhase := dbPhaseTimer if mainThreadPhase == nil { mainThreadPhase = metrics.NewPhaseTimer(meter, "seidb_main_thread") @@ -164,6 +191,10 @@ func NewCryptosimMetrics( processReadCountTotal: processReadCountTotal, processWriteCountTotal: processWriteCountTotal, uptimeSeconds: uptimeSeconds, + receiptBlockWriteDuration: receiptBlockWriteDuration, + receiptChannelDepth: receiptChannelDepth, + receiptsWrittenTotal: receiptsWrittenTotal, + receiptErrorsTotal: receiptErrorsTotal, mainThreadPhase: mainThreadPhase, transactionPhaseTimerFactory: transactionPhaseTimerFactory, } @@ -401,3 +432,45 @@ func (m *CryptosimMetrics) SetMainThreadPhase(phase string) { } m.mainThreadPhase.SetPhase(phase) } + +func (m *CryptosimMetrics) RecordReceiptBlockWriteDuration(seconds float64) { + if m == nil || m.receiptBlockWriteDuration == nil { + return + } + m.receiptBlockWriteDuration.Record(context.Background(), seconds) +} + +func (m *CryptosimMetrics) ReportReceiptsWritten(count int64) { + if m == nil || m.receiptsWrittenTotal == nil { + return + } + m.receiptsWrittenTotal.Add(context.Background(), count) +} + +func (m *CryptosimMetrics) ReportReceiptError() { + if m == nil || m.receiptErrorsTotal == nil { + return + } + m.receiptErrorsTotal.Add(context.Background(), 1) +} + +// startReceiptChannelDepthSampling periodically records the depth of the receipt channel. +func (m *CryptosimMetrics) startReceiptChannelDepthSampling(ch <-chan *block, intervalSeconds int) { + if m == nil || m.receiptChannelDepth == nil || intervalSeconds <= 0 || ch == nil { + return + } + interval := time.Duration(intervalSeconds) * time.Second + ctx := context.Background() + go func() { + ticker := time.NewTicker(interval) + defer ticker.Stop() + for { + select { + case <-m.ctx.Done(): + return + case <-ticker.C: + m.receiptChannelDepth.Record(ctx, int64(len(ch))) + } + } + }() +} diff --git a/sei-db/state_db/bench/cryptosim/reciept_store_simulator.go b/sei-db/state_db/bench/cryptosim/reciept_store_simulator.go index aeb2bbdb5c..91808b06db 100644 --- a/sei-db/state_db/bench/cryptosim/reciept_store_simulator.go +++ b/sei-db/state_db/bench/cryptosim/reciept_store_simulator.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "path/filepath" + "time" "github.com/ethereum/go-ethereum/common" ethtypes "github.com/ethereum/go-ethereum/core/types" @@ -22,7 +23,8 @@ type RecieptStoreSimulator struct { recieptsChan chan *block - store *parquet.Store + store *parquet.Store + metrics *CryptosimMetrics } // Creates a new reciept store simulator. @@ -30,6 +32,7 @@ func NewRecieptStoreSimulator( ctx context.Context, config *CryptoSimConfig, recieptsChan chan *block, + metrics *CryptosimMetrics, ) (*RecieptStoreSimulator, error) { derivedCtx, cancel := context.WithCancel(ctx) @@ -52,6 +55,7 @@ func NewRecieptStoreSimulator( config: config, recieptsChan: recieptsChan, store: store, + metrics: metrics, } go r.mainLoop() return r, nil @@ -77,6 +81,7 @@ func (r *RecieptStoreSimulator) processBlock(blk *block) { inputs := make([]parquet.ReceiptInput, 0, len(blk.reciepts)) var logStartIndex uint + var marshalErrors int64 for _, receipt := range blk.reciepts { if receipt == nil { @@ -86,6 +91,7 @@ func (r *RecieptStoreSimulator) processBlock(blk *block) { receiptBytes, err := receipt.Marshal() if err != nil { fmt.Printf("failed to marshal receipt: %v\n", err) + marshalErrors++ continue } @@ -108,11 +114,19 @@ func (r *RecieptStoreSimulator) processBlock(blk *block) { }) } + for range marshalErrors { + r.metrics.ReportReceiptError() + } + if len(inputs) > 0 { + start := time.Now() if err := r.store.WriteReceipts(inputs); err != nil { fmt.Printf("failed to write receipts for block %d: %v\n", blockNumber, err) + r.metrics.ReportReceiptError() return } + r.metrics.RecordReceiptBlockWriteDuration(time.Since(start).Seconds()) + r.metrics.ReportReceiptsWritten(int64(len(inputs))) } r.store.UpdateLatestVersion(int64(blockNumber)) //nolint:gosec // block numbers won't exceed int64 max } @@ -149,4 +163,3 @@ func mapTopics(topics []string) []common.Hash { } return result } - From 2cc5efaf2850d2d9339ce0e736ada5f42f3260c1 Mon Sep 17 00:00:00 2001 From: Cody Littley Date: Mon, 16 Mar 2026 12:43:36 -0500 Subject: [PATCH 12/20] Add new config flags --- .../bench/cryptosim/config/basic-config.json | 4 ++- sei-db/state_db/bench/cryptosim/cryptosim.go | 29 ++++++++++++++++++- .../bench/cryptosim/cryptosim_config.go | 17 ++++++++++- .../bench/cryptosim/transaction_executor.go | 7 +++++ 4 files changed, 54 insertions(+), 3 deletions(-) diff --git a/sei-db/state_db/bench/cryptosim/config/basic-config.json b/sei-db/state_db/bench/cryptosim/config/basic-config.json index 9d708815b8..e395ad3538 100644 --- a/sei-db/state_db/bench/cryptosim/config/basic-config.json +++ b/sei-db/state_db/bench/cryptosim/config/basic-config.json @@ -31,5 +31,7 @@ "TransactionMetricsSampleRate": 0.001, "BackgroundMetricsScrapeInterval": 60, "BlockChannelCapacity": 8, - "GenerateReceipts": false + "GenerateReceipts": false, + "DisableTransactionExecution": false, + "MaxTPS": 0 } diff --git a/sei-db/state_db/bench/cryptosim/cryptosim.go b/sei-db/state_db/bench/cryptosim/cryptosim.go index 64130b263a..b9969617bc 100644 --- a/sei-db/state_db/bench/cryptosim/cryptosim.go +++ b/sei-db/state_db/bench/cryptosim/cryptosim.go @@ -7,6 +7,7 @@ import ( "time" "github.com/sei-protocol/sei-chain/sei-db/state_db/bench/wrappers" + "golang.org/x/time/rate" ) const ( @@ -78,6 +79,9 @@ type CryptoSim struct { // The channel that holds blocks sent to the reciept store. recieptsChan chan *block + + // Enforces a maximum transaction rate (if enabled). + rateLimiter *rate.Limiter } // Creates a new cryptosim benchmark runner. @@ -153,7 +157,7 @@ func NewCryptoSim( executors := make([]*TransactionExecutor, threadCount) for i := 0; i < threadCount; i++ { executors[i] = NewTransactionExecutor( - ctx, cancel, database, dataGenerator.FeeCollectionAddress(), config.ExecutorQueueSize, metrics) + ctx, cancel, config, database, dataGenerator.FeeCollectionAddress(), config.ExecutorQueueSize, metrics) } blockBuilder := NewBlockBuilder(ctx, config, metrics, dataGenerator, dataGenerator.InitialNextBlockNumber()) @@ -169,6 +173,11 @@ func NewCryptoSim( metrics.startReceiptChannelDepthSampling(recieptsChan, config.BackgroundMetricsScrapeInterval) } + var rateLimiter *rate.Limiter + if config.MaxTPS > 0 { + rateLimiter = rate.NewLimiter(rate.Limit(config.MaxTPS), config.TransactionsPerBlock) + } + c := &CryptoSim{ ctx: ctx, cancel: cancel, @@ -184,6 +193,7 @@ func NewCryptoSim( metrics: metrics, suspendChan: make(chan bool, 1), recieptsChan: recieptsChan, + rateLimiter: rateLimiter, } database.SetFlushFunc(c.flushExecutors) @@ -382,6 +392,7 @@ func (c *CryptoSim) run() { c.cancel() return case blk := <-c.blockBuilder.blocksChan: + c.maybeThrottle() c.handleNextBlock(blk) } @@ -389,6 +400,22 @@ func (c *CryptoSim) run() { } } +// Potentially block for a while if we are throttling the transaction rate. +func (c *CryptoSim) maybeThrottle() { + if c.config.MaxTPS == 0 { + // Throttling is disabled. + return + } + + c.metrics.SetMainThreadPhase("throttling") + + if err := c.rateLimiter.WaitN(c.ctx, int(c.config.TransactionsPerBlock)); err != nil { + fmt.Printf("failed to wait for rate limit: %v\n", err) + c.cancel() + return + } +} + // Execute and finalize the next block. func (c *CryptoSim) handleNextBlock(blk *block) { c.mostRecentBlock = blk diff --git a/sei-db/state_db/bench/cryptosim/cryptosim_config.go b/sei-db/state_db/bench/cryptosim/cryptosim_config.go index 56ca095c11..adc25cb87d 100644 --- a/sei-db/state_db/bench/cryptosim/cryptosim_config.go +++ b/sei-db/state_db/bench/cryptosim/cryptosim_config.go @@ -142,6 +142,17 @@ type CryptoSimConfig struct { // The capacity of the channel that holds blocks sent to the reciept store. RecieptChannelCapacity int + + // If true, disables simulation of transaction execution, and writes very little to the database. This is + // potentially useful when benchmarking things other than state storage (e.g. the receipt store). + // + // Note that switching execution on after previously running with execution disabled may result in buggy behavior, + // as the benchmark will not be properly maintaining DB state when transaction execution is disabled. In order + // to switch transaction execution back on, it is necessary to delete the on-disk database and start over. + DisableTransactionExecution bool + + // If greater than 0, the benchmark will throttle the transaction rate to this value, in hertz. + MaxTPS float64 } // Returns the default configuration for the cryptosim benchmark. @@ -183,6 +194,8 @@ func DefaultCryptoSimConfig() *CryptoSimConfig { BlockChannelCapacity: 8, GenerateReceipts: false, RecieptChannelCapacity: 32, + DisableTransactionExecution: false, + MaxTPS: 0, } } @@ -261,7 +274,9 @@ func (c *CryptoSimConfig) Validate() error { if c.RecieptChannelCapacity < 1 { return fmt.Errorf("RecieptChannelCapacity must be at least 1 (got %d)", c.RecieptChannelCapacity) } - + if c.MaxTPS < 0 { + return fmt.Errorf("MaxTPS must be non-negative (got %f)", c.MaxTPS) + } return nil } diff --git a/sei-db/state_db/bench/cryptosim/transaction_executor.go b/sei-db/state_db/bench/cryptosim/transaction_executor.go index 40c23c309c..17271e1f1f 100644 --- a/sei-db/state_db/bench/cryptosim/transaction_executor.go +++ b/sei-db/state_db/bench/cryptosim/transaction_executor.go @@ -10,6 +10,7 @@ import ( type TransactionExecutor struct { ctx context.Context cancel context.CancelFunc + config *CryptoSimConfig // The database for the benchmark. database *Database @@ -33,6 +34,7 @@ type flushRequest struct { func NewTransactionExecutor( ctx context.Context, cancel context.CancelFunc, + config *CryptoSimConfig, database *Database, feeCollectionAddress []byte, queueSize int, @@ -41,6 +43,7 @@ func NewTransactionExecutor( e := &TransactionExecutor{ ctx: ctx, cancel: cancel, + config: config, database: database, feeCollectionAddress: feeCollectionAddress, workChan: make(chan any, queueSize), @@ -86,6 +89,10 @@ func (e *TransactionExecutor) mainLoop() { switch request := request.(type) { case *transaction: + if e.config.DisableTransactionExecution { + continue + } + var phaseTimer *metrics.PhaseTimer if request.ShouldCaptureMetrics() { phaseTimer = e.phaseTimer From e13b1e27c3f8d2e787685e718b5235966d856146 Mon Sep 17 00:00:00 2001 From: Cody Littley Date: Mon, 16 Mar 2026 12:54:15 -0500 Subject: [PATCH 13/20] lint --- sei-db/ledger_db/parquet/store.go | 4 ++-- sei-db/state_db/bench/cryptosim/block.go | 2 +- sei-db/state_db/bench/cryptosim/block_builder.go | 2 +- sei-db/state_db/bench/cryptosim/cryptosim.go | 4 ++-- sei-db/state_db/bench/cryptosim/cryptosim_config.go | 4 ++-- sei-db/state_db/bench/cryptosim/data_generator.go | 2 +- sei-db/state_db/bench/cryptosim/receipt.go | 12 ++++++++---- .../bench/cryptosim/reciept_store_simulator.go | 6 +++--- 8 files changed, 20 insertions(+), 16 deletions(-) diff --git a/sei-db/ledger_db/parquet/store.go b/sei-db/ledger_db/parquet/store.go index 78693f6bb4..28b1941947 100644 --- a/sei-db/ledger_db/parquet/store.go +++ b/sei-db/ledger_db/parquet/store.go @@ -32,8 +32,8 @@ type StoreConfig struct { DBDirectory string KeepRecent int64 PruneIntervalSeconds int64 - BlockFlushInterval uint64 - MaxBlocksPerFile uint64 + BlockFlushInterval uint64 + MaxBlocksPerFile uint64 } // DefaultStoreConfig returns the default store configuration. diff --git a/sei-db/state_db/bench/cryptosim/block.go b/sei-db/state_db/bench/cryptosim/block.go index c066d3b183..0d4408ae75 100644 --- a/sei-db/state_db/bench/cryptosim/block.go +++ b/sei-db/state_db/bench/cryptosim/block.go @@ -13,7 +13,7 @@ type block struct { // The transactions in the block. transactions []*transaction - // If reciept generation is enabled, this will contain the receipts for each transaction in the block. + // If receipt generation is enabled, this will contain the receipts for each transaction in the block. reciepts []*evmtypes.Receipt // The block number. diff --git a/sei-db/state_db/bench/cryptosim/block_builder.go b/sei-db/state_db/bench/cryptosim/block_builder.go index cf2e9f2630..595126a859 100644 --- a/sei-db/state_db/bench/cryptosim/block_builder.go +++ b/sei-db/state_db/bench/cryptosim/block_builder.go @@ -77,7 +77,7 @@ func (b *blockBuilder) buildBlock() *block { b.dataGenerator.Rand(), b.dataGenerator.FeeCollectionAddress(), blk.BlockNumber(), - uint32(i), + uint32(i), //nolint:gosec txn, ) if err != nil { diff --git a/sei-db/state_db/bench/cryptosim/cryptosim.go b/sei-db/state_db/bench/cryptosim/cryptosim.go index b9969617bc..49914c9785 100644 --- a/sei-db/state_db/bench/cryptosim/cryptosim.go +++ b/sei-db/state_db/bench/cryptosim/cryptosim.go @@ -77,7 +77,7 @@ type CryptoSim struct { // the creation of new ERC20 contracts during the benchmark. nextERC20ContractID int64 - // The channel that holds blocks sent to the reciept store. + // The channel that holds blocks sent to the receipt store. recieptsChan chan *block // Enforces a maximum transaction rate (if enabled). @@ -409,7 +409,7 @@ func (c *CryptoSim) maybeThrottle() { c.metrics.SetMainThreadPhase("throttling") - if err := c.rateLimiter.WaitN(c.ctx, int(c.config.TransactionsPerBlock)); err != nil { + if err := c.rateLimiter.WaitN(c.ctx, c.config.TransactionsPerBlock); err != nil { fmt.Printf("failed to wait for rate limit: %v\n", err) c.cancel() return diff --git a/sei-db/state_db/bench/cryptosim/cryptosim_config.go b/sei-db/state_db/bench/cryptosim/cryptosim_config.go index adc25cb87d..4ceaa28321 100644 --- a/sei-db/state_db/bench/cryptosim/cryptosim_config.go +++ b/sei-db/state_db/bench/cryptosim/cryptosim_config.go @@ -137,10 +137,10 @@ type CryptoSimConfig struct { BlockChannelCapacity int // If true, the benchmark will generate receipts for each transaction in each block and - // feed those reciepts into the reciept store. + // feed those receipts into the receipt store. GenerateReceipts bool - // The capacity of the channel that holds blocks sent to the reciept store. + // The capacity of the channel that holds blocks sent to the receipt store. RecieptChannelCapacity int // If true, disables simulation of transaction execution, and writes very little to the database. This is diff --git a/sei-db/state_db/bench/cryptosim/data_generator.go b/sei-db/state_db/bench/cryptosim/data_generator.go index 9eefd0ff8e..ec1b10460a 100644 --- a/sei-db/state_db/bench/cryptosim/data_generator.go +++ b/sei-db/state_db/bench/cryptosim/data_generator.go @@ -100,7 +100,7 @@ func NewDataGenerator( nextBlockNumber = binary.BigEndian.Uint64(nextBlockNumberBinary) } - fmt.Printf("Next block number: %s.\n", int64Commas(int64(nextBlockNumber))) + fmt.Printf("Next block number: %s.\n", int64Commas(int64(nextBlockNumber))) //nolint:gosec // Use EVMKeyCode for account data; EVMKeyNonce only accepts 8-byte values. feeCollectionAddress := evm.BuildMemIAVLEVMKey( diff --git a/sei-db/state_db/bench/cryptosim/receipt.go b/sei-db/state_db/bench/cryptosim/receipt.go index d1b76c95ff..1b34e9a647 100644 --- a/sei-db/state_db/bench/cryptosim/receipt.go +++ b/sei-db/state_db/bench/cryptosim/receipt.go @@ -107,11 +107,15 @@ func BuildERC20TransferReceipt( txType = uint32(ethtypes.LegacyTxType) } - gasUsed := syntheticReceiptGasUsedBase + uint64(crand.Int64Range(0, int64(syntheticReceiptGasUsedSpan))) - previousGas := syntheticReceiptPreviousGasBase + uint64(crand.Int64Range(0, int64(syntheticReceiptPreviousGasSpan))) + gasUsed := syntheticReceiptGasUsedBase + + uint64(crand.Int64Range(0, int64(syntheticReceiptGasUsedSpan))) //nolint:gosec // constants fit in int64 + previousGas := syntheticReceiptPreviousGasBase + + uint64(crand.Int64Range(0, int64(syntheticReceiptPreviousGasSpan))) //nolint:gosec // constants fit in int64 cumulativeGasUsed := gasUsed + uint64(txIndex)*previousGas - effectiveGasPrice := syntheticReceiptGasPriceBase + uint64(crand.Int64Range(0, int64(syntheticReceiptGasPriceSpan))) - transferAmount := syntheticReceiptTransferBase + uint64(crand.Int64Range(0, int64(syntheticReceiptTransferSpan))) + effectiveGasPrice := syntheticReceiptGasPriceBase + + uint64(crand.Int64Range(0, int64(syntheticReceiptGasPriceSpan))) //nolint:gosec // constants fit in int64 + transferAmount := syntheticReceiptTransferBase + + uint64(crand.Int64Range(0, int64(syntheticReceiptTransferSpan))) //nolint:gosec // constants fit in int64 var senderTopic [hashLen]byte copy(senderTopic[indexedAddressBase:], senderAddressBytes) diff --git a/sei-db/state_db/bench/cryptosim/reciept_store_simulator.go b/sei-db/state_db/bench/cryptosim/reciept_store_simulator.go index 91808b06db..8e822c490e 100644 --- a/sei-db/state_db/bench/cryptosim/reciept_store_simulator.go +++ b/sei-db/state_db/bench/cryptosim/reciept_store_simulator.go @@ -14,7 +14,7 @@ import ( evmtypes "github.com/sei-protocol/sei-chain/x/evm/types" ) -// A simulated reciept store. +// A simulated receipt store. type RecieptStoreSimulator struct { ctx context.Context cancel context.CancelFunc @@ -27,7 +27,7 @@ type RecieptStoreSimulator struct { metrics *CryptosimMetrics } -// Creates a new reciept store simulator. +// Creates a new receipt store simulator. func NewRecieptStoreSimulator( ctx context.Context, config *CryptoSimConfig, @@ -62,7 +62,7 @@ func NewRecieptStoreSimulator( } func (r *RecieptStoreSimulator) mainLoop() { - defer r.store.Close() + defer func() { _ = r.store.Close() }() for { select { case <-r.ctx.Done(): From 2f2e4f9134018c039bd51d712542e2bd339e26c8 Mon Sep 17 00:00:00 2001 From: Jeremy Wei Date: Tue, 17 Mar 2026 10:17:19 -0400 Subject: [PATCH 14/20] fix --- .../bench/cryptosim/reciept_store_simulator.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/sei-db/state_db/bench/cryptosim/reciept_store_simulator.go b/sei-db/state_db/bench/cryptosim/reciept_store_simulator.go index 10029f6847..eaecfb0b67 100644 --- a/sei-db/state_db/bench/cryptosim/reciept_store_simulator.go +++ b/sei-db/state_db/bench/cryptosim/reciept_store_simulator.go @@ -35,11 +35,11 @@ func NewRecieptStoreSimulator( ) (*RecieptStoreSimulator, error) { derivedCtx, cancel := context.WithCancel(ctx) - maxBlocksPerFile := uint64(config.ReceiptMaxBlocksPerFile) + maxBlocksPerFile := uint64(max(config.ReceiptMaxBlocksPerFile, 0)) //nolint:gosec // validated non-negative if maxBlocksPerFile == 0 { maxBlocksPerFile = 500 } - blockFlushInterval := uint64(config.ReceiptBlockFlushInterval) + blockFlushInterval := uint64(max(config.ReceiptBlockFlushInterval, 0)) //nolint:gosec // validated non-negative if blockFlushInterval == 0 { blockFlushInterval = 1 } @@ -70,7 +70,11 @@ func NewRecieptStoreSimulator( } func (r *RecieptStoreSimulator) mainLoop() { - defer r.store.Close() + defer func() { + if err := r.store.Close(); err != nil { + fmt.Printf("failed to close receipt store: %v\n", err) + } + }() for { select { case <-r.ctx.Done(): From 13912da574918c47223ca979b3ad3592d62229e2 Mon Sep 17 00:00:00 2001 From: Jeremy Wei Date: Tue, 17 Mar 2026 11:40:11 -0400 Subject: [PATCH 15/20] use ledger cache --- .../bench/cryptosim/cryptosim_config.go | 8 -- .../state_db/bench/cryptosim/receipt_test.go | 110 +++++++++++++----- .../cryptosim/reciept_store_simulator.go | 103 ++++++++-------- 3 files changed, 130 insertions(+), 91 deletions(-) diff --git a/sei-db/state_db/bench/cryptosim/cryptosim_config.go b/sei-db/state_db/bench/cryptosim/cryptosim_config.go index 57f3e0fe44..4b1ea07a14 100644 --- a/sei-db/state_db/bench/cryptosim/cryptosim_config.go +++ b/sei-db/state_db/bench/cryptosim/cryptosim_config.go @@ -159,12 +159,6 @@ type CryptoSimConfig struct { // Interval in seconds between prune checks. 0 disables pruning. ReceiptPruneIntervalSeconds int64 - - // Maximum number of blocks stored per parquet file before rotation. Default 500. - ReceiptMaxBlocksPerFile int - - // Number of blocks to buffer before flushing to parquet. Default 1 (matches real node). - ReceiptBlockFlushInterval int } // Returns the default configuration for the cryptosim benchmark. @@ -210,8 +204,6 @@ func DefaultCryptoSimConfig() *CryptoSimConfig { MaxTPS: 0, ReceiptKeepRecent: 100_000, ReceiptPruneIntervalSeconds: 600, - ReceiptMaxBlocksPerFile: 500, - ReceiptBlockFlushInterval: 1, } } diff --git a/sei-db/state_db/bench/cryptosim/receipt_test.go b/sei-db/state_db/bench/cryptosim/receipt_test.go index 3147721d47..03a2b0e40c 100644 --- a/sei-db/state_db/bench/cryptosim/receipt_test.go +++ b/sei-db/state_db/bench/cryptosim/receipt_test.go @@ -3,55 +3,113 @@ package cryptosim import ( "testing" + ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/sei-protocol/sei-chain/sei-db/common/evm" ) +func makeTestKeys(t *testing.T) (feeAccount, srcAccount, dstAccount, senderSlot, receiverSlot, erc20Contract []byte) { + t.Helper() + keyRand := NewCannedRandom(4096, 1) + + feeAccount = evm.BuildMemIAVLEVMKey(evm.EVMKeyCode, keyRand.Address(accountPrefix, 0, AddressLen)) + srcAddr := keyRand.Address(accountPrefix, 1, AddressLen) + srcAccount = evm.BuildMemIAVLEVMKey(evm.EVMKeyCode, srcAddr) + dstAddr := keyRand.Address(accountPrefix, 2, AddressLen) + dstAccount = evm.BuildMemIAVLEVMKey(evm.EVMKeyCode, dstAddr) + + senderSlotBytes := make([]byte, StorageKeyLen) + copy(senderSlotBytes[:AddressLen], srcAddr) + copy(senderSlotBytes[AddressLen:], keyRand.SeededBytes(SlotLen, 11)) + senderSlot = evm.BuildMemIAVLEVMKey(evm.EVMKeyStorage, senderSlotBytes) + + receiverSlotBytes := make([]byte, StorageKeyLen) + copy(receiverSlotBytes[:AddressLen], dstAddr) + copy(receiverSlotBytes[AddressLen:], keyRand.SeededBytes(SlotLen, 12)) + receiverSlot = evm.BuildMemIAVLEVMKey(evm.EVMKeyStorage, receiverSlotBytes) + + erc20Contract = evm.BuildMemIAVLEVMKey(evm.EVMKeyCode, keyRand.Address(contractPrefix, 0, AddressLen)) + return +} + +func TestBuildERC20TransferReceipt(t *testing.T) { + crand := NewCannedRandom(1<<20, 42) + feeAccount, srcAccount, dstAccount, senderSlot, receiverSlot, erc20Contract := makeTestKeys(t) + + receipt, err := BuildERC20TransferReceipt( + crand, feeAccount, srcAccount, dstAccount, + senderSlot, receiverSlot, erc20Contract, + 1_000_000, 0, + ) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + if receipt.Status != uint32(ethtypes.ReceiptStatusSuccessful) { + t.Errorf("expected successful status, got %d", receipt.Status) + } + if receipt.BlockNumber != 1_000_000 { + t.Errorf("expected block number 1000000, got %d", receipt.BlockNumber) + } + if len(receipt.Logs) != 1 { + t.Fatalf("expected 1 log, got %d", len(receipt.Logs)) + } + if receipt.Logs[0].Topics[0] != erc20TransferEventSignatureHex { + t.Error("first log topic should be ERC20 Transfer event signature") + } + + // Receipt must be marshallable (used by the write path). + data, err := receipt.Marshal() + if err != nil { + t.Fatalf("failed to marshal receipt: %v", err) + } + if len(data) == 0 { + t.Fatal("marshalled receipt is empty") + } +} + +func TestBuildERC20TransferReceipt_InvalidInputs(t *testing.T) { + crand := NewCannedRandom(1<<20, 42) + feeAccount, srcAccount, dstAccount, senderSlot, receiverSlot, erc20Contract := makeTestKeys(t) + + if _, err := BuildERC20TransferReceipt(nil, feeAccount, srcAccount, dstAccount, senderSlot, receiverSlot, erc20Contract, 1_000_000, 0); err == nil { + t.Error("expected error for nil CannedRandom") + } + if _, err := BuildERC20TransferReceipt(crand, []byte("bad"), srcAccount, dstAccount, senderSlot, receiverSlot, erc20Contract, 1_000_000, 0); err == nil { + t.Error("expected error for invalid fee account key") + } + if _, err := BuildERC20TransferReceipt(crand, feeAccount, srcAccount, dstAccount, []byte("bad"), receiverSlot, erc20Contract, 1_000_000, 0); err == nil { + t.Error("expected error for invalid sender slot key") + } +} + func BenchmarkBuildERC20TransferReceipt(b *testing.B) { keyRand := NewCannedRandom(4096, 1) receiptRand := NewCannedRandom(1<<20, 2) feeAccount := evm.BuildMemIAVLEVMKey(evm.EVMKeyCode, keyRand.Address(accountPrefix, 0, AddressLen)) - - srcAccountAddress := keyRand.Address(accountPrefix, 1, AddressLen) - srcAccount := evm.BuildMemIAVLEVMKey(evm.EVMKeyCode, srcAccountAddress) - - dstAccountAddress := keyRand.Address(accountPrefix, 2, AddressLen) - dstAccount := evm.BuildMemIAVLEVMKey(evm.EVMKeyCode, dstAccountAddress) + srcAddr := keyRand.Address(accountPrefix, 1, AddressLen) + srcAccount := evm.BuildMemIAVLEVMKey(evm.EVMKeyCode, srcAddr) + dstAddr := keyRand.Address(accountPrefix, 2, AddressLen) + dstAccount := evm.BuildMemIAVLEVMKey(evm.EVMKeyCode, dstAddr) senderSlotBytes := make([]byte, StorageKeyLen) - copy(senderSlotBytes[:AddressLen], srcAccountAddress) + copy(senderSlotBytes[:AddressLen], srcAddr) copy(senderSlotBytes[AddressLen:], keyRand.SeededBytes(SlotLen, 11)) senderSlot := evm.BuildMemIAVLEVMKey(evm.EVMKeyStorage, senderSlotBytes) receiverSlotBytes := make([]byte, StorageKeyLen) - copy(receiverSlotBytes[:AddressLen], dstAccountAddress) + copy(receiverSlotBytes[:AddressLen], dstAddr) copy(receiverSlotBytes[AddressLen:], keyRand.SeededBytes(SlotLen, 12)) receiverSlot := evm.BuildMemIAVLEVMKey(evm.EVMKeyStorage, receiverSlotBytes) erc20Contract := evm.BuildMemIAVLEVMKey(evm.EVMKeyCode, keyRand.Address(contractPrefix, 0, AddressLen)) - blockNumber := syntheticReceiptMinBlockNumber - txIndex := uint32(0) b.ReportAllocs() b.ResetTimer() - for i := 0; i < b.N; i++ { - receipt, err := BuildERC20TransferReceipt( - receiptRand, - feeAccount, - srcAccount, - dstAccount, - senderSlot, - receiverSlot, - erc20Contract, - blockNumber, - txIndex, - ) + _, err := BuildERC20TransferReceipt(receiptRand, feeAccount, srcAccount, dstAccount, senderSlot, receiverSlot, erc20Contract, syntheticReceiptMinBlockNumber, 0) if err != nil { - b.Fatalf("BuildERC20TransferReceipt failed: %v", err) - } - if len(receipt.Logs) != 1 { - b.Fatalf("expected 1 log, got %d", len(receipt.Logs)) + b.Fatal(err) } } } diff --git a/sei-db/state_db/bench/cryptosim/reciept_store_simulator.go b/sei-db/state_db/bench/cryptosim/reciept_store_simulator.go index eaecfb0b67..c953f5432f 100644 --- a/sei-db/state_db/bench/cryptosim/reciept_store_simulator.go +++ b/sei-db/state_db/bench/cryptosim/reciept_store_simulator.go @@ -8,12 +8,15 @@ import ( "github.com/ethereum/go-ethereum/common" ethtypes "github.com/ethereum/go-ethereum/core/types" - "github.com/sei-protocol/sei-chain/sei-db/ledger_db/parquet" - receiptpkg "github.com/sei-protocol/sei-chain/sei-db/ledger_db/receipt" + sdk "github.com/sei-protocol/sei-chain/sei-cosmos/types" + dbconfig "github.com/sei-protocol/sei-chain/sei-db/config" + "github.com/sei-protocol/sei-chain/sei-db/ledger_db/receipt" + tmproto "github.com/sei-protocol/sei-chain/sei-tendermint/proto/tendermint/types" evmtypes "github.com/sei-protocol/sei-chain/x/evm/types" ) -// A simulated receipt store with WAL, flush, rotation, and pruning. +// A simulated receipt store using the real production receipt.ReceiptStore +// (cached parquet backend with WAL, flush, rotation, and pruning). type RecieptStoreSimulator struct { ctx context.Context cancel context.CancelFunc @@ -22,11 +25,12 @@ type RecieptStoreSimulator struct { recieptsChan chan *block - store *parquet.Store + store receipt.ReceiptStore metrics *CryptosimMetrics } -// Creates a new receipt store simulator. +// Creates a new receipt store simulator backed by the production ReceiptStore +// (parquet backend + ledger cache), matching the real node write path. func NewRecieptStoreSimulator( ctx context.Context, config *CryptoSimConfig, @@ -35,26 +39,18 @@ func NewRecieptStoreSimulator( ) (*RecieptStoreSimulator, error) { derivedCtx, cancel := context.WithCancel(ctx) - maxBlocksPerFile := uint64(max(config.ReceiptMaxBlocksPerFile, 0)) //nolint:gosec // validated non-negative - if maxBlocksPerFile == 0 { - maxBlocksPerFile = 500 - } - blockFlushInterval := uint64(max(config.ReceiptBlockFlushInterval, 0)) //nolint:gosec // validated non-negative - if blockFlushInterval == 0 { - blockFlushInterval = 1 - } - - storeCfg := parquet.StoreConfig{ + storeCfg := dbconfig.ReceiptStoreConfig{ DBDirectory: filepath.Join(config.DataDir, "receipts"), - BlockFlushInterval: blockFlushInterval, - MaxBlocksPerFile: maxBlocksPerFile, - KeepRecent: config.ReceiptKeepRecent, - PruneIntervalSeconds: config.ReceiptPruneIntervalSeconds, + Backend: "parquet", + KeepRecent: int(config.ReceiptKeepRecent), + PruneIntervalSeconds: int(config.ReceiptPruneIntervalSeconds), } - store, err := parquet.NewStore(storeCfg) + + // nil StoreKey is safe: the parquet write path never touches the legacy KV store. + store, err := receipt.NewReceiptStore(storeCfg, nil) if err != nil { cancel() - return nil, fmt.Errorf("failed to create parquet receipt store: %w", err) + return nil, fmt.Errorf("failed to create receipt store: %w", err) } r := &RecieptStoreSimulator{ @@ -85,44 +81,31 @@ func (r *RecieptStoreSimulator) mainLoop() { } } -// Processes a block of receipts: marshal, write to parquet (WAL + buffer). +// Processes a block of receipts using the production ReceiptStore.SetReceipts path, +// which writes to parquet (WAL + buffer + rotation) and populates the ledger cache. func (r *RecieptStoreSimulator) processBlock(blk *block) { blockNumber := uint64(blk.BlockNumber()) //nolint:gosec - blockHash := common.Hash{} - - inputs := make([]parquet.ReceiptInput, 0, len(blk.reciepts)) - var logStartIndex uint + records := make([]receipt.ReceiptRecord, 0, len(blk.reciepts)) var marshalErrors int64 - for _, receipt := range blk.reciepts { - if receipt == nil { + for _, rcpt := range blk.reciepts { + if rcpt == nil { continue } - receiptBytes, err := receipt.Marshal() + receiptBytes, err := rcpt.Marshal() if err != nil { fmt.Printf("failed to marshal receipt: %v\n", err) marshalErrors++ continue } - txHash := common.HexToHash(receipt.TxHashHex) - txLogs := convertLogsForTx(receipt, logStartIndex) - logStartIndex += uint(len(txLogs)) - for _, lg := range txLogs { - lg.BlockHash = blockHash - } - - inputs = append(inputs, parquet.ReceiptInput{ - BlockNumber: blockNumber, - Receipt: parquet.ReceiptRecord{ - TxHash: parquet.CopyBytes(txHash[:]), - BlockNumber: blockNumber, - ReceiptBytes: parquet.CopyBytesOrEmpty(receiptBytes), - }, - Logs: receiptpkg.BuildParquetLogRecords(txLogs, blockHash), - ReceiptBytes: parquet.CopyBytesOrEmpty(receiptBytes), + txHash := common.HexToHash(rcpt.TxHashHex) + records = append(records, receipt.ReceiptRecord{ + TxHash: txHash, + Receipt: rcpt, + ReceiptBytes: receiptBytes, }) } @@ -130,40 +113,46 @@ func (r *RecieptStoreSimulator) processBlock(blk *block) { r.metrics.ReportReceiptError() } - if len(inputs) > 0 { + if len(records) > 0 { + // Build a minimal sdk.Context with the block height set. + // The parquet write path only uses ctx.BlockHeight() and ctx.Context(). + sdkCtx := sdk.NewContext(nil, tmproto.Header{Height: int64(blockNumber)}, false) //nolint:gosec + start := time.Now() - if err := r.store.WriteReceipts(inputs); err != nil { + if err := r.store.SetReceipts(sdkCtx, records); err != nil { fmt.Printf("failed to write receipts for block %d: %v\n", blockNumber, err) r.metrics.ReportReceiptError() return } r.metrics.RecordReceiptBlockWriteDuration(time.Since(start).Seconds()) - r.metrics.ReportReceiptsWritten(int64(len(inputs))) + r.metrics.ReportReceiptsWritten(int64(len(records))) } - r.store.UpdateLatestVersion(int64(blockNumber)) //nolint:gosec + if err := r.store.SetLatestVersion(int64(blockNumber)); err != nil { //nolint:gosec + fmt.Printf("failed to update latest version for block %d: %v\n", blockNumber, err) + } } // convertLogsForTx converts evmtypes.Log entries to ethtypes.Log entries. // Mirrors receipt.getLogsForTx. -func convertLogsForTx(receipt *evmtypes.Receipt, logStartIndex uint) []*ethtypes.Log { - logs := make([]*ethtypes.Log, 0, len(receipt.Logs)) - for _, l := range receipt.Logs { - logs = append(logs, convertLogEntry(l, receipt, logStartIndex)) +func convertLogsForTx(rcpt *evmtypes.Receipt, logStartIndex uint) []*ethtypes.Log { + logs := make([]*ethtypes.Log, 0, len(rcpt.Logs)) + for _, l := range rcpt.Logs { + logs = append(logs, convertLogEntry(l, rcpt, logStartIndex)) } return logs } // convertLogEntry converts a single evmtypes.Log to an ethtypes.Log. // Mirrors receipt.convertLog. -func convertLogEntry(l *evmtypes.Log, receipt *evmtypes.Receipt, logStartIndex uint) *ethtypes.Log { +func convertLogEntry(l *evmtypes.Log, rcpt *evmtypes.Receipt, logStartIndex uint) *ethtypes.Log { return ðtypes.Log{ Address: common.HexToAddress(l.Address), Topics: mapTopics(l.Topics), Data: l.Data, - BlockNumber: receipt.BlockNumber, - TxHash: common.HexToHash(receipt.TxHashHex), - TxIndex: uint(receipt.TransactionIndex), + BlockNumber: rcpt.BlockNumber, + TxHash: common.HexToHash(rcpt.TxHashHex), + TxIndex: uint(rcpt.TransactionIndex), Index: uint(l.Index) + logStartIndex, } } From d32901967e48ff440632c0e1f37d23e719593324 Mon Sep 17 00:00:00 2001 From: Jeremy Wei Date: Tue, 17 Mar 2026 15:15:56 -0400 Subject: [PATCH 16/20] fix receipt buckets --- .../dashboards/cryptosim-dashboard.json | 470 +++++++++++++++++- .../bench/cryptosim/cryptosim_metrics.go | 7 + 2 files changed, 460 insertions(+), 17 deletions(-) diff --git a/docker/monitornode/dashboards/cryptosim-dashboard.json b/docker/monitornode/dashboards/cryptosim-dashboard.json index 12d6542a09..288035db07 100644 --- a/docker/monitornode/dashboards/cryptosim-dashboard.json +++ b/docker/monitornode/dashboards/cryptosim-dashboard.json @@ -2587,6 +2587,442 @@ "x": 0, "y": 37 }, + "id": 277, + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 0 + }, + "id": 278, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.4.0", + "targets": [ + { + "editorMode": "code", + "expr": "histogram_quantile(0.99, rate(cryptosim_receipt_block_write_duration_seconds_bucket[$__rate_interval]))", + "legendFormat": "p99", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.95, rate(cryptosim_receipt_block_write_duration_seconds_bucket[$__rate_interval]))", + "instant": false, + "legendFormat": "p95", + "range": true, + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.50, rate(cryptosim_receipt_block_write_duration_seconds_bucket[$__rate_interval]))", + "instant": false, + "legendFormat": "p50", + "range": true, + "refId": "C" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "rate(cryptosim_receipt_block_write_duration_seconds_sum[$__rate_interval]) / rate(cryptosim_receipt_block_write_duration_seconds_count[$__rate_interval])", + "instant": false, + "legendFormat": "average", + "range": true, + "refId": "D" + } + ], + "title": "Receipt Write Latency", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 0 + }, + "id": 279, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.4.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "rate(cryptosim_receipts_written_total[$__rate_interval])", + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "Receipts Written/sec", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "locale" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 8 + }, + "id": 280, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.4.0", + "targets": [ + { + "editorMode": "code", + "expr": "cryptosim_receipt_channel_depth", + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "Receipt Channel Depth", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 8 + }, + "id": 281, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.4.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "rate(cryptosim_receipt_errors_total[$__rate_interval])", + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "Receipt Errors", + "type": "timeseries" + } + ], + "title": "Receipts", + "type": "row" + }, + { + "collapsed": true, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 38 + }, "id": 29, "panels": [ { @@ -2884,7 +3320,7 @@ "h": 1, "w": 24, "x": 0, - "y": 38 + "y": 39 }, "id": 35, "panels": [ @@ -3182,7 +3618,7 @@ "h": 1, "w": 24, "x": 0, - "y": 39 + "y": 40 }, "id": 37, "panels": [ @@ -3671,7 +4107,7 @@ "h": 1, "w": 24, "x": 0, - "y": 40 + "y": 41 }, "id": 44, "panels": [ @@ -3780,7 +4216,7 @@ "h": 1, "w": 24, "x": 0, - "y": 41 + "y": 42 }, "id": 117, "panels": [ @@ -4364,7 +4800,7 @@ "h": 1, "w": 24, "x": 0, - "y": 42 + "y": 43 }, "id": 191, "panels": [ @@ -5041,7 +5477,7 @@ "h": 1, "w": 24, "x": 0, - "y": 43 + "y": 44 }, "id": 118, "panels": [ @@ -6378,7 +6814,7 @@ "h": 1, "w": 24, "x": 0, - "y": 44 + "y": 45 }, "id": 115, "panels": [ @@ -7337,7 +7773,7 @@ "h": 1, "w": 24, "x": 0, - "y": 45 + "y": 46 }, "id": 193, "panels": [ @@ -8667,7 +9103,7 @@ "h": 1, "w": 24, "x": 0, - "y": 46 + "y": 47 }, "id": 192, "panels": [ @@ -9437,7 +9873,7 @@ "h": 1, "w": 24, "x": 0, - "y": 47 + "y": 48 }, "id": 194, "panels": [ @@ -11345,7 +11781,7 @@ "h": 1, "w": 24, "x": 0, - "y": 48 + "y": 49 }, "id": 195, "panels": [ @@ -12020,7 +12456,7 @@ "h": 1, "w": 24, "x": 0, - "y": 49 + "y": 50 }, "id": 210, "panels": [ @@ -12794,7 +13230,7 @@ "h": 1, "w": 24, "x": 0, - "y": 50 + "y": 51 }, "id": 230, "panels": [ @@ -13186,7 +13622,7 @@ "h": 1, "w": 24, "x": 0, - "y": 51 + "y": 52 }, "id": 250, "panels": [ @@ -13675,7 +14111,7 @@ "h": 1, "w": 24, "x": 0, - "y": 52 + "y": 53 }, "id": 100, "panels": [ @@ -15308,6 +15744,6 @@ "timezone": "browser", "title": "CryptoSim", "uid": "adnqfm4", - "version": 29, + "version": 30, "weekStart": "" -} \ No newline at end of file +} diff --git a/sei-db/state_db/bench/cryptosim/cryptosim_metrics.go b/sei-db/state_db/bench/cryptosim/cryptosim_metrics.go index 08454b3cb2..70d122472b 100644 --- a/sei-db/state_db/bench/cryptosim/cryptosim_metrics.go +++ b/sei-db/state_db/bench/cryptosim/cryptosim_metrics.go @@ -19,6 +19,12 @@ import ( const cryptosimMeterName = "cryptosim" +var receiptWriteLatencyBuckets = []float64{ + 0.001, 0.0025, 0.005, 0.0075, 0.01, + 0.015, 0.02, 0.03, 0.05, 0.075, + 0.1, 0.25, 0.5, 0.75, 1, 2.5, 5, +} + // CryptosimMetrics holds OpenTelemetry metrics for the cryptosim benchmark. // Metrics are exported via whatever exporter is configured on the global OTel // MeterProvider (e.g., Prometheus, OTLP). This package does not import Prometheus. @@ -149,6 +155,7 @@ func NewCryptosimMetrics( receiptBlockWriteDuration, _ := meter.Float64Histogram( "cryptosim_receipt_block_write_duration_seconds", metric.WithDescription("Time to write a block of receipts to the parquet store"), + metric.WithExplicitBucketBoundaries(receiptWriteLatencyBuckets...), metric.WithUnit("s"), ) receiptChannelDepth, _ := meter.Int64Gauge( From fe39260d938986ca08fff5121b26a76996d9d548 Mon Sep 17 00:00:00 2001 From: Jeremy Wei Date: Wed, 18 Mar 2026 13:27:32 -0400 Subject: [PATCH 17/20] use duration --- sei-db/state_db/bench/cryptosim/cryptosim_metrics.go | 4 ++-- sei-db/state_db/bench/cryptosim/reciept_store_simulator.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/sei-db/state_db/bench/cryptosim/cryptosim_metrics.go b/sei-db/state_db/bench/cryptosim/cryptosim_metrics.go index 70d122472b..3afd904ca9 100644 --- a/sei-db/state_db/bench/cryptosim/cryptosim_metrics.go +++ b/sei-db/state_db/bench/cryptosim/cryptosim_metrics.go @@ -440,11 +440,11 @@ func (m *CryptosimMetrics) SetMainThreadPhase(phase string) { m.mainThreadPhase.SetPhase(phase) } -func (m *CryptosimMetrics) RecordReceiptBlockWriteDuration(seconds float64) { +func (m *CryptosimMetrics) RecordReceiptBlockWriteDuration(latency time.Duration) { if m == nil || m.receiptBlockWriteDuration == nil { return } - m.receiptBlockWriteDuration.Record(context.Background(), seconds) + m.receiptBlockWriteDuration.Record(context.Background(), latency.Seconds()) } func (m *CryptosimMetrics) ReportReceiptsWritten(count int64) { diff --git a/sei-db/state_db/bench/cryptosim/reciept_store_simulator.go b/sei-db/state_db/bench/cryptosim/reciept_store_simulator.go index c953f5432f..309c03667e 100644 --- a/sei-db/state_db/bench/cryptosim/reciept_store_simulator.go +++ b/sei-db/state_db/bench/cryptosim/reciept_store_simulator.go @@ -124,7 +124,7 @@ func (r *RecieptStoreSimulator) processBlock(blk *block) { r.metrics.ReportReceiptError() return } - r.metrics.RecordReceiptBlockWriteDuration(time.Since(start).Seconds()) + r.metrics.RecordReceiptBlockWriteDuration(time.Since(start)) r.metrics.ReportReceiptsWritten(int64(len(records))) } From a20def561117a6683e9816be79dbd6f14f601597 Mon Sep 17 00:00:00 2001 From: Jeremy Wei Date: Wed, 18 Mar 2026 13:36:51 -0400 Subject: [PATCH 18/20] fix --- sei-db/state_db/bench/cryptosim/cryptosim.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/sei-db/state_db/bench/cryptosim/cryptosim.go b/sei-db/state_db/bench/cryptosim/cryptosim.go index d445d6acca..2b1b593250 100644 --- a/sei-db/state_db/bench/cryptosim/cryptosim.go +++ b/sei-db/state_db/bench/cryptosim/cryptosim.go @@ -18,8 +18,9 @@ const ( // EVM key sizes (matches sei-db/common/evm). const ( - AddressLen = 20 // EVM address length - SlotLen = 32 // EVM storage slot length + AddressLen = 20 // EVM address length + SlotLen = 32 // EVM storage slot length + StorageKeyLen = AddressLen + SlotLen ) // The test runner for the cryptosim benchmark. From 4ebeaa7eaae28f3390eee7ae360b8e6d1642ed9b Mon Sep 17 00:00:00 2001 From: Jeremy Wei Date: Wed, 18 Mar 2026 14:20:54 -0400 Subject: [PATCH 19/20] use CodeHashKeyPrefix --- sei-db/state_db/bench/cryptosim/receipt.go | 24 +++++++++++++------ .../state_db/bench/cryptosim/receipt_test.go | 12 +++++----- 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/sei-db/state_db/bench/cryptosim/receipt.go b/sei-db/state_db/bench/cryptosim/receipt.go index 1b34e9a647..4ab348aa2c 100644 --- a/sei-db/state_db/bench/cryptosim/receipt.go +++ b/sei-db/state_db/bench/cryptosim/receipt.go @@ -15,8 +15,9 @@ const ( erc20TransferEventSignatureHex = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef" // These mirror immutable memiavl EVM key prefixes and are duplicated here to keep the hot path minimal. - evmCodeKeyPrefixByte = 0x07 - evmStorageKeyPrefixByte = 0x03 + evmCodeKeyPrefixByte = 0x07 + evmCodeHashKeyPrefixByte = 0x08 + evmStorageKeyPrefixByte = 0x03 hashLen = 32 indexedAddressBase = hashLen - AddressLen @@ -80,14 +81,14 @@ func BuildERC20TransferReceipt( return nil, errors.New("canned random is required") } - if err := validateCodeKey("fee collection account", feeCollectionAccount); err != nil { + if err := validateAccountKey("fee collection account", feeCollectionAccount); err != nil { return nil, err } - srcAddressBytes, err := extractCodeKeyBytes("src account", srcAccount) + srcAddressBytes, err := extractAccountKeyBytes("src account", srcAccount) if err != nil { return nil, err } - if err := validateCodeKey("dst account", dstAccount); err != nil { + if err := validateAccountKey("dst account", dstAccount); err != nil { return nil, err } senderAddressBytes, err := extractStorageKeyAddressBytes("sender slot", senderSlot) @@ -158,11 +159,20 @@ func BuildERC20TransferReceipt( }, nil } -func validateCodeKey(name string, key []byte) error { - _, err := extractCodeKeyBytes(name, key) +func validateAccountKey(name string, key []byte) error { + _, err := extractAccountKeyBytes(name, key) return err } +// extractAccountKeyBytes accepts keys with either EVMKeyCode (0x07) or EVMKeyCodeHash (0x08) prefix, +// since cryptosim uses EVMKeyCodeHash for accounts while ERC20 contracts use EVMKeyCode. +func extractAccountKeyBytes(name string, key []byte) ([]byte, error) { + if len(key) != 1+AddressLen || (key[0] != evmCodeKeyPrefixByte && key[0] != evmCodeHashKeyPrefixByte) { + return nil, fmt.Errorf("%s must be an EVM code key with %d address bytes", name, AddressLen) + } + return key[1:], nil +} + func extractCodeKeyBytes(name string, key []byte) ([]byte, error) { if len(key) != 1+AddressLen || key[0] != evmCodeKeyPrefixByte { return nil, fmt.Errorf("%s must be an EVM code key with %d address bytes", name, AddressLen) diff --git a/sei-db/state_db/bench/cryptosim/receipt_test.go b/sei-db/state_db/bench/cryptosim/receipt_test.go index 03a2b0e40c..45bd3bb036 100644 --- a/sei-db/state_db/bench/cryptosim/receipt_test.go +++ b/sei-db/state_db/bench/cryptosim/receipt_test.go @@ -11,11 +11,11 @@ func makeTestKeys(t *testing.T) (feeAccount, srcAccount, dstAccount, senderSlot, t.Helper() keyRand := NewCannedRandom(4096, 1) - feeAccount = evm.BuildMemIAVLEVMKey(evm.EVMKeyCode, keyRand.Address(accountPrefix, 0, AddressLen)) + feeAccount = evm.BuildMemIAVLEVMKey(evm.EVMKeyCodeHash, keyRand.Address(accountPrefix, 0, AddressLen)) srcAddr := keyRand.Address(accountPrefix, 1, AddressLen) - srcAccount = evm.BuildMemIAVLEVMKey(evm.EVMKeyCode, srcAddr) + srcAccount = evm.BuildMemIAVLEVMKey(evm.EVMKeyCodeHash, srcAddr) dstAddr := keyRand.Address(accountPrefix, 2, AddressLen) - dstAccount = evm.BuildMemIAVLEVMKey(evm.EVMKeyCode, dstAddr) + dstAccount = evm.BuildMemIAVLEVMKey(evm.EVMKeyCodeHash, dstAddr) senderSlotBytes := make([]byte, StorageKeyLen) copy(senderSlotBytes[:AddressLen], srcAddr) @@ -86,11 +86,11 @@ func BenchmarkBuildERC20TransferReceipt(b *testing.B) { keyRand := NewCannedRandom(4096, 1) receiptRand := NewCannedRandom(1<<20, 2) - feeAccount := evm.BuildMemIAVLEVMKey(evm.EVMKeyCode, keyRand.Address(accountPrefix, 0, AddressLen)) + feeAccount := evm.BuildMemIAVLEVMKey(evm.EVMKeyCodeHash, keyRand.Address(accountPrefix, 0, AddressLen)) srcAddr := keyRand.Address(accountPrefix, 1, AddressLen) - srcAccount := evm.BuildMemIAVLEVMKey(evm.EVMKeyCode, srcAddr) + srcAccount := evm.BuildMemIAVLEVMKey(evm.EVMKeyCodeHash, srcAddr) dstAddr := keyRand.Address(accountPrefix, 2, AddressLen) - dstAccount := evm.BuildMemIAVLEVMKey(evm.EVMKeyCode, dstAddr) + dstAccount := evm.BuildMemIAVLEVMKey(evm.EVMKeyCodeHash, dstAddr) senderSlotBytes := make([]byte, StorageKeyLen) copy(senderSlotBytes[:AddressLen], srcAddr) From 00f5e88c52d6c96f7dc05eceed6ffaee6e91f957 Mon Sep 17 00:00:00 2001 From: Jeremy Wei Date: Wed, 18 Mar 2026 14:28:01 -0400 Subject: [PATCH 20/20] fix --- .../bench/cryptosim/data_generator.go | 2 +- .../state_db/bench/cryptosim/receipt_test.go | 51 +++++++++++++++++++ 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/sei-db/state_db/bench/cryptosim/data_generator.go b/sei-db/state_db/bench/cryptosim/data_generator.go index 7508d14136..09ead76273 100644 --- a/sei-db/state_db/bench/cryptosim/data_generator.go +++ b/sei-db/state_db/bench/cryptosim/data_generator.go @@ -270,7 +270,7 @@ func (d *DataGenerator) randomAccountSlot(accountID int64) ([]byte, error) { slotNumber := d.rand.Int64Range(0, int64(d.config.Erc20InteractionsPerAccount)) slotID := accountID*int64(d.config.Erc20InteractionsPerAccount) + slotNumber - storageKeyBytes := d.rand.Address(ethStoragePrefix, slotID, AddressLen) + storageKeyBytes := d.rand.Address(ethStoragePrefix, slotID, StorageKeyLen) return evm.BuildMemIAVLEVMKey(evm.EVMKeyStorage, storageKeyBytes), nil } diff --git a/sei-db/state_db/bench/cryptosim/receipt_test.go b/sei-db/state_db/bench/cryptosim/receipt_test.go index 45bd3bb036..f1e61109fb 100644 --- a/sei-db/state_db/bench/cryptosim/receipt_test.go +++ b/sei-db/state_db/bench/cryptosim/receipt_test.go @@ -82,6 +82,57 @@ func TestBuildERC20TransferReceipt_InvalidInputs(t *testing.T) { } } +// Regression test: account keys with EVMKeyCode prefix must still be accepted. +func TestBuildERC20TransferReceipt_EVMKeyCodeAccounts(t *testing.T) { + crand := NewCannedRandom(1<<20, 42) + keyRand := NewCannedRandom(4096, 1) + + feeAccount := evm.BuildMemIAVLEVMKey(evm.EVMKeyCode, keyRand.Address(accountPrefix, 0, AddressLen)) + srcAddr := keyRand.Address(accountPrefix, 1, AddressLen) + srcAccount := evm.BuildMemIAVLEVMKey(evm.EVMKeyCode, srcAddr) + dstAccount := evm.BuildMemIAVLEVMKey(evm.EVMKeyCode, keyRand.Address(accountPrefix, 2, AddressLen)) + + senderSlotBytes := make([]byte, StorageKeyLen) + copy(senderSlotBytes[:AddressLen], srcAddr) + copy(senderSlotBytes[AddressLen:], keyRand.SeededBytes(SlotLen, 11)) + senderSlot := evm.BuildMemIAVLEVMKey(evm.EVMKeyStorage, senderSlotBytes) + + receiverSlotBytes := make([]byte, StorageKeyLen) + copy(receiverSlotBytes[:AddressLen], keyRand.Address(accountPrefix, 2, AddressLen)) + copy(receiverSlotBytes[AddressLen:], keyRand.SeededBytes(SlotLen, 12)) + receiverSlot := evm.BuildMemIAVLEVMKey(evm.EVMKeyStorage, receiverSlotBytes) + + erc20Contract := evm.BuildMemIAVLEVMKey(evm.EVMKeyCode, keyRand.Address(contractPrefix, 0, AddressLen)) + + _, err := BuildERC20TransferReceipt(crand, feeAccount, srcAccount, dstAccount, senderSlot, receiverSlot, erc20Contract, 1_000_000, 0) + if err != nil { + t.Fatalf("EVMKeyCode accounts should be accepted: %v", err) + } +} + +// Regression test: uses the exact key formats produced by data_generator.go +// (EVMKeyCodeHash for accounts, EVMKeyStorage with full StorageKeyLen payload). +func TestBuildERC20TransferReceipt_DataGeneratorKeyFormats(t *testing.T) { + crand := NewCannedRandom(1<<20, 42) + keyRand := NewCannedRandom(4096, 1) + + feeAccount := evm.BuildMemIAVLEVMKey(evm.EVMKeyCodeHash, keyRand.Address(accountPrefix, 0, AddressLen)) + srcAccount := evm.BuildMemIAVLEVMKey(evm.EVMKeyCodeHash, keyRand.Address(accountPrefix, 1, AddressLen)) + dstAccount := evm.BuildMemIAVLEVMKey(evm.EVMKeyCodeHash, keyRand.Address(accountPrefix, 2, AddressLen)) + + senderSlot := evm.BuildMemIAVLEVMKey(evm.EVMKeyStorage, keyRand.Address(ethStoragePrefix, 10, StorageKeyLen)) + receiverSlot := evm.BuildMemIAVLEVMKey(evm.EVMKeyStorage, keyRand.Address(ethStoragePrefix, 20, StorageKeyLen)) + erc20Contract := evm.BuildMemIAVLEVMKey(evm.EVMKeyCode, keyRand.Address(contractPrefix, 0, AddressLen)) + + receipt, err := BuildERC20TransferReceipt(crand, feeAccount, srcAccount, dstAccount, senderSlot, receiverSlot, erc20Contract, 1_000_000, 0) + if err != nil { + t.Fatalf("data_generator key formats should be accepted: %v", err) + } + if receipt.Status != uint32(ethtypes.ReceiptStatusSuccessful) { + t.Errorf("expected successful status, got %d", receipt.Status) + } +} + func BenchmarkBuildERC20TransferReceipt(b *testing.B) { keyRand := NewCannedRandom(4096, 1) receiptRand := NewCannedRandom(1<<20, 2)