diff --git a/README.md b/README.md index 05e68dbb..acadf804 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,12 @@ optimism_package: # Seconds per slots seconds_per_slot: 2 + + # Additional services to run alongside the network + # Defaults to [] + # Available services: + # - blockscout + additional_services: [] ``` ### Additional configuration recommendations @@ -74,6 +80,8 @@ optimism_package: participants: - el_type: op-geth cl_type: op-node + additional_services: + - blockscout ethereum_package: participants: - el_type: geth diff --git a/main.star b/main.star index e2e68dc0..5aecf3ec 100644 --- a/main.star +++ b/main.star @@ -5,6 +5,7 @@ static_files = import_module( "github.com/kurtosis-tech/ethereum-package/src/static_files/static_files.star" ) participant_network = import_module("./src/participant_network.star") +blockscout = import_module("./src/blockscout/blockscout_launcher.star") def get_l1_stuff(all_l1_participants, l1_network_params): @@ -69,6 +70,7 @@ def run(plan, args={}): el_cl_data, gs_private_keys, l2oo_address, + l1_bridge_address, ) = contract_deployer.launch_contract_deployer( plan, l1_priv_key, @@ -84,7 +86,7 @@ def run(plan, args={}): name="op_jwt_file", ) - all_participants = participant_network.launch_participant_network( + all_l2_participants = participant_network.launch_participant_network( plan, args_with_right_defaults.participants, jwt_file, @@ -95,4 +97,23 @@ def run(plan, args={}): l2oo_address, ) - plan.print(all_participants) + all_el_contexts = [] + all_cl_contexts = [] + for participant in all_l2_participants: + all_el_contexts.append(participant.el_context) + all_cl_contexts.append(participant.cl_context) + + for additional_service in args_with_right_defaults.additional_services: + if additional_service == "blockscout": + plan.print("Launching op-blockscout") + blockscout_launcher = blockscout.launch_blockscout( + plan, all_l1_participants[0].el_context # first L1 EL url, + ) + plan.print("Successfully launched op-blockscout") + + plan.print(all_l2_participants) + plan.print( + "Begin your L2 adventures by depositing some L1 Kurtosis ETH to: {0}".format( + l1_bridge_address + ) + ) diff --git a/network_params.yaml b/network_params.yaml index 4e69e278..915fbccb 100644 --- a/network_params.yaml +++ b/network_params.yaml @@ -1,9 +1,9 @@ optimism_package: participants: - el_type: op-geth - cl_type: op-node - count: 3 - el_type: op-reth + additional_services: + - blockscout ethereum_package: participants: - el_type: geth diff --git a/src/blockscout/blockscout_launcher.star b/src/blockscout/blockscout_launcher.star new file mode 100644 index 00000000..56fb6e7b --- /dev/null +++ b/src/blockscout/blockscout_launcher.star @@ -0,0 +1,156 @@ +shared_utils = import_module( + "github.com/kurtosis-tech/ethereum-package/src/shared_utils/shared_utils.star" +) +constants = import_module( + "github.com/kurtosis-tech/ethereum-package/src/package_io/constants.star" +) + +postgres = import_module("github.com/kurtosis-tech/postgres-package/main.star") + +IMAGE_NAME_BLOCKSCOUT = "blockscout/blockscout:6.6.0" +IMAGE_NAME_BLOCKSCOUT_VERIF = "ghcr.io/blockscout/smart-contract-verifier:v1.6.0" + +SERVICE_NAME_BLOCKSCOUT = "op-blockscout" + +HTTP_PORT_ID = "http" +HTTP_PORT_NUMBER = 4000 +HTTP_PORT_NUMBER_VERIF = 8050 + +BLOCKSCOUT_MIN_CPU = 100 +BLOCKSCOUT_MAX_CPU = 1000 +BLOCKSCOUT_MIN_MEMORY = 1024 +BLOCKSCOUT_MAX_MEMORY = 2048 + +BLOCKSCOUT_VERIF_MIN_CPU = 10 +BLOCKSCOUT_VERIF_MAX_CPU = 1000 +BLOCKSCOUT_VERIF_MIN_MEMORY = 10 +BLOCKSCOUT_VERIF_MAX_MEMORY = 1024 + +USED_PORTS = { + HTTP_PORT_ID: shared_utils.new_port_spec( + HTTP_PORT_NUMBER, + shared_utils.TCP_PROTOCOL, + shared_utils.HTTP_APPLICATION_PROTOCOL, + ) +} + +VERIF_USED_PORTS = { + HTTP_PORT_ID: shared_utils.new_port_spec( + HTTP_PORT_NUMBER_VERIF, + shared_utils.TCP_PROTOCOL, + shared_utils.HTTP_APPLICATION_PROTOCOL, + ) +} + + +def launch_blockscout( + plan, + el_context, +): + postgres_output = postgres.run( + plan, + service_name="op-{}-postgres".format(SERVICE_NAME_BLOCKSCOUT), + database="op-blockscout", + extra_configs=["max_connections=1000"], + ) + + config_verif = get_config_verif() + verif_service_name = "{}-verif".format(SERVICE_NAME_BLOCKSCOUT) + verif_service = plan.add_service(verif_service_name, config_verif) + verif_url = "http://{}:{}/api".format( + verif_service.hostname, verif_service.ports["http"].number + ) + + config_backend = get_config_backend( + postgres_output, + el_context, + verif_url, + ) + blockscout_service = plan.add_service(SERVICE_NAME_BLOCKSCOUT, config_backend) + plan.print(blockscout_service) + + blockscout_url = "http://{}:{}".format( + blockscout_service.hostname, blockscout_service.ports["http"].number + ) + + return blockscout_url + + +def get_config_verif(): + return ServiceConfig( + image=IMAGE_NAME_BLOCKSCOUT_VERIF, + ports=VERIF_USED_PORTS, + env_vars={ + "SMART_CONTRACT_VERIFIER__SERVER__HTTP__ADDR": "0.0.0.0:{}".format( + HTTP_PORT_NUMBER_VERIF + ) + }, + min_cpu=BLOCKSCOUT_VERIF_MIN_CPU, + max_cpu=BLOCKSCOUT_VERIF_MAX_CPU, + min_memory=BLOCKSCOUT_VERIF_MIN_MEMORY, + max_memory=BLOCKSCOUT_VERIF_MAX_MEMORY, + ) + + +def get_config_backend(postgres_output, el_context, verif_url): + database_url = "{protocol}://{user}:{password}@{hostname}:{port}/{database}".format( + protocol="postgresql", + user=postgres_output.user, + password=postgres_output.password, + hostname=postgres_output.service.hostname, + port=postgres_output.port.number, + database=postgres_output.database, + ) + + optimism_env_vars = { + "CHAIN_TYPE": "optimism", + "INDEXER_OPTIMISM_L1_RPC": el_context.rpc_http_url, + "INDEXER_OPTIMISM_L1_PORTAL_CONTRACT": "", + "INDEXER_OPTIMISM_L1_BATCH_START_BLOCK": "", + "INDEXER_OPTIMISM_L1_BATCH_INBOX": "0xff00000000000000000000000000000000042069", + "INDEXER_OPTIMISM_L1_BATCH_SUBMITTER": "0x776463f498A63a42Ac1AFc7c64a4e5A9ccBB4d32", + "INDEXER_OPTIMISM_L1_BATCH_BLOCKSCOUT_BLOBS_API_URL": verif_url + "/blobs", + "INDEXER_OPTIMISM_L1_BATCH_BLOCKS_CHUNK_SIZE": "4", + "INDEXER_OPTIMISM_L2_BATCH_GENESIS_BLOCK_NUMBER": "", + "INDEXER_OPTIMISM_L1_OUTPUT_ROOTS_START_BLOCK": "", + "INDEXER_OPTIMISM_L1_OUTPUT_ORACLE_CONTRACT": "", + "INDEXER_OPTIMISM_L1_DEPOSITS_START_BLOCK": "", + "INDEXER_OPTIMISM_L1_DEPOSITS_BATCH_SIZE": "500", + "INDEXER_OPTIMISM_L1_WITHDRAWALS_START_BLOCK": "", + "INDEXER_OPTIMISM_L2_WITHDRAWALS_START_BLOCK": "", + "INDEXER_OPTIMISM_L2_MESSAGE_PASSER_CONTRACT": "", + } + + return ServiceConfig( + image=IMAGE_NAME_BLOCKSCOUT, + ports=USED_PORTS, + cmd=[ + "/bin/sh", + "-c", + 'bin/blockscout eval "Elixir.Explorer.ReleaseTasks.create_and_migrate()" && bin/blockscout start', + ], + env_vars={ + "ETHEREUM_JSONRPC_VARIANT": "erigon" + if el_context.client_name == "erigon" or el_context.client_name == "reth" + else el_context.client_name, + "ETHEREUM_JSONRPC_HTTP_URL": el_context.rpc_http_url, + "ETHEREUM_JSONRPC_TRACE_URL": el_context.rpc_http_url, + "DATABASE_URL": database_url, + "COIN": "opETH", + "MICROSERVICE_SC_VERIFIER_ENABLED": "true", + "MICROSERVICE_SC_VERIFIER_URL": verif_url, + "MICROSERVICE_SC_VERIFIER_TYPE": "sc_verifier", + "INDEXER_DISABLE_PENDING_TRANSACTIONS_FETCHER": "true", + "ECTO_USE_SSL": "false", + "NETWORK": "Kurtosis", + "SUBNETWORK": "Kurtosis", + "API_V2_ENABLED": "true", + "PORT": "{}".format(HTTP_PORT_NUMBER), + "SECRET_KEY_BASE": "56NtB48ear7+wMSf0IQuWDAAazhpb31qyc7GiyspBP2vh7t5zlCsF5QDv76chXeN", + } + | optimism_env_vars, + min_cpu=BLOCKSCOUT_MIN_CPU, + max_cpu=BLOCKSCOUT_MAX_CPU, + min_memory=BLOCKSCOUT_MIN_MEMORY, + max_memory=BLOCKSCOUT_MAX_MEMORY, + ) diff --git a/src/contracts/contract_deployer.star b/src/contracts/contract_deployer.star index 8c62f7fa..d52f44d8 100644 --- a/src/contracts/contract_deployer.star +++ b/src/contracts/contract_deployer.star @@ -103,10 +103,21 @@ def launch_contract_deployer( files={"/network-configs": op_genesis.files_artifacts[0]}, ) + l1_bridge_address = plan.run_sh( + description="Getting the L1StandardBridgeProxy address", + run="cat /network-configs/L1StandardBridgeProxy.json | tr -d '\n'", + files={"/network-configs": op_genesis.files_artifacts[0]}, + ) + private_keys = { "GS_SEQUENCER_PRIVATE_KEY": gs_sequencer_private_key.output, "GS_BATCHER_PRIVATE_KEY": gs_batcher_private_key.output, "GS_PROPOSER_PRIVATE_KEY": gs_proposer_private_key.output, } - return op_genesis.files_artifacts[0], private_keys, l2oo_address.output + return ( + op_genesis.files_artifacts[0], + private_keys, + l2oo_address.output, + l1_bridge_address.output, + ) diff --git a/src/package_io/input_parser.star b/src/package_io/input_parser.star index fe9958b8..3b0c20ff 100644 --- a/src/package_io/input_parser.star +++ b/src/package_io/input_parser.star @@ -25,6 +25,9 @@ ATTR_TO_BE_SKIPPED_AT_ROOT = ( ) +DEFAULT_ADDITIONAL_SERVICES = [] + + def input_parser(plan, input_args): result = parse_network_params(plan, input_args) @@ -44,6 +47,9 @@ def input_parser(plan, input_args): network_id=result["network_params"]["network_id"], seconds_per_slot=result["network_params"]["seconds_per_slot"], ), + additional_services=result.get( + "additional_services", DEFAULT_ADDITIONAL_SERVICES + ), )