diff --git a/.github/tests/mix-with-tools.yaml b/.github/tests/mix-with-tools.yaml index 90c5e70ec..23e35bb67 100644 --- a/.github/tests/mix-with-tools.yaml +++ b/.github/tests/mix-with-tools.yaml @@ -21,3 +21,4 @@ additional_services: - goomy_blob - full_beaconchain_explorer - custom_flood + - blobscan diff --git a/README.md b/README.md index bcb4cdba5..73720bb05 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ Specifically, this [package][package-reference] will: 3. Spin up a [transaction spammer](https://github.com/MariusVanDerWijden/tx-fuzz) to send fake transactions to the network 4. Spin up and connect a [testnet verifier](https://github.com/ethereum/merge-testnet-verifier) 5. Spin up a Grafana and Prometheus instance to observe the network +6. Spin up a Blobscan instance to analyze blob transactions (EIP-4844) Optional features (enabled via flags or parameter files at runtime): @@ -268,6 +269,7 @@ additional_services: - dora - full_beaconchain_explorer - prometheus_grafana + - blobscan # If set, the package will block until a finalized epoch has occurred. wait_for_finalization: false diff --git a/main.star b/main.star index 26c84e147..4f87b44ba 100644 --- a/main.star +++ b/main.star @@ -21,6 +21,7 @@ beacon_metrics_gazer = import_module( "./src/beacon_metrics_gazer/beacon_metrics_gazer_launcher.star" ) dora = import_module("./src/dora/dora_launcher.star") +blobscan = import_module("./src/blobscan/blobscan_launcher.star") full_beaconchain_explorer = import_module( "./src/full_beaconchain/full_beaconchain_launcher.star" ) @@ -336,6 +337,15 @@ def run(plan, args={}): network_params.electra_fork_epoch, ) plan.print("Successfully launched dora") + elif additional_service == "blobscan": + plan.print("Launching blobscan") + blobscan.launch_blobscan( + plan, + all_cl_client_contexts, + all_el_client_contexts, + network_params.network_id, + ) + plan.print("Successfully launched blobscan") elif additional_service == "full_beaconchain_explorer": plan.print("Launching full-beaconchain-explorer") full_beaconchain_explorer_config_template = read_file( diff --git a/src/blobscan/blobscan_launcher.star b/src/blobscan/blobscan_launcher.star new file mode 100644 index 000000000..6105af942 --- /dev/null +++ b/src/blobscan/blobscan_launcher.star @@ -0,0 +1,124 @@ +shared_utils = import_module("../shared_utils/shared_utils.star") +postgres = import_module("github.com/kurtosis-tech/postgres-package/main.star") + +WEB_SERVICE_NAME = "blobscan-web" +API_SERVICE_NAME = "blobscan-api" +INDEXER_SERVICE_NAME = "blobscan-indexer" + +HTTP_PORT_ID = "http" +WEB_HTTP_PORT_NUMBER = 3000 +API_HTTP_PORT_NUMBER = 3001 + +WEB_PORTS = { + HTTP_PORT_ID: shared_utils.new_port_spec( + WEB_HTTP_PORT_NUMBER, + shared_utils.TCP_PROTOCOL, + shared_utils.HTTP_APPLICATION_PROTOCOL, + ) +} + + +API_PORTS = { + HTTP_PORT_ID: shared_utils.new_port_spec( + API_HTTP_PORT_NUMBER, + shared_utils.TCP_PROTOCOL, + shared_utils.HTTP_APPLICATION_PROTOCOL, + ) +} + +ENTRYPOINT_ARGS = ["/bin/sh", "-c"] + + +def launch_blobscan( + plan, + cl_client_contexts, + el_client_contexts, + chain_id, +): + beacon_node_rpc_uri = "http://{0}:{1}".format( + cl_client_contexts[0].ip_addr, cl_client_contexts[0].http_port_num + ) + execution_node_rpc_uri = "http://{0}:{1}".format( + el_client_contexts[0].ip_addr, el_client_contexts[0].rpc_port_num + ) + + postgres_output = postgres.run(plan, service_name="blobscan-postgres") + api_config = get_api_config(postgres_output.url, beacon_node_rpc_uri, chain_id) + blobscan_config = plan.add_service(API_SERVICE_NAME, api_config) + + blobscan_api_url = "http://{0}:{1}".format( + blobscan_config.ip_address, blobscan_config.ports[HTTP_PORT_ID].number + ) + + web_config = get_web_config( + postgres_output.url, beacon_node_rpc_uri, chain_id + ) + plan.add_service(WEB_SERVICE_NAME, web_config) + + indexer_config = get_indexer_config( + beacon_node_rpc_uri, execution_node_rpc_uri, blobscan_api_url + ) + plan.add_service(INDEXER_SERVICE_NAME, indexer_config) + + +def get_api_config(database_url, beacon_node_rpc, chain_id): + IMAGE_NAME = "blossomlabs/blobscan:stable" + + return ServiceConfig( + image=IMAGE_NAME, + ports=API_PORTS, + env_vars={ + "BEACON_NODE_ENDPOINT": beacon_node_rpc, + "CHAIN_ID": chain_id, + "DATABASE_URL": database_url, + "SECRET_KEY": "supersecret", + }, + cmd=["api"], + ready_conditions=ReadyCondition( + recipe=GetHttpRequestRecipe( + port_id="http", + endpoint="/api/healthcheck", + ), + field="code", + assertion="==", + target_value=200, + interval="5s", + timeout="5s", + ), + ) + + +def get_web_config(database_url, beacon_node_rpc, chain_id): + # TODO: https://github.com/kurtosis-tech/kurtosis/issues/1861 + # Configure NEXT_PUBLIC_BEACON_BASE_URL and NEXT_PUBLIC_EXPLORER_BASE env vars + # once retrieving external URLs from services are supported in Kurtosis. + IMAGE_NAME = "blossomlabs/blobscan:stable" + + return ServiceConfig( + image=IMAGE_NAME, + ports=WEB_PORTS, + env_vars={ + "DATABASE_URL": database_url, + "SECRET_KEY": "supersecret", + "NEXT_PUBLIC_NETWORK_NAME": "kurtosis-devnet", + "BEACON_NODE_ENDPOINT": beacon_node_rpc, + "CHAIN_ID": chain_id, + }, + cmd=["web"], + ) + + +def get_indexer_config(beacon_node_rpc, execution_node_rpc, blobscan_api_url): + IMAGE_NAME = "blossomlabs/blobscan-indexer:master" + + return ServiceConfig( + image=IMAGE_NAME, + env_vars={ + "SECRET_KEY": "supersecret", + "BLOBSCAN_API_ENDPOINT": blobscan_api_url, + "EXECUTION_NODE_ENDPOINT": execution_node_rpc, + "BEACON_NODE_ENDPOINT": beacon_node_rpc, + }, + entrypoint=ENTRYPOINT_ARGS, + cmd=[" && ".join(["sleep 90", "/app/blob-indexer"])], + )