diff --git a/README.md b/README.md index ff05f1f72..11a4ac7f9 100644 --- a/README.md +++ b/README.md @@ -971,6 +971,7 @@ additional_services: - mempool_bridge - prometheus - rakoon + - slashoor - spamoor - tempo - tracoor @@ -1429,6 +1430,44 @@ 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:latest + # 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 + # 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: [] + # 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 ba6b8309d..ead736ce2 100644 --- a/main.star +++ b/main.star @@ -63,6 +63,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" @@ -999,6 +1000,24 @@ 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, + network_params, + args_with_right_defaults.additional_services, + ) + 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 4adbe46cd..c7a18f1cb 100644 --- a/src/package_io/constants.star +++ b/src/package_io/constants.star @@ -101,6 +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:latest" 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 204166b37..4f7c40460 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", @@ -128,6 +129,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() result["buildoor_params"] = get_default_buildoor_params() @@ -204,6 +206,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] @@ -853,6 +859,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"], @@ -1876,6 +1901,28 @@ 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, + "proposer_enabled": True, + "submitter_enabled": True, + "submitter_dry_run": False, + "dora_enabled": True, + "dora_url": "", + "dora_scan_on_startup": True, + "backfill_slots": 64, + } + + 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/package_io/sanity_check.star b/src/package_io/sanity_check.star index caf48088c..e61af3855 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", @@ -460,6 +479,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 new file mode 100644 index 000000000..f0e1426e3 --- /dev/null +++ b/src/slashoor/slashoor_launcher.star @@ -0,0 +1,125 @@ +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, + network_params, + additional_services, +): + 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) + + 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( + 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, + 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/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 = ( diff --git a/static_files/slashoor-config/config.yaml.tmpl b/static_files/slashoor-config/config.yaml.tmpl new file mode 100644 index 000000000..ba9dafd05 --- /dev/null +++ b/static_files/slashoor-config/config.yaml.tmpl @@ -0,0 +1,28 @@ +beacon: + endpoints: +{{- range .BeaconEndpoints }} + - "{{ . }}" +{{- end }} + timeout: {{ .BeaconTimeout }} + +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