diff --git a/README.md b/README.md index 6f9a9f1e8..2069db091 100644 --- a/README.md +++ b/README.md @@ -186,7 +186,26 @@ To configure the package behaviour, you can modify your `eth2-package-params.yam // Default: None - no mev boost, mev builder, mev flood or relays are spun up // mock - mock-builder & mev-boost are spun up // full - mev-boost, relays, flooder and builder are all spun up - "mev_type": None + "mev_type": None, + + // Parameters if MEV is used + "mev_params": { + // The image to use for MEV boot relay + // This uses the h4ck3rk3y image instead of the flashbots image as that isn't published yet + "mev_relay_image": "h4ck3rk3y/mev-boost-relay", + // Extra parameters to send to the API + "mev_relay_api_extra_args": [], + // Extra parameters to send to the housekeeper + "mev_relay_housekeeper_extra_args": [], + // Extra parameters to send to the website + "mev_relay_website_extra_args": [], + // Extra parameters to send to the builder + "mev_builder_extra_args": [], + // Image to use for mev-flood + "mev_flood_image": "flashbots/mev-flood", + // Extra parameters to send to mev-flood + "mev_flood_extra_args": [] + } } ``` diff --git a/main.star b/main.star index 56c6f3655..f9dea1a4d 100644 --- a/main.star +++ b/main.star @@ -13,6 +13,8 @@ grafana =import_module("github.com/kurtosis-tech/eth2-package/src/grafana/grafan testnet_verifier = import_module("github.com/kurtosis-tech/eth2-package/src/testnet_verifier/testnet_verifier.star") mev_boost_launcher_module = import_module("github.com/kurtosis-tech/eth2-package/src/mev_boost/mev_boost_launcher.star") mock_mev_launcher_module = import_module("github.com/kurtosis-tech/eth2-package/src/mock_mev/mock_mev_launcher.star") +mev_relay_launcher_module = import_module("github.com/kurtosis-tech/eth2-package/src/mev_relay/mev_relay_launcher.star") +mev_flood_module = import_module("github.com/kurtosis-tech/eth2-package/src/mev_flood/mev_flood_launcher.star") GRAFANA_USER = "admin" GRAFANA_PASSWORD = "admin" @@ -23,12 +25,15 @@ HTTP_PORT_ID_FOR_FACT = "http" MEV_BOOST_SHOULD_CHECK_RELAY = True MOCK_MEV_TYPE = "mock" +FULL_MEV_TYPE = "full" +PATH_TO_PARSED_BEACON_STATE = "/genesis/output/parsedBeaconState.json" def run(plan, args): args_with_right_defaults, args_with_defaults_dict = parse_input.parse_input(args) num_participants = len(args_with_right_defaults.participants) network_params = args_with_right_defaults.network_params + mev_params = args_with_right_defaults.mev_params grafana_datasource_config_template = read_file(static_files.GRAFANA_DATASOURCE_CONFIG_TEMPLATE_FILEPATH) grafana_dashboards_config_template = read_file(static_files.GRAFANA_DASHBOARD_PROVIDERS_CONFIG_TEMPLATE_FILEPATH) @@ -37,7 +42,7 @@ def run(plan, args): plan.print("Read the prometheus, grafana templates") plan.print("Launching participant network with {0} participants and the following network params {1}".format(num_participants, network_params)) - all_participants, cl_genesis_timestamp, _ = eth_network_module.run(plan, args_with_defaults_dict) + all_participants, cl_genesis_timestamp, genesis_validators_root = eth_network_module.run(plan, args_with_defaults_dict) all_el_client_contexts = [] all_cl_client_contexts = [] @@ -57,8 +62,29 @@ def run(plan, args): beacon_uri = "{0}:{1}".format(all_cl_client_contexts[0].ip_addr, all_cl_client_contexts[0].http_port_num) jwt_secret = all_el_client_contexts[0].jwt_secret endpoint = mock_mev_launcher_module.launch_mock_mev(plan, el_uri, beacon_uri, jwt_secret) + mev_endpoints.append(endpoint) + elif args_with_right_defaults.mev_type and args_with_right_defaults.mev_type == FULL_MEV_TYPE: + el_uri = "http://{0}:{1}".format(all_el_client_contexts[0].ip_addr, all_el_client_contexts[0].rpc_port_num) + builder_uri = "http://{0}:{1}".format(all_el_client_contexts[-1].ip_addr, all_el_client_contexts[-1].rpc_port_num) + beacon_uri = ["http://{0}:{1}".format(context.ip_addr, context.http_port_num) for context in all_cl_client_contexts][-1] + beacon_uris = beacon_uri + first_cl_client = all_cl_client_contexts[0] + first_client_beacon_name = first_cl_client.beacon_service_name + mev_flood_module.launch_mev_flood(plan, mev_params.mev_flood_image, el_uri) + epoch_recipe = GetHttpRequestRecipe( + endpoint = "/eth/v1/beacon/blocks/head", + port_id = HTTP_PORT_ID_FOR_FACT, + extract = { + "epoch": ".data.message.body.attestations[0].data.target.epoch" + } + ) + plan.wait(recipe = epoch_recipe, field = "extract.epoch", assertion = ">=", target_value = str(network_params.capella_fork_epoch), timeout = "20m", service_name = first_client_beacon_name) + plan.print("epoch 2 reached, can begin mev stuff") + endpoint = mev_relay_launcher_module.launch_mev_relay(plan, mev_params, network_params.network_id, beacon_uris, genesis_validators_root, builder_uri) + mev_flood_module.spam_in_background(plan, el_uri, mev_params.mev_flood_extra_args) mev_endpoints.append(endpoint) + # spin up the mev boost contexts if some endpoints for relays have been passed all_mevboost_contexts = [] if mev_endpoints: diff --git a/src/mev_boost/mev_boost_launcher.star b/src/mev_boost/mev_boost_launcher.star index ffc89af78..de9d19d02 100644 --- a/src/mev_boost/mev_boost_launcher.star +++ b/src/mev_boost/mev_boost_launcher.star @@ -34,11 +34,14 @@ def get_config(mev_boost_launcher, network_id): ports = USED_PORTS, cmd = command, env_vars = { - # TODO remove the hardcoding + # TODO(maybe) remove the hardcoding # This is set to match this file https://github.com/kurtosis-tech/eth-network-package/blob/main/static_files/genesis-generation-config/cl/config.yaml.tmpl#L11 + # latest-notes + # does this need genesis time to be set as well "GENESIS_FORK_VERSION": "0x10000038", "BOOST_LISTEN_ADDR": "0.0.0.0:{0}".format(parse_input.FLASHBOTS_MEV_BOOST_PORT), - "SKIP_RELAY_SIGNATURE_CHECK": "true", + # maybe this is breaking; this isn't verifyign the bid and not sending it to the validator + "SKIP_RELAY_SIGNATURE_CHECK": "1", "RELAYS": mev_boost_launcher.relay_end_points[0] } ) diff --git a/src/mev_flood/mev_flood_launcher.star b/src/mev_flood/mev_flood_launcher.star new file mode 100644 index 000000000..25412cd0d --- /dev/null +++ b/src/mev_flood/mev_flood_launcher.star @@ -0,0 +1,30 @@ +ADMIN_KEY = "0xef5177cd0b6b21c87db5a0bf35d4084a8a57a9d6a064f86d51ac85f2b873a4e2" +USER_KEY = "0x7988b3a148716ff800414935b305436493e1f25237a2a03e5eebc343735e2f31" + +def launch_mev_flood(plan, image, el_uri): + plan.add_service( + name = "mev-flood", + config = ServiceConfig( + image = image, + entrypoint = ["/bin/sh", "-c", "touch main.log && tail -F main.log"] + ) + ) + + plan.exec( + service_name = "mev-flood", + recipe = ExecRecipe( + command = ["/bin/sh", "-c", "./run init -r {0} -k {1} -u {2} -s deployment.json".format(el_uri, ADMIN_KEY, USER_KEY)] + ) + ) + +def spam_in_background(plan, el_uri, mev_flood_extra_args): + command = ["/bin/sh", "-c", "nohup ./run spam -r {0} -k {1} -u {2} -l deployment.json --secondsPerBundle 15 >main.log 2>&1 &".format(el_uri, ADMIN_KEY, USER_KEY)] + if mev_flood_extra_args: + joined_extra_args = " ".join(mev_flood_extra_args) + command = ["/bin/sh", "-c", "nohup ./run spam -r {0} -k {1} -u {2} -l deployment.json --secondsPerBundle 15 {3} >main.log 2>&1 &".format(el_uri, ADMIN_KEY, USER_KEY, joined_extra_args)] + plan.exec( + service_name = "mev-flood", + recipe = ExecRecipe( + command = command + ) + ) diff --git a/src/mev_relay/mev_relay_launcher.star b/src/mev_relay/mev_relay_launcher.star new file mode 100644 index 000000000..6a8716d26 --- /dev/null +++ b/src/mev_relay/mev_relay_launcher.star @@ -0,0 +1,71 @@ +redis_module = import_module("github.com/kurtosis-tech/redis-package/main.star") +postgres_module = import_module("github.com/kurtosis-tech/postgres-package/main.star") + +DUMMY_SECRET_KEY = "0x607a11b45a7219cc61a3d9c5fd08c7eebd602a6a19a977f8d3771d5711a550f2" +DUMMY_PUB_KEY = "0xa55c1285d84ba83a5ad26420cd5ad3091e49c55a813eee651cd467db38a8c8e63192f47955e9376f6b42f6d190571cb5" + +MEV_RELAY_WEBSITE = "mev-relay-website" +MEV_RELAY_ENDPOINT = "mev-relay-api" +MEV_RELAY_HOUSEKEEPER = "mev-relay-housekeeper" + +MEV_RELAY_ENDPOINT_PORT = 9062 +MEV_RELAY_WEBSITE_PORT = 9060 + +NETWORK_ID_TO_NAME = { + "5": "goerli", + "11155111": "sepolia", + "3": "ropsten", +} + +def launch_mev_relay(plan, mev_params, network_id, beacon_uris, validator_root, builder_uri): + redis = redis_module.run(plan, {}) + # making the password postgres as the relay expects it to be postgres + postgres = postgres_module.run(plan, {"password": "postgres", "user": "postgres", "database": "postgres", "name": "postgres"}) + + network_name = NETWORK_ID_TO_NAME.get(network_id, network_id) + + image = mev_params.mev_relay_image + + # TODO(maybe) remove hardocded values for the forks + env_vars= { + "GENESIS_FORK_VERSION": "0x10000038", + "BELLATRIX_FORK_VERSION": "0x30000038", + "CAPELLA_FORK_VERSION": "0x40000038", + "DENEB_FORK_VERSION": "0x50000038", + "GENESIS_VALIDATORS_ROOT": validator_root + } + + plan.add_service( + name = MEV_RELAY_HOUSEKEEPER, + config = ServiceConfig( + image = image, + cmd = ["housekeeper", "--network", "custom", "--db", "postgres://postgres:postgres@postgres:5432/postgres?sslmode=disable", "--redis-uri", "redis:6379", "--beacon-uris", beacon_uris] + mev_params.mev_relay_housekeeper_extra_args, + env_vars= env_vars + ) + ) + + api = plan.add_service( + name = MEV_RELAY_ENDPOINT, + config = ServiceConfig( + image = image, + cmd = ["api", "--network", "custom", "--db", "postgres://postgres:postgres@postgres:5432/postgres?sslmode=disable", "--secret-key", DUMMY_SECRET_KEY, "--listen-addr", "0.0.0.0:{0}".format(MEV_RELAY_ENDPOINT_PORT), "--redis-uri", "redis:6379", "--beacon-uris", beacon_uris, "--blocksim", builder_uri] + mev_params.mev_relay_api_extra_args, + ports = { + "api": PortSpec(number = MEV_RELAY_ENDPOINT_PORT, transport_protocol= "TCP") + }, + env_vars= env_vars + ) + ) + + plan.add_service( + name = MEV_RELAY_WEBSITE, + config = ServiceConfig( + image = image, + cmd = ["website", "--network", "custom", "--db", "postgres://postgres:postgres@postgres:5432/postgres?sslmode=disable", "--listen-addr", "0.0.0.0:{0}".format(MEV_RELAY_WEBSITE_PORT), "--redis-uri", "redis:6379", "https://{0}@{1}".format(DUMMY_PUB_KEY, MEV_RELAY_ENDPOINT)] + mev_params.mev_relay_website_extra_args, + ports = { + "api": PortSpec(number = MEV_RELAY_WEBSITE_PORT, transport_protocol= "TCP", application_protocol="http") + }, + env_vars= env_vars + ) + ) + + return "http://{0}@{1}:{2}".format(DUMMY_PUB_KEY, api.ip_address, MEV_RELAY_ENDPOINT_PORT) diff --git a/src/package_io/parse_input.star b/src/package_io/parse_input.star index 0fb6a95e2..17f96ad61 100644 --- a/src/package_io/parse_input.star +++ b/src/package_io/parse_input.star @@ -24,6 +24,8 @@ FLASHBOTS_MEV_BOOST_PORT = 18550 MEV_BOOST_SERVICE_NAME_PREFIX = "mev-boost-" +package_io = import_module("github.com/kurtosis-tech/eth-network-package/package_io/constants.star") + def parse_input(input_args): result = default_input_args() for attr in input_args: @@ -35,6 +37,10 @@ def parse_input(input_args): for sub_attr in input_args["network_params"]: sub_value = input_args["network_params"][sub_attr] result["network_params"][sub_attr] = sub_value + elif attr == "mev_params": + for sub_attr in input_args["mev_params"]: + sub_value = input_args["mev_params"]["sub_attr"] + result["mev_params"][sub_attr] = sub_value elif attr == "participants": participants = [] for participant in input_args["participants"]: @@ -139,6 +145,15 @@ def parse_input(input_args): deneb_fork_epoch=result["network_params"]["deneb_fork_epoch"], genesis_delay=result["network_params"]["genesis_delay"] ), + mev_params = struct( + mev_relay_image = result["mev_params"]["mev_relay_image"], + mev_relay_api_extra_args = result["mev_params"]["mev_relay_api_extra_args"], + mev_relay_housekeeper_extra_args = result["mev_params"]["mev_relay_housekeeper_extra_args"], + mev_relay_website_extra_args = result["mev_params"]["mev_relay_website_extra_args"], + mev_builder_extra_args = result["mev_params"]["mev_builder_extra_args"], + mev_flood_image = result["mev_params"]["mev_flood_image"], + mev_flood_extra_args = result["mev_params"]["mev_flood_extra_args"] + ), launch_additional_services=result["launch_additional_services"], wait_for_finalization=result["wait_for_finalization"], wait_for_verifications=result["wait_for_verifications"], @@ -158,6 +173,7 @@ def get_client_log_level_or_default(participant_log_level, global_log_level, cli def default_input_args(): network_params = default_network_params() participants = [default_participant()] + mev_params = get_default_mev_params() return { "mev_type": None, "participants": participants, @@ -167,6 +183,7 @@ def default_input_args(): "wait_for_verifications": False, "verifications_epoch_limit": 5, "global_client_log_level": "info", + "mev_params": mev_params, "snooper_enabled": False, } @@ -174,19 +191,18 @@ def default_network_params(): # this is temporary till we get params working return { "preregistered_validator_keys_mnemonic": "giant issue aisle success illegal bike spike question tent bar rely arctic volcano long crawl hungry vocal artwork sniff fantasy very lucky have athlete", - "num_validator_keys_per_node": 64, - "network_id": "3151908", - "deposit_contract_address": "0x4242424242424242424242424242424242424242", - "seconds_per_slot": 12, - "slots_per_epoch": 32, - "genesis_delay": 120, - "capella_fork_epoch": 1, - # arbitrarily large while we sort out https://github.com/kurtosis-tech/eth-network-package/issues/42 - # this will take 53~ hoours for now - "deneb_fork_epoch": 500, + "num_validator_keys_per_node": 64, + "network_id": "3151908", + "deposit_contract_address": "0x4242424242424242424242424242424242424242", + "seconds_per_slot": 12, + "slots_per_epoch": 32, + "genesis_delay": 120, + "capella_fork_epoch": 1, + "deneb_fork_epoch": 500 } def default_participant(): + # TODO(now) add support for mev boost image and extra parameters return { "el_client_type": "geth", "el_client_image": "", @@ -201,6 +217,18 @@ def default_participant(): "count": 1 } +def get_default_mev_params(): + return { + # TODO fix this when Capella Signature verification works - change it to flashbots/ + "mev_relay_image": "h4ck3rk3y/mev-boost-relay", + "mev_relay_api_extra_args": [], + "mev_relay_housekeeper_extra_args": [], + "mev_relay_website_extra_args": [], + "mev_builder_extra_args": [], + "mev_flood_image": "flashbots/mev-flood", + "mev_flood_extra_args": [] + } + # TODO perhaps clean this up into a map def enrich_mev_extra_params(parsed_arguments_dict, mev_prefix, mev_port): @@ -220,4 +248,27 @@ def enrich_mev_extra_params(parsed_arguments_dict, mev_prefix, mev_port): if participant["cl_client_type"] == "prysm": participant["validator_extra_params"].append("--enable-builder") participant["beacon_extra_params"].append("--http-mev-relay={0}".format(mev_url)) + + num_participants = len(parsed_arguments_dict["participants"]) + + mev_url = "http://{0}{1}:{2}".format(mev_prefix, num_participants, mev_port) + + mev_participant = { + "el_client_type": "geth", + # TODO replace with actual when flashbots/builder is published + "el_client_image": "h4ck3rk3y/builder", + "el_client_log_level": "", + "cl_client_type": "lighthouse", + # THIS overrides the beacon image + "cl_client_image": "sigp/lighthouse", + "cl_client_log_level": "", + "beacon_extra_params": ["--builder={0}".format(mev_url), "--always-prepare-payload", "--prepare-payload-lookahead", "12000"], + # TODO(maybe) make parts of this more passable like the mev-relay-endpoint & forks + "el_extra_params": ["--builder", "--builder.remote_relay_endpoint=http://mev-relay-api:9062", "--builder.beacon_endpoints=http://cl-{0}-lighthouse-geth:4000".format(num_participants+1), "--builder.bellatrix_fork_version=0x30000038", "--builder.genesis_fork_version=0x10000038", "--builder.genesis_validators_root={0}".format(package_io.GENESIS_VALIDATORS_ROOT_PLACEHOLDER), "--miner.extradata=\"Illuminate Dmocratize Dstribute\"", "--miner.algotype=greedy"] + parsed_arguments_dict["mev_params"]["mev_builder_extra_args"], + "validator_extra_params": ["--builder-proposals"], + "builder_network_params": None + } + + parsed_arguments_dict["participants"].append(mev_participant) + return parsed_arguments_dict