From 1c774d0403821ae8563cd7bec03f2b3993fe0294 Mon Sep 17 00:00:00 2001 From: Barnabas Busa Date: Mon, 12 Jan 2026 12:42:17 +0100 Subject: [PATCH 01/12] feat: add custom binary execution --- README.md | 21 ++++++++++++++ main.star | 32 +++++++++++++++++++++- src/cl/cl_launcher.star | 9 ++++++ src/cl/grandine/grandine_launcher.star | 3 ++ src/cl/lighthouse/lighthouse_launcher.star | 17 +++++++++++- src/cl/lodestar/lodestar_launcher.star | 3 ++ src/cl/nimbus/nimbus_launcher.star | 3 ++ src/cl/prysm/prysm_launcher.star | 3 ++ src/cl/teku/teku_launcher.star | 3 ++ src/el/besu/besu_launcher.star | 3 ++ src/el/dummy/dummy_launcher.star | 3 ++ src/el/el_launcher.star | 8 ++++++ src/el/erigon/erigon_launcher.star | 3 ++ src/el/ethereumjs/ethereumjs_launcher.star | 3 ++ src/el/ethrex/ethrex_launcher.star | 3 ++ src/el/geth/geth_launcher.star | 2 ++ src/el/nethermind/nethermind_launcher.star | 3 ++ src/el/nimbus-eth1/nimbus_launcher.star | 3 ++ src/el/reth/reth_launcher.star | 17 +++++++++++- src/package_io/input_parser.star | 6 ++++ src/package_io/sanity_check.star | 7 +++++ src/participant_network.star | 3 ++ 22 files changed, 155 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 1c00fc0a3..5235a7143 100644 --- a/README.md +++ b/README.md @@ -193,6 +193,13 @@ participants: # - dummy: ethpandaops/dummy-el:master el_image: "" + # Path to a local EL binary to inject into the container (Docker only) + # When set, the binary will be uploaded and mounted into the container, + # replacing the default binary from the Docker image + # Useful for rapid debugging with locally compiled binaries + # Example: el_binary_path: "/path/to/reth/target/release/reth" + el_binary_path: "" + # The log level string that this participant's EL client should log at # If this is emptystring then the global `logLevel` parameter's value will be translated into a string appropriate for the client (e.g. if # global `logLevel` = `info` then Geth would receive `3`, Besu would receive `INFO`, etc.) @@ -272,6 +279,13 @@ participants: # - grandine: sifrai/grandine:stable cl_image: "" + # Path to a local CL binary to inject into the container (Docker only) + # When set, the binary will be uploaded and mounted into the container, + # replacing the default binary from the Docker image + # Useful for rapid debugging with locally compiled binaries + # Example: cl_binary_path: "/path/to/lighthouse/target/release/lighthouse" + cl_binary_path: "" + # The log level string that this participant's CL client should log at # If this is emptystring then the global `logLevel` parameter's value will be translated into a string appropriate for the client (e.g. if # global `logLevel` = `info` then Teku would receive `INFO`, Prysm would receive `info`, etc.) @@ -354,6 +368,13 @@ participants: # - vero: ghcr.io/serenita-org/vero:latest vc_image: "" + # Path to a local VC binary to inject into the container (Docker only) + # When set, the binary will be uploaded and mounted into the container, + # replacing the default binary from the Docker image + # Useful for rapid debugging with locally compiled binaries + # Example: vc_binary_path: "/path/to/lighthouse/target/release/lighthouse" + vc_binary_path: "" + # The log level string that this participant's validator client should log at # If this is emptystring then the global `logLevel` parameter's value will be translated into a string appropriate for the client (e.g. if # global `logLevel` = `info` then Teku would receive `INFO`, Prysm would receive `info`, etc.) diff --git a/main.star b/main.star index ff6fb7f79..5cdf52ee0 100644 --- a/main.star +++ b/main.star @@ -85,6 +85,9 @@ def run(plan, args={}): num_participants = len(args_with_right_defaults.participants) network_params = args_with_right_defaults.network_params + # Detect the backend type early - needed for binary injection validation + detected_backend = plan.get_cluster_type() + # Process extra_files - create artifacts from provided content extra_files_artifacts = {} extra_files = getattr(args_with_right_defaults, "extra_files", {}) @@ -96,6 +99,33 @@ def run(plan, args={}): artifact = plan.render_templates(template_data, name + "_artifact") extra_files_artifacts[name] = artifact + # Process binary injection - upload local binaries for participants + # Users can specify el_binary_path/cl_binary_path to inject a local binary + # The binary will be mounted into the container and executed instead of the default + # NOTE: Binary injection is only supported with Docker backend + binary_artifacts = {} + for index, participant in enumerate(args_with_right_defaults.participants): + has_binary_path = participant.el_binary_path or participant.cl_binary_path or participant.vc_binary_path + if has_binary_path and detected_backend != "docker": + fail("Binary injection (*_binary_path) is only supported with Docker backend, detected: {0}".format(detected_backend)) + participant_binaries = {} + if participant.el_binary_path: + plan.print("Uploading EL binary for participant {0}: {1}".format(index + 1, participant.el_binary_path)) + el_binary_artifact = plan.upload_files( + src=participant.el_binary_path, + name="el-binary-{0}".format(index + 1), + ) + participant_binaries["el"] = el_binary_artifact + if participant.cl_binary_path: + plan.print("Uploading CL binary for participant {0}: {1}".format(index + 1, participant.cl_binary_path)) + cl_binary_artifact = plan.upload_files( + src=participant.cl_binary_path, + name="cl-binary-{0}".format(index + 1), + ) + participant_binaries["cl"] = cl_binary_artifact + if participant_binaries: + binary_artifacts[index] = participant_binaries + mev_params = args_with_right_defaults.mev_params parallel_keystore_generation = args_with_right_defaults.parallel_keystore_generation persistent = args_with_right_defaults.persistent @@ -106,7 +136,6 @@ def run(plan, args={}): apache_port = args_with_right_defaults.apache_port nginx_port = args_with_right_defaults.nginx_port docker_cache_params = args_with_right_defaults.docker_cache_params - detected_backend = plan.get_cluster_type() for index, participant in enumerate(args_with_right_defaults.participants): if ( @@ -255,6 +284,7 @@ def run(plan, args={}): extra_files_artifacts, tempo_otlp_grpc_url, detected_backend, + binary_artifacts, ) plan.print( diff --git a/src/cl/cl_launcher.star b/src/cl/cl_launcher.star index 84731ad03..3cbc641fc 100644 --- a/src/cl/cl_launcher.star +++ b/src/cl/cl_launcher.star @@ -36,6 +36,7 @@ def launch( extra_files_artifacts, backend, bootnodoor_enr=None, + binary_artifacts={}, ): plan.print("Launching CL network") @@ -225,6 +226,12 @@ def launch( all_snooper_el_engine_contexts.append(snooper_el_engine_context) full_name = "{0}-{1}-{2}".format(index_str, el_type, cl_type) + + # Get binary artifact for this participant if it exists + cl_binary_artifact = None + if index in binary_artifacts and "cl" in binary_artifacts[index]: + cl_binary_artifact = binary_artifacts[index]["cl"] + if index == 0: cl_context = launch_method( plan, @@ -249,6 +256,7 @@ def launch( backend, tempo_otlp_grpc_url, bootnode_enr_override, + cl_binary_artifact, ) blobber_config = get_blobber_config( @@ -300,6 +308,7 @@ def launch( backend, tempo_otlp_grpc_url, bootnode_enr_override, + cl_binary_artifact, ) cl_participant_info[cl_service_name] = { diff --git a/src/cl/grandine/grandine_launcher.star b/src/cl/grandine/grandine_launcher.star index c2390b989..ab58ba71e 100644 --- a/src/cl/grandine/grandine_launcher.star +++ b/src/cl/grandine/grandine_launcher.star @@ -59,6 +59,7 @@ def launch( backend, tempo_otlp_grpc_url=None, bootnode_enr_override=None, + cl_binary_artifact=None, ): config = get_beacon_config( plan, @@ -83,6 +84,7 @@ def launch( backend, tempo_otlp_grpc_url, bootnode_enr_override, + cl_binary_artifact, ) beacon_service = plan.add_service(beacon_service_name, config) @@ -123,6 +125,7 @@ def get_beacon_config( backend, tempo_otlp_grpc_url, bootnode_enr_override=None, + cl_binary_artifact=None, ): log_level = input_parser.get_client_log_level_or_default( participant.cl_log_level, global_log_level, VERBOSITY_LEVELS diff --git a/src/cl/lighthouse/lighthouse_launcher.star b/src/cl/lighthouse/lighthouse_launcher.star index 20e9ffa7f..08d964673 100644 --- a/src/cl/lighthouse/lighthouse_launcher.star +++ b/src/cl/lighthouse/lighthouse_launcher.star @@ -61,6 +61,7 @@ def launch( backend, tempo_otlp_grpc_url=None, bootnode_enr_override=None, + cl_binary_artifact=None, ): # Launch Beacon node beacon_config = get_beacon_config( @@ -86,6 +87,7 @@ def launch( backend, tempo_otlp_grpc_url, bootnode_enr_override, + cl_binary_artifact, ) beacon_service = plan.add_service(beacon_service_name, beacon_config) @@ -126,6 +128,7 @@ def get_beacon_config( backend, tempo_otlp_grpc_url, bootnode_enr_override=None, + cl_binary_artifact=None, ): log_level = input_parser.get_client_log_level_or_default( participant.cl_log_level, global_log_level, VERBOSITY_LEVELS @@ -188,6 +191,7 @@ def get_beacon_config( used_ports = shared_utils.get_port_specs(used_port_assignments, wait=None) else: used_ports = shared_utils.get_port_specs(used_port_assignments) + cmd = [ LIGHTHOUSE_ENTRYPOINT_COMMAND, "beacon_node", @@ -309,15 +313,26 @@ def get_beacon_config( for mount_path, artifact in processed_mounts.items(): files[mount_path] = artifact + # Binary injection - mount custom binary directory if provided + # The artifact is a directory, so we mount it and reference the binary inside + if cl_binary_artifact != None: + files["/opt/bin"] = cl_binary_artifact + env_vars = {RUST_BACKTRACE_ENVVAR_NAME: RUST_FULL_BACKTRACE_KEYWORD} env_vars.update(participant.cl_extra_env_vars) + # Build the command string, using injected binary if provided + if cl_binary_artifact != None: + cmd_str = "chmod +x /opt/bin/lighthouse && exec /opt/bin/lighthouse " + " ".join(cmd[1:]) + else: + cmd_str = "exec " + " ".join(cmd) + config_args = { "image": participant.cl_image, "ports": used_ports, "public_ports": public_ports, "entrypoint": ["sh", "-c"], - "cmd": ["exec " + " ".join(cmd)], + "cmd": [cmd_str], "files": files, "env_vars": env_vars, "private_ip_address_placeholder": constants.PRIVATE_IP_ADDRESS_PLACEHOLDER, diff --git a/src/cl/lodestar/lodestar_launcher.star b/src/cl/lodestar/lodestar_launcher.star index ae4876983..ed9704e8c 100644 --- a/src/cl/lodestar/lodestar_launcher.star +++ b/src/cl/lodestar/lodestar_launcher.star @@ -50,6 +50,7 @@ def launch( backend, tempo_otlp_grpc_url=None, bootnode_enr_override=None, + cl_binary_artifact=None, ): # Launch Beacon node beacon_config = get_beacon_config( @@ -75,6 +76,7 @@ def launch( backend, tempo_otlp_grpc_url, bootnode_enr_override, + cl_binary_artifact, ) beacon_service = plan.add_service(beacon_service_name, beacon_config) @@ -115,6 +117,7 @@ def get_beacon_config( backend, tempo_otlp_grpc_url, bootnode_enr_override=None, + cl_binary_artifact=None, ): log_level = input_parser.get_client_log_level_or_default( participant.cl_log_level, global_log_level, VERBOSITY_LEVELS diff --git a/src/cl/nimbus/nimbus_launcher.star b/src/cl/nimbus/nimbus_launcher.star index 7de69229b..eeb8dd418 100644 --- a/src/cl/nimbus/nimbus_launcher.star +++ b/src/cl/nimbus/nimbus_launcher.star @@ -69,6 +69,7 @@ def launch( backend, tempo_otlp_grpc_url=None, bootnode_enr_override=None, + cl_binary_artifact=None, ): beacon_config = get_beacon_config( plan, @@ -93,6 +94,7 @@ def launch( backend, tempo_otlp_grpc_url, bootnode_enr_override, + cl_binary_artifact, ) beacon_service = plan.add_service(beacon_service_name, beacon_config) @@ -133,6 +135,7 @@ def get_beacon_config( backend, tempo_otlp_grpc_url, bootnode_enr_override=None, + cl_binary_artifact=None, ): log_level = input_parser.get_client_log_level_or_default( participant.cl_log_level, global_log_level, VERBOSITY_LEVELS diff --git a/src/cl/prysm/prysm_launcher.star b/src/cl/prysm/prysm_launcher.star index a3627d0a4..5873b9f3d 100644 --- a/src/cl/prysm/prysm_launcher.star +++ b/src/cl/prysm/prysm_launcher.star @@ -54,6 +54,7 @@ def launch( backend, tempo_otlp_grpc_url=None, bootnode_enr_override=None, + cl_binary_artifact=None, ): beacon_config = get_beacon_config( plan, @@ -78,6 +79,7 @@ def launch( backend, tempo_otlp_grpc_url, bootnode_enr_override, + cl_binary_artifact, ) beacon_service = plan.add_service(beacon_service_name, beacon_config) @@ -118,6 +120,7 @@ def get_beacon_config( backend, tempo_otlp_grpc_url, bootnode_enr_override=None, + cl_binary_artifact=None, ): log_level = input_parser.get_client_log_level_or_default( participant.cl_log_level, global_log_level, VERBOSITY_LEVELS diff --git a/src/cl/teku/teku_launcher.star b/src/cl/teku/teku_launcher.star index 874ba3cf4..192e0c850 100644 --- a/src/cl/teku/teku_launcher.star +++ b/src/cl/teku/teku_launcher.star @@ -55,6 +55,7 @@ def launch( backend, tempo_otlp_grpc_url=None, bootnode_enr_override=None, + cl_binary_artifact=None, ): config = get_beacon_config( plan, @@ -79,6 +80,7 @@ def launch( backend, tempo_otlp_grpc_url, bootnode_enr_override, + cl_binary_artifact, ) beacon_service = plan.add_service(beacon_service_name, config) @@ -119,6 +121,7 @@ def get_beacon_config( backend, tempo_otlp_grpc_url, bootnode_enr_override=None, + cl_binary_artifact=None, ): log_level = input_parser.get_client_log_level_or_default( participant.cl_log_level, global_log_level, VERBOSITY_LEVELS diff --git a/src/el/besu/besu_launcher.star b/src/el/besu/besu_launcher.star index d36e68991..9a64ea0df 100644 --- a/src/el/besu/besu_launcher.star +++ b/src/el/besu/besu_launcher.star @@ -45,6 +45,7 @@ def launch( network_params, extra_files_artifacts, bootnodoor_enode=None, + el_binary_artifact=None, ): cl_client_name = service_name.split("-")[3] @@ -64,6 +65,7 @@ def launch( network_params, extra_files_artifacts, bootnodoor_enode, + el_binary_artifact, ) service = plan.add_service(service_name, config) @@ -92,6 +94,7 @@ def get_config( network_params, extra_files_artifacts, bootnodoor_enode=None, + el_binary_artifact=None, ): log_level = input_parser.get_client_log_level_or_default( participant.el_log_level, global_log_level, VERBOSITY_LEVELS diff --git a/src/el/dummy/dummy_launcher.star b/src/el/dummy/dummy_launcher.star index 696163c70..427bf2c0c 100644 --- a/src/el/dummy/dummy_launcher.star +++ b/src/el/dummy/dummy_launcher.star @@ -35,6 +35,7 @@ def launch( network_params, extra_files_artifacts, bootnodoor_enode=None, + el_binary_artifact=None, ): cl_client_name = service_name.split("-")[3] @@ -54,6 +55,7 @@ def launch( network_params, extra_files_artifacts, bootnodoor_enode, + el_binary_artifact, ) service = plan.add_service(service_name, config) @@ -82,6 +84,7 @@ def get_config( network_params, extra_files_artifacts, bootnodoor_enode=None, + el_binary_artifact=None, ): log_level = input_parser.get_client_log_level_or_default( participant.el_log_level, global_log_level, VERBOSITY_LEVELS diff --git a/src/el/el_launcher.star b/src/el/el_launcher.star index 40ea1daac..f7fc45b22 100644 --- a/src/el/el_launcher.star +++ b/src/el/el_launcher.star @@ -33,6 +33,7 @@ def launch( mev_params, extra_files_artifacts={}, bootnodoor_enode=None, + binary_artifacts={}, ): el_launchers = { constants.EL_TYPE.geth: { @@ -175,6 +176,11 @@ def launch( el_service_name = "el-{0}-{1}-{2}".format(index_str, el_type, cl_type) + # Get binary artifact for this participant if it exists + el_binary_artifact = None + if index in binary_artifacts and "el" in binary_artifacts[index]: + el_binary_artifact = binary_artifacts[index]["el"] + if index == 0: el_context = launch_method( plan, @@ -191,6 +197,7 @@ def launch( network_params, extra_files_artifacts, bootnodoor_enode, + el_binary_artifact, ) # Add participant el additional prometheus metrics @@ -216,6 +223,7 @@ def launch( network_params, extra_files_artifacts, bootnodoor_enode, + el_binary_artifact, ) el_participant_info[el_service_name] = { diff --git a/src/el/erigon/erigon_launcher.star b/src/el/erigon/erigon_launcher.star index 8a530fecc..97d213580 100644 --- a/src/el/erigon/erigon_launcher.star +++ b/src/el/erigon/erigon_launcher.star @@ -43,6 +43,7 @@ def launch( network_params, extra_files_artifacts, bootnodoor_enode=None, + el_binary_artifact=None, ): cl_client_name = service_name.split("-")[3] @@ -62,6 +63,7 @@ def launch( network_params, extra_files_artifacts, bootnodoor_enode, + el_binary_artifact, ) service = plan.add_service(service_name, config) @@ -90,6 +92,7 @@ def get_config( network_params, extra_files_artifacts, bootnodoor_enode=None, + el_binary_artifact=None, ): log_level = input_parser.get_client_log_level_or_default( participant.el_log_level, global_log_level, VERBOSITY_LEVELS diff --git a/src/el/ethereumjs/ethereumjs_launcher.star b/src/el/ethereumjs/ethereumjs_launcher.star index ec2fc62c1..84d29a910 100644 --- a/src/el/ethereumjs/ethereumjs_launcher.star +++ b/src/el/ethereumjs/ethereumjs_launcher.star @@ -45,6 +45,7 @@ def launch( network_params, extra_files_artifacts, bootnodoor_enode=None, + el_binary_artifact=None, ): cl_client_name = service_name.split("-")[3] @@ -64,6 +65,7 @@ def launch( network_params, extra_files_artifacts, bootnodoor_enode, + el_binary_artifact, ) service = plan.add_service(service_name, config) @@ -92,6 +94,7 @@ def get_config( network_params, extra_files_artifacts, bootnodoor_enode=None, + el_binary_artifact=None, ): log_level = input_parser.get_client_log_level_or_default( participant.el_log_level, global_log_level, VERBOSITY_LEVELS diff --git a/src/el/ethrex/ethrex_launcher.star b/src/el/ethrex/ethrex_launcher.star index 65c38f67d..4d35a2d55 100644 --- a/src/el/ethrex/ethrex_launcher.star +++ b/src/el/ethrex/ethrex_launcher.star @@ -60,6 +60,7 @@ def launch( network_params, extra_files_artifacts, bootnodoor_enode=None, + el_binary_artifact=None, ): cl_client_name = service_name.split("-")[3] @@ -79,6 +80,7 @@ def launch( network_params, extra_files_artifacts, bootnodoor_enode, + el_binary_artifact, ) service = plan.add_service(service_name, config) @@ -107,6 +109,7 @@ def get_config( network_params, extra_files_artifacts, bootnodoor_enode=None, + el_binary_artifact=None, ): public_ports = {} public_ports_for_component = None diff --git a/src/el/geth/geth_launcher.star b/src/el/geth/geth_launcher.star index 37e442eb9..11b33672b 100644 --- a/src/el/geth/geth_launcher.star +++ b/src/el/geth/geth_launcher.star @@ -52,6 +52,7 @@ def launch( network_params, extra_files_artifacts, bootnodoor_enode=None, + el_binary_artifact=None, ): cl_client_name = service_name.split("-")[3] @@ -99,6 +100,7 @@ def get_config( network_params, extra_files_artifacts, bootnodoor_enode=None, + el_binary_artifact=None, ): log_level = input_parser.get_client_log_level_or_default( participant.el_log_level, global_log_level, VERBOSITY_LEVELS diff --git a/src/el/nethermind/nethermind_launcher.star b/src/el/nethermind/nethermind_launcher.star index 6a13b00dd..1a971971b 100644 --- a/src/el/nethermind/nethermind_launcher.star +++ b/src/el/nethermind/nethermind_launcher.star @@ -41,6 +41,7 @@ def launch( network_params, extra_files_artifacts, bootnodoor_enode=None, + el_binary_artifact=None, ): cl_client_name = service_name.split("-")[3] @@ -60,6 +61,7 @@ def launch( network_params, extra_files_artifacts, bootnodoor_enode, + el_binary_artifact, ) service = plan.add_service(service_name, config) @@ -88,6 +90,7 @@ def get_config( network_params, extra_files_artifacts, bootnodoor_enode=None, + el_binary_artifact=None, ): log_level = input_parser.get_client_log_level_or_default( participant.el_log_level, global_log_level, VERBOSITY_LEVELS diff --git a/src/el/nimbus-eth1/nimbus_launcher.star b/src/el/nimbus-eth1/nimbus_launcher.star index fdfa4cdf9..9f2be34a4 100644 --- a/src/el/nimbus-eth1/nimbus_launcher.star +++ b/src/el/nimbus-eth1/nimbus_launcher.star @@ -41,6 +41,7 @@ def launch( network_params, extra_files_artifacts, bootnodoor_enode=None, + el_binary_artifact=None, ): cl_client_name = service_name.split("-")[3] @@ -60,6 +61,7 @@ def launch( network_params, extra_files_artifacts, bootnodoor_enode, + el_binary_artifact, ) service = plan.add_service(service_name, config) @@ -88,6 +90,7 @@ def get_config( network_params, extra_files_artifacts, bootnodoor_enode=None, + el_binary_artifact=None, ): log_level = input_parser.get_client_log_level_or_default( participant.el_log_level, global_log_level, VERBOSITY_LEVELS diff --git a/src/el/reth/reth_launcher.star b/src/el/reth/reth_launcher.star index 54a3f5b57..337a6162f 100644 --- a/src/el/reth/reth_launcher.star +++ b/src/el/reth/reth_launcher.star @@ -50,6 +50,7 @@ def launch( network_params, extra_files_artifacts, bootnodoor_enode=None, + el_binary_artifact=None, ): cl_client_name = service_name.split("-")[3] @@ -69,6 +70,7 @@ def launch( network_params, extra_files_artifacts, bootnodoor_enode, + el_binary_artifact, ) service = plan.add_service(service_name, config) @@ -97,6 +99,7 @@ def get_config( network_params, extra_files_artifacts, bootnodoor_enode=None, + el_binary_artifact=None, ): log_level = input_parser.get_client_log_level_or_default( participant.el_log_level, global_log_level, VERBOSITY_LEVELS @@ -267,6 +270,11 @@ def get_config( for mount_path, artifact in processed_mounts.items(): files[mount_path] = artifact + # Binary injection - mount custom binary directory if provided + # The artifact is a directory, so we mount it and reference the binary inside + if el_binary_artifact != None: + files["/opt/bin"] = el_binary_artifact + env_vars = {} image = participant.el_image if launcher.builder_type == constants.MEV_RS_MEV_TYPE: @@ -300,11 +308,18 @@ def get_config( } ) + # Build the command string, using injected binary if provided + if el_binary_artifact != None: + cmd_str = "chmod +x /opt/bin/reth && exec /opt/bin/reth " + " ".join(cmd) + else: + cmd_str = "exec reth " + " ".join(cmd) + config_args = { "image": image, "ports": used_ports, "public_ports": public_ports, - "cmd": cmd, + "entrypoint": ["sh", "-c"], + "cmd": [cmd_str], "files": files, "private_ip_address_placeholder": constants.PRIVATE_IP_ADDRESS_PLACEHOLDER, "env_vars": env_vars | participant.el_extra_env_vars, diff --git a/src/package_io/input_parser.star b/src/package_io/input_parser.star index 61af6fc6e..ed8bdd00b 100644 --- a/src/package_io/input_parser.star +++ b/src/package_io/input_parser.star @@ -447,6 +447,7 @@ def input_parser(plan, input_args): struct( el_type=participant["el_type"], el_image=participant["el_image"], + el_binary_path=participant["el_binary_path"], el_log_level=participant["el_log_level"], el_storage_type=participant["el_storage_type"], el_volume_size=participant["el_volume_size"], @@ -458,6 +459,7 @@ def input_parser(plan, input_args): el_tolerations=participant["el_tolerations"], cl_type=participant["cl_type"], cl_image=participant["cl_image"], + cl_binary_path=participant["cl_binary_path"], cl_log_level=participant["cl_log_level"], cl_volume_size=participant["cl_volume_size"], cl_extra_env_vars=participant["cl_extra_env_vars"], @@ -465,6 +467,7 @@ def input_parser(plan, input_args): use_separate_vc=participant["use_separate_vc"], vc_type=participant["vc_type"], vc_image=participant["vc_image"], + vc_binary_path=participant["vc_binary_path"], vc_log_level=participant["vc_log_level"], vc_tolerations=participant["vc_tolerations"], cl_extra_params=participant["cl_extra_params"], @@ -1455,6 +1458,7 @@ def default_participant(): return { "el_type": "geth", "el_image": "", + "el_binary_path": "", "el_log_level": "", "el_storage_type": "", "el_extra_env_vars": {}, @@ -1470,6 +1474,7 @@ def default_participant(): "el_max_mem": 0, "cl_type": "lighthouse", "cl_image": "", + "cl_binary_path": "", "cl_log_level": "", "cl_extra_env_vars": {}, "cl_extra_labels": {}, @@ -1486,6 +1491,7 @@ def default_participant(): "use_separate_vc": None, "vc_type": "", "vc_image": "", + "vc_binary_path": "", "vc_log_level": "", "vc_extra_env_vars": {}, "vc_extra_labels": {}, diff --git a/src/package_io/sanity_check.star b/src/package_io/sanity_check.star index a237a9813..f2ad82767 100644 --- a/src/package_io/sanity_check.star +++ b/src/package_io/sanity_check.star @@ -2,6 +2,7 @@ PARTICIPANT_CATEGORIES = { "participants": [ "el_type", "el_image", + "el_binary_path", "el_log_level", "el_storage_type", "el_extra_env_vars", @@ -17,6 +18,7 @@ PARTICIPANT_CATEGORIES = { "el_max_mem", "cl_type", "cl_image", + "cl_binary_path", "cl_log_level", "cl_extra_env_vars", "cl_extra_labels", @@ -33,6 +35,7 @@ PARTICIPANT_CATEGORIES = { "use_separate_vc", "vc_type", "vc_image", + "vc_binary_path", "vc_log_level", "vc_extra_env_vars", "vc_extra_labels", @@ -79,6 +82,7 @@ PARTICIPANT_MATRIX_PARAMS = { "el": [ "el_type", "el_image", + "el_binary_path", "el_log_level", "el_storage_type", "el_extra_env_vars", @@ -96,6 +100,7 @@ PARTICIPANT_MATRIX_PARAMS = { "cl": [ "cl_type", "cl_image", + "cl_binary_path", "cl_log_level", "cl_extra_env_vars", "cl_extra_labels", @@ -111,6 +116,7 @@ PARTICIPANT_MATRIX_PARAMS = { "use_separate_vc", "vc_type", "vc_image", + "vc_binary_path", "vc_log_level", "vc_extra_env_vars", "vc_extra_labels", @@ -130,6 +136,7 @@ PARTICIPANT_MATRIX_PARAMS = { "vc": [ "vc_type", "vc_image", + "vc_binary_path", "vc_log_level", "vc_extra_env_vars", "vc_extra_labels", diff --git a/src/participant_network.star b/src/participant_network.star index c70c33c9c..21900abc6 100644 --- a/src/participant_network.star +++ b/src/participant_network.star @@ -50,6 +50,7 @@ def launch_participant_network( extra_files_artifacts, tempo_otlp_grpc_url, backend, + binary_artifacts={}, ): network_id = network_params.network_id num_participants = len(args_with_right_defaults.participants) @@ -178,6 +179,7 @@ def launch_participant_network( args_with_right_defaults.mev_params, extra_files_artifacts, bootnodoor_enode, + binary_artifacts, ) # Launch all consensus layer clients @@ -218,6 +220,7 @@ def launch_participant_network( extra_files_artifacts, backend, bootnodoor_enr, + binary_artifacts, ) # Stop beacon nodes for participants with skip_start enabled From ffa0d62e560051513143c9ef612dd9e411f461e0 Mon Sep 17 00:00:00 2001 From: Barnabas Busa Date: Mon, 12 Jan 2026 17:56:41 +0100 Subject: [PATCH 02/12] remove chmod x --- .github/tests/binary.yaml | 17 +++++++++++++++++ src/cl/lighthouse/lighthouse_launcher.star | 2 +- src/el/reth/reth_launcher.star | 2 +- 3 files changed, 19 insertions(+), 2 deletions(-) create mode 100644 .github/tests/binary.yaml diff --git a/.github/tests/binary.yaml b/.github/tests/binary.yaml new file mode 100644 index 000000000..379d2e390 --- /dev/null +++ b/.github/tests/binary.yaml @@ -0,0 +1,17 @@ +participants: + - el_type: geth + cl_type: lighthouse + cl_image: ethpandaops/lighthouse:stable + cl_binary_path: binary/lighthouse + - cl_type: lighthouse +additional_services: + - dora + +port_publisher: + cl: + enabled: true + public_port_start: 33000 + additional_services: + enabled: true + public_port_start: 34000 + diff --git a/src/cl/lighthouse/lighthouse_launcher.star b/src/cl/lighthouse/lighthouse_launcher.star index 08d964673..8ad49b740 100644 --- a/src/cl/lighthouse/lighthouse_launcher.star +++ b/src/cl/lighthouse/lighthouse_launcher.star @@ -323,7 +323,7 @@ def get_beacon_config( # Build the command string, using injected binary if provided if cl_binary_artifact != None: - cmd_str = "chmod +x /opt/bin/lighthouse && exec /opt/bin/lighthouse " + " ".join(cmd[1:]) + cmd_str = "exec /opt/bin/lighthouse " + " ".join(cmd[1:]) else: cmd_str = "exec " + " ".join(cmd) diff --git a/src/el/reth/reth_launcher.star b/src/el/reth/reth_launcher.star index 337a6162f..efda6a72e 100644 --- a/src/el/reth/reth_launcher.star +++ b/src/el/reth/reth_launcher.star @@ -310,7 +310,7 @@ def get_config( # Build the command string, using injected binary if provided if el_binary_artifact != None: - cmd_str = "chmod +x /opt/bin/reth && exec /opt/bin/reth " + " ".join(cmd) + cmd_str = "exec /opt/bin/reth " + " ".join(cmd) else: cmd_str = "exec reth " + " ".join(cmd) From a94224492b7a72f40cb5a7eafdd63ae505229d3d Mon Sep 17 00:00:00 2001 From: Barnabas Busa Date: Mon, 12 Jan 2026 17:57:31 +0100 Subject: [PATCH 03/12] fix path to binary --- .github/tests/binary.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/tests/binary.yaml b/.github/tests/binary.yaml index 379d2e390..82cea939c 100644 --- a/.github/tests/binary.yaml +++ b/.github/tests/binary.yaml @@ -2,7 +2,7 @@ participants: - el_type: geth cl_type: lighthouse cl_image: ethpandaops/lighthouse:stable - cl_binary_path: binary/lighthouse + cl_binary_path: binary/release/lighthouse - cl_type: lighthouse additional_services: - dora From 597d567c227ecc7189087fe6b168c28d04b411f0 Mon Sep 17 00:00:00 2001 From: Barnabas Busa Date: Mon, 12 Jan 2026 18:28:56 +0100 Subject: [PATCH 04/12] fix it for all --- main.star | 39 +++++++++++----------- network_params.yaml | 3 ++ src/cl/grandine/grandine_launcher.star | 13 +++++++- src/cl/lighthouse/lighthouse_launcher.star | 7 ++-- src/cl/lodestar/lodestar_launcher.star | 15 ++++++++- src/cl/nimbus/nimbus_launcher.star | 10 ++++++ src/cl/prysm/prysm_launcher.star | 13 +++++++- src/cl/teku/teku_launcher.star | 13 +++++++- src/el/besu/besu_launcher.star | 14 +++++++- src/el/erigon/erigon_launcher.star | 23 +++++++++++-- src/el/ethrex/ethrex_launcher.star | 14 +++++++- src/el/geth/geth_launcher.star | 12 ++++++- src/el/nethermind/nethermind_launcher.star | 17 +++++++++- src/el/nimbus-eth1/nimbus_launcher.star | 17 +++++++++- src/el/reth/reth_launcher.star | 7 ++-- src/participant_network.star | 4 +++ src/vc/lighthouse.star | 15 ++++++++- src/vc/lodestar.star | 18 +++++++++- src/vc/nimbus.star | 18 +++++++++- src/vc/prysm.star | 18 +++++++++- src/vc/teku.star | 17 +++++++++- src/vc/vc_launcher.star | 7 ++++ src/vc/vero.star | 15 ++++++++- 23 files changed, 286 insertions(+), 43 deletions(-) diff --git a/main.star b/main.star index 5cdf52ee0..698495081 100644 --- a/main.star +++ b/main.star @@ -100,29 +100,30 @@ def run(plan, args={}): extra_files_artifacts[name] = artifact # Process binary injection - upload local binaries for participants - # Users can specify el_binary_path/cl_binary_path to inject a local binary - # The binary will be mounted into the container and executed instead of the default # NOTE: Binary injection is only supported with Docker backend binary_artifacts = {} for index, participant in enumerate(args_with_right_defaults.participants): - has_binary_path = participant.el_binary_path or participant.cl_binary_path or participant.vc_binary_path - if has_binary_path and detected_backend != "docker": - fail("Binary injection (*_binary_path) is only supported with Docker backend, detected: {0}".format(detected_backend)) participant_binaries = {} - if participant.el_binary_path: - plan.print("Uploading EL binary for participant {0}: {1}".format(index + 1, participant.el_binary_path)) - el_binary_artifact = plan.upload_files( - src=participant.el_binary_path, - name="el-binary-{0}".format(index + 1), - ) - participant_binaries["el"] = el_binary_artifact - if participant.cl_binary_path: - plan.print("Uploading CL binary for participant {0}: {1}".format(index + 1, participant.cl_binary_path)) - cl_binary_artifact = plan.upload_files( - src=participant.cl_binary_path, - name="cl-binary-{0}".format(index + 1), - ) - participant_binaries["cl"] = cl_binary_artifact + for bin_type, bin_path in [ + ("el", participant.el_binary_path), + ("cl", participant.cl_binary_path), + ("vc", participant.vc_binary_path), + ]: + if bin_path: + if detected_backend != "docker": + fail( + "Binary injection (*_binary_path) is only supported with Docker backend, detected: {0}".format( + detected_backend + ) + ) + plan.print( + "Uploading {0} binary for participant {1}: {2}".format( + bin_type.upper(), index + 1, bin_path + ) + ) + participant_binaries[bin_type] = plan.upload_files( + src=bin_path, name="{0}-binary-{1}".format(bin_type, index + 1) + ) if participant_binaries: binary_artifacts[index] = participant_binaries diff --git a/network_params.yaml b/network_params.yaml index 2c875d7f8..554cc96bd 100644 --- a/network_params.yaml +++ b/network_params.yaml @@ -2,6 +2,7 @@ participants: # EL - el_type: geth el_image: ethereum/client-go:latest + el_binary_path: "" el_log_level: "" el_extra_env_vars: {} el_extra_labels: {} @@ -17,6 +18,7 @@ participants: # CL cl_type: lighthouse cl_image: sigp/lighthouse:latest + cl_binary_path: "" cl_log_level: "" cl_extra_env_vars: {} cl_extra_labels: {} @@ -34,6 +36,7 @@ participants: # Validator vc_type: lighthouse vc_image: sigp/lighthouse:latest + vc_binary_path: "" vc_log_level: "" vc_extra_env_vars: {} vc_extra_labels: {} diff --git a/src/cl/grandine/grandine_launcher.star b/src/cl/grandine/grandine_launcher.star index ab58ba71e..7ff7e6dbc 100644 --- a/src/cl/grandine/grandine_launcher.star +++ b/src/cl/grandine/grandine_launcher.star @@ -341,12 +341,23 @@ def get_beacon_config( for mount_path, artifact in processed_mounts.items(): files[mount_path] = artifact + # Binary injection - mount custom binary directory if provided + if cl_binary_artifact != None: + files["/opt/bin"] = cl_binary_artifact + + # Build the command string, copying injected binary if provided + cmd_str = " ".join(cmd) + if cl_binary_artifact != None: + cmd_str = "cp /opt/bin/grandine /usr/local/bin/grandine && exec " + cmd_str + else: + cmd_str = "exec " + cmd_str + config_args = { "image": participant.cl_image, "ports": used_ports, "public_ports": public_ports, "entrypoint": ["sh", "-c"], - "cmd": ["exec " + " ".join(cmd)], + "cmd": [cmd_str], "files": files, "env_vars": participant.cl_extra_env_vars, "private_ip_address_placeholder": constants.PRIVATE_IP_ADDRESS_PLACEHOLDER, diff --git a/src/cl/lighthouse/lighthouse_launcher.star b/src/cl/lighthouse/lighthouse_launcher.star index 8ad49b740..2ddd9d0bc 100644 --- a/src/cl/lighthouse/lighthouse_launcher.star +++ b/src/cl/lighthouse/lighthouse_launcher.star @@ -321,11 +321,12 @@ def get_beacon_config( env_vars = {RUST_BACKTRACE_ENVVAR_NAME: RUST_FULL_BACKTRACE_KEYWORD} env_vars.update(participant.cl_extra_env_vars) - # Build the command string, using injected binary if provided + # Build the command string, copying injected binary if provided + cmd_str = " ".join(cmd) if cl_binary_artifact != None: - cmd_str = "exec /opt/bin/lighthouse " + " ".join(cmd[1:]) + cmd_str = "cp /opt/bin/lighthouse /usr/local/bin/lighthouse && exec " + cmd_str else: - cmd_str = "exec " + " ".join(cmd) + cmd_str = "exec " + cmd_str config_args = { "image": participant.cl_image, diff --git a/src/cl/lodestar/lodestar_launcher.star b/src/cl/lodestar/lodestar_launcher.star index ed9704e8c..af64e8a86 100644 --- a/src/cl/lodestar/lodestar_launcher.star +++ b/src/cl/lodestar/lodestar_launcher.star @@ -289,17 +289,30 @@ def get_beacon_config( for mount_path, artifact in processed_mounts.items(): files[mount_path] = artifact + # Binary injection - mount custom binary directory if provided + if cl_binary_artifact != None: + files["/opt/bin"] = cl_binary_artifact + env_vars = participant.cl_extra_env_vars if network_params.preset == "minimal": env_vars["LODESTAR_PRESET"] = "minimal" + # Build the command string, copying injected binary if provided + cmd_str = " ".join(cmd) + if cl_binary_artifact != None: + cmd_str = ( + "cp /opt/bin/lodestar /usr/app/packages/cli/bin/lodestar && exec " + cmd_str + ) + else: + cmd_str = "exec " + cmd_str + config_args = { "image": participant.cl_image, "ports": used_ports, "public_ports": public_ports, "entrypoint": ["sh", "-c"], - "cmd": ["exec " + " ".join(cmd)], + "cmd": [cmd_str], "files": files, "env_vars": env_vars, "private_ip_address_placeholder": constants.PRIVATE_IP_ADDRESS_PLACEHOLDER, diff --git a/src/cl/nimbus/nimbus_launcher.star b/src/cl/nimbus/nimbus_launcher.star index eeb8dd418..99dbd59f8 100644 --- a/src/cl/nimbus/nimbus_launcher.star +++ b/src/cl/nimbus/nimbus_launcher.star @@ -345,7 +345,17 @@ def get_beacon_config( for mount_path, artifact in processed_mounts.items(): files[mount_path] = artifact + # Binary injection - mount custom binary directory if provided + if cl_binary_artifact != None: + files["/opt/bin"] = cl_binary_artifact + cmd_str = " ".join(cmd) + # Add binary copy prefix if injected + if cl_binary_artifact != None: + cmd_str = ( + "cp /opt/bin/nimbus_beacon_node /home/user/nimbus-eth2/build/nimbus_beacon_node && " + + cmd_str + ) if checkpoint_sync_enabled: command_str = " && ".join([nimbus_checkpoint_sync_subtask_str, cmd_str]) else: diff --git a/src/cl/prysm/prysm_launcher.star b/src/cl/prysm/prysm_launcher.star index 5873b9f3d..d42a014b8 100644 --- a/src/cl/prysm/prysm_launcher.star +++ b/src/cl/prysm/prysm_launcher.star @@ -330,12 +330,23 @@ def get_beacon_config( for mount_path, artifact in processed_mounts.items(): files[mount_path] = artifact + # Binary injection - mount custom binary directory if provided + if cl_binary_artifact != None: + files["/opt/bin"] = cl_binary_artifact + + # Build the command string, copying injected binary if provided + cmd_str = " ".join(cmd) + if cl_binary_artifact != None: + cmd_str = "cp /opt/bin/beacon-chain /beacon-chain && exec " + cmd_str + else: + cmd_str = "exec " + cmd_str + config_args = { "image": participant.cl_image, "ports": used_ports, "public_ports": public_ports, "entrypoint": ["sh", "-c"], - "cmd": ["exec " + " ".join(cmd)], + "cmd": [cmd_str], "files": files, "env_vars": participant.cl_extra_env_vars, "private_ip_address_placeholder": constants.PRIVATE_IP_ADDRESS_PLACEHOLDER, diff --git a/src/cl/teku/teku_launcher.star b/src/cl/teku/teku_launcher.star index 192e0c850..7083142b9 100644 --- a/src/cl/teku/teku_launcher.star +++ b/src/cl/teku/teku_launcher.star @@ -352,12 +352,23 @@ def get_beacon_config( for mount_path, artifact in processed_mounts.items(): files[mount_path] = artifact + # Binary injection - mount custom binary directory if provided + if cl_binary_artifact != None: + files["/opt/bin"] = cl_binary_artifact + + # Build the command string, copying injected binary if provided + cmd_str = " ".join(cmd) + if cl_binary_artifact != None: + cmd_str = "cp /opt/bin/teku /opt/teku/bin/teku && exec " + cmd_str + else: + cmd_str = "exec " + cmd_str + config_args = { "image": participant.cl_image, "ports": used_ports, "public_ports": public_ports, "entrypoint": ["sh", "-c"], - "cmd": ["exec " + " ".join(cmd)], + "cmd": [cmd_str], "files": files, "env_vars": participant.cl_extra_env_vars, "private_ip_address_placeholder": constants.PRIVATE_IP_ADDRESS_PLACEHOLDER, diff --git a/src/el/besu/besu_launcher.star b/src/el/besu/besu_launcher.star index 9a64ea0df..46725a91c 100644 --- a/src/el/besu/besu_launcher.star +++ b/src/el/besu/besu_launcher.star @@ -249,11 +249,23 @@ def get_config( for mount_path, artifact in processed_mounts.items(): files[mount_path] = artifact + # Binary injection - mount custom binary directory if provided + if el_binary_artifact != None: + files["/opt/bin"] = el_binary_artifact + + # Build the command string, copying injected binary if provided + if el_binary_artifact != None: + final_cmd_str = ( + "cp /opt/bin/besu /opt/besu/bin/besu && exec /opt/besu/bin/besu " + cmd_str + ) + else: + final_cmd_str = "exec /opt/besu/bin/besu " + cmd_str + config_args = { "image": participant.el_image, "ports": used_ports, "public_ports": public_ports, - "cmd": [cmd_str], + "cmd": [final_cmd_str], "files": files, "entrypoint": ENTRYPOINT_ARGS, "private_ip_address_placeholder": constants.PRIVATE_IP_ADDRESS_PLACEHOLDER, diff --git a/src/el/erigon/erigon_launcher.star b/src/el/erigon/erigon_launcher.star index 97d213580..2b77a69fe 100644 --- a/src/el/erigon/erigon_launcher.star +++ b/src/el/erigon/erigon_launcher.star @@ -247,11 +247,28 @@ def get_config( if len(participant.el_extra_params) > 0: cmd.extend([param for param in participant.el_extra_params]) + # Binary injection - mount custom binary directory if provided + if el_binary_artifact != None: + files["/opt/bin"] = el_binary_artifact + + # Build command with optional binary copy + cmd_str = " ".join(cmd) if network_params.network not in constants.PUBLIC_NETWORKS: - command_arg = [init_datadir_cmd_str, " ".join(cmd)] - command_arg_str = " && ".join(command_arg) + if el_binary_artifact != None: + command_arg_str = ( + init_datadir_cmd_str + + " && cp /opt/bin/erigon /usr/local/bin/erigon && exec " + + cmd_str + ) + else: + command_arg_str = init_datadir_cmd_str + " && exec " + cmd_str else: - command_arg_str = " ".join(cmd) + if el_binary_artifact != None: + command_arg_str = ( + "cp /opt/bin/erigon /usr/local/bin/erigon && exec " + cmd_str + ) + else: + command_arg_str = "exec " + cmd_str env_vars = participant.el_extra_env_vars config_args = { diff --git a/src/el/ethrex/ethrex_launcher.star b/src/el/ethrex/ethrex_launcher.star index 4d35a2d55..dfc23d9c6 100644 --- a/src/el/ethrex/ethrex_launcher.star +++ b/src/el/ethrex/ethrex_launcher.star @@ -233,11 +233,23 @@ def get_config( for mount_path, artifact in processed_mounts.items(): files[mount_path] = artifact + # Binary injection - mount custom binary directory if provided + if el_binary_artifact != None: + files["/opt/bin"] = el_binary_artifact + + # Build the command string, copying injected binary if provided + cmd_str = " ".join(cmd) + if el_binary_artifact != None: + cmd_str = "cp /opt/bin/ethrex /usr/local/bin/ethrex && exec ethrex " + cmd_str + else: + cmd_str = "exec ethrex " + cmd_str + config_args = { "image": participant.el_image, "ports": used_ports, "public_ports": public_ports, - "cmd": cmd, + "entrypoint": ["sh", "-c"], + "cmd": [cmd_str], "files": files, "private_ip_address_placeholder": constants.PRIVATE_IP_ADDRESS_PLACEHOLDER, "env_vars": participant.el_extra_env_vars, diff --git a/src/el/geth/geth_launcher.star b/src/el/geth/geth_launcher.star index 11b33672b..c3c82ac86 100644 --- a/src/el/geth/geth_launcher.star +++ b/src/el/geth/geth_launcher.star @@ -281,12 +281,22 @@ def get_config( for mount_path, artifact in processed_mounts.items(): files[mount_path] = artifact + # Binary injection - mount custom binary directory if provided + if el_binary_artifact != None: + files["/opt/bin"] = el_binary_artifact + + # Build the command string, copying injected binary if provided + if el_binary_artifact != None: + cmd_str = "cp /opt/bin/geth /usr/local/bin/geth && exec geth " + command_str + else: + cmd_str = "exec geth " + command_str + env_vars = participant.el_extra_env_vars config_args = { "image": participant.el_image, "ports": used_ports, "public_ports": public_ports, - "cmd": [command_str], + "cmd": [cmd_str], "files": files, "entrypoint": ENTRYPOINT_ARGS, "private_ip_address_placeholder": constants.PRIVATE_IP_ADDRESS_PLACEHOLDER, diff --git a/src/el/nethermind/nethermind_launcher.star b/src/el/nethermind/nethermind_launcher.star index 1a971971b..d4c021d2c 100644 --- a/src/el/nethermind/nethermind_launcher.star +++ b/src/el/nethermind/nethermind_launcher.star @@ -242,12 +242,27 @@ def get_config( for mount_path, artifact in processed_mounts.items(): files[mount_path] = artifact + # Binary injection - mount custom binary directory if provided + if el_binary_artifact != None: + files["/opt/bin"] = el_binary_artifact + + # Build the command string, copying injected binary if provided + cmd_str = " ".join(cmd) + if el_binary_artifact != None: + cmd_str = ( + "cp /opt/bin/nethermind /nethermind/nethermind && exec /nethermind/nethermind " + + cmd_str + ) + else: + cmd_str = "exec /nethermind/nethermind " + cmd_str + env_vars = participant.el_extra_env_vars config_args = { "image": participant.el_image, "ports": used_ports, "public_ports": public_ports, - "cmd": cmd, + "entrypoint": ["sh", "-c"], + "cmd": [cmd_str], "files": files, "private_ip_address_placeholder": constants.PRIVATE_IP_ADDRESS_PLACEHOLDER, "env_vars": env_vars, diff --git a/src/el/nimbus-eth1/nimbus_launcher.star b/src/el/nimbus-eth1/nimbus_launcher.star index 9f2be34a4..3603f4190 100644 --- a/src/el/nimbus-eth1/nimbus_launcher.star +++ b/src/el/nimbus-eth1/nimbus_launcher.star @@ -223,12 +223,27 @@ def get_config( for mount_path, artifact in processed_mounts.items(): files[mount_path] = artifact + # Binary injection - mount custom binary directory if provided + if el_binary_artifact != None: + files["/opt/bin"] = el_binary_artifact + + # Build the command string, copying injected binary if provided + cmd_str = " ".join(cmd) + if el_binary_artifact != None: + cmd_str = ( + "cp /opt/bin/nimbus_execution_client /home/user/nimbus-eth1/build/nimbus_execution_client && exec /home/user/nimbus-eth1/build/nimbus_execution_client " + + cmd_str + ) + else: + cmd_str = "exec /home/user/nimbus-eth1/build/nimbus_execution_client " + cmd_str + env_vars = participant.el_extra_env_vars config_args = { "image": participant.el_image, "ports": used_ports, "public_ports": public_ports, - "cmd": cmd, + "entrypoint": ["sh", "-c"], + "cmd": [cmd_str], "files": files, "private_ip_address_placeholder": constants.PRIVATE_IP_ADDRESS_PLACEHOLDER, "env_vars": env_vars, diff --git a/src/el/reth/reth_launcher.star b/src/el/reth/reth_launcher.star index efda6a72e..628533333 100644 --- a/src/el/reth/reth_launcher.star +++ b/src/el/reth/reth_launcher.star @@ -308,11 +308,12 @@ def get_config( } ) - # Build the command string, using injected binary if provided + # Build the command string, copying injected binary if provided + cmd_str = " ".join(cmd) if el_binary_artifact != None: - cmd_str = "exec /opt/bin/reth " + " ".join(cmd) + cmd_str = "cp /opt/bin/reth /usr/local/bin/reth && exec reth " + cmd_str else: - cmd_str = "exec reth " + " ".join(cmd) + cmd_str = "exec reth " + cmd_str config_args = { "image": image, diff --git a/src/participant_network.star b/src/participant_network.star index 21900abc6..a72a98f46 100644 --- a/src/participant_network.star +++ b/src/participant_network.star @@ -500,6 +500,9 @@ def launch_participant_network( remote_signer_context.metrics_info["config"] = participant.prometheus_config service_name = "vc-{0}".format(full_name) + vc_binary_artifact = None + if index in binary_artifacts and "vc" in binary_artifacts[index]: + vc_binary_artifact = binary_artifacts[index]["vc"] vc_service_config = vc.get_vc_config( plan=plan, launcher=vc.new_vc_launcher(el_cl_genesis_data=el_cl_data), @@ -526,6 +529,7 @@ def launch_participant_network( vc_index=current_vc_index, extra_files_artifacts=extra_files_artifacts, tempo_otlp_grpc_url=tempo_otlp_grpc_url, + vc_binary_artifact=vc_binary_artifact, ) if vc_service_config == None: continue diff --git a/src/vc/lighthouse.star b/src/vc/lighthouse.star index 12db57afb..029ff023b 100644 --- a/src/vc/lighthouse.star +++ b/src/vc/lighthouse.star @@ -35,6 +35,7 @@ def get_config( vc_index, extra_files_artifacts, tempo_otlp_grpc_url=None, + vc_binary_artifact=None, ): log_level = input_parser.get_client_log_level_or_default( participant.vc_log_level, global_log_level, VERBOSITY_LEVELS @@ -129,11 +130,23 @@ def get_config( for mount_path, artifact in processed_mounts.items(): files[mount_path] = artifact + # Binary injection - mount custom binary directory if provided + if vc_binary_artifact != None: + files["/opt/bin"] = vc_binary_artifact + + # Build the command string, copying injected binary if provided + cmd_str = " ".join(cmd) + if vc_binary_artifact != None: + cmd_str = "cp /opt/bin/lighthouse /usr/local/bin/lighthouse && exec " + cmd_str + else: + cmd_str = "exec " + cmd_str + config_args = { "image": image, "ports": ports, "public_ports": public_ports, - "cmd": cmd, + "entrypoint": ["sh", "-c"], + "cmd": [cmd_str], "files": files, "env_vars": env, "labels": shared_utils.label_maker( diff --git a/src/vc/lodestar.star b/src/vc/lodestar.star index 8898a3d3e..a1f633859 100644 --- a/src/vc/lodestar.star +++ b/src/vc/lodestar.star @@ -32,6 +32,7 @@ def get_config( port_publisher, vc_index, extra_files_artifacts, + vc_binary_artifact=None, ): log_level = input_parser.get_client_log_level_or_default( participant.vc_log_level, global_log_level, VERBOSITY_LEVELS @@ -132,15 +133,30 @@ def get_config( for mount_path, artifact in processed_mounts.items(): files[mount_path] = artifact + # Binary injection - mount custom binary directory if provided + if vc_binary_artifact != None: + files["/opt/bin"] = vc_binary_artifact + env_vars = participant.vc_extra_env_vars if network_params.preset == "minimal": env_vars["LODESTAR_PRESET"] = "minimal" + # Build the command string, copying injected binary if provided + cmd_str = " ".join(cmd) + if vc_binary_artifact != None: + cmd_str = ( + "cp /opt/bin/lodestar /usr/app/packages/cli/bin/lodestar && exec node /usr/app/packages/cli/bin/lodestar " + + cmd_str + ) + else: + cmd_str = "exec node /usr/app/packages/cli/bin/lodestar " + cmd_str + config_args = { "image": image, "ports": ports, "public_ports": public_ports, - "cmd": cmd, + "entrypoint": ["sh", "-c"], + "cmd": [cmd_str], "files": files, "env_vars": env_vars, "labels": shared_utils.label_maker( diff --git a/src/vc/nimbus.star b/src/vc/nimbus.star index 4826dc978..95ea9dad5 100644 --- a/src/vc/nimbus.star +++ b/src/vc/nimbus.star @@ -22,6 +22,7 @@ def get_config( port_publisher, vc_index, extra_files_artifacts, + vc_binary_artifact=None, ): validator_keys_dirpath = "" validator_secrets_dirpath = "" @@ -115,11 +116,26 @@ def get_config( for mount_path, artifact in processed_mounts.items(): files[mount_path] = artifact + # Binary injection - mount custom binary directory if provided + if vc_binary_artifact != None: + files["/opt/bin"] = vc_binary_artifact + + # Build the command string, copying injected binary if provided + cmd_str = " ".join(cmd) + if vc_binary_artifact != None: + cmd_str = ( + "cp /opt/bin/nimbus_validator_client /usr/bin/nimbus_validator_client && exec /usr/bin/nimbus_validator_client " + + cmd_str + ) + else: + cmd_str = "exec /usr/bin/nimbus_validator_client " + cmd_str + config_args = { "image": image, "ports": ports, "public_ports": public_ports, - "cmd": cmd, + "entrypoint": ["sh", "-c"], + "cmd": [cmd_str], "files": files, "env_vars": participant.vc_extra_env_vars, "labels": shared_utils.label_maker( diff --git a/src/vc/prysm.star b/src/vc/prysm.star index a3276cadc..bd2f11069 100644 --- a/src/vc/prysm.star +++ b/src/vc/prysm.star @@ -27,6 +27,7 @@ def get_config( port_publisher, vc_index, extra_files_artifacts, + vc_binary_artifact=None, ): validator_keys_dirpath = shared_utils.path_join( constants.VALIDATOR_KEYS_DIRPATH_ON_SERVICE_CONTAINER, @@ -134,11 +135,26 @@ def get_config( for mount_path, artifact in processed_mounts.items(): files[mount_path] = artifact + # Binary injection - mount custom binary directory if provided + if vc_binary_artifact != None: + files["/opt/bin"] = vc_binary_artifact + + # Build the command string, copying injected binary if provided + cmd_str = " ".join(cmd) + if vc_binary_artifact != None: + cmd_str = ( + "cp /opt/bin/validator /app/cmd/validator/validator && exec /app/cmd/validator/validator " + + cmd_str + ) + else: + cmd_str = "exec /app/cmd/validator/validator " + cmd_str + config_args = { "image": image, "ports": ports, "public_ports": public_ports, - "cmd": cmd, + "entrypoint": ["sh", "-c"], + "cmd": [cmd_str], "files": files, "env_vars": participant.vc_extra_env_vars, "labels": shared_utils.label_maker( diff --git a/src/vc/teku.star b/src/vc/teku.star index b8fc73634..038102123 100644 --- a/src/vc/teku.star +++ b/src/vc/teku.star @@ -22,6 +22,7 @@ def get_config( port_publisher, vc_index, extra_files_artifacts, + vc_binary_artifact=None, ): validator_keys_dirpath = "" validator_secrets_dirpath = "" @@ -127,11 +128,25 @@ def get_config( for mount_path, artifact in processed_mounts.items(): files[mount_path] = artifact + # Binary injection - mount custom binary directory if provided + if vc_binary_artifact != None: + files["/opt/bin"] = vc_binary_artifact + + # Build the command string, copying injected binary if provided + cmd_str = " ".join(cmd) + if vc_binary_artifact != None: + cmd_str = ( + "cp /opt/bin/teku /opt/teku/bin/teku && exec /opt/teku/bin/teku " + cmd_str + ) + else: + cmd_str = "exec /opt/teku/bin/teku " + cmd_str + config_args = { "image": image, "ports": ports, "public_ports": public_ports, - "cmd": cmd, + "entrypoint": ["sh", "-c"], + "cmd": [cmd_str], "files": files, "env_vars": participant.vc_extra_env_vars, "labels": shared_utils.label_maker( diff --git a/src/vc/vc_launcher.star b/src/vc/vc_launcher.star index 5b82c32f2..f0fbab588 100644 --- a/src/vc/vc_launcher.star +++ b/src/vc/vc_launcher.star @@ -39,6 +39,7 @@ def get_vc_config( vc_index, extra_files_artifacts, tempo_otlp_grpc_url=None, + vc_binary_artifact=None, ): if node_keystore_files == None: return None @@ -94,6 +95,7 @@ def get_vc_config( vc_index=vc_index, extra_files_artifacts=extra_files_artifacts, tempo_otlp_grpc_url=tempo_otlp_grpc_url, + vc_binary_artifact=vc_binary_artifact, ) elif vc_type == constants.VC_TYPE.lodestar: config = lodestar.get_config( @@ -116,6 +118,7 @@ def get_vc_config( port_publisher=port_publisher, vc_index=vc_index, extra_files_artifacts=extra_files_artifacts, + vc_binary_artifact=vc_binary_artifact, ) elif vc_type == constants.VC_TYPE.teku: config = teku.get_config( @@ -137,6 +140,7 @@ def get_vc_config( port_publisher=port_publisher, vc_index=vc_index, extra_files_artifacts=extra_files_artifacts, + vc_binary_artifact=vc_binary_artifact, ) elif vc_type == constants.VC_TYPE.nimbus: config = nimbus.get_config( @@ -158,6 +162,7 @@ def get_vc_config( port_publisher=port_publisher, vc_index=vc_index, extra_files_artifacts=extra_files_artifacts, + vc_binary_artifact=vc_binary_artifact, ) elif vc_type == constants.VC_TYPE.prysm: config = prysm.get_config( @@ -181,6 +186,7 @@ def get_vc_config( port_publisher=port_publisher, vc_index=vc_index, extra_files_artifacts=extra_files_artifacts, + vc_binary_artifact=vc_binary_artifact, ) elif vc_type == constants.VC_TYPE.vero: if remote_signer_context == None: @@ -202,6 +208,7 @@ def get_vc_config( port_publisher=port_publisher, vc_index=vc_index, extra_files_artifacts=extra_files_artifacts, + vc_binary_artifact=vc_binary_artifact, ) elif vc_type == constants.VC_TYPE.grandine: fail("Grandine VC is not yet supported") diff --git a/src/vc/vero.star b/src/vc/vero.star index 26a6ccfb2..47b6c4b2b 100644 --- a/src/vc/vero.star +++ b/src/vc/vero.star @@ -27,6 +27,7 @@ def get_config( port_publisher, vc_index, extra_files_artifacts, + vc_binary_artifact=None, ): log_level = input_parser.get_client_log_level_or_default( participant.vc_log_level, global_log_level, VERBOSITY_LEVELS @@ -73,11 +74,23 @@ def get_config( for mount_path, artifact in processed_mounts.items(): files[mount_path] = artifact + # Binary injection - mount custom binary directory if provided + if vc_binary_artifact != None: + files["/opt/bin"] = vc_binary_artifact + + # Build the command string, copying injected binary if provided + cmd_str = " ".join(cmd) + if vc_binary_artifact != None: + cmd_str = "cp /opt/bin/vero /usr/local/bin/vero && exec vero " + cmd_str + else: + cmd_str = "exec vero " + cmd_str + config_args = { "image": image, "ports": ports, "public_ports": public_ports, - "cmd": cmd, + "entrypoint": ["sh", "-c"], + "cmd": [cmd_str], "files": files, "env_vars": participant.vc_extra_env_vars, "labels": shared_utils.label_maker( From 12312baf9a0782c1527e41c6f0d37c2b280802f4 Mon Sep 17 00:00:00 2001 From: Barnabas Busa Date: Mon, 12 Jan 2026 18:47:37 +0100 Subject: [PATCH 05/12] fix revert broken changes --- src/el/besu/besu_launcher.star | 10 +++------- src/el/erigon/erigon_launcher.star | 10 ++++------ src/el/ethrex/ethrex_launcher.star | 17 ++++++++--------- src/el/geth/geth_launcher.star | 8 +++----- src/el/nethermind/nethermind_launcher.star | 21 +++++++++------------ src/el/nimbus-eth1/nimbus_launcher.star | 21 +++++++++------------ src/el/reth/reth_launcher.star | 17 ++++++++--------- src/vc/lighthouse.star | 18 +++++++++--------- src/vc/lodestar.star | 21 +++++++++------------ src/vc/nimbus.star | 21 +++++++++------------ src/vc/prysm.star | 21 +++++++++------------ src/vc/teku.star | 19 ++++++++----------- src/vc/vero.star | 17 ++++++++--------- 13 files changed, 96 insertions(+), 125 deletions(-) diff --git a/src/el/besu/besu_launcher.star b/src/el/besu/besu_launcher.star index 46725a91c..4a62914a0 100644 --- a/src/el/besu/besu_launcher.star +++ b/src/el/besu/besu_launcher.star @@ -252,14 +252,10 @@ def get_config( # Binary injection - mount custom binary directory if provided if el_binary_artifact != None: files["/opt/bin"] = el_binary_artifact - - # Build the command string, copying injected binary if provided - if el_binary_artifact != None: - final_cmd_str = ( - "cp /opt/bin/besu /opt/besu/bin/besu && exec /opt/besu/bin/besu " + cmd_str - ) + # Copy injected binary to override default, then run original command + final_cmd_str = "cp /opt/bin/besu /opt/besu/bin/besu && " + cmd_str else: - final_cmd_str = "exec /opt/besu/bin/besu " + cmd_str + final_cmd_str = cmd_str config_args = { "image": participant.el_image, diff --git a/src/el/erigon/erigon_launcher.star b/src/el/erigon/erigon_launcher.star index 2b77a69fe..274ff4c5d 100644 --- a/src/el/erigon/erigon_launcher.star +++ b/src/el/erigon/erigon_launcher.star @@ -257,18 +257,16 @@ def get_config( if el_binary_artifact != None: command_arg_str = ( init_datadir_cmd_str - + " && cp /opt/bin/erigon /usr/local/bin/erigon && exec " + + " && cp /opt/bin/erigon /usr/local/bin/erigon && " + cmd_str ) else: - command_arg_str = init_datadir_cmd_str + " && exec " + cmd_str + command_arg_str = init_datadir_cmd_str + " && " + cmd_str else: if el_binary_artifact != None: - command_arg_str = ( - "cp /opt/bin/erigon /usr/local/bin/erigon && exec " + cmd_str - ) + command_arg_str = "cp /opt/bin/erigon /usr/local/bin/erigon && " + cmd_str else: - command_arg_str = "exec " + cmd_str + command_arg_str = cmd_str env_vars = participant.el_extra_env_vars config_args = { diff --git a/src/el/ethrex/ethrex_launcher.star b/src/el/ethrex/ethrex_launcher.star index dfc23d9c6..c077f7d04 100644 --- a/src/el/ethrex/ethrex_launcher.star +++ b/src/el/ethrex/ethrex_launcher.star @@ -237,19 +237,11 @@ def get_config( if el_binary_artifact != None: files["/opt/bin"] = el_binary_artifact - # Build the command string, copying injected binary if provided - cmd_str = " ".join(cmd) - if el_binary_artifact != None: - cmd_str = "cp /opt/bin/ethrex /usr/local/bin/ethrex && exec ethrex " + cmd_str - else: - cmd_str = "exec ethrex " + cmd_str - config_args = { "image": participant.el_image, "ports": used_ports, "public_ports": public_ports, - "entrypoint": ["sh", "-c"], - "cmd": [cmd_str], + "cmd": cmd, "files": files, "private_ip_address_placeholder": constants.PRIVATE_IP_ADDRESS_PLACEHOLDER, "env_vars": participant.el_extra_env_vars, @@ -266,6 +258,13 @@ def get_config( "node_selectors": node_selectors, } + # Binary injection - override entrypoint and cmd only when binary is provided + if el_binary_artifact != None: + config_args["entrypoint"] = ["sh", "-c"] + config_args["cmd"] = [ + "cp /opt/bin/ethrex /usr/local/bin/ethrex && ethrex " + " ".join(cmd) + ] + if participant.el_min_cpu > 0: config_args["min_cpu"] = participant.el_min_cpu if participant.el_max_cpu > 0: diff --git a/src/el/geth/geth_launcher.star b/src/el/geth/geth_launcher.star index c3c82ac86..7919d1c65 100644 --- a/src/el/geth/geth_launcher.star +++ b/src/el/geth/geth_launcher.star @@ -284,12 +284,10 @@ def get_config( # Binary injection - mount custom binary directory if provided if el_binary_artifact != None: files["/opt/bin"] = el_binary_artifact - - # Build the command string, copying injected binary if provided - if el_binary_artifact != None: - cmd_str = "cp /opt/bin/geth /usr/local/bin/geth && exec geth " + command_str + # Copy injected binary to override default, then run original command + cmd_str = "cp /opt/bin/geth /usr/local/bin/geth && " + command_str else: - cmd_str = "exec geth " + command_str + cmd_str = command_str env_vars = participant.el_extra_env_vars config_args = { diff --git a/src/el/nethermind/nethermind_launcher.star b/src/el/nethermind/nethermind_launcher.star index d4c021d2c..9205bbaa5 100644 --- a/src/el/nethermind/nethermind_launcher.star +++ b/src/el/nethermind/nethermind_launcher.star @@ -246,23 +246,12 @@ def get_config( if el_binary_artifact != None: files["/opt/bin"] = el_binary_artifact - # Build the command string, copying injected binary if provided - cmd_str = " ".join(cmd) - if el_binary_artifact != None: - cmd_str = ( - "cp /opt/bin/nethermind /nethermind/nethermind && exec /nethermind/nethermind " - + cmd_str - ) - else: - cmd_str = "exec /nethermind/nethermind " + cmd_str - env_vars = participant.el_extra_env_vars config_args = { "image": participant.el_image, "ports": used_ports, "public_ports": public_ports, - "entrypoint": ["sh", "-c"], - "cmd": [cmd_str], + "cmd": cmd, "files": files, "private_ip_address_placeholder": constants.PRIVATE_IP_ADDRESS_PLACEHOLDER, "env_vars": env_vars, @@ -279,6 +268,14 @@ def get_config( "node_selectors": node_selectors, } + # Binary injection - override entrypoint and cmd only when binary is provided + if el_binary_artifact != None: + config_args["entrypoint"] = ["sh", "-c"] + config_args["cmd"] = [ + "cp /opt/bin/nethermind /nethermind/nethermind && /nethermind/nethermind " + + " ".join(cmd) + ] + if participant.el_min_cpu > 0: config_args["min_cpu"] = participant.el_min_cpu if participant.el_max_cpu > 0: diff --git a/src/el/nimbus-eth1/nimbus_launcher.star b/src/el/nimbus-eth1/nimbus_launcher.star index 3603f4190..b116b354f 100644 --- a/src/el/nimbus-eth1/nimbus_launcher.star +++ b/src/el/nimbus-eth1/nimbus_launcher.star @@ -227,23 +227,12 @@ def get_config( if el_binary_artifact != None: files["/opt/bin"] = el_binary_artifact - # Build the command string, copying injected binary if provided - cmd_str = " ".join(cmd) - if el_binary_artifact != None: - cmd_str = ( - "cp /opt/bin/nimbus_execution_client /home/user/nimbus-eth1/build/nimbus_execution_client && exec /home/user/nimbus-eth1/build/nimbus_execution_client " - + cmd_str - ) - else: - cmd_str = "exec /home/user/nimbus-eth1/build/nimbus_execution_client " + cmd_str - env_vars = participant.el_extra_env_vars config_args = { "image": participant.el_image, "ports": used_ports, "public_ports": public_ports, - "entrypoint": ["sh", "-c"], - "cmd": [cmd_str], + "cmd": cmd, "files": files, "private_ip_address_placeholder": constants.PRIVATE_IP_ADDRESS_PLACEHOLDER, "env_vars": env_vars, @@ -260,6 +249,14 @@ def get_config( "node_selectors": node_selectors, } + # Binary injection - override entrypoint and cmd only when binary is provided + if el_binary_artifact != None: + config_args["entrypoint"] = ["sh", "-c"] + config_args["cmd"] = [ + "cp /opt/bin/nimbus_execution_client /home/user/nimbus-eth1/build/nimbus_execution_client && /home/user/nimbus-eth1/build/nimbus_execution_client " + + " ".join(cmd) + ] + if participant.el_min_cpu > 0: config_args["min_cpu"] = participant.el_min_cpu if participant.el_max_cpu > 0: diff --git a/src/el/reth/reth_launcher.star b/src/el/reth/reth_launcher.star index 628533333..2f8074e81 100644 --- a/src/el/reth/reth_launcher.star +++ b/src/el/reth/reth_launcher.star @@ -308,19 +308,11 @@ def get_config( } ) - # Build the command string, copying injected binary if provided - cmd_str = " ".join(cmd) - if el_binary_artifact != None: - cmd_str = "cp /opt/bin/reth /usr/local/bin/reth && exec reth " + cmd_str - else: - cmd_str = "exec reth " + cmd_str - config_args = { "image": image, "ports": used_ports, "public_ports": public_ports, - "entrypoint": ["sh", "-c"], - "cmd": [cmd_str], + "cmd": cmd, "files": files, "private_ip_address_placeholder": constants.PRIVATE_IP_ADDRESS_PLACEHOLDER, "env_vars": env_vars | participant.el_extra_env_vars, @@ -337,6 +329,13 @@ def get_config( "node_selectors": node_selectors, } + # Binary injection - override entrypoint and cmd only when binary is provided + if el_binary_artifact != None: + config_args["entrypoint"] = ["sh", "-c"] + config_args["cmd"] = [ + "cp /opt/bin/reth /usr/local/bin/reth && reth " + " ".join(cmd) + ] + if participant.el_min_cpu > 0: config_args["min_cpu"] = participant.el_min_cpu if participant.el_max_cpu > 0: diff --git a/src/vc/lighthouse.star b/src/vc/lighthouse.star index 029ff023b..703657f52 100644 --- a/src/vc/lighthouse.star +++ b/src/vc/lighthouse.star @@ -134,19 +134,11 @@ def get_config( if vc_binary_artifact != None: files["/opt/bin"] = vc_binary_artifact - # Build the command string, copying injected binary if provided - cmd_str = " ".join(cmd) - if vc_binary_artifact != None: - cmd_str = "cp /opt/bin/lighthouse /usr/local/bin/lighthouse && exec " + cmd_str - else: - cmd_str = "exec " + cmd_str - config_args = { "image": image, "ports": ports, "public_ports": public_ports, - "entrypoint": ["sh", "-c"], - "cmd": [cmd_str], + "cmd": cmd, "files": files, "env_vars": env, "labels": shared_utils.label_maker( @@ -162,6 +154,14 @@ def get_config( "node_selectors": node_selectors, } + # Binary injection - override entrypoint and cmd only when binary is provided + if vc_binary_artifact != None: + config_args["entrypoint"] = ["sh", "-c"] + config_args["cmd"] = [ + "cp /opt/bin/lighthouse /usr/local/bin/lighthouse && lighthouse " + + " ".join(cmd) + ] + if participant.vc_min_cpu > 0: config_args["min_cpu"] = participant.vc_min_cpu if participant.vc_max_cpu > 0: diff --git a/src/vc/lodestar.star b/src/vc/lodestar.star index a1f633859..a17a16299 100644 --- a/src/vc/lodestar.star +++ b/src/vc/lodestar.star @@ -141,22 +141,11 @@ def get_config( if network_params.preset == "minimal": env_vars["LODESTAR_PRESET"] = "minimal" - # Build the command string, copying injected binary if provided - cmd_str = " ".join(cmd) - if vc_binary_artifact != None: - cmd_str = ( - "cp /opt/bin/lodestar /usr/app/packages/cli/bin/lodestar && exec node /usr/app/packages/cli/bin/lodestar " - + cmd_str - ) - else: - cmd_str = "exec node /usr/app/packages/cli/bin/lodestar " + cmd_str - config_args = { "image": image, "ports": ports, "public_ports": public_ports, - "entrypoint": ["sh", "-c"], - "cmd": [cmd_str], + "cmd": cmd, "files": files, "env_vars": env_vars, "labels": shared_utils.label_maker( @@ -172,6 +161,14 @@ def get_config( "node_selectors": node_selectors, } + # Binary injection - override entrypoint and cmd only when binary is provided + if vc_binary_artifact != None: + config_args["entrypoint"] = ["sh", "-c"] + config_args["cmd"] = [ + "cp /opt/bin/lodestar /usr/app/packages/cli/bin/lodestar && node /usr/app/packages/cli/bin/lodestar " + + " ".join(cmd) + ] + if participant.vc_min_cpu > 0: config_args["min_cpu"] = participant.vc_min_cpu if participant.vc_max_cpu > 0: diff --git a/src/vc/nimbus.star b/src/vc/nimbus.star index 95ea9dad5..26ebe9db5 100644 --- a/src/vc/nimbus.star +++ b/src/vc/nimbus.star @@ -120,22 +120,11 @@ def get_config( if vc_binary_artifact != None: files["/opt/bin"] = vc_binary_artifact - # Build the command string, copying injected binary if provided - cmd_str = " ".join(cmd) - if vc_binary_artifact != None: - cmd_str = ( - "cp /opt/bin/nimbus_validator_client /usr/bin/nimbus_validator_client && exec /usr/bin/nimbus_validator_client " - + cmd_str - ) - else: - cmd_str = "exec /usr/bin/nimbus_validator_client " + cmd_str - config_args = { "image": image, "ports": ports, "public_ports": public_ports, - "entrypoint": ["sh", "-c"], - "cmd": [cmd_str], + "cmd": cmd, "files": files, "env_vars": participant.vc_extra_env_vars, "labels": shared_utils.label_maker( @@ -152,6 +141,14 @@ def get_config( "user": User(uid=0, gid=0), } + # Binary injection - override entrypoint and cmd only when binary is provided + if vc_binary_artifact != None: + config_args["entrypoint"] = ["sh", "-c"] + config_args["cmd"] = [ + "cp /opt/bin/nimbus_validator_client /usr/bin/nimbus_validator_client && /usr/bin/nimbus_validator_client " + + " ".join(cmd) + ] + if participant.vc_min_cpu > 0: config_args["min_cpu"] = participant.vc_min_cpu if participant.vc_max_cpu > 0: diff --git a/src/vc/prysm.star b/src/vc/prysm.star index bd2f11069..b2445a4cc 100644 --- a/src/vc/prysm.star +++ b/src/vc/prysm.star @@ -139,22 +139,11 @@ def get_config( if vc_binary_artifact != None: files["/opt/bin"] = vc_binary_artifact - # Build the command string, copying injected binary if provided - cmd_str = " ".join(cmd) - if vc_binary_artifact != None: - cmd_str = ( - "cp /opt/bin/validator /app/cmd/validator/validator && exec /app/cmd/validator/validator " - + cmd_str - ) - else: - cmd_str = "exec /app/cmd/validator/validator " + cmd_str - config_args = { "image": image, "ports": ports, "public_ports": public_ports, - "entrypoint": ["sh", "-c"], - "cmd": [cmd_str], + "cmd": cmd, "files": files, "env_vars": participant.vc_extra_env_vars, "labels": shared_utils.label_maker( @@ -171,6 +160,14 @@ def get_config( "tty_enabled": True, } + # Binary injection - override entrypoint and cmd only when binary is provided + if vc_binary_artifact != None: + config_args["entrypoint"] = ["sh", "-c"] + config_args["cmd"] = [ + "cp /opt/bin/validator /app/cmd/validator/validator && /app/cmd/validator/validator " + + " ".join(cmd) + ] + if participant.vc_min_cpu > 0: config_args["min_cpu"] = participant.vc_min_cpu if participant.vc_max_cpu > 0: diff --git a/src/vc/teku.star b/src/vc/teku.star index 038102123..f6219ac4e 100644 --- a/src/vc/teku.star +++ b/src/vc/teku.star @@ -132,21 +132,11 @@ def get_config( if vc_binary_artifact != None: files["/opt/bin"] = vc_binary_artifact - # Build the command string, copying injected binary if provided - cmd_str = " ".join(cmd) - if vc_binary_artifact != None: - cmd_str = ( - "cp /opt/bin/teku /opt/teku/bin/teku && exec /opt/teku/bin/teku " + cmd_str - ) - else: - cmd_str = "exec /opt/teku/bin/teku " + cmd_str - config_args = { "image": image, "ports": ports, "public_ports": public_ports, - "entrypoint": ["sh", "-c"], - "cmd": [cmd_str], + "cmd": cmd, "files": files, "env_vars": participant.vc_extra_env_vars, "labels": shared_utils.label_maker( @@ -162,6 +152,13 @@ def get_config( "node_selectors": node_selectors, } + # Binary injection - override entrypoint and cmd only when binary is provided + if vc_binary_artifact != None: + config_args["entrypoint"] = ["sh", "-c"] + config_args["cmd"] = [ + "cp /opt/bin/teku /opt/teku/bin/teku && /opt/teku/bin/teku " + " ".join(cmd) + ] + if participant.vc_min_cpu > 0: config_args["min_cpu"] = participant.vc_min_cpu if participant.vc_max_cpu > 0: diff --git a/src/vc/vero.star b/src/vc/vero.star index 47b6c4b2b..8c0ce5f79 100644 --- a/src/vc/vero.star +++ b/src/vc/vero.star @@ -78,19 +78,11 @@ def get_config( if vc_binary_artifact != None: files["/opt/bin"] = vc_binary_artifact - # Build the command string, copying injected binary if provided - cmd_str = " ".join(cmd) - if vc_binary_artifact != None: - cmd_str = "cp /opt/bin/vero /usr/local/bin/vero && exec vero " + cmd_str - else: - cmd_str = "exec vero " + cmd_str - config_args = { "image": image, "ports": ports, "public_ports": public_ports, - "entrypoint": ["sh", "-c"], - "cmd": [cmd_str], + "cmd": cmd, "files": files, "env_vars": participant.vc_extra_env_vars, "labels": shared_utils.label_maker( @@ -106,6 +98,13 @@ def get_config( "node_selectors": node_selectors, } + # Binary injection - override entrypoint and cmd only when binary is provided + if vc_binary_artifact != None: + config_args["entrypoint"] = ["sh", "-c"] + config_args["cmd"] = [ + "cp /opt/bin/vero /usr/local/bin/vero && vero " + " ".join(cmd) + ] + if participant.vc_min_cpu > 0: config_args["min_cpu"] = participant.vc_min_cpu if participant.vc_max_cpu > 0: From e3263bbe65a0330e3d4c2be3589607e1aaee61f8 Mon Sep 17 00:00:00 2001 From: Barnabas Busa Date: Mon, 12 Jan 2026 19:04:58 +0100 Subject: [PATCH 06/12] give an example --- README.md | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 5235a7143..ae9eb8e73 100644 --- a/README.md +++ b/README.md @@ -197,7 +197,12 @@ participants: # When set, the binary will be uploaded and mounted into the container, # replacing the default binary from the Docker image # Useful for rapid debugging with locally compiled binaries - # Example: el_binary_path: "/path/to/reth/target/release/reth" + # IMPORTANT: The path must be within the ethereum-package directory + # You must create a subdirectory in the cloned repository for your binary files + # IMPORTANT: The binary must be compiled on a Linux system with compatible libraries + # matching those in the client's Dockerfile to avoid dependency issues + # Example: Build reth with `cargo build --release --bin reth --target-dir ../ethereum-package/binaries/.` + # Then set: el_binary_path: "./binaries/release/reth" el_binary_path: "" # The log level string that this participant's EL client should log at @@ -283,7 +288,12 @@ participants: # When set, the binary will be uploaded and mounted into the container, # replacing the default binary from the Docker image # Useful for rapid debugging with locally compiled binaries - # Example: cl_binary_path: "/path/to/lighthouse/target/release/lighthouse" + # IMPORTANT: The path must be within the ethereum-package directory + # You must create a subdirectory in the cloned repository for your binary files + # IMPORTANT: The binary must be compiled on a Linux system with compatible libraries + # matching those in the client's Dockerfile to avoid dependency issues + # Example: Build lighthouse with `cargo build --release --bin lighthouse --target-dir ../ethereum-package/binaries/.` + # Then set: cl_binary_path: "./binaries/release/lighthouse" cl_binary_path: "" # The log level string that this participant's CL client should log at @@ -372,7 +382,12 @@ participants: # When set, the binary will be uploaded and mounted into the container, # replacing the default binary from the Docker image # Useful for rapid debugging with locally compiled binaries - # Example: vc_binary_path: "/path/to/lighthouse/target/release/lighthouse" + # IMPORTANT: The path must be within the ethereum-package directory + # You must create a subdirectory in the cloned repository for your binary files + # IMPORTANT: The binary must be compiled on a Linux system with compatible libraries + # matching those in the client's Dockerfile to avoid dependency issues + # Example: Build lighthouse with `cargo build --release --bin lighthouse --target-dir ../ethereum-package/binaries/.` + # Then set: vc_binary_path: "./binaries/release/lighthouse" vc_binary_path: "" # The log level string that this participant's validator client should log at From 61fe4474e7434512b9fc74a981f698b283682d88 Mon Sep 17 00:00:00 2001 From: Barnabas Busa Date: Tue, 13 Jan 2026 09:39:39 +0100 Subject: [PATCH 07/12] fix to be able to use any binary file name --- .gitignore | 4 +++ README.md | 30 +++++++++++++--------- main.star | 9 +++++-- src/cl/grandine/grandine_launcher.star | 9 +++++-- src/cl/lighthouse/lighthouse_launcher.star | 9 +++++-- src/cl/lodestar/lodestar_launcher.star | 7 +++-- src/cl/nimbus/nimbus_launcher.star | 6 +++-- src/cl/prysm/prysm_launcher.star | 7 +++-- src/cl/teku/teku_launcher.star | 9 +++++-- src/el/besu/besu_launcher.star | 7 +++-- src/el/dummy/dummy_launcher.star | 14 ++++++++++ src/el/erigon/erigon_launcher.star | 13 +++++++--- src/el/ethereumjs/ethereumjs_launcher.star | 14 ++++++++++ src/el/ethrex/ethrex_launcher.star | 7 +++-- src/el/geth/geth_launcher.star | 9 +++++-- src/el/nethermind/nethermind_launcher.star | 6 +++-- src/el/nimbus-eth1/nimbus_launcher.star | 6 +++-- src/el/reth/reth_launcher.star | 7 +++-- src/vc/lighthouse.star | 6 +++-- src/vc/lodestar.star | 6 +++-- src/vc/nimbus.star | 6 +++-- src/vc/prysm.star | 6 +++-- src/vc/teku.star | 7 +++-- src/vc/vero.star | 7 +++-- 24 files changed, 158 insertions(+), 53 deletions(-) diff --git a/.gitignore b/.gitignore index 8429d2d0e..0627e57f6 100644 --- a/.gitignore +++ b/.gitignore @@ -87,3 +87,7 @@ yarn-error.log # enclave dumps dump/ + +# binary files +binary +binaries diff --git a/README.md b/README.md index ae9eb8e73..ee9eb495c 100644 --- a/README.md +++ b/README.md @@ -197,12 +197,14 @@ participants: # When set, the binary will be uploaded and mounted into the container, # replacing the default binary from the Docker image # Useful for rapid debugging with locally compiled binaries - # IMPORTANT: The path must be within the ethereum-package directory - # You must create a subdirectory in the cloned repository for your binary files + # IMPORTANT: The binary file must live inside the ethereum-package directory + # Build the client in its own repo, then copy ONLY the binary to ethereum-package + # Do not run builds inside ethereum-package or copy build dependencies - only the final binary # IMPORTANT: The binary must be compiled on a Linux system with compatible libraries # matching those in the client's Dockerfile to avoid dependency issues - # Example: Build reth with `cargo build --release --bin reth --target-dir ../ethereum-package/binaries/.` - # Then set: el_binary_path: "./binaries/release/reth" + # Example workflow (from reth repo): + # cargo build --release --bin reth && cp target/release/reth ../ethereum-package/binaries/ + # Then set: el_binary_path: "./binaries/reth" el_binary_path: "" # The log level string that this participant's EL client should log at @@ -288,12 +290,14 @@ participants: # When set, the binary will be uploaded and mounted into the container, # replacing the default binary from the Docker image # Useful for rapid debugging with locally compiled binaries - # IMPORTANT: The path must be within the ethereum-package directory - # You must create a subdirectory in the cloned repository for your binary files + # IMPORTANT: The binary file must live inside the ethereum-package directory + # Build the client in its own repo, then copy ONLY the binary to ethereum-package + # Do not run builds inside ethereum-package or copy build dependencies - only the final binary # IMPORTANT: The binary must be compiled on a Linux system with compatible libraries # matching those in the client's Dockerfile to avoid dependency issues - # Example: Build lighthouse with `cargo build --release --bin lighthouse --target-dir ../ethereum-package/binaries/.` - # Then set: cl_binary_path: "./binaries/release/lighthouse" + # Example workflow (from lighthouse repo): + # cargo build --release --bin lighthouse && cp target/release/lighthouse ../ethereum-package/binaries/ + # Then set: cl_binary_path: "./binaries/lighthouse" cl_binary_path: "" # The log level string that this participant's CL client should log at @@ -382,12 +386,14 @@ participants: # When set, the binary will be uploaded and mounted into the container, # replacing the default binary from the Docker image # Useful for rapid debugging with locally compiled binaries - # IMPORTANT: The path must be within the ethereum-package directory - # You must create a subdirectory in the cloned repository for your binary files + # IMPORTANT: The binary file must live inside the ethereum-package directory + # Build the client in its own repo, then copy ONLY the binary to ethereum-package + # Do not run builds inside ethereum-package or copy build dependencies - only the final binary # IMPORTANT: The binary must be compiled on a Linux system with compatible libraries # matching those in the client's Dockerfile to avoid dependency issues - # Example: Build lighthouse with `cargo build --release --bin lighthouse --target-dir ../ethereum-package/binaries/.` - # Then set: vc_binary_path: "./binaries/release/lighthouse" + # Example workflow (from lighthouse repo): + # cargo build --release --bin lighthouse && cp target/release/lighthouse ../ethereum-package/binaries/ + # Then set: vc_binary_path: "./binaries/lighthouse" vc_binary_path: "" # The log level string that this participant's validator client should log at diff --git a/main.star b/main.star index 698495081..c42a4ccb0 100644 --- a/main.star +++ b/main.star @@ -121,8 +121,13 @@ def run(plan, args={}): bin_type.upper(), index + 1, bin_path ) ) - participant_binaries[bin_type] = plan.upload_files( - src=bin_path, name="{0}-binary-{1}".format(bin_type, index + 1) + # Extract filename from path and store both artifact and filename + filename = bin_path.split("/")[-1] + participant_binaries[bin_type] = struct( + artifact=plan.upload_files( + src=bin_path, name="{0}-binary-{1}".format(bin_type, index + 1) + ), + filename=filename, ) if participant_binaries: binary_artifacts[index] = participant_binaries diff --git a/src/cl/grandine/grandine_launcher.star b/src/cl/grandine/grandine_launcher.star index 7ff7e6dbc..1004a98cd 100644 --- a/src/cl/grandine/grandine_launcher.star +++ b/src/cl/grandine/grandine_launcher.star @@ -343,12 +343,17 @@ def get_beacon_config( # Binary injection - mount custom binary directory if provided if cl_binary_artifact != None: - files["/opt/bin"] = cl_binary_artifact + files["/opt/bin"] = cl_binary_artifact.artifact # Build the command string, copying injected binary if provided cmd_str = " ".join(cmd) if cl_binary_artifact != None: - cmd_str = "cp /opt/bin/grandine /usr/local/bin/grandine && exec " + cmd_str + cmd_str = ( + "cp /opt/bin/{0} /usr/local/bin/grandine && exec ".format( + cl_binary_artifact.filename + ) + + cmd_str + ) else: cmd_str = "exec " + cmd_str diff --git a/src/cl/lighthouse/lighthouse_launcher.star b/src/cl/lighthouse/lighthouse_launcher.star index 2ddd9d0bc..b8ab8cc94 100644 --- a/src/cl/lighthouse/lighthouse_launcher.star +++ b/src/cl/lighthouse/lighthouse_launcher.star @@ -316,7 +316,7 @@ def get_beacon_config( # Binary injection - mount custom binary directory if provided # The artifact is a directory, so we mount it and reference the binary inside if cl_binary_artifact != None: - files["/opt/bin"] = cl_binary_artifact + files["/opt/bin"] = cl_binary_artifact.artifact env_vars = {RUST_BACKTRACE_ENVVAR_NAME: RUST_FULL_BACKTRACE_KEYWORD} env_vars.update(participant.cl_extra_env_vars) @@ -324,7 +324,12 @@ def get_beacon_config( # Build the command string, copying injected binary if provided cmd_str = " ".join(cmd) if cl_binary_artifact != None: - cmd_str = "cp /opt/bin/lighthouse /usr/local/bin/lighthouse && exec " + cmd_str + cmd_str = ( + "cp /opt/bin/{0} /usr/local/bin/lighthouse && exec ".format( + cl_binary_artifact.filename + ) + + cmd_str + ) else: cmd_str = "exec " + cmd_str diff --git a/src/cl/lodestar/lodestar_launcher.star b/src/cl/lodestar/lodestar_launcher.star index af64e8a86..f84ac13c4 100644 --- a/src/cl/lodestar/lodestar_launcher.star +++ b/src/cl/lodestar/lodestar_launcher.star @@ -291,7 +291,7 @@ def get_beacon_config( # Binary injection - mount custom binary directory if provided if cl_binary_artifact != None: - files["/opt/bin"] = cl_binary_artifact + files["/opt/bin"] = cl_binary_artifact.artifact env_vars = participant.cl_extra_env_vars @@ -302,7 +302,10 @@ def get_beacon_config( cmd_str = " ".join(cmd) if cl_binary_artifact != None: cmd_str = ( - "cp /opt/bin/lodestar /usr/app/packages/cli/bin/lodestar && exec " + cmd_str + "cp /opt/bin/{0} /usr/app/packages/cli/bin/lodestar && exec ".format( + cl_binary_artifact.filename + ) + + cmd_str ) else: cmd_str = "exec " + cmd_str diff --git a/src/cl/nimbus/nimbus_launcher.star b/src/cl/nimbus/nimbus_launcher.star index 99dbd59f8..b5c512b41 100644 --- a/src/cl/nimbus/nimbus_launcher.star +++ b/src/cl/nimbus/nimbus_launcher.star @@ -347,13 +347,15 @@ def get_beacon_config( # Binary injection - mount custom binary directory if provided if cl_binary_artifact != None: - files["/opt/bin"] = cl_binary_artifact + files["/opt/bin"] = cl_binary_artifact.artifact cmd_str = " ".join(cmd) # Add binary copy prefix if injected if cl_binary_artifact != None: cmd_str = ( - "cp /opt/bin/nimbus_beacon_node /home/user/nimbus-eth2/build/nimbus_beacon_node && " + "cp /opt/bin/{0} /home/user/nimbus-eth2/build/nimbus_beacon_node && ".format( + cl_binary_artifact.filename + ) + cmd_str ) if checkpoint_sync_enabled: diff --git a/src/cl/prysm/prysm_launcher.star b/src/cl/prysm/prysm_launcher.star index d42a014b8..98ee3ad32 100644 --- a/src/cl/prysm/prysm_launcher.star +++ b/src/cl/prysm/prysm_launcher.star @@ -332,12 +332,15 @@ def get_beacon_config( # Binary injection - mount custom binary directory if provided if cl_binary_artifact != None: - files["/opt/bin"] = cl_binary_artifact + files["/opt/bin"] = cl_binary_artifact.artifact # Build the command string, copying injected binary if provided cmd_str = " ".join(cmd) if cl_binary_artifact != None: - cmd_str = "cp /opt/bin/beacon-chain /beacon-chain && exec " + cmd_str + cmd_str = ( + "cp /opt/bin/{0} /beacon-chain && exec ".format(cl_binary_artifact.filename) + + cmd_str + ) else: cmd_str = "exec " + cmd_str diff --git a/src/cl/teku/teku_launcher.star b/src/cl/teku/teku_launcher.star index 7083142b9..87a9ce620 100644 --- a/src/cl/teku/teku_launcher.star +++ b/src/cl/teku/teku_launcher.star @@ -354,12 +354,17 @@ def get_beacon_config( # Binary injection - mount custom binary directory if provided if cl_binary_artifact != None: - files["/opt/bin"] = cl_binary_artifact + files["/opt/bin"] = cl_binary_artifact.artifact # Build the command string, copying injected binary if provided cmd_str = " ".join(cmd) if cl_binary_artifact != None: - cmd_str = "cp /opt/bin/teku /opt/teku/bin/teku && exec " + cmd_str + cmd_str = ( + "cp /opt/bin/{0} /opt/teku/bin/teku && exec ".format( + cl_binary_artifact.filename + ) + + cmd_str + ) else: cmd_str = "exec " + cmd_str diff --git a/src/el/besu/besu_launcher.star b/src/el/besu/besu_launcher.star index 4a62914a0..8e70c3cb4 100644 --- a/src/el/besu/besu_launcher.star +++ b/src/el/besu/besu_launcher.star @@ -251,9 +251,12 @@ def get_config( # Binary injection - mount custom binary directory if provided if el_binary_artifact != None: - files["/opt/bin"] = el_binary_artifact + files["/opt/bin"] = el_binary_artifact.artifact # Copy injected binary to override default, then run original command - final_cmd_str = "cp /opt/bin/besu /opt/besu/bin/besu && " + cmd_str + final_cmd_str = ( + "cp /opt/bin/{0} /opt/besu/bin/besu && ".format(el_binary_artifact.filename) + + cmd_str + ) else: final_cmd_str = cmd_str diff --git a/src/el/dummy/dummy_launcher.star b/src/el/dummy/dummy_launcher.star index 427bf2c0c..952649fe8 100644 --- a/src/el/dummy/dummy_launcher.star +++ b/src/el/dummy/dummy_launcher.star @@ -154,6 +154,10 @@ def get_config( for mount_path, artifact in processed_mounts.items(): files[mount_path] = artifact + # Binary injection - mount custom binary directory if provided + if el_binary_artifact != None: + files["/opt/bin"] = el_binary_artifact.artifact + env_vars = participant.el_extra_env_vars if "RUST_LOG" not in env_vars: env_vars = env_vars | {"RUST_LOG": log_level} @@ -179,6 +183,16 @@ def get_config( "node_selectors": node_selectors, } + # Binary injection - override entrypoint and cmd only when binary is provided + if el_binary_artifact != None: + config_args["entrypoint"] = ["sh", "-c"] + config_args["cmd"] = [ + "cp /opt/bin/{0} /usr/local/bin/dummy && dummy ".format( + el_binary_artifact.filename + ) + + " ".join(cmd) + ] + if participant.el_min_cpu > 0: config_args["min_cpu"] = participant.el_min_cpu if participant.el_max_cpu > 0: diff --git a/src/el/erigon/erigon_launcher.star b/src/el/erigon/erigon_launcher.star index 274ff4c5d..beb6bb148 100644 --- a/src/el/erigon/erigon_launcher.star +++ b/src/el/erigon/erigon_launcher.star @@ -249,7 +249,7 @@ def get_config( # Binary injection - mount custom binary directory if provided if el_binary_artifact != None: - files["/opt/bin"] = el_binary_artifact + files["/opt/bin"] = el_binary_artifact.artifact # Build command with optional binary copy cmd_str = " ".join(cmd) @@ -257,14 +257,21 @@ def get_config( if el_binary_artifact != None: command_arg_str = ( init_datadir_cmd_str - + " && cp /opt/bin/erigon /usr/local/bin/erigon && " + + " && cp /opt/bin/{0} /usr/local/bin/erigon && ".format( + el_binary_artifact.filename + ) + cmd_str ) else: command_arg_str = init_datadir_cmd_str + " && " + cmd_str else: if el_binary_artifact != None: - command_arg_str = "cp /opt/bin/erigon /usr/local/bin/erigon && " + cmd_str + command_arg_str = ( + "cp /opt/bin/{0} /usr/local/bin/erigon && ".format( + el_binary_artifact.filename + ) + + cmd_str + ) else: command_arg_str = cmd_str diff --git a/src/el/ethereumjs/ethereumjs_launcher.star b/src/el/ethereumjs/ethereumjs_launcher.star index 84d29a910..fb4e548fd 100644 --- a/src/el/ethereumjs/ethereumjs_launcher.star +++ b/src/el/ethereumjs/ethereumjs_launcher.star @@ -240,6 +240,10 @@ def get_config( for mount_path, artifact in processed_mounts.items(): files[mount_path] = artifact + # Binary injection - mount custom binary directory if provided + if el_binary_artifact != None: + files["/opt/bin"] = el_binary_artifact.artifact + env_vars = participant.el_extra_env_vars config_args = { "image": participant.el_image, @@ -263,6 +267,16 @@ def get_config( "node_selectors": node_selectors, } + # Binary injection - override entrypoint and cmd only when binary is provided + if el_binary_artifact != None: + config_args["entrypoint"] = ["sh", "-c"] + config_args["cmd"] = [ + "cp /opt/bin/{0} /usr/app/packages/client/bin/cli.js && node /usr/app/packages/client/bin/cli.js ".format( + el_binary_artifact.filename + ) + + " ".join(cmd) + ] + if participant.el_min_cpu > 0: config_args["min_cpu"] = participant.el_min_cpu if participant.el_max_cpu > 0: diff --git a/src/el/ethrex/ethrex_launcher.star b/src/el/ethrex/ethrex_launcher.star index c077f7d04..927db1146 100644 --- a/src/el/ethrex/ethrex_launcher.star +++ b/src/el/ethrex/ethrex_launcher.star @@ -235,7 +235,7 @@ def get_config( # Binary injection - mount custom binary directory if provided if el_binary_artifact != None: - files["/opt/bin"] = el_binary_artifact + files["/opt/bin"] = el_binary_artifact.artifact config_args = { "image": participant.el_image, @@ -262,7 +262,10 @@ def get_config( if el_binary_artifact != None: config_args["entrypoint"] = ["sh", "-c"] config_args["cmd"] = [ - "cp /opt/bin/ethrex /usr/local/bin/ethrex && ethrex " + " ".join(cmd) + "cp /opt/bin/{0} /usr/local/bin/ethrex && ethrex ".format( + el_binary_artifact.filename + ) + + " ".join(cmd) ] if participant.el_min_cpu > 0: diff --git a/src/el/geth/geth_launcher.star b/src/el/geth/geth_launcher.star index 7919d1c65..7d96adb14 100644 --- a/src/el/geth/geth_launcher.star +++ b/src/el/geth/geth_launcher.star @@ -283,9 +283,14 @@ def get_config( # Binary injection - mount custom binary directory if provided if el_binary_artifact != None: - files["/opt/bin"] = el_binary_artifact + files["/opt/bin"] = el_binary_artifact.artifact # Copy injected binary to override default, then run original command - cmd_str = "cp /opt/bin/geth /usr/local/bin/geth && " + command_str + cmd_str = ( + "cp /opt/bin/{0} /usr/local/bin/geth && ".format( + el_binary_artifact.filename + ) + + command_str + ) else: cmd_str = command_str diff --git a/src/el/nethermind/nethermind_launcher.star b/src/el/nethermind/nethermind_launcher.star index 9205bbaa5..8407765af 100644 --- a/src/el/nethermind/nethermind_launcher.star +++ b/src/el/nethermind/nethermind_launcher.star @@ -244,7 +244,7 @@ def get_config( # Binary injection - mount custom binary directory if provided if el_binary_artifact != None: - files["/opt/bin"] = el_binary_artifact + files["/opt/bin"] = el_binary_artifact.artifact env_vars = participant.el_extra_env_vars config_args = { @@ -272,7 +272,9 @@ def get_config( if el_binary_artifact != None: config_args["entrypoint"] = ["sh", "-c"] config_args["cmd"] = [ - "cp /opt/bin/nethermind /nethermind/nethermind && /nethermind/nethermind " + "cp /opt/bin/{0} /nethermind/nethermind && /nethermind/nethermind ".format( + el_binary_artifact.filename + ) + " ".join(cmd) ] diff --git a/src/el/nimbus-eth1/nimbus_launcher.star b/src/el/nimbus-eth1/nimbus_launcher.star index b116b354f..f067f655b 100644 --- a/src/el/nimbus-eth1/nimbus_launcher.star +++ b/src/el/nimbus-eth1/nimbus_launcher.star @@ -225,7 +225,7 @@ def get_config( # Binary injection - mount custom binary directory if provided if el_binary_artifact != None: - files["/opt/bin"] = el_binary_artifact + files["/opt/bin"] = el_binary_artifact.artifact env_vars = participant.el_extra_env_vars config_args = { @@ -253,7 +253,9 @@ def get_config( if el_binary_artifact != None: config_args["entrypoint"] = ["sh", "-c"] config_args["cmd"] = [ - "cp /opt/bin/nimbus_execution_client /home/user/nimbus-eth1/build/nimbus_execution_client && /home/user/nimbus-eth1/build/nimbus_execution_client " + "cp /opt/bin/{0} /home/user/nimbus-eth1/build/nimbus_execution_client && /home/user/nimbus-eth1/build/nimbus_execution_client ".format( + el_binary_artifact.filename + ) + " ".join(cmd) ] diff --git a/src/el/reth/reth_launcher.star b/src/el/reth/reth_launcher.star index 2f8074e81..94123aa06 100644 --- a/src/el/reth/reth_launcher.star +++ b/src/el/reth/reth_launcher.star @@ -273,7 +273,7 @@ def get_config( # Binary injection - mount custom binary directory if provided # The artifact is a directory, so we mount it and reference the binary inside if el_binary_artifact != None: - files["/opt/bin"] = el_binary_artifact + files["/opt/bin"] = el_binary_artifact.artifact env_vars = {} image = participant.el_image @@ -333,7 +333,10 @@ def get_config( if el_binary_artifact != None: config_args["entrypoint"] = ["sh", "-c"] config_args["cmd"] = [ - "cp /opt/bin/reth /usr/local/bin/reth && reth " + " ".join(cmd) + "cp /opt/bin/{0} /usr/local/bin/reth && reth ".format( + el_binary_artifact.filename + ) + + " ".join(cmd) ] if participant.el_min_cpu > 0: diff --git a/src/vc/lighthouse.star b/src/vc/lighthouse.star index 703657f52..5e54862cf 100644 --- a/src/vc/lighthouse.star +++ b/src/vc/lighthouse.star @@ -132,7 +132,7 @@ def get_config( # Binary injection - mount custom binary directory if provided if vc_binary_artifact != None: - files["/opt/bin"] = vc_binary_artifact + files["/opt/bin"] = vc_binary_artifact.artifact config_args = { "image": image, @@ -158,7 +158,9 @@ def get_config( if vc_binary_artifact != None: config_args["entrypoint"] = ["sh", "-c"] config_args["cmd"] = [ - "cp /opt/bin/lighthouse /usr/local/bin/lighthouse && lighthouse " + "cp /opt/bin/{0} /usr/local/bin/lighthouse && lighthouse ".format( + vc_binary_artifact.filename + ) + " ".join(cmd) ] diff --git a/src/vc/lodestar.star b/src/vc/lodestar.star index a17a16299..61ff0445b 100644 --- a/src/vc/lodestar.star +++ b/src/vc/lodestar.star @@ -135,7 +135,7 @@ def get_config( # Binary injection - mount custom binary directory if provided if vc_binary_artifact != None: - files["/opt/bin"] = vc_binary_artifact + files["/opt/bin"] = vc_binary_artifact.artifact env_vars = participant.vc_extra_env_vars if network_params.preset == "minimal": @@ -165,7 +165,9 @@ def get_config( if vc_binary_artifact != None: config_args["entrypoint"] = ["sh", "-c"] config_args["cmd"] = [ - "cp /opt/bin/lodestar /usr/app/packages/cli/bin/lodestar && node /usr/app/packages/cli/bin/lodestar " + "cp /opt/bin/{0} /usr/app/packages/cli/bin/lodestar && node /usr/app/packages/cli/bin/lodestar ".format( + vc_binary_artifact.filename + ) + " ".join(cmd) ] diff --git a/src/vc/nimbus.star b/src/vc/nimbus.star index 26ebe9db5..201c3849a 100644 --- a/src/vc/nimbus.star +++ b/src/vc/nimbus.star @@ -118,7 +118,7 @@ def get_config( # Binary injection - mount custom binary directory if provided if vc_binary_artifact != None: - files["/opt/bin"] = vc_binary_artifact + files["/opt/bin"] = vc_binary_artifact.artifact config_args = { "image": image, @@ -145,7 +145,9 @@ def get_config( if vc_binary_artifact != None: config_args["entrypoint"] = ["sh", "-c"] config_args["cmd"] = [ - "cp /opt/bin/nimbus_validator_client /usr/bin/nimbus_validator_client && /usr/bin/nimbus_validator_client " + "cp /opt/bin/{0} /usr/bin/nimbus_validator_client && /usr/bin/nimbus_validator_client ".format( + vc_binary_artifact.filename + ) + " ".join(cmd) ] diff --git a/src/vc/prysm.star b/src/vc/prysm.star index b2445a4cc..171f69293 100644 --- a/src/vc/prysm.star +++ b/src/vc/prysm.star @@ -137,7 +137,7 @@ def get_config( # Binary injection - mount custom binary directory if provided if vc_binary_artifact != None: - files["/opt/bin"] = vc_binary_artifact + files["/opt/bin"] = vc_binary_artifact.artifact config_args = { "image": image, @@ -164,7 +164,9 @@ def get_config( if vc_binary_artifact != None: config_args["entrypoint"] = ["sh", "-c"] config_args["cmd"] = [ - "cp /opt/bin/validator /app/cmd/validator/validator && /app/cmd/validator/validator " + "cp /opt/bin/{0} /app/cmd/validator/validator && /app/cmd/validator/validator ".format( + vc_binary_artifact.filename + ) + " ".join(cmd) ] diff --git a/src/vc/teku.star b/src/vc/teku.star index f6219ac4e..838c2bfc2 100644 --- a/src/vc/teku.star +++ b/src/vc/teku.star @@ -130,7 +130,7 @@ def get_config( # Binary injection - mount custom binary directory if provided if vc_binary_artifact != None: - files["/opt/bin"] = vc_binary_artifact + files["/opt/bin"] = vc_binary_artifact.artifact config_args = { "image": image, @@ -156,7 +156,10 @@ def get_config( if vc_binary_artifact != None: config_args["entrypoint"] = ["sh", "-c"] config_args["cmd"] = [ - "cp /opt/bin/teku /opt/teku/bin/teku && /opt/teku/bin/teku " + " ".join(cmd) + "cp /opt/bin/{0} /opt/teku/bin/teku && /opt/teku/bin/teku ".format( + vc_binary_artifact.filename + ) + + " ".join(cmd) ] if participant.vc_min_cpu > 0: diff --git a/src/vc/vero.star b/src/vc/vero.star index 8c0ce5f79..44c1b960e 100644 --- a/src/vc/vero.star +++ b/src/vc/vero.star @@ -76,7 +76,7 @@ def get_config( # Binary injection - mount custom binary directory if provided if vc_binary_artifact != None: - files["/opt/bin"] = vc_binary_artifact + files["/opt/bin"] = vc_binary_artifact.artifact config_args = { "image": image, @@ -102,7 +102,10 @@ def get_config( if vc_binary_artifact != None: config_args["entrypoint"] = ["sh", "-c"] config_args["cmd"] = [ - "cp /opt/bin/vero /usr/local/bin/vero && vero " + " ".join(cmd) + "cp /opt/bin/{0} /usr/local/bin/vero && vero ".format( + vc_binary_artifact.filename + ) + + " ".join(cmd) ] if participant.vc_min_cpu > 0: From 2574b70edae0c6f9f5d2df6360470e2a81cdbcf9 Mon Sep 17 00:00:00 2001 From: Barnabas Busa Date: Tue, 13 Jan 2026 14:27:34 +0100 Subject: [PATCH 08/12] feat: add force_restart - to be able to replace images/binaries without killing enclave --- README.md | 18 ++++++++++++++++++ src/cl/cl_launcher.star | 9 ++++++++- src/cl/lighthouse/lighthouse_launcher.star | 4 ++++ src/el/el_launcher.star | 1 - src/package_io/input_parser.star | 6 ++++++ src/package_io/sanity_check.star | 7 +++++++ 6 files changed, 43 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ee9eb495c..c4b0879cb 100644 --- a/README.md +++ b/README.md @@ -271,6 +271,12 @@ participants: el_min_mem: 0 el_max_mem: 0 + # Force container recreation on next run (Docker only) + # When set to true, the container will be recreated even if the image tag hasn't changed + # Useful when rebuilding Docker images with the same tag or recompiling binaries with the same name + # Defaults to false + el_force_restart: false + # CL(Consensus Layer) Specific flags # The type of CL client that should be started # Valid values are nimbus, lighthouse, lodestar, teku, prysm, and grandine @@ -355,6 +361,12 @@ participants: cl_min_mem: 0 cl_max_mem: 0 + # Force container recreation on next run (Docker only) + # When set to true, the container will be recreated even if the image tag hasn't changed + # Useful when rebuilding Docker images with the same tag or recompiling binaries with the same name + # Defaults to false + cl_force_restart: false + # Whether to act as a supernode for the network # Supernodes will subscribe to all subnet topics # This flag should only be used with peerdas @@ -446,6 +458,12 @@ participants: vc_min_mem: 0 vc_max_mem: 0 + # Force container recreation on next run (Docker only) + # When set to true, the container will be recreated even if the image tag hasn't changed + # Useful when rebuilding Docker images with the same tag or recompiling binaries with the same name + # Defaults to false + vc_force_restart: false + # A list of indices of the beacon nodes that the validator client should connect to # Defaults to null vc_beacon_node_indices: null diff --git a/src/cl/cl_launcher.star b/src/cl/cl_launcher.star index 3cbc641fc..e15cace03 100644 --- a/src/cl/cl_launcher.star +++ b/src/cl/cl_launcher.star @@ -227,7 +227,6 @@ def launch( all_snooper_el_engine_contexts.append(snooper_el_engine_context) full_name = "{0}-{1}-{2}".format(index_str, el_type, cl_type) - # Get binary artifact for this participant if it exists cl_binary_artifact = None if index in binary_artifacts and "cl" in binary_artifacts[index]: cl_binary_artifact = binary_artifacts[index]["cl"] @@ -319,6 +318,7 @@ def launch( "get_cl_context": get_cl_context, "get_blobber_config": get_blobber_config, "participant_index": index, + "cl_type": cl_type, } # add rest of cl's in parallel to speed package execution @@ -326,6 +326,13 @@ def launch( if len(cl_service_configs) > 0: cl_services = plan.add_services(cl_service_configs) + # Handle force_restart for lighthouse services by removing and re-adding + for service_name in cl_services.keys(): + info = cl_participant_info[service_name] + if info["cl_type"] == constants.CL_TYPE.lighthouse and info["participant"].cl_force_restart: + plan.remove_service(service_name) + cl_services[service_name] = plan.add_service(service_name, cl_service_configs[service_name]) + # Create CL contexts ordered by participant index cl_contexts_temp = {} blobber_configs_temp = {} diff --git a/src/cl/lighthouse/lighthouse_launcher.star b/src/cl/lighthouse/lighthouse_launcher.star index b8ab8cc94..9cf602c1d 100644 --- a/src/cl/lighthouse/lighthouse_launcher.star +++ b/src/cl/lighthouse/lighthouse_launcher.star @@ -92,6 +92,10 @@ def launch( beacon_service = plan.add_service(beacon_service_name, beacon_config) + if participant.cl_force_restart: + plan.remove_service(beacon_service_name) + beacon_service = plan.add_service(beacon_service_name, beacon_config) + cl_context_obj = get_cl_context( plan, beacon_service_name, diff --git a/src/el/el_launcher.star b/src/el/el_launcher.star index f7fc45b22..de7333360 100644 --- a/src/el/el_launcher.star +++ b/src/el/el_launcher.star @@ -176,7 +176,6 @@ def launch( el_service_name = "el-{0}-{1}-{2}".format(index_str, el_type, cl_type) - # Get binary artifact for this participant if it exists el_binary_artifact = None if index in binary_artifacts and "el" in binary_artifacts[index]: el_binary_artifact = binary_artifacts[index]["el"] diff --git a/src/package_io/input_parser.star b/src/package_io/input_parser.star index ed8bdd00b..b094ed4fe 100644 --- a/src/package_io/input_parser.star +++ b/src/package_io/input_parser.star @@ -494,14 +494,17 @@ def input_parser(plan, input_args): el_max_cpu=participant["el_max_cpu"], el_min_mem=participant["el_min_mem"], el_max_mem=participant["el_max_mem"], + el_force_restart=participant["el_force_restart"], cl_min_cpu=participant["cl_min_cpu"], cl_max_cpu=participant["cl_max_cpu"], cl_min_mem=participant["cl_min_mem"], cl_max_mem=participant["cl_max_mem"], + cl_force_restart=participant["cl_force_restart"], vc_min_cpu=participant["vc_min_cpu"], vc_max_cpu=participant["vc_max_cpu"], vc_min_mem=participant["vc_min_mem"], vc_max_mem=participant["vc_max_mem"], + vc_force_restart=participant["vc_force_restart"], remote_signer_min_cpu=participant["remote_signer_min_cpu"], remote_signer_max_cpu=participant["remote_signer_max_cpu"], remote_signer_min_mem=participant["remote_signer_min_mem"], @@ -1472,6 +1475,7 @@ def default_participant(): "el_max_cpu": 0, "el_min_mem": 0, "el_max_mem": 0, + "el_force_restart": False, "cl_type": "lighthouse", "cl_image": "", "cl_binary_path": "", @@ -1487,6 +1491,7 @@ def default_participant(): "cl_max_cpu": 0, "cl_min_mem": 0, "cl_max_mem": 0, + "cl_force_restart": False, "supernode": False, "use_separate_vc": None, "vc_type": "", @@ -1503,6 +1508,7 @@ def default_participant(): "vc_max_cpu": 0, "vc_min_mem": 0, "vc_max_mem": 0, + "vc_force_restart": False, "use_remote_signer": None, "remote_signer_type": "web3signer", "remote_signer_image": "", diff --git a/src/package_io/sanity_check.star b/src/package_io/sanity_check.star index f2ad82767..0e5d7b239 100644 --- a/src/package_io/sanity_check.star +++ b/src/package_io/sanity_check.star @@ -16,6 +16,7 @@ PARTICIPANT_CATEGORIES = { "el_max_cpu", "el_min_mem", "el_max_mem", + "el_force_restart", "cl_type", "cl_image", "cl_binary_path", @@ -31,6 +32,7 @@ PARTICIPANT_CATEGORIES = { "cl_max_cpu", "cl_min_mem", "cl_max_mem", + "cl_force_restart", "supernode", "use_separate_vc", "vc_type", @@ -47,6 +49,7 @@ PARTICIPANT_CATEGORIES = { "vc_max_cpu", "vc_min_mem", "vc_max_mem", + "vc_force_restart", "validator_count", "use_remote_signer", "remote_signer_type", @@ -96,6 +99,7 @@ PARTICIPANT_MATRIX_PARAMS = { "el_max_cpu", "el_min_mem", "el_max_mem", + "el_force_restart", ], "cl": [ "cl_type", @@ -127,11 +131,13 @@ PARTICIPANT_MATRIX_PARAMS = { "vc_max_cpu", "vc_min_mem", "vc_max_mem", + "vc_force_restart", "validator_count", "count", "supernode", "vc_beacon_node_indices", "checkpoint_sync_enabled", + "cl_force_restart", ], "vc": [ "vc_type", @@ -148,6 +154,7 @@ PARTICIPANT_MATRIX_PARAMS = { "vc_max_cpu", "vc_min_mem", "vc_max_mem", + "vc_force_restart", "validator_count", ], "remote_signer": [ From 8c76dd9907fdda3d0a30e0b42fb158ba6b035845 Mon Sep 17 00:00:00 2001 From: Barnabas Busa Date: Tue, 13 Jan 2026 17:05:33 +0100 Subject: [PATCH 09/12] fix force restart for el/vc/cl --- main.star | 40 ++++--------- src/cl/cl_launcher.star | 35 ++++++++---- src/cl/lighthouse/lighthouse_launcher.star | 9 +-- src/el/besu/besu_launcher.star | 4 +- src/el/dummy/dummy_launcher.star | 4 +- src/el/el_launcher.star | 29 ++++++++-- src/el/erigon/erigon_launcher.star | 4 +- src/el/ethereumjs/ethereumjs_launcher.star | 4 +- src/el/ethrex/ethrex_launcher.star | 4 +- src/el/geth/geth_launcher.star | 5 +- src/el/nethermind/nethermind_launcher.star | 4 +- src/el/nimbus-eth1/nimbus_launcher.star | 4 +- src/el/reth/reth_launcher.star | 4 +- src/participant_network.star | 66 ++++++++++++++++++++-- 14 files changed, 147 insertions(+), 69 deletions(-) diff --git a/main.star b/main.star index c42a4ccb0..d6b1b9907 100644 --- a/main.star +++ b/main.star @@ -99,38 +99,19 @@ def run(plan, args={}): artifact = plan.render_templates(template_data, name + "_artifact") extra_files_artifacts[name] = artifact - # Process binary injection - upload local binaries for participants - # NOTE: Binary injection is only supported with Docker backend - binary_artifacts = {} - for index, participant in enumerate(args_with_right_defaults.participants): - participant_binaries = {} - for bin_type, bin_path in [ - ("el", participant.el_binary_path), - ("cl", participant.cl_binary_path), - ("vc", participant.vc_binary_path), + # Validate binary injection - only supported with Docker backend + for participant in args_with_right_defaults.participants: + for bin_path in [ + participant.el_binary_path, + participant.cl_binary_path, + participant.vc_binary_path, ]: - if bin_path: - if detected_backend != "docker": - fail( - "Binary injection (*_binary_path) is only supported with Docker backend, detected: {0}".format( - detected_backend - ) - ) - plan.print( - "Uploading {0} binary for participant {1}: {2}".format( - bin_type.upper(), index + 1, bin_path + if bin_path and detected_backend != "docker": + fail( + "Binary injection (*_binary_path) is only supported with Docker backend, detected: {0}".format( + detected_backend ) ) - # Extract filename from path and store both artifact and filename - filename = bin_path.split("/")[-1] - participant_binaries[bin_type] = struct( - artifact=plan.upload_files( - src=bin_path, name="{0}-binary-{1}".format(bin_type, index + 1) - ), - filename=filename, - ) - if participant_binaries: - binary_artifacts[index] = participant_binaries mev_params = args_with_right_defaults.mev_params parallel_keystore_generation = args_with_right_defaults.parallel_keystore_generation @@ -290,7 +271,6 @@ def run(plan, args={}): extra_files_artifacts, tempo_otlp_grpc_url, detected_backend, - binary_artifacts, ) plan.print( diff --git a/src/cl/cl_launcher.star b/src/cl/cl_launcher.star index e15cace03..ec328c8ef 100644 --- a/src/cl/cl_launcher.star +++ b/src/cl/cl_launcher.star @@ -227,9 +227,7 @@ def launch( all_snooper_el_engine_contexts.append(snooper_el_engine_context) full_name = "{0}-{1}-{2}".format(index_str, el_type, cl_type) - cl_binary_artifact = None - if index in binary_artifacts and "cl" in binary_artifacts[index]: - cl_binary_artifact = binary_artifacts[index]["cl"] + cl_binary_artifact = binary_artifacts.get(index, {}).get("cl", None) if index == 0: cl_context = launch_method( @@ -322,16 +320,29 @@ def launch( } # add rest of cl's in parallel to speed package execution + # Identify which services need force_update + force_restart_service_names = [] + for service_name in cl_service_configs.keys(): + participant = cl_participant_info[service_name]["participant"] + if participant.cl_force_restart == True: + force_restart_service_names.append(service_name) + + # Remove force_restart services from batch config + regular_configs = { + k: v + for k, v in cl_service_configs.items() + if k not in force_restart_service_names + } + cl_services = {} - if len(cl_service_configs) > 0: - cl_services = plan.add_services(cl_service_configs) - - # Handle force_restart for lighthouse services by removing and re-adding - for service_name in cl_services.keys(): - info = cl_participant_info[service_name] - if info["cl_type"] == constants.CL_TYPE.lighthouse and info["participant"].cl_force_restart: - plan.remove_service(service_name) - cl_services[service_name] = plan.add_service(service_name, cl_service_configs[service_name]) + if len(regular_configs) > 0: + cl_services = plan.add_services(regular_configs) + + # Add force_restart services individually with force_update=True + for service_name in force_restart_service_names: + cl_services[service_name] = plan.add_service( + service_name, cl_service_configs[service_name], force_update=True + ) # Create CL contexts ordered by participant index cl_contexts_temp = {} diff --git a/src/cl/lighthouse/lighthouse_launcher.star b/src/cl/lighthouse/lighthouse_launcher.star index 9cf602c1d..427a4b620 100644 --- a/src/cl/lighthouse/lighthouse_launcher.star +++ b/src/cl/lighthouse/lighthouse_launcher.star @@ -63,7 +63,6 @@ def launch( bootnode_enr_override=None, cl_binary_artifact=None, ): - # Launch Beacon node beacon_config = get_beacon_config( plan, launcher, @@ -90,11 +89,9 @@ def launch( cl_binary_artifact, ) - beacon_service = plan.add_service(beacon_service_name, beacon_config) - - if participant.cl_force_restart: - plan.remove_service(beacon_service_name) - beacon_service = plan.add_service(beacon_service_name, beacon_config) + beacon_service = plan.add_service( + beacon_service_name, beacon_config, force_update=participant.cl_force_restart + ) cl_context_obj = get_cl_context( plan, diff --git a/src/el/besu/besu_launcher.star b/src/el/besu/besu_launcher.star index 8e70c3cb4..885d3941f 100644 --- a/src/el/besu/besu_launcher.star +++ b/src/el/besu/besu_launcher.star @@ -68,7 +68,9 @@ def launch( el_binary_artifact, ) - service = plan.add_service(service_name, config) + service = plan.add_service( + service_name, config, force_update=participant.el_force_restart + ) return get_el_context( plan, diff --git a/src/el/dummy/dummy_launcher.star b/src/el/dummy/dummy_launcher.star index 952649fe8..aa1e7ef12 100644 --- a/src/el/dummy/dummy_launcher.star +++ b/src/el/dummy/dummy_launcher.star @@ -58,7 +58,9 @@ def launch( el_binary_artifact, ) - service = plan.add_service(service_name, config) + service = plan.add_service( + service_name, config, force_update=participant.el_force_restart + ) return get_el_context( plan, diff --git a/src/el/el_launcher.star b/src/el/el_launcher.star index de7333360..285402433 100644 --- a/src/el/el_launcher.star +++ b/src/el/el_launcher.star @@ -175,10 +175,7 @@ def launch( index_str = shared_utils.zfill_custom(index + 1, len(str(len(participants)))) el_service_name = "el-{0}-{1}-{2}".format(index_str, el_type, cl_type) - - el_binary_artifact = None - if index in binary_artifacts and "el" in binary_artifacts[index]: - el_binary_artifact = binary_artifacts[index]["el"] + el_binary_artifact = binary_artifacts.get(index, {}).get("el", None) if index == 0: el_context = launch_method( @@ -233,9 +230,29 @@ def launch( } # add remainder of el's in parallel to speed package execution + # Identify which services need force_update + force_restart_service_names = [] + for service_name in el_service_configs.keys(): + participant = el_participant_info[service_name]["participant"] + if participant.el_force_restart == True: + force_restart_service_names.append(service_name) + + # Remove force_restart services from batch config + regular_configs = { + k: v + for k, v in el_service_configs.items() + if k not in force_restart_service_names + } + el_services = {} - if len(el_service_configs) > 0: - el_services = plan.add_services(el_service_configs) + if len(regular_configs) > 0: + el_services = plan.add_services(regular_configs) + + # Add force_restart services individually with force_update=True + for service_name in force_restart_service_names: + el_services[service_name] = plan.add_service( + service_name, el_service_configs[service_name], force_update=True + ) # Create contexts ordered by participant index el_contexts_temp = {} diff --git a/src/el/erigon/erigon_launcher.star b/src/el/erigon/erigon_launcher.star index beb6bb148..5957a06af 100644 --- a/src/el/erigon/erigon_launcher.star +++ b/src/el/erigon/erigon_launcher.star @@ -66,7 +66,9 @@ def launch( el_binary_artifact, ) - service = plan.add_service(service_name, config) + service = plan.add_service( + service_name, config, force_update=participant.el_force_restart + ) return get_el_context( plan, diff --git a/src/el/ethereumjs/ethereumjs_launcher.star b/src/el/ethereumjs/ethereumjs_launcher.star index fb4e548fd..e45d7cd86 100644 --- a/src/el/ethereumjs/ethereumjs_launcher.star +++ b/src/el/ethereumjs/ethereumjs_launcher.star @@ -68,7 +68,9 @@ def launch( el_binary_artifact, ) - service = plan.add_service(service_name, config) + service = plan.add_service( + service_name, config, force_update=participant.el_force_restart + ) return get_el_context( plan, diff --git a/src/el/ethrex/ethrex_launcher.star b/src/el/ethrex/ethrex_launcher.star index 927db1146..5c5802f55 100644 --- a/src/el/ethrex/ethrex_launcher.star +++ b/src/el/ethrex/ethrex_launcher.star @@ -83,7 +83,9 @@ def launch( el_binary_artifact, ) - service = plan.add_service(service_name, config) + service = plan.add_service( + service_name, config, force_update=participant.el_force_restart + ) return get_el_context( plan, diff --git a/src/el/geth/geth_launcher.star b/src/el/geth/geth_launcher.star index 7d96adb14..d9c977dd2 100644 --- a/src/el/geth/geth_launcher.star +++ b/src/el/geth/geth_launcher.star @@ -72,9 +72,12 @@ def launch( network_params, extra_files_artifacts, bootnodoor_enode, + el_binary_artifact, ) - service = plan.add_service(service_name, config) + service = plan.add_service( + service_name, config, force_update=participant.el_force_restart + ) return get_el_context( plan, diff --git a/src/el/nethermind/nethermind_launcher.star b/src/el/nethermind/nethermind_launcher.star index 8407765af..13875e148 100644 --- a/src/el/nethermind/nethermind_launcher.star +++ b/src/el/nethermind/nethermind_launcher.star @@ -64,7 +64,9 @@ def launch( el_binary_artifact, ) - service = plan.add_service(service_name, config) + service = plan.add_service( + service_name, config, force_update=participant.el_force_restart + ) return get_el_context( plan, diff --git a/src/el/nimbus-eth1/nimbus_launcher.star b/src/el/nimbus-eth1/nimbus_launcher.star index f067f655b..deeec790c 100644 --- a/src/el/nimbus-eth1/nimbus_launcher.star +++ b/src/el/nimbus-eth1/nimbus_launcher.star @@ -64,7 +64,9 @@ def launch( el_binary_artifact, ) - service = plan.add_service(service_name, config) + service = plan.add_service( + service_name, config, force_update=participant.el_force_restart + ) return get_el_context( plan, diff --git a/src/el/reth/reth_launcher.star b/src/el/reth/reth_launcher.star index 94123aa06..563f8f066 100644 --- a/src/el/reth/reth_launcher.star +++ b/src/el/reth/reth_launcher.star @@ -73,7 +73,9 @@ def launch( el_binary_artifact, ) - service = plan.add_service(service_name, config) + service = plan.add_service( + service_name, config, force_update=participant.el_force_restart + ) return get_el_context( plan, diff --git a/src/participant_network.star b/src/participant_network.star index a72a98f46..d7556086f 100644 --- a/src/participant_network.star +++ b/src/participant_network.star @@ -50,7 +50,6 @@ def launch_participant_network( extra_files_artifacts, tempo_otlp_grpc_url, backend, - binary_artifacts={}, ): network_id = network_params.network_id num_participants = len(args_with_right_defaults.participants) @@ -161,6 +160,42 @@ def launch_participant_network( plan.print("Bootnodoor launched with ENR: {0}".format(bootnodoor_enr)) plan.print("Bootnodoor launched with ENODE: {0}".format(bootnodoor_enode)) + # Upload binary artifacts for participants (after genesis, before EL/CL/VC launch) + # Only upload when both binary_path and force_restart are enabled + # Path prefix needed because this file is in src/ subdirectory + binary_artifacts = {} + for index, participant in enumerate(args_with_right_defaults.participants): + participant_binaries = {} + if participant.el_binary_path and participant.el_force_restart: + filename = participant.el_binary_path.split("/")[-1] + participant_binaries["el"] = struct( + artifact=plan.upload_files( + src="../" + participant.el_binary_path, + name="el-binary-{0}".format(index + 1), + ), + filename=filename, + ) + if participant.cl_binary_path and participant.cl_force_restart: + filename = participant.cl_binary_path.split("/")[-1] + participant_binaries["cl"] = struct( + artifact=plan.upload_files( + src="../" + participant.cl_binary_path, + name="cl-binary-{0}".format(index + 1), + ), + filename=filename, + ) + if participant.vc_binary_path and participant.vc_force_restart: + filename = participant.vc_binary_path.split("/")[-1] + participant_binaries["vc"] = struct( + artifact=plan.upload_files( + src="../" + participant.vc_binary_path, + name="vc-binary-{0}".format(index + 1), + ), + filename=filename, + ) + if participant_binaries: + binary_artifacts[index] = participant_binaries + # Launch all execution layer clients all_el_contexts = el_client_launcher.launch( plan, @@ -500,9 +535,7 @@ def launch_participant_network( remote_signer_context.metrics_info["config"] = participant.prometheus_config service_name = "vc-{0}".format(full_name) - vc_binary_artifact = None - if index in binary_artifacts and "vc" in binary_artifacts[index]: - vc_binary_artifact = binary_artifacts[index]["vc"] + vc_binary_artifact = binary_artifacts.get(index, {}).get("vc", None) vc_service_config = vc.get_vc_config( plan=plan, launcher=vc.new_vc_launcher(el_cl_genesis_data=el_cl_data), @@ -538,13 +571,34 @@ def launch_participant_network( vc_service_info[service_name] = { "client_name": vc_type, "participant_index": index, + "participant": participant, } current_vc_index += 1 # add vc's in parallel to speed package execution + # Identify which services need force_update + force_restart_service_names = [] + for service_name in vc_service_configs.keys(): + participant = vc_service_info[service_name]["participant"] + if participant.vc_force_restart == True: + force_restart_service_names.append(service_name) + + # Remove force_restart services from batch config + regular_configs = { + k: v + for k, v in vc_service_configs.items() + if k not in force_restart_service_names + } + vc_services = {} - if len(vc_service_configs) > 0: - vc_services = plan.add_services(vc_service_configs) + if len(regular_configs) > 0: + vc_services = plan.add_services(regular_configs) + + # Add force_restart services individually with force_update=True + for service_name in force_restart_service_names: + vc_services[service_name] = plan.add_service( + service_name, vc_service_configs[service_name], force_update=True + ) # Create VC contexts ordered by participant index vc_contexts_temp = {} From 5c578c526f8deb1e4116f4c8e50a28cac7bf2f57 Mon Sep 17 00:00:00 2001 From: Barnabas Busa Date: Wed, 14 Jan 2026 11:23:43 +0100 Subject: [PATCH 10/12] fix readme and other participants --- README.md | 3 ++ src/cl/cl_launcher.star | 26 ++--------- src/el/el_launcher.star | 26 ++--------- src/participant_network.star | 70 ++++++++---------------------- src/shared_utils/shared_utils.star | 26 +++++++++++ 5 files changed, 52 insertions(+), 99 deletions(-) diff --git a/README.md b/README.md index a72d86b17..d68c26e5f 100644 --- a/README.md +++ b/README.md @@ -197,6 +197,7 @@ participants: # When set, the binary will be uploaded and mounted into the container, # replacing the default binary from the Docker image # Useful for rapid debugging with locally compiled binaries + # IMPORTANT: el_force_restart must be set to true when using this option # IMPORTANT: The binary file must live inside the ethereum-package directory # Build the client in its own repo, then copy ONLY the binary to ethereum-package # Do not run builds inside ethereum-package or copy build dependencies - only the final binary @@ -296,6 +297,7 @@ participants: # When set, the binary will be uploaded and mounted into the container, # replacing the default binary from the Docker image # Useful for rapid debugging with locally compiled binaries + # IMPORTANT: cl_force_restart must be set to true when using this option # IMPORTANT: The binary file must live inside the ethereum-package directory # Build the client in its own repo, then copy ONLY the binary to ethereum-package # Do not run builds inside ethereum-package or copy build dependencies - only the final binary @@ -398,6 +400,7 @@ participants: # When set, the binary will be uploaded and mounted into the container, # replacing the default binary from the Docker image # Useful for rapid debugging with locally compiled binaries + # IMPORTANT: vc_force_restart must be set to true when using this option # IMPORTANT: The binary file must live inside the ethereum-package directory # Build the client in its own repo, then copy ONLY the binary to ethereum-package # Do not run builds inside ethereum-package or copy build dependencies - only the final binary diff --git a/src/cl/cl_launcher.star b/src/cl/cl_launcher.star index ec328c8ef..783649e5f 100644 --- a/src/cl/cl_launcher.star +++ b/src/cl/cl_launcher.star @@ -320,29 +320,9 @@ def launch( } # add rest of cl's in parallel to speed package execution - # Identify which services need force_update - force_restart_service_names = [] - for service_name in cl_service_configs.keys(): - participant = cl_participant_info[service_name]["participant"] - if participant.cl_force_restart == True: - force_restart_service_names.append(service_name) - - # Remove force_restart services from batch config - regular_configs = { - k: v - for k, v in cl_service_configs.items() - if k not in force_restart_service_names - } - - cl_services = {} - if len(regular_configs) > 0: - cl_services = plan.add_services(regular_configs) - - # Add force_restart services individually with force_update=True - for service_name in force_restart_service_names: - cl_services[service_name] = plan.add_service( - service_name, cl_service_configs[service_name], force_update=True - ) + cl_services = shared_utils.add_services_with_force_restart( + plan, cl_service_configs, cl_participant_info, "cl_force_restart" + ) # Create CL contexts ordered by participant index cl_contexts_temp = {} diff --git a/src/el/el_launcher.star b/src/el/el_launcher.star index 285402433..38ed56012 100644 --- a/src/el/el_launcher.star +++ b/src/el/el_launcher.star @@ -230,29 +230,9 @@ def launch( } # add remainder of el's in parallel to speed package execution - # Identify which services need force_update - force_restart_service_names = [] - for service_name in el_service_configs.keys(): - participant = el_participant_info[service_name]["participant"] - if participant.el_force_restart == True: - force_restart_service_names.append(service_name) - - # Remove force_restart services from batch config - regular_configs = { - k: v - for k, v in el_service_configs.items() - if k not in force_restart_service_names - } - - el_services = {} - if len(regular_configs) > 0: - el_services = plan.add_services(regular_configs) - - # Add force_restart services individually with force_update=True - for service_name in force_restart_service_names: - el_services[service_name] = plan.add_service( - service_name, el_service_configs[service_name], force_update=True - ) + el_services = shared_utils.add_services_with_force_restart( + plan, el_service_configs, el_participant_info, "el_force_restart" + ) # Create contexts ordered by participant index el_contexts_temp = {} diff --git a/src/participant_network.star b/src/participant_network.star index d7556086f..2de8a970e 100644 --- a/src/participant_network.star +++ b/src/participant_network.star @@ -160,39 +160,23 @@ def launch_participant_network( plan.print("Bootnodoor launched with ENR: {0}".format(bootnodoor_enr)) plan.print("Bootnodoor launched with ENODE: {0}".format(bootnodoor_enode)) - # Upload binary artifacts for participants (after genesis, before EL/CL/VC launch) - # Only upload when both binary_path and force_restart are enabled - # Path prefix needed because this file is in src/ subdirectory + # Upload binary artifacts when both binary_path and force_restart are enabled binary_artifacts = {} for index, participant in enumerate(args_with_right_defaults.participants): participant_binaries = {} - if participant.el_binary_path and participant.el_force_restart: - filename = participant.el_binary_path.split("/")[-1] - participant_binaries["el"] = struct( - artifact=plan.upload_files( - src="../" + participant.el_binary_path, - name="el-binary-{0}".format(index + 1), - ), - filename=filename, - ) - if participant.cl_binary_path and participant.cl_force_restart: - filename = participant.cl_binary_path.split("/")[-1] - participant_binaries["cl"] = struct( - artifact=plan.upload_files( - src="../" + participant.cl_binary_path, - name="cl-binary-{0}".format(index + 1), - ), - filename=filename, - ) - if participant.vc_binary_path and participant.vc_force_restart: - filename = participant.vc_binary_path.split("/")[-1] - participant_binaries["vc"] = struct( - artifact=plan.upload_files( - src="../" + participant.vc_binary_path, - name="vc-binary-{0}".format(index + 1), - ), - filename=filename, - ) + for bin_type, bin_path, force_restart in [ + ("el", participant.el_binary_path, participant.el_force_restart), + ("cl", participant.cl_binary_path, participant.cl_force_restart), + ("vc", participant.vc_binary_path, participant.vc_force_restart), + ]: + if bin_path and force_restart: + participant_binaries[bin_type] = struct( + artifact=plan.upload_files( + src="../" + bin_path, + name="{0}-binary-{1}".format(bin_type, index + 1), + ), + filename=bin_path.split("/")[-1], + ) if participant_binaries: binary_artifacts[index] = participant_binaries @@ -576,29 +560,9 @@ def launch_participant_network( current_vc_index += 1 # add vc's in parallel to speed package execution - # Identify which services need force_update - force_restart_service_names = [] - for service_name in vc_service_configs.keys(): - participant = vc_service_info[service_name]["participant"] - if participant.vc_force_restart == True: - force_restart_service_names.append(service_name) - - # Remove force_restart services from batch config - regular_configs = { - k: v - for k, v in vc_service_configs.items() - if k not in force_restart_service_names - } - - vc_services = {} - if len(regular_configs) > 0: - vc_services = plan.add_services(regular_configs) - - # Add force_restart services individually with force_update=True - for service_name in force_restart_service_names: - vc_services[service_name] = plan.add_service( - service_name, vc_service_configs[service_name], force_update=True - ) + vc_services = shared_utils.add_services_with_force_restart( + plan, vc_service_configs, vc_service_info, "vc_force_restart" + ) # Create VC contexts ordered by participant index vc_contexts_temp = {} diff --git a/src/shared_utils/shared_utils.star b/src/shared_utils/shared_utils.star index ee4023442..83147f81d 100644 --- a/src/shared_utils/shared_utils.star +++ b/src/shared_utils/shared_utils.star @@ -492,3 +492,29 @@ def get_tolerations( ) ) return toleration_list + + +def add_services_with_force_restart( + plan, service_configs, participant_info, force_restart_attr +): + force_restart_names = [ + name + for name in service_configs.keys() + if getattr(participant_info[name]["participant"], force_restart_attr, False) + == True + ] + + regular_configs = { + k: v for k, v in service_configs.items() if k not in force_restart_names + } + + services = {} + if len(regular_configs) > 0: + services = plan.add_services(regular_configs) + + for service_name in force_restart_names: + services[service_name] = plan.add_service( + service_name, service_configs[service_name], force_update=True + ) + + return services From f417422c3ddb8e8000c918a6650ccfa94ce5aa0b Mon Sep 17 00:00:00 2001 From: Barnabas Busa Date: Wed, 14 Jan 2026 11:25:50 +0100 Subject: [PATCH 11/12] fix up some tests --- .github/tests/binary.norun..yaml | 10 ++++++++++ .github/tests/binary.yaml | 17 ----------------- .github/tests/binary_force.norun.yaml | 11 +++++++++++ .github/tests/force.yaml | 8 ++++++++ .github/tests/minimal.yaml | 13 +++++++------ 5 files changed, 36 insertions(+), 23 deletions(-) create mode 100644 .github/tests/binary.norun..yaml delete mode 100644 .github/tests/binary.yaml create mode 100644 .github/tests/binary_force.norun.yaml create mode 100644 .github/tests/force.yaml diff --git a/.github/tests/binary.norun..yaml b/.github/tests/binary.norun..yaml new file mode 100644 index 000000000..5db618362 --- /dev/null +++ b/.github/tests/binary.norun..yaml @@ -0,0 +1,10 @@ +participants: + - cl_type: lighthouse + cl_image: ethpandaops/lighthouse:unstable + count: 4 + - el_type: geth + cl_type: lighthouse + cl_image: ethpandaops/lighthouse:unstable + cl_binary_path: binary/lighthouse +additional_services: + - dora diff --git a/.github/tests/binary.yaml b/.github/tests/binary.yaml deleted file mode 100644 index 82cea939c..000000000 --- a/.github/tests/binary.yaml +++ /dev/null @@ -1,17 +0,0 @@ -participants: - - el_type: geth - cl_type: lighthouse - cl_image: ethpandaops/lighthouse:stable - cl_binary_path: binary/release/lighthouse - - cl_type: lighthouse -additional_services: - - dora - -port_publisher: - cl: - enabled: true - public_port_start: 33000 - additional_services: - enabled: true - public_port_start: 34000 - diff --git a/.github/tests/binary_force.norun.yaml b/.github/tests/binary_force.norun.yaml new file mode 100644 index 000000000..3ea6e2d56 --- /dev/null +++ b/.github/tests/binary_force.norun.yaml @@ -0,0 +1,11 @@ +participants: + - cl_type: lighthouse + cl_image: ethpandaops/lighthouse:unstable + count: 4 + - el_type: geth + cl_type: lighthouse + cl_image: ethpandaops/lighthouse:unstable + cl_binary_path: binary/lighthouse + cl_force_restart: true +additional_services: + - dora diff --git a/.github/tests/force.yaml b/.github/tests/force.yaml new file mode 100644 index 000000000..c2f9c3bf1 --- /dev/null +++ b/.github/tests/force.yaml @@ -0,0 +1,8 @@ +participants: + - cl_type: lighthouse + cl_image: ethpandaops/lighthouse:unstable + count: 2 + - el_type: geth + cl_type: lighthouse + cl_image: ethpandaops/lighthouse:unstable + cl_force_restart: true diff --git a/.github/tests/minimal.yaml b/.github/tests/minimal.yaml index 053e9e483..d7fa42fb0 100644 --- a/.github/tests/minimal.yaml +++ b/.github/tests/minimal.yaml @@ -3,13 +3,14 @@ participants: cl_type: teku - el_type: geth cl_type: prysm - # - el_type: erigon - # cl_type: nimbus - #- el_type: besu - # cl_type: lighthouse + - el_type: erigon + cl_type: nimbus + - el_type: besu + cl_type: lighthouse - el_type: reth cl_type: lodestar - # - el_type: geth - # cl_type: grandine + cl_image: bbusa/lodestar:yes + - el_type: geth + cl_type: grandine network_params: preset: minimal From 409c655a30429a7b34eaef1b93b3cf1a25a803c7 Mon Sep 17 00:00:00 2001 From: Barnabas Busa Date: Wed, 14 Jan 2026 17:39:35 +0100 Subject: [PATCH 12/12] fix minimal --- .github/tests/minimal.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/tests/minimal.yaml b/.github/tests/minimal.yaml index d7fa42fb0..aea361d67 100644 --- a/.github/tests/minimal.yaml +++ b/.github/tests/minimal.yaml @@ -9,7 +9,6 @@ participants: cl_type: lighthouse - el_type: reth cl_type: lodestar - cl_image: bbusa/lodestar:yes - el_type: geth cl_type: grandine network_params: