diff --git a/.github/tests/mev-custom-helix-cb.yaml b/.github/tests/mev-custom-helix-cb.yaml
new file mode 100644
index 000000000..3c16f6315
--- /dev/null
+++ b/.github/tests/mev-custom-helix-cb.yaml
@@ -0,0 +1,112 @@
+participants:
+ - el_type: geth
+ el_image: ethpandaops/geth:master
+ cl_type: lighthouse
+ cl_image: ethpandaops/lighthouse:unstable
+ vc_image: ethpandaops/lighthouse:unstable
+ supernode: true
+ count: 2
+
+additional_services:
+ - dora
+ - spamoor
+
+# Custom MEV config: Helix relay + Commit-Boost sidecar + Flashbots builder
+# Tests inline helix_relay_config override. commit-boost uses default template.
+mev_type: custom
+mev_params:
+ mev_relay: helix
+ mev_sidecar: commit-boost
+ mev_builder: flashbots
+ helix_relay_image: ghcr.io/gattaca-com/helix-relay:main
+ mev_boost_image: ghcr.io/commit-boost/pbs:latest
+ mev_builder_image: ethpandaops/reth-rbuilder:develop
+ mev_builder_cl_image: ethpandaops/lighthouse:unstable
+ mev_builder_subsidy: 1
+ helix_relay_config: |
+ instance_id: "custom-helix-cb-test"
+
+ network_config: !Custom
+ dir_path: "{{ .GENESIS_CONFIG_MOUNT_PATH_ON_CONTAINER }}/config.json"
+ genesis_validator_root: "{{ .GENESIS_VALIDATORS_ROOT }}"
+ genesis_time: {{ .GENESIS_TIME }}
+
+ postgres:
+ hostname: "{{ .POSTGRES_HOST_NAME }}"
+ port: {{ .POSTGRES_PORT }}
+ db_name: "{{ .POSTGRES_DB }}"
+ user: "{{ .POSTGRES_USER }}"
+ password: "{{ .POSTGRES_PASS }}"
+ region: 0
+ region_name: "LOCAL"
+
+ beacon_clients:
+ - url: "{{ .BEACON_URI }}"
+
+ gossip_payload_on_header: false
+
+ simulators:
+ - url: "{{ .BLOCKSIM_URI }}"
+ namespace: flashbots
+ is_merging_simulator: false
+ max_concurrent_tasks: 32
+
+ router_config:
+ enabled_routes:
+ - route: GetValidators
+ - route: SubmitBlock
+ - route: GetTopBid
+ - route: GetHeader
+ rate_limit:
+ replenish_ms: 50
+ burst_size: 20
+ - route: GetPayload
+ - route: RegisterValidators
+ - route: Status
+ - route: ProposerPayloadDelivered
+ - route: BuilderBidsReceived
+ - route: ValidatorRegistration
+ shutdown_delay_ms: 12000
+
+ timing_game_config:
+ max_header_delay_ms: 400
+ latest_header_delay_ms_in_slot: 1500
+ default_client_latency_ms: 50
+
+ target_get_payload_propagation_duration_ms: 500
+
+ is_submission_instance: true
+ is_registration_instance: true
+
+ admin_token: "test_admin_token"
+
+ logging:
+ type: Console
+
+ cores:
+ auctioneer: 0
+ sub_workers: [0]
+ reg_workers: [0]
+ tokio: [0]
+ tcp_bid_submissions_tile: 2
+
+ is_local_dev: false
+
+
+spamoor_params:
+ spammers:
+ - name: "Blob Spammer"
+ scenario: "blobs"
+ config:
+ throughput: 10
+ - name: "ERC txs"
+ scenario: "erctx"
+ config:
+ throughput: 50
+ max_pending: 400
+
+network_params:
+ min_validator_withdrawability_delay: 1
+ shard_committee_period: 1
+ churn_limit_quotient: 16
+ prefunded_accounts: '{"0xb9e79d19f651a941757b35830232e7efc77e1c79": {"balance": "100000ETH"}}'
diff --git a/README.md b/README.md
index e425b290d..4c69098ad 100644
--- a/README.md
+++ b/README.md
@@ -1470,6 +1470,7 @@ mempool_bridge_params:
# Note: Helix uses TimescaleDB (PostgreSQL with time-series extension) for data storage
# "buildoor" - a self-contained builder+relay service & mev-boost are spun up, powered by [buildoor](https://github.com/ethpandaops/buildoor)
# Supports both legacy builder API and ePBS bidding. No separate relay infrastructure or builder participant needed.
+# "custom" - mix and match relay, sidecar, and builder components via mev_params.mev_relay, mev_params.mev_sidecar, mev_params.mev_builder
# We have seen instances of multibuilder instances failing to start mev-relay-api with non zero epochs
mev_type: null
@@ -1538,6 +1539,20 @@ mev_params:
# [logs.stdout]
# level = "debug"
commit_boost_config: ""
+ # Inline Helix relay config template. When set, replaces the default auto-generated config.
+ # Template variables {{ .GENESIS_TIME }}, {{ .BEACON_URI }}, {{ .GENESIS_VALIDATORS_ROOT }},
+ # {{ .POSTGRES_HOST_NAME }}, {{ .POSTGRES_PORT }}, {{ .POSTGRES_DB }}, {{ .POSTGRES_USER }},
+ # {{ .POSTGRES_PASS }}, {{ .BLOCKSIM_URI }}, {{ .GENESIS_CONFIG_MOUNT_PATH_ON_CONTAINER }}
+ # are rendered at enclave creation.
+ helix_relay_config: ""
+ # Component overrides for custom mev_type (required when mev_type is "custom", optional otherwise)
+ # mev_relay: which relay to launch. Valid: "flashbots", "helix", "mev-rs", "mock", "none"
+ # mev_sidecar: which per-validator PBS service. Valid: "mev-boost", "commit-boost", "mev-rs", "none"
+ # mev_builder: which block builder. Valid: "flashbots", "mev-rs", "buildoor", "mock", "none"
+ # When used with a preset mev_type (e.g. "flashbots"), these override individual components.
+ mev_relay: ""
+ mev_sidecar: ""
+ mev_builder: ""
# Parameters for the buildoor builder+relay service (used when mev_type is "buildoor")
buildoor_params:
@@ -1985,6 +2000,42 @@ network_params:
+
+ Custom MEV: Helix relay + Commit-Boost sidecar + Flashbots builder
+
+```yaml
+participants:
+ - el_type: geth
+ el_image: ethpandaops/geth:master
+ cl_type: lighthouse
+ cl_image: ethpandaops/lighthouse:unstable
+ vc_image: ethpandaops/lighthouse:unstable
+ supernode: true
+ count: 2
+
+mev_type: custom
+mev_params:
+ mev_relay: helix
+ mev_sidecar: commit-boost
+ mev_builder: flashbots
+ helix_relay_image: ghcr.io/gattaca-com/helix-relay:main
+ mev_boost_image: ghcr.io/commit-boost/pbs:latest
+ mev_builder_image: ethpandaops/reth-rbuilder:develop
+ mev_builder_cl_image: ethpandaops/lighthouse:unstable
+ mev_builder_subsidy: 1
+
+additional_services:
+ - dora
+ - spamoor
+
+network_params:
+ min_validator_withdrawability_delay: 1
+ shard_committee_period: 1
+ prefunded_accounts: '{"0xb9e79d19f651a941757b35830232e7efc77e1c79": {"balance": "100000ETH"}}'
+```
+
+
+
A 2-node geth/lighthouse network with optional services (Grafana, Prometheus, tx_fuzz, EngineAPI snooper)
@@ -2156,9 +2207,19 @@ The package also supports other MEV implementations:
- `"mev_type": "mev-rs"` - Alternative relay implementation powered by [mev-rs](https://github.com/ralexstokes/mev-rs/)
- `"mev_type": "commit-boost"` - Infrastructure powered by [commit-boost](https://github.com/Commit-Boost/commit-boost-client)
- `"mev_type": "buildoor"` - A self-contained builder+relay service powered by [buildoor](https://github.com/ethpandaops/buildoor). Supports both legacy builder API and ePBS bidding without requiring separate relay infrastructure or a dedicated builder participant.
+- `"mev_type": "custom"` - Mix and match any relay, sidecar, and builder via `mev_params.mev_relay`, `mev_params.mev_sidecar`, `mev_params.mev_builder`. For example, Helix relay + Commit-Boost sidecar + Flashbots builder.
Each implementation provides different features and performance characteristics suitable for various testing and development scenarios.
+You can also override individual components on any preset. For example, to use commit-boost as the sidecar with an otherwise-standard flashbots setup:
+
+```yaml
+mev_type: flashbots
+mev_params:
+ mev_sidecar: commit-boost
+ mev_boost_image: ghcr.io/commit-boost/pbs:latest
+```
+
Caveats when using "mev_type": "flashbots"
diff --git a/main.star b/main.star
index dbd35aaf2..a2e64b187 100644
--- a/main.star
+++ b/main.star
@@ -1,5 +1,6 @@
input_parser = import_module("./src/package_io/input_parser.star")
constants = import_module("./src/package_io/constants.star")
+mev_resolver = import_module("./src/package_io/mev_resolver.star")
participant_network = import_module("./src/participant_network.star")
shared_utils = import_module("./src/shared_utils/shared_utils.star")
static_files = import_module("./src/static_files/static_files.star")
@@ -228,7 +229,9 @@ def run(plan, args={}):
tempo.SERVICE_NAME, tempo.HTTP_PORT_NUMBER
)
- if args_with_right_defaults.mev_type == constants.MEV_RS_MEV_TYPE:
+ mev_components = args_with_right_defaults.mev_components
+
+ if mev_components != None and mev_components.builder == "mev-rs":
plan.print("Generating mev-rs builder config file")
mev_rs_builder_config_file = mev_rs_mev_builder.new_builder_config(
plan,
@@ -239,15 +242,11 @@ def run(plan, args={}):
args_with_right_defaults.mev_params.mev_builder_extra_data,
global_node_selectors,
)
- elif (
- args_with_right_defaults.mev_type == constants.FLASHBOTS_MEV_TYPE
- or args_with_right_defaults.mev_type == constants.COMMIT_BOOST_MEV_TYPE
- or args_with_right_defaults.mev_type == constants.HELIX_MEV_TYPE
- ):
+ elif mev_components != None and mev_components.builder == "flashbots":
plan.print("Generating flashbots builder config file")
flashbots_builder_config_file = flashbots_mev_rbuilder.new_builder_config(
plan,
- args_with_right_defaults.mev_type,
+ mev_components.relay,
network_params,
constants.VALIDATING_REWARDS_ACCOUNT,
network_params.preregistered_validator_keys_mnemonic,
@@ -373,7 +372,6 @@ def run(plan, args={}):
mev_endpoints = []
mev_endpoint_names = []
# passed external relays get priority
- # perhaps add mev_type External or remove this
if (
hasattr(participant, "builder_network_params")
and participant.builder_network_params != None
@@ -381,205 +379,153 @@ def run(plan, args={}):
mev_endpoints = participant.builder_network_params.relay_end_points
for idx, mev_endpoint in enumerate(mev_endpoints):
mev_endpoint_names.append("relay-{0}".format(idx + 1))
- # otherwise dummy relays spinup if chosen
- elif (
- args_with_right_defaults.mev_type
- and args_with_right_defaults.mev_type == constants.MOCK_MEV_TYPE
- ):
- el_uri = "{0}:{1}".format(
- all_el_contexts[0].dns_name,
- all_el_contexts[0].engine_rpc_port_num,
- )
+ elif mev_components != None:
+ # ─── RELAY LAUNCH ─────────────────────────────────────────────────
+ # Launch each relay in the resolved relay list
+ relay_index = 0
+ for relay_name in mev_components.relay:
+ if relay_name == "none":
+ continue
- # beacon uri for mock mev needs to use ip address and not dns name
- beacon_uri_for_mock_mev = "{0}:{1}".format(
- all_cl_contexts[0].ip_address,
- all_cl_contexts[0].http_port,
- )
-
- endpoint = mock_mev.launch_mock_mev(
- plan,
- el_uri,
- beacon_uri_for_mock_mev,
- jwt_file,
- args_with_right_defaults.global_log_level,
- global_node_selectors,
- global_tolerations,
- args_with_right_defaults.mev_params,
- )
- mev_endpoints.append(endpoint)
- mev_endpoint_names.append(constants.MOCK_MEV_TYPE)
- elif (
- args_with_right_defaults.mev_type
- and args_with_right_defaults.mev_type == constants.BUILDOOR_MEV_TYPE
- ):
- beacon_uri = "http://{0}:{1}".format(
- all_cl_contexts[0].beacon_service_name,
- all_cl_contexts[0].http_port,
- )
- el_rpc_uri = "http://{0}:{1}".format(
- all_el_contexts[0].dns_name,
- all_el_contexts[0].rpc_port_num,
- )
- engine_rpc_uri = "http://{0}:{1}".format(
- all_el_contexts[0].dns_name,
- all_el_contexts[0].engine_rpc_port_num,
- )
- endpoint = buildoor.launch_buildoor(
- plan,
- beacon_uri,
- el_rpc_uri,
- engine_rpc_uri,
- jwt_file,
- prefunded_accounts[0].private_key,
- args_with_right_defaults.buildoor_params,
- global_node_selectors,
- global_tolerations,
- builder_bls_secret_key,
- )
- mev_endpoints.append(endpoint)
- mev_endpoint_names.append(constants.BUILDOOR_MEV_TYPE)
- elif args_with_right_defaults.mev_type and (
- args_with_right_defaults.mev_type == constants.FLASHBOTS_MEV_TYPE
- or args_with_right_defaults.mev_type == constants.MEV_RS_MEV_TYPE
- or args_with_right_defaults.mev_type == constants.COMMIT_BOOST_MEV_TYPE
- or args_with_right_defaults.mev_type == constants.HELIX_MEV_TYPE
- ):
- builder_cl_context = all_cl_contexts[-1]
- blocksim_uri = "http://{0}:{1}".format(
- all_el_contexts[-1].dns_name, all_el_contexts[-1].rpc_port_num
- )
- beacon_uri = builder_cl_context.beacon_http_url
+ if relay_name == "mock":
+ el_uri = "{0}:{1}".format(
+ all_el_contexts[0].dns_name,
+ all_el_contexts[0].engine_rpc_port_num,
+ )
+ beacon_uri_for_mock_mev = "{0}:{1}".format(
+ all_cl_contexts[0].ip_address,
+ all_cl_contexts[0].http_port,
+ )
+ endpoint = mock_mev.launch_mock_mev(
+ plan,
+ el_uri,
+ beacon_uri_for_mock_mev,
+ jwt_file,
+ args_with_right_defaults.global_log_level,
+ global_node_selectors,
+ global_tolerations,
+ args_with_right_defaults.mev_params,
+ )
+ elif relay_name == "flashbots":
+ builder_cl_context = all_cl_contexts[-1]
+ blocksim_uri = "http://{0}:{1}".format(
+ all_el_contexts[-1].dns_name, all_el_contexts[-1].rpc_port_num
+ )
+ beacon_uri = builder_cl_context.beacon_http_url
+ endpoint = flashbots_mev_relay.launch_mev_relay(
+ plan,
+ mev_params,
+ network_id,
+ beacon_uri,
+ genesis_validators_root,
+ blocksim_uri,
+ network_params,
+ persistent,
+ args_with_right_defaults.port_publisher,
+ num_participants + relay_index,
+ global_node_selectors,
+ global_tolerations,
+ builder_cl_context.beacon_service_name,
+ )
+ elif relay_name == "helix":
+ builder_cl_context = all_cl_contexts[-1]
+ blocksim_uri = "http://{0}:{1}".format(
+ all_el_contexts[-1].dns_name, all_el_contexts[-1].rpc_port_num
+ )
+ beacon_uri = builder_cl_context.beacon_http_url
+ endpoint = helix_relay.launch_helix_relay(
+ plan,
+ network_params,
+ mev_params,
+ beacon_uri,
+ genesis_validators_root,
+ final_genesis_timestamp,
+ blocksim_uri,
+ persistent,
+ args_with_right_defaults.port_publisher,
+ num_participants + relay_index,
+ global_node_selectors,
+ global_tolerations,
+ el_cl_data_files_artifact_uuid,
+ )
+ elif relay_name == "mev-rs":
+ builder_cl_context = all_cl_contexts[-1]
+ beacon_uri = builder_cl_context.beacon_http_url
+ endpoint, _relay_ip, _relay_port = mev_rs_mev_relay.launch_mev_relay(
+ plan,
+ mev_params,
+ network_params.network,
+ beacon_uri,
+ el_cl_data_files_artifact_uuid,
+ args_with_right_defaults.port_publisher,
+ num_participants + relay_index,
+ global_node_selectors,
+ global_tolerations,
+ )
+ else:
+ fail("Unsupported relay type: {0}".format(relay_name))
- first_cl_client = all_cl_contexts[0]
- first_client_beacon_name = first_cl_client.beacon_service_name
+ mev_endpoints.append(endpoint)
+ mev_endpoint_names.append(relay_name)
+ relay_index += 1
- # Check if we should run multiple relays (flashbots + helix)
- if mev_params.run_multiple_relays:
- plan.print("Launching multiple MEV relays (flashbots + helix)")
- # Launch flashbots relay first
- flashbots_endpoint = flashbots_mev_relay.launch_mev_relay(
- plan,
- mev_params,
- network_id,
- beacon_uri,
- genesis_validators_root,
- blocksim_uri,
- network_params,
- persistent,
- args_with_right_defaults.port_publisher,
- num_participants,
- global_node_selectors,
- global_tolerations,
- builder_cl_context.beacon_service_name,
+ # ─── BUILDOOR (has its own relay-like endpoint) ───────────────────
+ if mev_components.builder == "buildoor":
+ beacon_uri = "http://{0}:{1}".format(
+ all_cl_contexts[0].beacon_service_name,
+ all_cl_contexts[0].http_port,
)
- mev_endpoints.append(flashbots_endpoint)
- mev_endpoint_names.append("flashbots")
-
- # Launch helix relay second
- helix_endpoint = helix_relay.launch_helix_relay(
- plan,
- network_params,
- mev_params,
- beacon_uri,
- genesis_validators_root,
- final_genesis_timestamp,
- blocksim_uri,
- persistent,
- args_with_right_defaults.port_publisher,
- num_participants + 1, # Use different index for port allocation
- global_node_selectors,
- global_tolerations,
- el_cl_data_files_artifact_uuid,
- mev_params.helix_relay_image, # Use the helix-specific image
+ el_rpc_uri = "http://{0}:{1}".format(
+ all_el_contexts[0].dns_name,
+ all_el_contexts[0].rpc_port_num,
)
- mev_endpoints.append(helix_endpoint)
- mev_endpoint_names.append("helix")
- elif (
- args_with_right_defaults.mev_type == constants.FLASHBOTS_MEV_TYPE
- or args_with_right_defaults.mev_type == constants.COMMIT_BOOST_MEV_TYPE
- ):
- endpoint = flashbots_mev_relay.launch_mev_relay(
- plan,
- mev_params,
- network_id,
- beacon_uri,
- genesis_validators_root,
- blocksim_uri,
- network_params,
- persistent,
- args_with_right_defaults.port_publisher,
- num_participants,
- global_node_selectors,
- global_tolerations,
- builder_cl_context.beacon_service_name,
+ engine_rpc_uri = "http://{0}:{1}".format(
+ all_el_contexts[0].dns_name,
+ all_el_contexts[0].engine_rpc_port_num,
)
- mev_endpoints.append(endpoint)
- mev_endpoint_names.append(args_with_right_defaults.mev_type)
- elif args_with_right_defaults.mev_type == constants.MEV_RS_MEV_TYPE:
- endpoint, relay_ip_address, relay_port = mev_rs_mev_relay.launch_mev_relay(
+ endpoint = buildoor.launch_buildoor(
plan,
- mev_params,
- network_params.network,
beacon_uri,
- el_cl_data_files_artifact_uuid,
- args_with_right_defaults.port_publisher,
- num_participants,
+ el_rpc_uri,
+ engine_rpc_uri,
+ jwt_file,
+ prefunded_accounts[0].private_key,
+ args_with_right_defaults.buildoor_params,
global_node_selectors,
global_tolerations,
+ builder_bls_secret_key,
)
- mev_endpoints.append(endpoint)
- mev_endpoint_names.append(args_with_right_defaults.mev_type)
- elif args_with_right_defaults.mev_type == constants.HELIX_MEV_TYPE:
- endpoint = helix_relay.launch_helix_relay(
- plan,
- network_params,
- mev_params,
- beacon_uri,
- genesis_validators_root,
- final_genesis_timestamp,
- blocksim_uri,
- persistent,
- args_with_right_defaults.port_publisher,
- num_participants,
- global_node_selectors,
- global_tolerations,
- el_cl_data_files_artifact_uuid,
- )
- mev_endpoints.append(endpoint)
- mev_endpoint_names.append(args_with_right_defaults.mev_type)
- else:
- fail("Invalid MEV type")
+ # Only add endpoint if there's a sidecar to consume it
+ if mev_components.sidecar != "none":
+ mev_endpoints.append(endpoint)
+ mev_endpoint_names.append("buildoor")
- # spin up the mev boost contexts if some endpoints for relays have been passed
+ # ─── SIDECAR LAUNCH (per validator) ───────────────────────────────────
all_mevboost_contexts = []
- if mev_endpoints:
+ if mev_components != None and mev_components.sidecar == "none":
+ # ePBS: no sidecar needed, CL handles builder natively
+ pass
+ elif mev_endpoints:
for index, participant in enumerate(all_participants):
index_str = shared_utils.zfill_custom(
index + 1, len(str(len(all_participants)))
)
- plan.print(
- "args_with_right_defaults.participants[index].validator_count {0}".format(
- args_with_right_defaults.participants[index].validator_count
- )
- )
if args_with_right_defaults.participants[index].validator_count != 0:
- if (
- args_with_right_defaults.mev_type == constants.FLASHBOTS_MEV_TYPE
- or args_with_right_defaults.mev_type == constants.MOCK_MEV_TYPE
- or args_with_right_defaults.mev_type == constants.HELIX_MEV_TYPE
- or args_with_right_defaults.mev_type == constants.BUILDOOR_MEV_TYPE
- ):
+ sidecar_prefix = mev_resolver.get_sidecar_service_prefix(
+ mev_components.sidecar
+ )
+ mev_boost_service_name = "{0}-{1}-{2}-{3}".format(
+ sidecar_prefix,
+ index_str,
+ participant.cl_type,
+ participant.el_type,
+ )
+
+ if mev_components.sidecar == "mev-boost":
mev_boost_launcher = flashbots_mev_boost.new_mev_boost_launcher(
MEV_BOOST_SHOULD_CHECK_RELAY,
mev_endpoints,
)
- mev_boost_service_name = "{0}-{1}-{2}-{3}".format(
- constants.MEV_BOOST_SERVICE_NAME_PREFIX,
- index_str,
- participant.cl_type,
- participant.el_type,
- )
mev_boost_context = flashbots_mev_boost.launch(
plan,
mev_boost_launcher,
@@ -594,19 +540,13 @@ def run(plan, args={}):
global_node_selectors,
global_tolerations,
)
- elif args_with_right_defaults.mev_type == constants.MEV_RS_MEV_TYPE:
- plan.print("Launching mev-rs mev boost")
- mev_boost_launcher = mev_rs_mev_boost.new_mev_boost_launcher(
+ elif mev_components.sidecar == "commit-boost":
+ plan.print("Launching commit-boost PBS service")
+ mev_boost_launcher = commit_boost_mev_boost.new_mev_boost_launcher(
MEV_BOOST_SHOULD_CHECK_RELAY,
mev_endpoints,
)
- mev_boost_service_name = "{0}-{1}-{2}-{3}".format(
- constants.MEV_BOOST_SERVICE_NAME_PREFIX,
- index_str,
- participant.cl_type,
- participant.el_type,
- )
- mev_boost_context = mev_rs_mev_boost.launch(
+ mev_boost_context = commit_boost_mev_boost.launch(
plan,
mev_boost_launcher,
mev_boost_service_name,
@@ -618,22 +558,15 @@ def run(plan, args={}):
index,
global_node_selectors,
global_tolerations,
+ final_genesis_timestamp,
)
- elif (
- args_with_right_defaults.mev_type == constants.COMMIT_BOOST_MEV_TYPE
- ):
- plan.print("Launching commit-boost PBS service")
- mev_boost_launcher = commit_boost_mev_boost.new_mev_boost_launcher(
+ elif mev_components.sidecar == "mev-rs":
+ plan.print("Launching mev-rs mev boost")
+ mev_boost_launcher = mev_rs_mev_boost.new_mev_boost_launcher(
MEV_BOOST_SHOULD_CHECK_RELAY,
mev_endpoints,
)
- mev_boost_service_name = "{0}-{1}-{2}-{3}".format(
- constants.COMMIT_BOOST_SERVICE_NAME_PREFIX,
- index_str,
- participant.cl_type,
- participant.el_type,
- )
- mev_boost_context = commit_boost_mev_boost.launch(
+ mev_boost_context = mev_rs_mev_boost.launch(
plan,
mev_boost_launcher,
mev_boost_service_name,
@@ -645,10 +578,7 @@ def run(plan, args={}):
index,
global_node_selectors,
global_tolerations,
- final_genesis_timestamp,
)
- else:
- fail("Invalid MEV type")
all_mevboost_contexts.append(mev_boost_context)
if len(args_with_right_defaults.additional_services) == 0:
diff --git a/network_params.yaml b/network_params.yaml
index 37e26c88d..1d98473cb 100644
--- a/network_params.yaml
+++ b/network_params.yaml
@@ -207,7 +207,7 @@ ethereum_metrics_exporter_enabled: false
parallel_keystore_generation: false
disable_peer_scoring: false
persistent: false
-# Supports: null, mock, flashbots, mev-rs, commit-boost, helix, buildoor
+# Supports: null, mock, flashbots, mev-rs, commit-boost, helix, buildoor, epbs, custom
mev_type: null
mev_params:
mev_relay_image: ethpandaops/mev-boost-relay:main
@@ -229,6 +229,7 @@ mev_params:
run_multiple_relays: false
helix_relay_image: ghcr.io/gattaca-com/helix-relay:main
commit_boost_config: ""
+ helix_relay_config: ""
custom_flood_params:
interval_between_transactions: 1
buildoor_params:
diff --git a/src/el/reth/reth_launcher.star b/src/el/reth/reth_launcher.star
index 5b32a33ac..3e2c997dc 100644
--- a/src/el/reth/reth_launcher.star
+++ b/src/el/reth/reth_launcher.star
@@ -120,11 +120,7 @@ def get_config(
constants.RPC_PORT_ID: public_ports_for_component[3],
constants.WS_PORT_ID: public_ports_for_component[4],
}
- if (
- launcher.builder_type == constants.FLASHBOTS_MEV_TYPE
- or launcher.builder_type == constants.COMMIT_BOOST_MEV_TYPE
- or launcher.builder_type == constants.HELIX_MEV_TYPE
- ):
+ if launcher.builder_type == "flashbots":
additional_public_port_assignments[
constants.RBUILDER_PORT_ID
] = public_ports_for_component[5]
@@ -155,11 +151,7 @@ def get_config(
constants.METRICS_PORT_ID: METRICS_PORT_NUM,
}
- if (
- launcher.builder_type == constants.FLASHBOTS_MEV_TYPE
- or launcher.builder_type == constants.COMMIT_BOOST_MEV_TYPE
- or launcher.builder_type == constants.HELIX_MEV_TYPE
- ):
+ if launcher.builder_type == "flashbots":
used_port_assignments[constants.RBUILDER_PORT_ID] = RBUILDER_PORT_NUM
used_port_assignments[
constants.RBUILDER_METRICS_PORT_ID
@@ -187,11 +179,7 @@ def get_config(
"--http.addr=0.0.0.0",
"--http.corsdomain=*",
"--http.api=admin,net,eth,web3,debug,txpool,trace{0}".format(
- ",flashbots"
- if launcher.builder_type == constants.FLASHBOTS_MEV_TYPE
- or launcher.builder_type == constants.COMMIT_BOOST_MEV_TYPE
- or launcher.builder_type == constants.HELIX_MEV_TYPE
- else ""
+ ",flashbots" if launcher.builder_type == "flashbots" else ""
),
"--ws",
"--ws.addr=0.0.0.0",
@@ -279,15 +267,11 @@ def get_config(
env_vars = {}
image = participant.el_image
- if launcher.builder_type == constants.MEV_RS_MEV_TYPE:
+ if launcher.builder_type == "mev-rs":
files[
mev_rs_builder.MEV_BUILDER_MOUNT_DIRPATH_ON_SERVICE
] = mev_rs_builder.MEV_BUILDER_FILES_ARTIFACT_NAME
- elif (
- launcher.builder_type == constants.FLASHBOTS_MEV_TYPE
- or launcher.builder_type == constants.COMMIT_BOOST_MEV_TYPE
- or launcher.builder_type == constants.HELIX_MEV_TYPE
- ):
+ elif launcher.builder_type == "flashbots":
image = launcher.mev_params.mev_builder_image
cl_client_name = service_name.split("-")[4]
cmd.append("--rbuilder.config=" + flashbots_rbuilder.MEV_FILE_PATH_ON_CONTAINER)
diff --git a/src/mev/commit-boost/mev_boost/mev_boost_launcher.star b/src/mev/commit-boost/mev_boost/mev_boost_launcher.star
index 0bec0abf0..4c3dc89ff 100644
--- a/src/mev/commit-boost/mev_boost/mev_boost_launcher.star
+++ b/src/mev/commit-boost/mev_boost/mev_boost_launcher.star
@@ -10,7 +10,9 @@ CB_CONFIG_FILES_ARTIFACT_NAME = "commit-boost-config"
USED_PORTS = {
"http": shared_utils.new_port_spec(
- constants.MEV_BOOST_PORT, shared_utils.TCP_PROTOCOL
+ constants.MEV_BOOST_PORT,
+ shared_utils.TCP_PROTOCOL,
+ wait="15s",
)
}
diff --git a/src/mev/flashbots/mev_builder/mev_builder_launcher.star b/src/mev/flashbots/mev_builder/mev_builder_launcher.star
index e695e2a53..787bb326f 100644
--- a/src/mev/flashbots/mev_builder/mev_builder_launcher.star
+++ b/src/mev/flashbots/mev_builder/mev_builder_launcher.star
@@ -17,7 +17,7 @@ MEV_FILE_PATH_ON_CONTAINER = (
def new_builder_config(
plan,
- mev_type,
+ relay_list,
network_params,
fee_recipient,
mnemonic,
@@ -25,6 +25,12 @@ def new_builder_config(
participants,
global_node_selectors,
):
+ """
+ Generate rbuilder config file.
+
+ Args:
+ relay_list: List of relay name strings (e.g., ["flashbots"], ["flashbots", "helix"])
+ """
num_of_participants = shared_utils.zfill_custom(
len(participants), len(str(len(participants)))
)
@@ -37,8 +43,7 @@ def new_builder_config(
mev_params.mev_builder_extra_data,
num_of_participants,
mev_params.mev_builder_subsidy,
- mev_type,
- mev_params.run_multiple_relays,
+ relay_list,
)
flashbots_builder_config_template = read_file(
static_files.FLASHBOTS_RBUILDER_CONFIG_FILEPATH
@@ -73,48 +78,37 @@ def new_builder_config_template_data(
extra_data,
num_of_participants,
subsidy,
- mev_type,
- run_multiple_relays=False,
+ relay_list,
):
- # Build the list of relays based on configuration
- relays = []
+ """
+ Build template data using the resolved relay list directly.
- if run_multiple_relays:
- # Add both flashbots and helix relays
- relays.append(
- {
- "Name": "flashbots",
- "Service": "mev-relay-api",
- "Port": flashbots_relay.MEV_RELAY_ENDPOINT_PORT,
- "Priority": 0,
- }
- )
- relays.append(
- {
- "Name": "helix",
- "Service": "helix-relay",
- "Port": helix_relay.HELIX_RELAY_ENDPOINT_PORT,
- "Priority": 1,
- }
- )
- elif mev_type == constants.HELIX_MEV_TYPE:
- relays.append(
- {
- "Name": "helix",
- "Service": "helix-relay",
- "Port": helix_relay.HELIX_RELAY_ENDPOINT_PORT,
- "Priority": 0,
- }
- )
- else:
- relays.append(
- {
- "Name": "flashbots",
- "Service": "mev-relay-api",
- "Port": flashbots_relay.MEV_RELAY_ENDPOINT_PORT,
- "Priority": 0,
- }
- )
+ Args:
+ relay_list: List of relay name strings (e.g., ["flashbots", "helix"])
+ """
+ relays = []
+ for priority, relay_name in enumerate(relay_list):
+ if relay_name == "none":
+ continue
+ elif relay_name == "flashbots":
+ relays.append(
+ {
+ "Name": "flashbots",
+ "Service": "mev-relay-api",
+ "Port": flashbots_relay.MEV_RELAY_ENDPOINT_PORT,
+ "Priority": priority,
+ }
+ )
+ elif relay_name == "helix":
+ relays.append(
+ {
+ "Name": "helix",
+ "Service": "helix-relay",
+ "Port": helix_relay.HELIX_RELAY_ENDPOINT_PORT,
+ "Priority": priority,
+ }
+ )
+ # mev-rs relay is not supported in rbuilder config (different protocol)
# Build enabled_relays string for the config: "relay1", "relay2"
enabled_relays = ", ".join(['"{}"'.format(r["Name"]) for r in relays])
diff --git a/src/mev/helix/helix_relay_launcher.star b/src/mev/helix/helix_relay_launcher.star
index f0afeec65..2f83dae4a 100644
--- a/src/mev/helix/helix_relay_launcher.star
+++ b/src/mev/helix/helix_relay_launcher.star
@@ -6,26 +6,28 @@ static_files = import_module("../../static_files/static_files.star")
HELIX_RELAY_NAME = "helix-relay"
-HTTP_PORT_ID = "http"
ENDPOINT_PORT_ID = "endpoint"
+ADMIN_PORT_ID = "admin"
HELIX_RELAY_CONFIG_FILENAME = "config.yaml"
HELIX_RELAY_MOUNT_DIRPATH_ON_SERVICE = "/config/"
HELIX_RELAY_FILES_ARTIFACT_NAME = "helix-relay-config"
HELIX_RELAY_ENDPOINT_PORT = 4040
-HELIX_RELAY_WEBSITE_PORT = 9060
+HELIX_RELAY_ADMIN_PORT = 4050
USED_PORTS = {
ENDPOINT_PORT_ID: shared_utils.new_port_spec(
HELIX_RELAY_ENDPOINT_PORT,
shared_utils.TCP_PROTOCOL,
shared_utils.HTTP_APPLICATION_PROTOCOL,
+ wait="60s",
),
- HTTP_PORT_ID: shared_utils.new_port_spec(
- HELIX_RELAY_WEBSITE_PORT,
+ ADMIN_PORT_ID: shared_utils.new_port_spec(
+ HELIX_RELAY_ADMIN_PORT,
shared_utils.TCP_PROTOCOL,
shared_utils.HTTP_APPLICATION_PROTOCOL,
+ wait="60s",
),
}
@@ -67,14 +69,6 @@ def launch_helix_relay(
0,
)
public_ports.update(endpoint_public_port)
- website_public_port = shared_utils.get_mev_public_port(
- port_publisher,
- HTTP_PORT_ID,
- index,
- 1,
- )
- public_ports.update(website_public_port)
-
node_selectors = global_node_selectors
postgres = postgres_module.run(
@@ -104,8 +98,11 @@ def launch_helix_relay(
postgres,
)
- # Read the helix config template
- helix_config_template = read_file(static_files.HELIX_RELAY_CONFIG_FILEPATH)
+ # Read the helix config template (allow inline override)
+ if hasattr(mev_params, "helix_relay_config") and mev_params.helix_relay_config:
+ helix_config_template = mev_params.helix_relay_config
+ else:
+ helix_config_template = read_file(static_files.HELIX_RELAY_CONFIG_FILEPATH)
template_and_data = shared_utils.new_template_and_data(
helix_config_template, helix_template_data
)
@@ -128,10 +125,12 @@ def launch_helix_relay(
env_vars = {
"RELAY_KEY": constants.DEFAULT_MEV_SECRET_KEY,
+ "POSTGRES_PASSWORD": "postgres",
+ "ADMIN_TOKEN": "admin_token",
}
- # Use provided relay_image if available, otherwise use mev_params.mev_relay_image
- helix_image = relay_image if relay_image else mev_params.mev_relay_image
+ # Use provided relay_image if available, otherwise use mev_params.helix_relay_image
+ helix_image = relay_image if relay_image else mev_params.helix_relay_image
endpoint = plan.add_service(
name=HELIX_RELAY_NAME,
@@ -179,7 +178,6 @@ def new_helix_relay_config_template_data(
"POSTGRES_USER": "postgres",
"POSTGRES_PASS": "postgres",
"HELIX_RELAY_ENDPOINT_PORT": HELIX_RELAY_ENDPOINT_PORT,
- "HELIX_RELAY_WEBSITE_PORT": HELIX_RELAY_WEBSITE_PORT,
"HELIX_RELAY_ENDPOINT_URL": "helix-relay:{}".format(HELIX_RELAY_ENDPOINT_PORT),
"HELIX_RELAY_PUBKEY": constants.DEFAULT_MEV_PUBKEY,
"GENESIS_CONFIG_MOUNT_PATH_ON_CONTAINER": constants.GENESIS_DATA_MOUNTPOINT_ON_CLIENTS,
diff --git a/src/package_io/constants.star b/src/package_io/constants.star
index 940b56e09..38a451c58 100644
--- a/src/package_io/constants.star
+++ b/src/package_io/constants.star
@@ -100,6 +100,9 @@ MEV_RS_MEV_TYPE = "mev-rs"
COMMIT_BOOST_MEV_TYPE = "commit-boost"
HELIX_MEV_TYPE = "helix"
BUILDOOR_MEV_TYPE = "buildoor"
+CUSTOM_MEV_TYPE = "custom"
+EPBS_MEV_TYPE = "epbs"
+MEV_NONE = "none"
DEFAULT_DORA_IMAGE = "ethpandaops/dora:latest"
DEFAULT_CHECKPOINTZ_IMAGE = "ethpandaops/checkpointz:latest"
DEFAULT_SPAMOOR_IMAGE = "ethpandaops/spamoor:latest"
diff --git a/src/package_io/input_parser.star b/src/package_io/input_parser.star
index 00dd98c25..5955ed10d 100644
--- a/src/package_io/input_parser.star
+++ b/src/package_io/input_parser.star
@@ -3,6 +3,7 @@ shared_utils = import_module("../shared_utils/shared_utils.star")
genesis_constants = import_module(
"../prelaunch_data_generator/genesis_constants/genesis_constants.star"
)
+mev_resolver = import_module("./mev_resolver.star")
sanity_check = import_module("./sanity_check.star")
@@ -257,28 +258,20 @@ def input_parser(plan, input_args):
if result.get("disable_peer_scoring"):
result = enrich_disable_peer_scoring(result)
- if result.get("mev_type") in (
- constants.MOCK_MEV_TYPE,
- constants.FLASHBOTS_MEV_TYPE,
- constants.MEV_RS_MEV_TYPE,
- constants.COMMIT_BOOST_MEV_TYPE,
- constants.HELIX_MEV_TYPE,
- constants.BUILDOOR_MEV_TYPE,
- ):
+ # Resolve mev_type into (relay, sidecar, builder) components
+ mev_components = mev_resolver.resolve_mev_components(
+ result.get("mev_type"), result["mev_params"]
+ )
+ result["mev_components"] = mev_components
+
+ if mev_components != None:
result = enrich_mev_extra_params(
result,
- constants.MEV_BOOST_SERVICE_NAME_PREFIX,
constants.MEV_BOOST_PORT,
- result.get("mev_type"),
+ mev_components,
)
elif result.get("mev_type") == None:
pass
- else:
- fail(
- "Unsupported MEV type: {0}, please use 'mock', 'flashbots', 'mev-rs', 'commit-boost', 'helix' or 'buildoor' type".format(
- result.get("mev_type")
- )
- )
if (
result["mev_params"].get("mev_builder_subsidy") != 0
@@ -892,6 +885,10 @@ def input_parser(plan, input_args):
run_multiple_relays=result["mev_params"]["run_multiple_relays"],
helix_relay_image=result["mev_params"]["helix_relay_image"],
commit_boost_config=result["mev_params"].get("commit_boost_config", ""),
+ helix_relay_config=result["mev_params"].get("helix_relay_config", ""),
+ mev_relay=result["mev_params"].get("mev_relay"),
+ mev_sidecar=result["mev_params"].get("mev_sidecar"),
+ mev_builder=result["mev_params"].get("mev_builder"),
)
if result["mev_params"]
else None,
@@ -1033,6 +1030,7 @@ def input_parser(plan, input_args):
wait_for_finalization=result["wait_for_finalization"],
global_log_level=result["global_log_level"],
mev_type=result["mev_type"],
+ mev_components=result["mev_components"],
snooper_enabled=result["snooper_enabled"],
snooper_params=struct(
enabled=result["snooper_params"]["enabled"],
@@ -2068,6 +2066,10 @@ def get_default_mev_params(mev_type, preset):
"run_multiple_relays": False,
"helix_relay_image": constants.DEFAULT_HELIX_RELAY_IMAGE,
"commit_boost_config": "",
+ "helix_relay_config": "",
+ "mev_relay": None,
+ "mev_sidecar": None,
+ "mev_builder": None,
}
@@ -2362,77 +2364,83 @@ def enrich_disable_peer_scoring(parsed_arguments_dict):
return parsed_arguments_dict
-# TODO perhaps clean this up into a map
-def enrich_mev_extra_params(parsed_arguments_dict, mev_prefix, mev_port, mev_type):
- for index, participant in enumerate(parsed_arguments_dict["participants"]):
- index_str = shared_utils.zfill_custom(
- index + 1, len(str(len(parsed_arguments_dict["participants"])))
- )
-
- if mev_type == constants.COMMIT_BOOST_MEV_TYPE:
- prefix = constants.COMMIT_BOOST_SERVICE_NAME_PREFIX
- else:
- prefix = constants.MEV_BOOST_SERVICE_NAME_PREFIX
-
- mev_url = "http://{0}-{1}-{2}-{3}:{4}".format(
- prefix,
- index_str,
- participant["cl_type"],
- participant["el_type"],
- mev_port,
- )
+def enrich_mev_extra_params(parsed_arguments_dict, mev_port, mev_components):
+ """
+ Enrich participant CL/VC params with builder configuration.
+ Two responsibilities:
+ 1. Inject --builder= into CL/VC params (only when sidecar != "none")
+ 2. Append builder participant to participant list (for flashbots/mev-rs builders)
+
+ Args:
+ parsed_arguments_dict: The full parsed arguments dict
+ mev_port: The MEV boost port number
+ mev_components: Resolved struct with .relay, .sidecar, .builder
+ """
+ # Part 1: CL/VC builder flags (only when there IS a sidecar to point them at)
+ if mev_components.sidecar != "none":
+ prefix = mev_resolver.get_sidecar_service_prefix(mev_components.sidecar)
- if participant["cl_type"] == "lighthouse":
- participant["cl_extra_params"].append("--builder={0}".format(mev_url))
- if participant["vc_type"] == "lighthouse":
- if (
- parsed_arguments_dict["network_params"]["gas_limit"] == 0
- ): # if the gas limit is set we already enable builder-proposals
- participant["vc_extra_params"].append("--builder-proposals")
- if participant["cl_type"] == "lodestar":
- participant["cl_extra_params"].append("--builder")
- participant["cl_extra_params"].append("--builder.urls={0}".format(mev_url))
- if participant["vc_type"] == "lodestar":
- participant["vc_extra_params"].append("--builder")
- if participant["cl_type"] == "nimbus":
- participant["cl_extra_params"].append("--payload-builder=true")
- participant["cl_extra_params"].append(
- "--payload-builder-url={0}".format(mev_url)
+ for index, participant in enumerate(parsed_arguments_dict["participants"]):
+ index_str = shared_utils.zfill_custom(
+ index + 1, len(str(len(parsed_arguments_dict["participants"])))
)
- if participant["vc_type"] == "nimbus":
- participant["vc_extra_params"].append("--payload-builder=true")
- if participant["cl_type"] == "teku":
- participant["cl_extra_params"].append(
- "--builder-endpoint={0}".format(mev_url)
- )
- participant["cl_extra_params"].append(
- "--validators-builder-registration-default-enabled=true"
- )
- if participant["vc_type"] == "teku":
- participant["vc_extra_params"].append(
- "--validators-builder-registration-default-enabled=true"
- )
- if participant["cl_type"] == "prysm":
- participant["cl_extra_params"].append(
- "--http-mev-relay={0}".format(mev_url)
+
+ mev_url = "http://{0}-{1}-{2}-{3}:{4}".format(
+ prefix,
+ index_str,
+ participant["cl_type"],
+ participant["el_type"],
+ mev_port,
)
- if participant["vc_type"] == "prysm":
- participant["vc_extra_params"].append("--enable-builder")
- if participant["cl_type"] == "grandine":
- participant["cl_extra_params"].append("--builder-url={0}".format(mev_url))
- if participant["vc_type"] == "vero":
- participant["vc_extra_params"].append("--use-external-builder")
+ if participant["cl_type"] == "lighthouse":
+ participant["cl_extra_params"].append("--builder={0}".format(mev_url))
+ if participant["vc_type"] == "lighthouse":
+ if (
+ parsed_arguments_dict["network_params"]["gas_limit"] == 0
+ ): # if the gas limit is set we already enable builder-proposals
+ participant["vc_extra_params"].append("--builder-proposals")
+ if participant["cl_type"] == "lodestar":
+ participant["cl_extra_params"].append("--builder")
+ participant["cl_extra_params"].append(
+ "--builder.urls={0}".format(mev_url)
+ )
+ if participant["vc_type"] == "lodestar":
+ participant["vc_extra_params"].append("--builder")
+ if participant["cl_type"] == "nimbus":
+ participant["cl_extra_params"].append("--payload-builder=true")
+ participant["cl_extra_params"].append(
+ "--payload-builder-url={0}".format(mev_url)
+ )
+ if participant["vc_type"] == "nimbus":
+ participant["vc_extra_params"].append("--payload-builder=true")
+ if participant["cl_type"] == "teku":
+ participant["cl_extra_params"].append(
+ "--builder-endpoint={0}".format(mev_url)
+ )
+ participant["cl_extra_params"].append(
+ "--validators-builder-registration-default-enabled=true"
+ )
+ if participant["vc_type"] == "teku":
+ participant["vc_extra_params"].append(
+ "--validators-builder-registration-default-enabled=true"
+ )
+ if participant["cl_type"] == "prysm":
+ participant["cl_extra_params"].append(
+ "--http-mev-relay={0}".format(mev_url)
+ )
+ if participant["vc_type"] == "prysm":
+ participant["vc_extra_params"].append("--enable-builder")
+ if participant["cl_type"] == "grandine":
+ participant["cl_extra_params"].append(
+ "--builder-url={0}".format(mev_url)
+ )
- num_participants = len(parsed_arguments_dict["participants"])
- index_str = shared_utils.zfill_custom(
- num_participants + 1, len(str(num_participants + 1))
- )
- if (
- mev_type == constants.FLASHBOTS_MEV_TYPE
- or mev_type == constants.COMMIT_BOOST_MEV_TYPE
- or mev_type == constants.HELIX_MEV_TYPE
- ):
+ if participant["vc_type"] == "vero":
+ participant["vc_extra_params"].append("--use-external-builder")
+
+ # Part 2: Builder participant (add a dedicated builder EL+CL pair)
+ if mev_components.builder == "flashbots":
mev_participant = default_participant()
mev_participant["el_type"] = "reth-builder"
mev_participant.update(
@@ -2457,10 +2465,8 @@ def enrich_mev_extra_params(parsed_arguments_dict, mev_prefix, mev_port, mev_typ
],
}
)
-
parsed_arguments_dict["participants"].append(mev_participant)
-
- if mev_type == constants.MEV_RS_MEV_TYPE:
+ elif mev_components.builder == "mev-rs":
mev_participant = default_participant()
mev_participant["el_type"] = "reth-builder"
mev_participant.update(
@@ -2482,10 +2488,12 @@ def enrich_mev_extra_params(parsed_arguments_dict, mev_prefix, mev_port, mev_typ
}
)
parsed_arguments_dict["participants"].append(mev_participant)
- if mev_type == constants.MOCK_MEV_TYPE:
+ elif mev_components.builder == "mock":
parsed_arguments_dict["mev_params"]["mock_mev_image"] = parsed_arguments_dict[
"mev_params"
]["mock_mev_image"]
+ # builder in ("buildoor", "none") -> no builder participant added
+
return parsed_arguments_dict
diff --git a/src/package_io/mev_resolver.star b/src/package_io/mev_resolver.star
new file mode 100644
index 000000000..705eb317b
--- /dev/null
+++ b/src/package_io/mev_resolver.star
@@ -0,0 +1,168 @@
+# MEV Component Resolver
+#
+# Decomposes mev_type (a preset shortcut) into three independent components:
+# - relay: which relay(s) to launch (flashbots, helix, mev-rs, mock, none)
+# - sidecar: which per-validator boost/PBS service to use (mev-boost, commit-boost, mev-rs, none)
+# - builder: which block builder to run (flashbots, mev-rs, buildoor, mock, none)
+#
+# This allows mix-and-match configurations (e.g., helix relay + commit-boost sidecar)
+# without patching the code.
+
+constants = import_module("./constants.star")
+
+# Preset expansion table: mev_type -> (relay, sidecar, builder)
+MEV_PRESETS = {
+ constants.FLASHBOTS_MEV_TYPE: {
+ "relay": "flashbots",
+ "sidecar": "mev-boost",
+ "builder": "flashbots",
+ },
+ constants.HELIX_MEV_TYPE: {
+ "relay": "helix",
+ "sidecar": "mev-boost",
+ "builder": "flashbots",
+ },
+ constants.COMMIT_BOOST_MEV_TYPE: {
+ "relay": "flashbots",
+ "sidecar": "commit-boost",
+ "builder": "flashbots",
+ },
+ constants.MEV_RS_MEV_TYPE: {
+ "relay": "mev-rs",
+ "sidecar": "mev-rs",
+ "builder": "mev-rs",
+ },
+ constants.MOCK_MEV_TYPE: {
+ "relay": "mock",
+ "sidecar": "mev-boost",
+ "builder": "mock",
+ },
+ constants.BUILDOOR_MEV_TYPE: {
+ "relay": "none",
+ "sidecar": "mev-boost",
+ "builder": "buildoor",
+ },
+ constants.EPBS_MEV_TYPE: {
+ "relay": "none",
+ "sidecar": "none",
+ "builder": "buildoor",
+ },
+}
+
+VALID_RELAYS = ["flashbots", "helix", "mev-rs", "mock", "none"]
+VALID_SIDECARS = ["mev-boost", "commit-boost", "mev-rs", "none"]
+VALID_BUILDERS = ["flashbots", "mev-rs", "buildoor", "mock", "none"]
+
+
+def resolve_mev_components(mev_type, mev_params):
+ if mev_type == None:
+ return None
+
+ if mev_type == constants.CUSTOM_MEV_TYPE:
+ relay = mev_params.get("mev_relay")
+ sidecar = mev_params.get("mev_sidecar")
+ builder = mev_params.get("mev_builder")
+ if not relay or not sidecar or not builder:
+ fail(
+ "mev_type 'custom' requires explicit mev_params.mev_relay, "
+ + "mev_params.mev_sidecar, and mev_params.mev_builder to be set"
+ )
+ elif mev_type in MEV_PRESETS:
+ preset = MEV_PRESETS[mev_type]
+ # Allow user to override individual components via mev_params
+ relay = (
+ mev_params.get("mev_relay")
+ if mev_params.get("mev_relay")
+ else preset["relay"]
+ )
+ sidecar = (
+ mev_params.get("mev_sidecar")
+ if mev_params.get("mev_sidecar")
+ else preset["sidecar"]
+ )
+ builder = (
+ mev_params.get("mev_builder")
+ if mev_params.get("mev_builder")
+ else preset["builder"]
+ )
+ else:
+ fail(
+ "Unsupported mev_type: '{0}'. Valid options: mock, flashbots, mev-rs, commit-boost, helix, buildoor, epbs, custom".format(
+ mev_type
+ )
+ )
+
+ # Legacy: run_multiple_relays overrides relay to a list
+ if mev_params.get("run_multiple_relays", False):
+ relay = ["flashbots", "helix"]
+
+ # Normalize relay to always be a list internally
+ if type(relay) == "string":
+ relay_list = [relay]
+ else:
+ relay_list = [r for r in relay]
+
+ # Validate relay values
+ for r in relay_list:
+ if r not in VALID_RELAYS:
+ fail("Invalid mev_relay value: '{0}'. Valid: {1}".format(r, VALID_RELAYS))
+
+ # Validate sidecar value
+ if sidecar not in VALID_SIDECARS:
+ fail(
+ "Invalid mev_sidecar value: '{0}'. Valid: {1}".format(
+ sidecar, VALID_SIDECARS
+ )
+ )
+
+ # Validate builder value
+ if builder not in VALID_BUILDERS:
+ fail(
+ "Invalid mev_builder value: '{0}'. Valid: {1}".format(
+ builder, VALID_BUILDERS
+ )
+ )
+
+ # Cross-component validation (hard errors for impossible combinations)
+ all_none_relays = len([r for r in relay_list if r != "none"]) == 0
+
+ if all_none_relays and builder == "flashbots":
+ fail(
+ "Invalid MEV config: mev_builder='flashbots' (rbuilder) requires at least one relay "
+ + "to submit blocks to, but mev_relay is 'none'. Use mev_builder='buildoor' for ePBS "
+ + "or set a relay."
+ )
+
+ if all_none_relays and sidecar != "none" and builder != "buildoor":
+ fail(
+ "Invalid MEV config: mev_sidecar='{0}' needs relay endpoints to connect to, ".format(
+ sidecar
+ )
+ + "but mev_relay is 'none' and builder is not 'buildoor' (which provides its own endpoint). "
+ + "Set a relay, use builder='buildoor', or set sidecar='none' for ePBS."
+ )
+
+ return struct(
+ relay=relay_list,
+ sidecar=sidecar,
+ builder=builder,
+ )
+
+
+def get_sidecar_service_prefix(sidecar):
+ if sidecar == "none":
+ fail("BUG: get_sidecar_service_prefix called with sidecar='none'")
+ if sidecar == "commit-boost":
+ return constants.COMMIT_BOOST_SERVICE_NAME_PREFIX
+ return constants.MEV_BOOST_SERVICE_NAME_PREFIX
+
+
+def get_relay_image(relay_name, mev_params):
+ if relay_name == "helix":
+ return mev_params.helix_relay_image or constants.DEFAULT_HELIX_RELAY_IMAGE
+ elif relay_name == "flashbots":
+ return mev_params.mev_relay_image or constants.DEFAULT_FLASHBOTS_RELAY_IMAGE
+ elif relay_name == "mev-rs":
+ return mev_params.mev_relay_image or constants.DEFAULT_MEV_RS_IMAGE
+ else:
+ return mev_params.mev_relay_image
diff --git a/src/package_io/sanity_check.star b/src/package_io/sanity_check.star
index bf7cb38cc..1969c064a 100644
--- a/src/package_io/sanity_check.star
+++ b/src/package_io/sanity_check.star
@@ -380,6 +380,10 @@ SUBCATEGORY_PARAMS = {
"run_multiple_relays",
"helix_relay_image",
"commit_boost_config",
+ "helix_relay_config",
+ "mev_relay",
+ "mev_sidecar",
+ "mev_builder",
],
"xatu_sentry_params": [
"xatu_sentry_image",
diff --git a/src/participant_network.star b/src/participant_network.star
index 1b4c0dd8e..717757d48 100644
--- a/src/participant_network.star
+++ b/src/participant_network.star
@@ -208,7 +208,9 @@ def launch_participant_network(
network_id,
num_participants,
args_with_right_defaults.port_publisher,
- args_with_right_defaults.mev_type,
+ args_with_right_defaults.mev_components.builder
+ if args_with_right_defaults.mev_components
+ else False,
args_with_right_defaults.mev_params,
extra_files_artifacts,
bootnodoor_enode,
diff --git a/static_files/mev/helix/config.yaml.tmpl b/static_files/mev/helix/config.yaml.tmpl
index 27d7376d9..30e8d6b0e 100644
--- a/static_files/mev/helix/config.yaml.tmpl
+++ b/static_files/mev/helix/config.yaml.tmpl
@@ -37,7 +37,7 @@ router_config:
- route: GetValidators
- route: SubmitBlock
- route: GetTopBid
-
+
# Proposer API routes
- route: GetHeader
rate_limit:
@@ -46,12 +46,12 @@ router_config:
- route: GetPayload
- route: RegisterValidators
- route: Status
-
+
# Data API routes
- route: ProposerPayloadDelivered
- route: BuilderBidsReceived
- route: ValidatorRegistration
-
+
# Graceful shutdown delay (milliseconds)
shutdown_delay_ms: 12000
@@ -75,25 +75,13 @@ admin_token: "admin_token"
logging:
type: Console
-# Optional: Website/dashboard
-website:
- enabled: true
- port: {{ .HELIX_RELAY_WEBSITE_PORT }}
- listen_address: "0.0.0.0"
- show_config_details: false
- network_name: "Kurtosis Helix Relay" # TODO - we can configure this to show network
- relay_url: "https://{{ .HELIX_RELAY_ENDPOINT_URL }}"
- relay_pubkey: "{{ .HELIX_RELAY_PUBKEY }}"
- link_beaconchain: "https://hoodi.beaconcha.in"
- link_etherscan: "https://hoodi.etherscan.io"
- link_data_api: "https://relay.example.com/relay/v1/data"
-
# CPU Core Pinning (CRITICAL for production performance)
cores:
auctioneer: 0 # Single core for main auctioneer thread
sub_workers: [0] # Cores for submission workers
reg_workers: [0] # Cores for registration workers
tokio: [0] # Cores for Tokio async runtime
+ tcp_bid_submissions_tile: 2
# Optional: Enable local development mode (disables some checks)
-is_local_dev: false
\ No newline at end of file
+is_local_dev: false