diff --git a/.github/tests/hildr.yaml b/.github/tests/hildr.yaml new file mode 100644 index 00000000..38f9d833 --- /dev/null +++ b/.github/tests/hildr.yaml @@ -0,0 +1,10 @@ +optimism_package: + participants: + - el_type: op-geth + cl_type: op-node + - el_type: op-geth + cl_type: hildr + - el_type: op-reth + cl_type: hildr + - el_type: op-erigon + cl_type: hildr diff --git a/README.md b/README.md index f5f8249b..7b9b2cf3 100644 --- a/README.md +++ b/README.md @@ -65,11 +65,13 @@ optimism_package: # The type of CL client that should be started # Valid values are: # op-node + # hildr cl_type: op-node # The Docker image that should be used for the CL client; leave blank to use the default for the client type # Defaults by client: # - op-node: parithoshj/op-node:v1 + # - hildr: ghcr.io/optimism-java/hildr:latest cl_image: "" # Count of nodes to spin up for this participant diff --git a/main.star b/main.star index 1849201a..7b085f3c 100644 --- a/main.star +++ b/main.star @@ -93,6 +93,7 @@ def get_l1_config(all_l1_participants, l1_network_params, l1_network_id): env_vars["WEB3_RPC_URL"] = str(all_l1_participants[0].el_context.rpc_http_url) env_vars["L1_RPC_URL"] = str(all_l1_participants[0].el_context.rpc_http_url) env_vars["CL_RPC_URL"] = str(all_l1_participants[0].cl_context.beacon_http_url) + env_vars["L1_WS_URL"] = str(all_l1_participants[0].el_context.ws_url) env_vars["L1_CHAIN_ID"] = str(l1_network_id) env_vars["L1_BLOCK_TIME"] = str(l1_network_params.seconds_per_slot) env_vars["DEPLOYMENT_OUTFILE"] = ( diff --git a/src/cl/hildr/hildr_launcher.star b/src/cl/hildr/hildr_launcher.star new file mode 100644 index 00000000..cdf8b403 --- /dev/null +++ b/src/cl/hildr/hildr_launcher.star @@ -0,0 +1,206 @@ +shared_utils = import_module( + "github.com/ethpandaops/ethereum-package/src/shared_utils/shared_utils.star" +) + +cl_context = import_module( + "github.com/ethpandaops/ethereum-package/src/cl/cl_context.star" +) + +cl_node_ready_conditions = import_module( + "github.com/ethpandaops/ethereum-package/src/cl/cl_node_ready_conditions.star" +) +constants = import_module( + "github.com/ethpandaops/ethereum-package/src/package_io/constants.star" +) + +# ---------------------------------- Beacon client ------------------------------------- + +# The Docker container runs as the "hildr" user so we can't write to root +BEACON_DATA_DIRPATH_ON_SERVICE_CONTAINER = "/data/hildr/hildr-beacon-data" +ROLLUP_CONFIG_MOUNT_PATH_ON_CONTAINER = "/network-configs/rollup.json" +# Port IDs +BEACON_TCP_DISCOVERY_PORT_ID = "tcp-discovery" +BEACON_UDP_DISCOVERY_PORT_ID = "udp-discovery" +BEACON_HTTP_PORT_ID = "http" + +# Port nums +BEACON_DISCOVERY_PORT_NUM = 9003 +BEACON_HTTP_PORT_NUM = 8547 + + +def get_used_ports(discovery_port): + used_ports = { + BEACON_TCP_DISCOVERY_PORT_ID: shared_utils.new_port_spec( + discovery_port, shared_utils.TCP_PROTOCOL, wait=None + ), + BEACON_UDP_DISCOVERY_PORT_ID: shared_utils.new_port_spec( + discovery_port, shared_utils.UDP_PROTOCOL, wait=None + ), + BEACON_HTTP_PORT_ID: shared_utils.new_port_spec( + BEACON_HTTP_PORT_NUM, + shared_utils.TCP_PROTOCOL, + shared_utils.HTTP_APPLICATION_PROTOCOL, + ), + } + return used_ports + + +ENTRYPOINT_ARGS = ["sh", "-c"] + +VERBOSITY_LEVELS = { + constants.GLOBAL_LOG_LEVEL.error: "ERROR", + constants.GLOBAL_LOG_LEVEL.warn: "WARN", + constants.GLOBAL_LOG_LEVEL.info: "INFO", + constants.GLOBAL_LOG_LEVEL.debug: "DEBUG", + constants.GLOBAL_LOG_LEVEL.trace: "TRACE", +} + + +def launch( + plan, + launcher, + service_name, + image, + el_context, + existing_cl_clients, + l1_config_env_vars, + gs_sequencer_private_key, + sequencer_enabled, +): + network_name = shared_utils.get_network_name(launcher.network) + + # beacon_node_identity_recipe = PostHttpRequestRecipe( + # endpoint="/", + # content_type="application/json", + # body='{"jsonrpc":"2.0","method":"opp2p_self","params":[],"id":1}', + # port_id=BEACON_HTTP_PORT_ID, + # extract={ + # "enr": ".result.ENR", + # "multiaddr": ".result.addresses[0]", + # "peer_id": ".result.peerID", + # }, + # ) + + config = get_beacon_config( + plan, + launcher.el_cl_genesis_data, + launcher.jwt_file, + image, + service_name, + el_context, + existing_cl_clients, + l1_config_env_vars, + gs_sequencer_private_key, + # beacon_node_identity_recipe, + sequencer_enabled, + ) + + beacon_service = plan.add_service(service_name, config) + + beacon_http_port = beacon_service.ports[BEACON_HTTP_PORT_ID] + beacon_http_url = "http://{0}:{1}".format( + beacon_service.ip_address, beacon_http_port.number + ) + + # response = plan.request( + # recipe=beacon_node_identity_recipe, service_name=service_name + # ) + + # beacon_node_enr = response["extract.enr"] + # beacon_multiaddr = response["extract.multiaddr"] + # beacon_peer_id = response["extract.peer_id"] + + return cl_context.new_cl_context( + "hildr", + # beacon_node_enr, + None, + beacon_service.ip_address, + beacon_http_port.number, + beacon_http_url, + None, + service_name, + multiaddr=None, + peer_id=None, + # multiaddr=beacon_multiaddr, + # peer_id=beacon_peer_id, + ) + + +def get_beacon_config( + plan, + el_cl_genesis_data, + jwt_file, + image, + service_name, + el_context, + existing_cl_clients, + l1_config_env_vars, + gs_sequencer_private_key, + # beacon_node_identity_recipe, + sequencer_enabled, +): + EXECUTION_ENGINE_ENDPOINT = "http://{0}:{1}".format( + el_context.ip_addr, + el_context.engine_rpc_port_num, + ) + EXECUTION_RPC_ENDPOINT = "http://{0}:{1}".format( + el_context.ip_addr, + el_context.rpc_port_num, + ) + + used_ports = get_used_ports(BEACON_DISCOVERY_PORT_NUM) + + cmd = [ + "--devnet", + "--jwt-file=" + constants.JWT_MOUNT_PATH_ON_CONTAINER, + "--l1-beacon-url={0}".format(l1_config_env_vars["CL_RPC_URL"]), + "--l1-rpc-url={0}".format(l1_config_env_vars["L1_RPC_URL"]), + "--l1-ws-rpc-url={0}".format(l1_config_env_vars["L1_WS_URL"]), + "--l2-engine-url={0}".format(EXECUTION_ENGINE_ENDPOINT), + "--l2-rpc-url={0}".format(EXECUTION_RPC_ENDPOINT), + "--rpc-addr=0.0.0.0", + "--rpc-port={0}".format(BEACON_HTTP_PORT_NUM), + "--sync-mode=full", + "--network=" + ROLLUP_CONFIG_MOUNT_PATH_ON_CONTAINER, + ] + + if sequencer_enabled: + cmd.append("--sequencer-enable") + + if len(existing_cl_clients) == 1: + cmd.append( + "--disc-boot-nodes=" + + ",".join( + [ctx.enr for ctx in existing_cl_clients[: constants.MAX_ENR_ENTRIES]] + ) + ) + + files = { + constants.GENESIS_DATA_MOUNTPOINT_ON_CLIENTS: el_cl_genesis_data, + constants.JWT_MOUNTPOINT_ON_CLIENTS: jwt_file, + } + ports = {} + ports.update(used_ports) + + return ServiceConfig( + image=image, + ports=ports, + cmd=cmd, + files=files, + private_ip_address_placeholder=constants.PRIVATE_IP_ADDRESS_PLACEHOLDER, + # ready_conditions=ReadyCondition( + # recipe=beacon_node_identity_recipe, + # field="code", + # assertion="==", + # target_value=200, + # timeout="1m", + # ), + ) + + +def new_hildr_launcher(el_cl_genesis_data, jwt_file, network_params): + return struct( + el_cl_genesis_data=el_cl_genesis_data, + jwt_file=jwt_file, + network=network_params.network, + ) diff --git a/src/el_cl_launcher.star b/src/el_cl_launcher.star index a788a7fa..d04f13e3 100644 --- a/src/el_cl_launcher.star +++ b/src/el_cl_launcher.star @@ -11,6 +11,7 @@ op_erigon = import_module("./el/op-erigon/op_erigon_launcher.star") op_nethermind = import_module("./el/op-nethermind/op_nethermind_launcher.star") # CL op_node = import_module("./cl/op-node/op_node_launcher.star") +hildr = import_module("./cl/hildr/hildr_launcher.star") def launch( @@ -70,6 +71,10 @@ def launch( ), "launch_method": op_node.launch, }, + "hildr": { + "launcher": hildr.new_hildr_launcher(el_cl_data, jwt_file, network_params), + "launch_method": hildr.launch, + }, } all_cl_contexts = [] diff --git a/src/package_io/input_parser.star b/src/package_io/input_parser.star index de03ddfe..c8a062f9 100644 --- a/src/package_io/input_parser.star +++ b/src/package_io/input_parser.star @@ -13,6 +13,7 @@ DEFAULT_EL_IMAGES = { DEFAULT_CL_IMAGES = { "op-node": "parithoshj/op-node:v1", + "hildr": "ghcr.io/optimism-java/hildr:latest", } DEFAULT_BATCHER_IMAGES = {