diff --git a/README.md b/README.md index 446088b8..35087af5 100644 --- a/README.md +++ b/README.md @@ -273,14 +273,20 @@ optimism_package: # Valid values are: # op-geth # op-reth + # op-rbuilder el_builder_type: "" # The Docker image that should be used for the builder EL client; leave blank to use the default for the client type # Defaults by client: # - op-geth: us-docker.pkg.dev/oplabs-tools-artifacts/images/op-geth:latest # - op-reth: parithoshj/op-reth:latest + # - op-rbuilder: ghcr.io/flashbots/op-rbuilder:latest el_builder_image: "" + # Builder secret key used by op-rbuilder to sign transactions + # Defaults to None - not used + el_builder_key: "" + # The type of builder CL client that should be started # Valid values are: # op-node @@ -609,7 +615,7 @@ To use rollup boost, you can add `rollup-boost` as an additional service and con optimism_package: chains: - participants: - - el_builder_type: op-geth + - el_builder_type: op-rbuilder cl_builder_type: op-node mev_params: rollup_boost_image: "flashbots/rollup-boost:latest" diff --git a/src/el/op-geth/op_geth_builder_launcher.star b/src/builder/op-geth/op_geth_launcher.star similarity index 100% rename from src/el/op-geth/op_geth_builder_launcher.star rename to src/builder/op-geth/op_geth_launcher.star diff --git a/src/builder/op-rbuilder/op_rbuilder_launcher.star b/src/builder/op-rbuilder/op_rbuilder_launcher.star new file mode 100644 index 00000000..864eff57 --- /dev/null +++ b/src/builder/op-rbuilder/op_rbuilder_launcher.star @@ -0,0 +1,291 @@ +ethereum_package_shared_utils = import_module( + "github.com/ethpandaops/ethereum-package/src/shared_utils/shared_utils.star" +) + +ethereum_package_el_context = import_module( + "github.com/ethpandaops/ethereum-package/src/el/el_context.star" +) +ethereum_package_el_admin_node_info = import_module( + "github.com/ethpandaops/ethereum-package/src/el/el_admin_node_info.star" +) + +ethereum_package_node_metrics = import_module( + "github.com/ethpandaops/ethereum-package/src/node_metrics_info.star" +) +ethereum_package_constants = import_module( + "github.com/ethpandaops/ethereum-package/src/package_io/constants.star" +) + +ethereum_package_input_parser = import_module( + "github.com/ethpandaops/ethereum-package/src/package_io/input_parser.star" +) + +constants = import_module("../../package_io/constants.star") +observability = import_module("../../observability/observability.star") +util = import_module("../../util.star") +interop_constants = import_module("../../interop/constants.star") + +RPC_PORT_NUM = 8545 +WS_PORT_NUM = 8546 +DISCOVERY_PORT_NUM = 30303 +ENGINE_RPC_PORT_NUM = 9551 + +# The min/max CPU/memory that the execution node can use +EXECUTION_MIN_CPU = 100 +EXECUTION_MIN_MEMORY = 256 + +# Port IDs +RPC_PORT_ID = "rpc" +WS_PORT_ID = "ws" +TCP_DISCOVERY_PORT_ID = "tcp-discovery" +UDP_DISCOVERY_PORT_ID = "udp-discovery" +ENGINE_RPC_PORT_ID = "engine-rpc" + +# Paths +METRICS_PATH = "/metrics" + +# The dirpath of the execution data directory on the client container +EXECUTION_DATA_DIRPATH_ON_CLIENT_CONTAINER = "/data/op-reth/execution-data" + + +def get_used_ports(discovery_port=DISCOVERY_PORT_NUM): + used_ports = { + RPC_PORT_ID: ethereum_package_shared_utils.new_port_spec( + RPC_PORT_NUM, + ethereum_package_shared_utils.TCP_PROTOCOL, + ethereum_package_shared_utils.HTTP_APPLICATION_PROTOCOL, + ), + WS_PORT_ID: ethereum_package_shared_utils.new_port_spec( + WS_PORT_NUM, ethereum_package_shared_utils.TCP_PROTOCOL + ), + TCP_DISCOVERY_PORT_ID: ethereum_package_shared_utils.new_port_spec( + discovery_port, ethereum_package_shared_utils.TCP_PROTOCOL + ), + UDP_DISCOVERY_PORT_ID: ethereum_package_shared_utils.new_port_spec( + discovery_port, ethereum_package_shared_utils.UDP_PROTOCOL + ), + ENGINE_RPC_PORT_ID: ethereum_package_shared_utils.new_port_spec( + ENGINE_RPC_PORT_NUM, ethereum_package_shared_utils.TCP_PROTOCOL + ), + } + return used_ports + + +VERBOSITY_LEVELS = { + ethereum_package_constants.GLOBAL_LOG_LEVEL.error: "v", + ethereum_package_constants.GLOBAL_LOG_LEVEL.warn: "vv", + ethereum_package_constants.GLOBAL_LOG_LEVEL.info: "vvv", + ethereum_package_constants.GLOBAL_LOG_LEVEL.debug: "vvvv", + ethereum_package_constants.GLOBAL_LOG_LEVEL.trace: "vvvvv", +} + + +def launch( + plan, + launcher, + service_name, + participant, + global_log_level, + persistent, + tolerations, + node_selectors, + existing_el_clients, + sequencer_enabled, + sequencer_context, + observability_helper, + interop_params, +): + log_level = ethereum_package_input_parser.get_client_log_level_or_default( + participant.el_builder_log_level, global_log_level, VERBOSITY_LEVELS + ) + + cl_client_name = service_name.split("-")[4] + + config = get_config( + plan, + launcher, + service_name, + participant, + log_level, + persistent, + tolerations, + node_selectors, + existing_el_clients, + cl_client_name, + sequencer_enabled, + sequencer_context, + observability_helper, + interop_params, + ) + + service = plan.add_service(service_name, config) + + enode = ethereum_package_el_admin_node_info.get_enode_for_node( + plan, service_name, RPC_PORT_ID + ) + + http_url = "http://{0}:{1}".format(service.ip_address, RPC_PORT_NUM) + + metrics_info = observability.new_metrics_info( + observability_helper, service, METRICS_PATH + ) + + return ethereum_package_el_context.new_el_context( + client_name="reth", + enode=enode, + ip_addr=service.ip_address, + rpc_port_num=RPC_PORT_NUM, + ws_port_num=WS_PORT_NUM, + engine_rpc_port_num=ENGINE_RPC_PORT_NUM, + rpc_http_url=http_url, + service_name=service_name, + el_metrics_info=[metrics_info], + ) + + +def get_config( + plan, + launcher, + service_name, + participant, + log_level, + persistent, + tolerations, + node_selectors, + existing_el_clients, + cl_client_name, + sequencer_enabled, + sequencer_context, + observability_helper, + interop_params, +): + discovery_port = DISCOVERY_PORT_NUM + ports = dict(get_used_ports(discovery_port)) + + cmd = [ + "node", + "--datadir=" + EXECUTION_DATA_DIRPATH_ON_CLIENT_CONTAINER, + "--chain={0}".format( + launcher.network + if launcher.network in ethereum_package_constants.PUBLIC_NETWORKS + else ethereum_package_constants.GENESIS_CONFIG_MOUNT_PATH_ON_CONTAINER + + "/genesis-{0}.json".format(launcher.network_id) + ), + "--http", + "--http.port={0}".format(RPC_PORT_NUM), + "--http.addr=0.0.0.0", + "--http.corsdomain=*", + # WARNING: The admin info endpoint is enabled so that we can easily get ENR/enode, which means + # that users should NOT store private information in these Kurtosis nodes! + "--http.api=admin,net,eth,web3,debug,trace", + "--ws", + "--ws.addr=0.0.0.0", + "--ws.port={0}".format(WS_PORT_NUM), + "--ws.api=net,eth", + "--ws.origins=*", + "--nat=extip:" + ethereum_package_constants.PRIVATE_IP_ADDRESS_PLACEHOLDER, + "--authrpc.port={0}".format(ENGINE_RPC_PORT_NUM), + "--authrpc.jwtsecret=" + ethereum_package_constants.JWT_MOUNT_PATH_ON_CONTAINER, + "--authrpc.addr=0.0.0.0", + "--discovery.port={0}".format(discovery_port), + "--port={0}".format(discovery_port), + "--rpc.eth-proof-window=302400", + ] + + # configure files + + files = { + ethereum_package_constants.GENESIS_DATA_MOUNTPOINT_ON_CLIENTS: launcher.deployment_output, + ethereum_package_constants.JWT_MOUNTPOINT_ON_CLIENTS: launcher.jwt_file, + } + if persistent: + files[EXECUTION_DATA_DIRPATH_ON_CLIENT_CONTAINER] = Directory( + persistent_key="data-{0}".format(service_name), + size=int(participant.el_builder_volume_size) + if int(participant.el_builder_volume_size) > 0 + else constants.VOLUME_SIZE[launcher.network][ + constants.EL_TYPE.op_reth + "_volume_size" + ], + ) + + # configure environment variables + + env_vars = participant.el_builder_extra_env_vars + + # apply customizations + + if observability_helper.enabled: + cmd.append("--metrics=0.0.0.0:{0}".format(observability.METRICS_PORT_NUM)) + + observability.expose_metrics_port(ports) + + if not sequencer_enabled: + cmd.append("--rollup.sequencer-http={0}".format(sequencer_context.rpc_http_url)) + + if len(existing_el_clients) > 0: + cmd.append( + "--bootnodes=" + + ",".join( + [ + ctx.enode + for ctx in existing_el_clients[ + : ethereum_package_constants.MAX_ENODE_ENTRIES + ] + ] + ) + ) + + cmd += participant.el_builder_extra_params + + builder_secret_key = participant.el_builder_key + if builder_secret_key != "": + cmd.append("--rollup.builder-secret-key={0}".format(builder_secret_key)) + + if interop_params.enabled: + cmd.append( + "--rollup.supervisor-url={0}".format(interop_constants.SUPERVISOR_ENDPOINT) + ) + + config_args = { + "image": participant.el_builder_image, + "ports": ports, + "cmd": cmd, + "files": files, + "private_ip_address_placeholder": ethereum_package_constants.PRIVATE_IP_ADDRESS_PLACEHOLDER, + "env_vars": env_vars, + "labels": ethereum_package_shared_utils.label_maker( + client=constants.EL_TYPE.op_reth, + client_type=constants.CLIENT_TYPES.el, + image=util.label_from_image(participant.el_builder_image), + connected_client=cl_client_name, + extra_labels=participant.el_builder_extra_labels, + ), + "tolerations": tolerations, + "node_selectors": node_selectors, + } + + # configure resources + + if participant.el_builder_min_cpu > 0: + config_args["min_cpu"] = participant.el_builder_min_cpu + if participant.el_builder_max_cpu > 0: + config_args["max_cpu"] = participant.el_builder_max_cpu + if participant.el_builder_min_mem > 0: + config_args["min_memory"] = participant.el_builder_min_mem + if participant.el_builder_max_mem > 0: + config_args["max_memory"] = participant.el_builder_max_mem + return ServiceConfig(**config_args) + + +def new_op_rbuilder_builder_launcher( + deployment_output, + jwt_file, + network, + network_id, +): + return struct( + deployment_output=deployment_output, + jwt_file=jwt_file, + network=network, + network_id=network_id, + ) diff --git a/src/el/op-reth/op_reth_builder_launcher.star b/src/builder/op-reth/op_reth_launcher.star similarity index 100% rename from src/el/op-reth/op_reth_builder_launcher.star rename to src/builder/op-reth/op_reth_launcher.star diff --git a/src/el_cl_launcher.star b/src/el_cl_launcher.star index 0a351be0..71173f80 100644 --- a/src/el_cl_launcher.star +++ b/src/el_cl_launcher.star @@ -22,8 +22,9 @@ hildr = import_module("./cl/hildr/hildr_launcher.star") # MEV rollup_boost = import_module("./mev/rollup-boost/rollup_boost_launcher.star") -op_geth_builder = import_module("./el/op-geth/op_geth_builder_launcher.star") -op_reth_builder = import_module("./el/op-reth/op_reth_builder_launcher.star") +op_geth_builder = import_module("./builder/op-geth/op_geth_launcher.star") +op_reth_builder = import_module("./builder/op-reth/op_reth_launcher.star") +op_rbuilder_builder = import_module("./builder/op-rbuilder/op_rbuilder_launcher.star") op_node_builder = import_module("./cl/op-node/op_node_builder_launcher.star") @@ -113,6 +114,15 @@ def launch( ), "launch_method": op_reth_builder.launch, }, + "op-rbuilder": { + "launcher": op_rbuilder_builder.new_op_rbuilder_builder_launcher( + deployment_output, + jwt_file, + network_params.network, + network_params.network_id, + ), + "launch_method": op_rbuilder_builder.launch, + }, } cl_launchers = { @@ -404,5 +414,10 @@ def launch( ) all_cl_contexts.append(cl_builder_context) + # We need to make sure that el_context and cl_context are first in the list, as down the line all_el_contexts[0] + # and all_cl_contexts[0] are used + all_el_contexts.insert(0, el_context) + all_cl_contexts.insert(0, cl_context) + plan.print("Successfully added {0} EL/CL participants".format(num_participants)) return all_el_contexts, all_cl_contexts diff --git a/src/package_io/input_parser.star b/src/package_io/input_parser.star index 725ff100..0c699d31 100644 --- a/src/package_io/input_parser.star +++ b/src/package_io/input_parser.star @@ -10,6 +10,7 @@ DEFAULT_EL_IMAGES = { "op-erigon": "testinprod/op-erigon:latest", "op-nethermind": "nethermind/nethermind:latest", "op-besu": "ghcr.io/optimism-java/op-besu:latest", + "op-rbuilder": "ghcr.io/flashbots/op-rbuilder:latest", } DEFAULT_CL_IMAGES = { @@ -154,6 +155,7 @@ def input_parser(plan, input_args): cl_max_mem=participant["cl_max_mem"], el_builder_type=participant["el_builder_type"], el_builder_image=participant["el_builder_image"], + el_builder_key=participant["el_builder_key"], el_builder_log_level=participant["el_builder_log_level"], el_builder_extra_env_vars=participant[ "el_builder_extra_env_vars" @@ -573,6 +575,7 @@ def default_participant(): "cl_max_mem": 0, "el_builder_type": "op-geth", "el_builder_image": "", + "el_builder_key": "", "el_builder_log_level": "", "el_builder_extra_env_vars": {}, "el_builder_extra_labels": {}, diff --git a/src/package_io/sanity_check.star b/src/package_io/sanity_check.star index 1044f451..9ae13e96 100644 --- a/src/package_io/sanity_check.star +++ b/src/package_io/sanity_check.star @@ -71,6 +71,7 @@ PARTICIPANT_CATEGORIES = { "cl_max_mem", "el_builder_type", "el_builder_image", + "el_builder_key", "cl_builder_type", "cl_builder_image", "node_selectors",