From 8fd4113435f18abacca862e155cbf6bbd1e48dad Mon Sep 17 00:00:00 2001 From: Barnabas Busa Date: Fri, 6 Feb 2026 16:26:19 +0100 Subject: [PATCH 1/4] feat: add slashoor --- main.star | 17 +++ src/package_io/constants.star | 1 + src/package_io/input_parser.star | 23 ++++ src/slashoor/slashoor_launcher.star | 100 ++++++++++++++++++ .../slashoor-config/config.yaml.tmpl | 16 +++ src/static_files/static_files.star | 6 ++ 6 files changed, 163 insertions(+) create mode 100644 src/slashoor/slashoor_launcher.star create mode 100644 src/static_files/slashoor-config/config.yaml.tmpl diff --git a/main.star b/main.star index 228f3f45b..61b8b2340 100644 --- a/main.star +++ b/main.star @@ -62,6 +62,7 @@ get_prefunded_accounts = import_module( "./src/prefunded_accounts/get_prefunded_accounts.star" ) spamoor = import_module("./src/spamoor/spamoor.star") +slashoor = import_module("./src/slashoor/slashoor_launcher.star") ews = import_module("./src/ews/ews_launcher.star") GRAFANA_USER = "admin" @@ -968,6 +969,22 @@ def run(plan, args={}): index, osaka_time, ) + plan.print("Successfully launched spamoor") + elif additional_service == "slashoor": + plan.print("Launching slashoor") + slashoor_config_template = read_file( + static_files.SLASHOOR_CONFIG_TEMPLATE_FILEPATH + ) + slashoor.launch_slashoor( + plan, + slashoor_config_template, + all_participants, + args_with_right_defaults.participants, + args_with_right_defaults.slashoor_params, + global_node_selectors, + global_tolerations, + ) + plan.print("Successfully launched slashoor") elif additional_service == "ews": plan.print("Launching execution-witness-sentry") ews_config_template = read_file(static_files.EWS_CONFIG_TEMPLATE_FILEPATH) diff --git a/src/package_io/constants.star b/src/package_io/constants.star index 791ff7f59..ac561efef 100644 --- a/src/package_io/constants.star +++ b/src/package_io/constants.star @@ -100,6 +100,7 @@ HELIX_MEV_TYPE = "helix" DEFAULT_DORA_IMAGE = "ethpandaops/dora:latest" DEFAULT_CHECKPOINTZ_IMAGE = "ethpandaops/checkpointz:latest" DEFAULT_SPAMOOR_IMAGE = "ethpandaops/spamoor:latest" +DEFAULT_SLASHOOR_IMAGE = "ethpandaops/slashoor:master" DEFAULT_ASSERTOOR_IMAGE = "ethpandaops/assertoor:latest" DEFAULT_SNOOPER_IMAGE = "ethpandaops/rpc-snooper:latest" DEFAULT_BOOTNODOOR_IMAGE = "ethpandaops/bootnodoor:latest" diff --git a/src/package_io/input_parser.star b/src/package_io/input_parser.star index 70fb08475..f9c2881c9 100644 --- a/src/package_io/input_parser.star +++ b/src/package_io/input_parser.star @@ -87,6 +87,7 @@ ATTR_TO_BE_SKIPPED_AT_ROOT = ( "xatu_sentry_params", "port_publisher", "spamoor_params", + "slashoor_params", "bootnodoor_params", "mempool_bridge_params", "ews_params", @@ -127,6 +128,7 @@ def input_parser(plan, input_args): result["global_node_selectors"] = {} result["port_publisher"] = get_port_publisher_params("default") result["spamoor_params"] = get_default_spamoor_params() + result["slashoor_params"] = get_default_slashoor_params() result["mempool_bridge_params"] = get_default_mempool_bridge_params() result["ews_params"] = get_default_ews_params() @@ -202,6 +204,10 @@ def input_parser(plan, input_args): for sub_attr in input_args["spamoor_params"]: sub_value = input_args["spamoor_params"][sub_attr] result["spamoor_params"][sub_attr] = sub_value + elif attr == "slashoor_params": + for sub_attr in input_args["slashoor_params"]: + sub_value = input_args["slashoor_params"][sub_attr] + result["slashoor_params"][sub_attr] = sub_value elif attr == "mempool_bridge_params": for sub_attr in input_args["mempool_bridge_params"]: sub_value = input_args["mempool_bridge_params"][sub_attr] @@ -1863,6 +1869,23 @@ def get_default_spamoor_params(): } +def get_default_slashoor_params(): + return { + "image": constants.DEFAULT_SLASHOOR_IMAGE, + "min_cpu": 100, + "max_cpu": 1000, + "min_mem": 128, + "max_mem": 512, + "extra_args": [], + "log_level": "info", + "beacon_timeout": "30s", + "max_epochs_to_keep": 54000, + "detector_enabled": True, + "submitter_enabled": True, + "submitter_dry_run": False, + } + + def get_default_custom_flood_params(): # this is a simple script that increases the balance of the coinbase address at a cadence return {"interval_between_transactions": 1} diff --git a/src/slashoor/slashoor_launcher.star b/src/slashoor/slashoor_launcher.star new file mode 100644 index 000000000..559b64226 --- /dev/null +++ b/src/slashoor/slashoor_launcher.star @@ -0,0 +1,100 @@ +shared_utils = import_module("../shared_utils/shared_utils.star") +constants = import_module("../package_io/constants.star") + +SERVICE_NAME = "slashoor" + +SLASHOOR_CONFIG_FILENAME = "config.yaml" +SLASHOOR_CONFIG_MOUNT_DIRPATH_ON_SERVICE = "/config" + + +def launch_slashoor( + plan, + config_template, + participant_contexts, + participant_configs, + slashoor_params, + global_node_selectors, + global_tolerations, +): + tolerations = shared_utils.get_tolerations(global_tolerations=global_tolerations) + + beacon_endpoints = [] + for index, participant in enumerate(participant_contexts): + beacon_http_url = participant.cl_context.beacon_http_url + beacon_endpoints.append(beacon_http_url) + + template_data = new_config_template_data( + beacon_endpoints, + slashoor_params, + ) + + template_and_data = shared_utils.new_template_and_data( + config_template, template_data + ) + template_and_data_by_rel_dest_filepath = { + SLASHOOR_CONFIG_FILENAME: template_and_data, + } + + config_files_artifact_name = plan.render_templates( + template_and_data_by_rel_dest_filepath, "slashoor-config" + ) + + config = get_config( + plan, + config_files_artifact_name, + slashoor_params, + global_node_selectors, + tolerations, + ) + plan.add_service(SERVICE_NAME, config) + + +def get_config( + plan, + config_files_artifact_name, + slashoor_params, + node_selectors, + tolerations, +): + config_file_path = shared_utils.path_join( + SLASHOOR_CONFIG_MOUNT_DIRPATH_ON_SERVICE, + SLASHOOR_CONFIG_FILENAME, + ) + + cmd = [ + "--config={}".format(config_file_path), + ] + + if slashoor_params.log_level: + cmd.append("--log-level={}".format(slashoor_params.log_level)) + + for extra_arg in slashoor_params.extra_args: + cmd.append(extra_arg) + + return ServiceConfig( + image=slashoor_params.image, + cmd=cmd, + min_cpu=slashoor_params.min_cpu, + max_cpu=slashoor_params.max_cpu, + min_memory=slashoor_params.min_mem, + max_memory=slashoor_params.max_mem, + node_selectors=node_selectors, + tolerations=tolerations, + files={ + SLASHOOR_CONFIG_MOUNT_DIRPATH_ON_SERVICE: config_files_artifact_name, + }, + ) + + +def new_config_template_data( + beacon_endpoints, + slashoor_params, +): + return { + "BeaconEndpoints": beacon_endpoints, + "BeaconTimeout": slashoor_params.beacon_timeout, + "MaxEpochsToKeep": slashoor_params.max_epochs_to_keep, + "DetectorEnabled": slashoor_params.detector_enabled, + "SubmitterEnabled": slashoor_params.submitter_enabled, + "SubmitterDryRun": slashoor_params.submitter_dry_run, + } diff --git a/src/static_files/slashoor-config/config.yaml.tmpl b/src/static_files/slashoor-config/config.yaml.tmpl new file mode 100644 index 000000000..b7c559639 --- /dev/null +++ b/src/static_files/slashoor-config/config.yaml.tmpl @@ -0,0 +1,16 @@ +beacon: + endpoints: +{{- range .BeaconEndpoints }} + - "{{ . }}" +{{- end }} + timeout: {{ .BeaconTimeout }} + +indexer: + max_epochs_to_keep: {{ .MaxEpochsToKeep }} + +detector: + enabled: {{ .DetectorEnabled }} + +submitter: + enabled: {{ .SubmitterEnabled }} + dry_run: {{ .SubmitterDryRun }} diff --git a/src/static_files/static_files.star b/src/static_files/static_files.star index 73fffb426..f64a920f1 100644 --- a/src/static_files/static_files.star +++ b/src/static_files/static_files.star @@ -67,6 +67,12 @@ SPAMOOR_HOSTS_TEMPLATE_FILEPATH = ( STATIC_FILES_DIRPATH + SPAMOOR_CONFIG_DIRPATH + "/rpc-hosts.txt.tmpl" ) +# slashoor config +SLASHOOR_CONFIG_DIRPATH = "/slashoor-config" +SLASHOOR_CONFIG_TEMPLATE_FILEPATH = ( + STATIC_FILES_DIRPATH + SLASHOOR_CONFIG_DIRPATH + "/config.yaml.tmpl" +) + # xatu-sentry config XATU_SENTRY_CONFIG_DIRPATH = "/xatu-sentry-config" XATU_SENTRY_CONFIG_TEMPLATE_FILEPATH = ( From 5fe7d6009c7cfc2e09cb82a4625a55603fd07cbe Mon Sep 17 00:00:00 2001 From: Barnabas Busa Date: Tue, 10 Feb 2026 17:25:35 +0100 Subject: [PATCH 2/4] add dora as a slashing db --- README.md | 29 +++++++++++++++++++ main.star | 2 ++ src/package_io/input_parser.star | 24 +++++++++++++++ src/package_io/sanity_check.star | 20 +++++++++++++ src/slashoor/slashoor_launcher.star | 25 ++++++++++++++++ .../slashoor-config/config.yaml.tmpl | 12 ++++++++ test.yaml | 3 -- 7 files changed, 112 insertions(+), 3 deletions(-) rename {src/static_files => static_files}/slashoor-config/config.yaml.tmpl (57%) delete mode 100644 test.yaml diff --git a/README.md b/README.md index 0924411d5..a5869c005 100644 --- a/README.md +++ b/README.md @@ -971,6 +971,7 @@ additional_services: - mempool_bridge - prometheus - rakoon + - slashoor - spamoor - tempo - tracoor @@ -1416,6 +1417,34 @@ spamoor_params: # A list of optional params that will be passed to the spamoor command for modifying its behaviour extra_args: [] +# Configuration place for slashoor - https://github.com/ethpandaops/slashoor +# Slashoor is a lazy slasher that monitors validators for slashing violations +# and automatically submits attester slashings to the beacon chain +slashoor_params: + # The image to use for slashoor + image: ethpandaops/slashoor:master + # Resource management for slashoor + # CPU is milicores + # RAM is in MB + min_cpu: 100 + max_cpu: 1000 + min_mem: 128 + max_mem: 512 + # Log level for slashoor (error, warn, info, debug, trace) + log_level: info + # Timeout for beacon API requests + beacon_timeout: 30s + # Maximum epochs to keep in memory for slashing detection + max_epochs_to_keep: 54000 + # Enable the detector service + detector_enabled: true + # Enable the submitter service + submitter_enabled: true + # Run in dry-run mode (detect but don't submit slashings) + submitter_dry_run: false + # A list of optional extra args + extra_args: [] + # Ethereum genesis generator params ethereum_genesis_generator_params: # The image to use for ethereum genesis generator diff --git a/main.star b/main.star index 61b8b2340..961da0648 100644 --- a/main.star +++ b/main.star @@ -983,6 +983,8 @@ def run(plan, args={}): args_with_right_defaults.slashoor_params, global_node_selectors, global_tolerations, + network_params, + args_with_right_defaults.additional_services, ) plan.print("Successfully launched slashoor") elif additional_service == "ews": diff --git a/src/package_io/input_parser.star b/src/package_io/input_parser.star index f9c2881c9..bc03525b8 100644 --- a/src/package_io/input_parser.star +++ b/src/package_io/input_parser.star @@ -852,6 +852,25 @@ def input_parser(plan, input_args): spammers=result["spamoor_params"]["spammers"], extra_args=result["spamoor_params"]["extra_args"], ), + slashoor_params=struct( + image=result["slashoor_params"]["image"], + min_cpu=result["slashoor_params"]["min_cpu"], + max_cpu=result["slashoor_params"]["max_cpu"], + min_mem=result["slashoor_params"]["min_mem"], + max_mem=result["slashoor_params"]["max_mem"], + extra_args=result["slashoor_params"]["extra_args"], + log_level=result["slashoor_params"]["log_level"], + beacon_timeout=result["slashoor_params"]["beacon_timeout"], + max_epochs_to_keep=result["slashoor_params"]["max_epochs_to_keep"], + detector_enabled=result["slashoor_params"]["detector_enabled"], + proposer_enabled=result["slashoor_params"]["proposer_enabled"], + submitter_enabled=result["slashoor_params"]["submitter_enabled"], + submitter_dry_run=result["slashoor_params"]["submitter_dry_run"], + dora_enabled=result["slashoor_params"]["dora_enabled"], + dora_url=result["slashoor_params"]["dora_url"], + dora_scan_on_startup=result["slashoor_params"]["dora_scan_on_startup"], + backfill_slots=result["slashoor_params"]["backfill_slots"], + ), mempool_bridge_params=struct( image=result["mempool_bridge_params"]["image"], source_enodes=result["mempool_bridge_params"]["source_enodes"], @@ -1881,8 +1900,13 @@ def get_default_slashoor_params(): "beacon_timeout": "30s", "max_epochs_to_keep": 54000, "detector_enabled": True, + "proposer_enabled": True, "submitter_enabled": True, "submitter_dry_run": False, + "dora_enabled": True, + "dora_url": "", + "dora_scan_on_startup": True, + "backfill_slots": 64, } diff --git a/src/package_io/sanity_check.star b/src/package_io/sanity_check.star index 528a4388e..c904b6427 100644 --- a/src/package_io/sanity_check.star +++ b/src/package_io/sanity_check.star @@ -400,6 +400,25 @@ SUBCATEGORY_PARAMS = { "extra_args", "spammers", ], + "slashoor_params": [ + "image", + "min_cpu", + "max_cpu", + "min_mem", + "max_mem", + "extra_args", + "log_level", + "beacon_timeout", + "max_epochs_to_keep", + "detector_enabled", + "proposer_enabled", + "submitter_enabled", + "submitter_dry_run", + "dora_enabled", + "dora_url", + "dora_scan_on_startup", + "backfill_slots", + ], "mempool_bridge_params": [ "image", "source_enodes", @@ -454,6 +473,7 @@ ADDITIONAL_SERVICES_PARAMS = [ "tracoor", "mempool_bridge", "rakoon", + "slashoor", "spamoor", "ews", ] diff --git a/src/slashoor/slashoor_launcher.star b/src/slashoor/slashoor_launcher.star index 559b64226..f0e1426e3 100644 --- a/src/slashoor/slashoor_launcher.star +++ b/src/slashoor/slashoor_launcher.star @@ -15,6 +15,8 @@ def launch_slashoor( slashoor_params, global_node_selectors, global_tolerations, + network_params, + additional_services, ): tolerations = shared_utils.get_tolerations(global_tolerations=global_tolerations) @@ -23,9 +25,12 @@ def launch_slashoor( beacon_http_url = participant.cl_context.beacon_http_url beacon_endpoints.append(beacon_http_url) + dora_url = get_dora_url(slashoor_params, network_params, additional_services) + template_data = new_config_template_data( beacon_endpoints, slashoor_params, + dora_url, ) template_and_data = shared_utils.new_template_and_data( @@ -89,12 +94,32 @@ def get_config( def new_config_template_data( beacon_endpoints, slashoor_params, + dora_url, ): return { "BeaconEndpoints": beacon_endpoints, "BeaconTimeout": slashoor_params.beacon_timeout, "MaxEpochsToKeep": slashoor_params.max_epochs_to_keep, + "BackfillSlots": slashoor_params.backfill_slots, "DetectorEnabled": slashoor_params.detector_enabled, + "ProposerEnabled": slashoor_params.proposer_enabled, "SubmitterEnabled": slashoor_params.submitter_enabled, "SubmitterDryRun": slashoor_params.submitter_dry_run, + "DoraEnabled": slashoor_params.dora_enabled, + "DoraURL": dora_url, + "DoraScanOnStartup": slashoor_params.dora_scan_on_startup, } + + +def get_dora_url(slashoor_params, network_params, additional_services): + if slashoor_params.dora_url: + return slashoor_params.dora_url + + if "dora" in additional_services: + return "http://dora:8080" + + network = network_params.network + if network in constants.PUBLIC_NETWORKS or "devnet" in network: + return "https://dora.{}.ethpandaops.io".format(network) + + return "" diff --git a/src/static_files/slashoor-config/config.yaml.tmpl b/static_files/slashoor-config/config.yaml.tmpl similarity index 57% rename from src/static_files/slashoor-config/config.yaml.tmpl rename to static_files/slashoor-config/config.yaml.tmpl index b7c559639..ba9dafd05 100644 --- a/src/static_files/slashoor-config/config.yaml.tmpl +++ b/static_files/slashoor-config/config.yaml.tmpl @@ -8,9 +8,21 @@ beacon: indexer: max_epochs_to_keep: {{ .MaxEpochsToKeep }} +backfill_slots: {{ .BackfillSlots }} + detector: enabled: {{ .DetectorEnabled }} +proposer: + enabled: {{ .ProposerEnabled }} + submitter: enabled: {{ .SubmitterEnabled }} dry_run: {{ .SubmitterDryRun }} + +dora: + enabled: {{ .DoraEnabled }} +{{- if .DoraURL }} + url: "{{ .DoraURL }}" +{{- end }} + scan_on_startup: {{ .DoraScanOnStartup }} diff --git a/test.yaml b/test.yaml deleted file mode 100644 index 0938e808c..000000000 --- a/test.yaml +++ /dev/null @@ -1,3 +0,0 @@ -port_publisher: - el: - enabled: true From b30896e58de2e1a8be54c0d889a30c63e731052e Mon Sep 17 00:00:00 2001 From: Barnabas Busa Date: Tue, 10 Feb 2026 17:28:08 +0100 Subject: [PATCH 3/4] bump docs --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index ac459d02f..f10159353 100644 --- a/README.md +++ b/README.md @@ -1449,12 +1449,22 @@ slashoor_params: beacon_timeout: 30s # Maximum epochs to keep in memory for slashing detection max_epochs_to_keep: 54000 + # Number of slots to backfill on startup + backfill_slots: 64 # Enable the detector service detector_enabled: true + # Enable the proposer slashing service + proposer_enabled: true # Enable the submitter service submitter_enabled: true # Run in dry-run mode (detect but don't submit slashings) submitter_dry_run: false + # Enable dora as a slashing database source + dora_enabled: true + # Custom dora URL (auto-detected if dora is in additional_services or on public networks) + dora_url: "" + # Scan dora on startup for existing slashings + dora_scan_on_startup: true # A list of optional extra args extra_args: [] From c15fe6c67624a54f81f11322617e25c1f5347af5 Mon Sep 17 00:00:00 2001 From: Barnabas Busa Date: Tue, 10 Feb 2026 17:54:22 +0100 Subject: [PATCH 4/4] bump img --- README.md | 2 +- src/package_io/constants.star | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f10159353..11a4ac7f9 100644 --- a/README.md +++ b/README.md @@ -1435,7 +1435,7 @@ spamoor_params: # and automatically submits attester slashings to the beacon chain slashoor_params: # The image to use for slashoor - image: ethpandaops/slashoor:master + image: ethpandaops/slashoor:latest # Resource management for slashoor # CPU is milicores # RAM is in MB diff --git a/src/package_io/constants.star b/src/package_io/constants.star index a3aa64120..c7a18f1cb 100644 --- a/src/package_io/constants.star +++ b/src/package_io/constants.star @@ -101,7 +101,7 @@ BUILDOOR_MEV_TYPE = "buildoor" DEFAULT_DORA_IMAGE = "ethpandaops/dora:latest" DEFAULT_CHECKPOINTZ_IMAGE = "ethpandaops/checkpointz:latest" DEFAULT_SPAMOOR_IMAGE = "ethpandaops/spamoor:latest" -DEFAULT_SLASHOOR_IMAGE = "ethpandaops/slashoor:master" +DEFAULT_SLASHOOR_IMAGE = "ethpandaops/slashoor:latest" DEFAULT_ASSERTOOR_IMAGE = "ethpandaops/assertoor:latest" DEFAULT_SNOOPER_IMAGE = "ethpandaops/rpc-snooper:latest" DEFAULT_BOOTNODOOR_IMAGE = "ethpandaops/bootnodoor:latest"