diff --git a/.editorconfig b/.editorconfig index 3562db48e..3e8358f7f 100644 --- a/.editorconfig +++ b/.editorconfig @@ -12,6 +12,9 @@ max_line_length = 160 file_header_template = SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited\nSPDX-License-Identifier: LGPL-3.0-only +[*.py] +indent_size = 4 + [*.cs] indent_size = 4 diff --git a/.github/workflows/run-node.yml b/.github/workflows/run-node.yml index 0a8fe728d..a208dee22 100644 --- a/.github/workflows/run-node.yml +++ b/.github/workflows/run-node.yml @@ -2,11 +2,155 @@ name: Run Node in Chain on: workflow_dispatch: + inputs: + nethermind-image: + description: Nethermind Arbitrum docker image + required: true + nitro-image: + description: Nitro docker image + required: true + chain: + description: Chain name + required: true + default: sepolia + type: choice + options: + - sepolia + - mainnet + timeout: + description: Timeout in hours for the machine to be auto removed + required: false + type: number + default: 24 + ssh-keys: + description: Comma separated SSH keys to add to the machine + required: false + type: string + default: "" + allowed-ips: + description: Comma separated allowed IPs to allow in the machine firewall + required: false + type: string + default: "" + tags: + description: Comma separated tags for the machine + required: false + type: string + default: "" jobs: run-node: runs-on: ubuntu-latest + env: + WORKFLOW_REF: "main" + # Generated by the workflow + BASE_TAG: "" steps: - # Mock workflow - name: Checkout repository uses: actions/checkout@v5 + + - name: Authenticate App + id: gh-app + uses: actions/create-github-app-token@v2 + with: + app-id: ${{ vars.APP_ID }} + private-key: ${{ secrets.APP_PRIVATE_KEY }} + repositories: "post-merge-smoke-tests" + + - name: Set up Python + uses: actions/setup-python@v6 + + - name: Install dependencies + run: | + pip install jinja2 jinja2-ansible-filters + + - name: Build base tag + env: + USER_NAME: ${{ github.actor }} + run: | + # Generate BASE_TAG using the first letter of USER_NAME and a random 4-digit number + BASE_TAG="${USER_NAME:0:1}$(shuf -i 1000-9999 -n 1)" + echo "BASE_TAG=$BASE_TAG" >> $GITHUB_ENV + + - name: Prepare chain URLs + id: chain-urls + run: | + # Create JSON object with chain URLs + CHAIN_URLS='{ + "sepolia": { + "rpc": "${{ secrets.SEPOLIA_RPC_URL }}", + "beacon": "${{ secrets.SEPOLIA_BEACON_URL }}" + }, + "mainnet": { + "rpc": "${{ secrets.MAINNET_RPC_URL }}", + "beacon": "${{ secrets.MAINNET_BEACON_URL }}" + } + }' + + # Extract URLs for the selected chain + CHAIN_RPC_URL=$(echo "$CHAIN_URLS" | jq -r ".${{ github.event.inputs.chain }}.rpc") + CHAIN_BEACON_URL=$(echo "$CHAIN_URLS" | jq -r ".${{ github.event.inputs.chain }}.beacon") + + echo "CHAIN_RPC_URL=$CHAIN_RPC_URL" >> $GITHUB_OUTPUT + echo "CHAIN_BEACON_URL=$CHAIN_BEACON_URL" >> $GITHUB_OUTPUT + + - name: Generate custom node script + env: + DOCKER_REGISTRY: ${{ secrets.DOCKER_REGISTRY }} + DOCKER_REGISTRY_USERNAME: ${{ secrets.DOCKER_REGISTRY_USERNAME }} + DOCKER_REGISTRY_PASSWORD: ${{ secrets.DOCKER_REGISTRY_PASSWORD }} + GH_TOKEN: ${{ steps.gh-app.outputs.token }} + GH_USERNAME: ${{ github.actor }} + BASE_TAG: ${{ env.BASE_TAG }} + TIMEOUT: ${{ github.event.inputs.timeout }} + CHAIN: ${{ github.event.inputs.chain }} + CHAIN_RPC_URL: ${{ steps.chain-urls.outputs.CHAIN_RPC_URL }} + CHAIN_BEACON_URL: ${{ steps.chain-urls.outputs.CHAIN_BEACON_URL }} + NITRO_IMAGE: ${{ github.event.inputs.nitro-image }} + NETHERMIND_IMAGE: ${{ github.event.inputs.nethermind-image }} + SETUP_SCRIPT_TEMPLATE: "scripts/setup_script.sh.j2" + PUSHGATEWAY_URL: ${{ secrets.NETHERMIND_PUSHGATEWAY }} + TAGS: ${{ github.event.inputs.tags }} + ALLOWED_IPS: ${{ github.event.inputs.allowed-ips }} + SSH_KEYS: ${{ github.event.inputs.ssh-keys }} + SEQ_URL: ${{ secrets.SEQ_URL }} + SEQ_API_KEY: ${{ secrets.SEQ_API_KEY }} + run: | + python scripts/generate_custom_node_data.py + + - name: Trigger Machine Creation + env: + GH_TOKEN: ${{ steps.gh-app.outputs.token }} + run: | + cat custom_node_data.json | gh workflow run run-custom-node.yml -R NethermindEth/post-merge-smoke-tests --ref='${{ env.WORKFLOW_REF }}' --json + + - name: Wait for Workflow completion + env: + GITHUB_TOKEN: ${{ steps.gh-app.outputs.token }} + WORKFLOW_ID: "run-custom-node.yml" + MAX_WAIT_MINUTES: "5" + INTERVAL: "5" + TIMEOUT: "20" + ORG_NAME: "NethermindEth" + REPO_NAME: "post-merge-smoke-tests" + REF: ${{ env.WORKFLOW_REF }} + run: | + bash ./scripts/wait_for_workflow.sh | tee workflow.log + RUN_ID=$(grep -oP 'Run ID: \K\d+' workflow.log) + echo "Run ID extracted is: $RUN_ID" + echo "RUN_ID=$RUN_ID" >> $GITHUB_ENV + + - name: Download machine details artifact + env: + GH_TOKEN: ${{ steps.gh-app.outputs.token }} + RUN_ID: ${{ env.RUN_ID }} + run: | + ARTIFACT_ID=$(curl -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${GH_TOKEN}" https://api.github.com/repos/NethermindEth/post-merge-smoke-tests/actions/runs/${RUN_ID}/artifacts | jq '.artifacts[0].id') + curl -L -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${GH_TOKEN}" -o artifact.zip https://api.github.com/repos/NethermindEth/post-merge-smoke-tests/actions/artifacts/${ARTIFACT_ID}/zip + unzip artifact.zip -d ./downloaded-artifacts/ + + - name: Display machine details + run: | + FILE=$(ls downloaded-artifacts/machine-details | head -n 1) + echo "# Machine Details" >> $GITHUB_STEP_SUMMARY + cat "downloaded-artifacts/machine-details/${FILE}" >> $GITHUB_STEP_SUMMARY diff --git a/scripts/generate_custom_node_data.py b/scripts/generate_custom_node_data.py new file mode 100644 index 000000000..03dad9aca --- /dev/null +++ b/scripts/generate_custom_node_data.py @@ -0,0 +1,411 @@ +import os +import json +import gzip +import base64 + +from pathlib import Path +from jinja2 import Template +from jinja2_ansible_filters import AnsibleCoreFiltersExtension + + +# Constants +CUSTOM_NODE_DATA_FILE = Path("custom_node_data.json") +CUSTOM_NODE_NAME = "nethermind-arb" +CUSTOM_MACHINE_TYPE_PER_CHAIN = { + "sepolia": "g6-standard-8", + "mainnet": "g6-standard-8", +} + +DEFAULT_TIMEOUT = 24 +DEFAULT_CUSTOM_MACHINE_TYPE = "g6-standard-8" +DEFAULT_BLOCK_PROCESSING_TIMEOUT = 60000 + + +class CustomNodeConfig: + def __init__( + self, + instance_name: str, + docker_registry: str, + docker_registry_username: str, + docker_registry_password: str, + gh_username: str, + base_tag: str, + chain: str, + chain_rpc_url: str, + chain_beacon_url: str, + nitro_image: str, + nethermind_image: str, + setup_script_template_file: Path, + pushgateway_url: str = "", + allowed_ips: str = "", + ssh_keys: str = "", + tags: str = "", + timeout: int = DEFAULT_TIMEOUT, + seq_url: str = "", + seq_api_key: str = "", + ): + self.instance_name = instance_name + self.docker_registry = docker_registry + self.docker_registry_username = docker_registry_username + self.docker_registry_password = docker_registry_password + self.gh_username = gh_username + self.base_tag = base_tag + self.chain = chain + self.chain_rpc_url = chain_rpc_url + self.chain_beacon_url = chain_beacon_url + self.nitro_image = nitro_image + self.nethermind_image = nethermind_image + self.setup_script_template_file = setup_script_template_file + self.pushgateway_url = pushgateway_url + self.allowed_ips = allowed_ips + self.ssh_keys = ssh_keys + self.tags = tags + self.timeout = timeout + self.seq_url = seq_url + self.seq_api_key = seq_api_key + + @staticmethod + def from_env() -> "CustomNodeConfig": + """Load configuration from environment variables with validation.""" + # Docker registry + docker_registry = os.environ.get("DOCKER_REGISTRY") + if not docker_registry: + raise ValueError("DOCKER_REGISTRY is not set") + + docker_registry_username = os.environ.get("DOCKER_REGISTRY_USERNAME") + if not docker_registry_username: + raise ValueError("DOCKER_REGISTRY_USERNAME is not set") + + docker_registry_password = os.environ.get("DOCKER_REGISTRY_PASSWORD") + if not docker_registry_password: + raise ValueError("DOCKER_REGISTRY_PASSWORD is not set") + + # GitHub workflow + gh_username = os.environ.get("GH_USERNAME") + if not gh_username: + raise ValueError("GH_USERNAME is not set") + + base_tag = os.environ.get("BASE_TAG") + if not base_tag: + raise ValueError("BASE_TAG is not set") + + # Timeout validation + try: + timeout = int(os.environ.get("TIMEOUT", DEFAULT_TIMEOUT)) + timeout = DEFAULT_TIMEOUT if timeout <= 0 else timeout + except ValueError: + raise ValueError("TIMEOUT is not a valid integer") + + # Setup script + chain = os.environ.get("CHAIN") + if not chain: + raise ValueError("CHAIN is not set") + + chain_rpc_url = os.environ.get("CHAIN_RPC_URL") + if not chain_rpc_url: + raise ValueError("CHAIN_RPC_URL is not set") + + chain_beacon_url = os.environ.get("CHAIN_BEACON_URL") + if not chain_beacon_url: + raise ValueError("CHAIN_BEACON_URL is not set") + + nitro_image = os.environ.get("NITRO_IMAGE") + if not nitro_image: + raise ValueError("NITRO_IMAGE is not set") + + nethermind_image = os.environ.get("NETHERMIND_IMAGE") + if not nethermind_image: + raise ValueError("NETHERMIND_IMAGE is not set") + + # Setup script template file validation + setup_script_template_file = os.environ.get("SETUP_SCRIPT_TEMPLATE") + if not setup_script_template_file: + raise ValueError("SETUP_SCRIPT_TEMPLATE is not set") + + try: + setup_script_template_path = Path(setup_script_template_file) + except ValueError: + raise ValueError("SETUP_SCRIPT_TEMPLATE is not a valid path") + + if not setup_script_template_path.exists(): + raise ValueError("SETUP_SCRIPT_TEMPLATE does not exist") + + # Optional environment variables + pushgateway_url = os.environ.get("PUSHGATEWAY_URL", "") + tags = os.environ.get("TAGS", "") + allowed_ips = os.environ.get("ALLOWED_IPS", "") + ssh_keys = os.environ.get("SSH_KEYS", "") + seq_url = os.environ.get("SEQ_URL", "") + seq_api_key = os.environ.get("SEQ_API_KEY", "") + + # Instance name for metrics identification + instance_name = f"{base_tag}-{gh_username}-{chain}" + + return CustomNodeConfig( + instance_name=instance_name, + docker_registry=docker_registry, + docker_registry_username=docker_registry_username, + docker_registry_password=docker_registry_password, + gh_username=gh_username, + base_tag=base_tag, + chain=chain, + chain_rpc_url=chain_rpc_url, + chain_beacon_url=chain_beacon_url, + nitro_image=nitro_image, + nethermind_image=nethermind_image, + setup_script_template_file=setup_script_template_path, + pushgateway_url=pushgateway_url, + allowed_ips=allowed_ips, + ssh_keys=ssh_keys, + tags=tags, + timeout=timeout, + seq_url=seq_url, + seq_api_key=seq_api_key, + ) + + +def get_nethermind_config( + instance_name: str, + chain: str, + nethermind_service_name: str, + nethermind_image: str, + nethermind_rpc_port: int, + nethermind_engine_port: int, + nethermind_p2p_port: int, + nethermind_metrics_port: int, + docker_network_name: str, + block_processing_timeout: int = DEFAULT_BLOCK_PROCESSING_TIMEOUT, + pushgateway_url: str = "", + seq_url: str = "", + seq_api_key: str = "", + # TODO: Add more flags options as needed +) -> dict: + # Paths + nethermind_data_dir = "/app/nethermind_db" + nethermind_host_data_dir = "./nethermind-data" + + nethermind_command = [] + if chain == "sepolia": + nethermind_command += [ + "-c=arbitrum-sepolia-archive", + ] + elif chain == "mainnet": + nethermind_command += [ + "-c=arbitrum-mainnet-archive", + ] + + # Add default flags + nethermind_command += [ + f"--data-dir={nethermind_data_dir}", + "--stylustarget-amd64=x86_64-linux-unknown", + "--JsonRpc.Host=0.0.0.0", + "--JsonRpc.EngineHost=0.0.0.0", + f"--Arbitrum.BlockProcessingTimeout={block_processing_timeout}", + ] + nethermind_command += [ + "--Init.DiscoveryEnabled=false", + "--Network.MaxActivePeers=0", + "--Sync.SnapSync=false", + "--Sync.FastSync=false", + ] + ## Add metrics flags + nethermind_command += [ + "--Metrics.Enabled=true", + f"--Metrics.ExposePort={nethermind_metrics_port}", + "--Metrics.ExposeHost=0.0.0.0", + f"--Metrics.NodeName={instance_name}", + ] + if pushgateway_url: + nethermind_command += [ + f"--Metrics.PushGatewayUrl={pushgateway_url}", + ] + + if seq_url and seq_api_key: + nethermind_command += [ + "--Seq.MinLevel=Info", + f"--Seq.ServerUrl={seq_url}", + f"--Seq.ApiKey={seq_api_key}", + ] + + # Return config + return { + "image": nethermind_image, + "container_name": nethermind_service_name, + "restart": "no", + "ports": [ + f"{nethermind_rpc_port}:{nethermind_rpc_port}", + f"{nethermind_engine_port}:{nethermind_engine_port}", + f"{nethermind_p2p_port}:{nethermind_p2p_port}/tcp", + f"{nethermind_p2p_port}:{nethermind_p2p_port}/udp", + f"{nethermind_metrics_port}:{nethermind_metrics_port}", + ], + "volumes": [f"{nethermind_host_data_dir}:{nethermind_data_dir}"], + "environment": [ + "STYLUS_ARCH=amd64", + "STYLUS_TARGET=x86_64-linux-unknown", + ], + "command": nethermind_command, + "networks": [docker_network_name], + "healthcheck": { + "test": [ + "CMD-SHELL", + f"timeout 5 bash -c ' dict: + # Paths + nitro_data_dir = "/tmp/nitro-data" + nitro_host_data_dir = "./nitro-data" + + # Command + nitro_command = [] + if chain == "sepolia": + nitro_command += [ + "--chain.id=421614", + ] + elif chain == "mainnet": + nitro_command += [ + "--chain.id=42161", + ] + + nitro_command += [ + f"--parent-chain.connection.url={chain_rpc_url}", + f"--parent-chain.blob-client.beacon-url={chain_beacon_url}", + "--persistent.global-config=/tmp/nitro-data", + "--execution.forwarding-target=null", + "--execution.enable-prefetch-block=false", + ] + return { + "image": nitro_image, + "container_name": nitro_service_name, + "depends_on": { + nethermind_service_name: { + "condition": "service_healthy", + }, + }, + "restart": "no", + "ports": [], + "volumes": [ + f"{nitro_host_data_dir}:{nitro_data_dir}", + ], + "environment": [ + "CGO_LDFLAGS=-Wl,-no_warn_duplicate_libraries", + "PR_IGNORE_CALLSTACK=false", + f"PR_NETH_RPC_CLIENT_URL={nitro_nethermind_rpc_url}", + "PR_EXECUTION_MODE=external", + ], + "command": nitro_command, + "networks": [docker_network_name], + } + + +def get_docker_compose_config(config: CustomNodeConfig) -> dict: + # General config + docker_network_name = "nethermind-network" + # Nethermind config + nethermind_service_name = "nethermind-l2" + nethermind_rpc_port = 20545 + nethermind_engine_port = 20551 + nethermind_p2p_port = 30303 + nethermind_metrics_port = 8008 + # Nitro config + nitro_service_name = "nitro" + nitro_nethermind_rpc_url = f"http://{nethermind_service_name}:{nethermind_rpc_port}" + # Return config + return { + "services": { + nethermind_service_name: get_nethermind_config( + instance_name=config.instance_name, + chain=config.chain, + nethermind_service_name=nethermind_service_name, + nethermind_image=config.nethermind_image, + nethermind_rpc_port=nethermind_rpc_port, + nethermind_engine_port=nethermind_engine_port, + nethermind_p2p_port=nethermind_p2p_port, + nethermind_metrics_port=nethermind_metrics_port, + docker_network_name=docker_network_name, + pushgateway_url=config.pushgateway_url, + seq_url=config.seq_url, + seq_api_key=config.seq_api_key, + ), + nitro_service_name: get_nitro_config( + chain=config.chain, + chain_rpc_url=config.chain_rpc_url, + chain_beacon_url=config.chain_beacon_url, + nitro_service_name=nitro_service_name, + nethermind_service_name=nethermind_service_name, + nitro_image=config.nitro_image, + nitro_nethermind_rpc_url=nitro_nethermind_rpc_url, + docker_network_name=docker_network_name, + ), + }, + "networks": { + docker_network_name: { + "name": docker_network_name, + }, + }, + } + + +def generate_custom_node_data(config: CustomNodeConfig) -> dict[str, str]: + setup_script_file = Template( + config.setup_script_template_file.read_text(), + extensions=[ + AnsibleCoreFiltersExtension, + ], + ) + + data = { + "docker_registry": { + "url": config.docker_registry, + "username": config.docker_registry_username, + "password": config.docker_registry_password, + }, + "docker_compose_file": get_docker_compose_config(config), + } + + setup_script = setup_script_file.render(**data) + setup_script_gzip = gzip.compress(setup_script.encode("utf-8")) + setup_script_b64 = base64.b64encode(setup_script_gzip).decode("utf-8") + + return { + "base_tag": config.base_tag, + "github_username": config.gh_username, + "custom_node_name": CUSTOM_NODE_NAME, + "custom_node_type": CUSTOM_MACHINE_TYPE_PER_CHAIN.get( + config.chain, + DEFAULT_CUSTOM_MACHINE_TYPE, + ), + "setup_script": setup_script_b64, + "tags": config.tags, + "allowed_ips": config.allowed_ips, + "ssh_keys": config.ssh_keys, + "timeout": str(config.timeout), + } + + +if __name__ == "__main__": + # Load configuration from environment variables + config = CustomNodeConfig.from_env() + + # Generate custom node data + custom_node_data = generate_custom_node_data(config) + + with CUSTOM_NODE_DATA_FILE.open("w") as f: + json.dump(custom_node_data, f) diff --git a/scripts/setup_script.sh.j2 b/scripts/setup_script.sh.j2 new file mode 100644 index 000000000..3b92c970b --- /dev/null +++ b/scripts/setup_script.sh.j2 @@ -0,0 +1,56 @@ +#!/bin/bash + +# Enable logging for the setup script +exec > >(tee /dev/ttyS0 ${HOME}/stackscript.log) 2>&1 + +# Install Docker +echo "Installing Docker..." +## Add Docker's official GPG key: +sudo apt-get update +sudo apt-get install ca-certificates curl +sudo install -m 0755 -d /etc/apt/keyrings +sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc +sudo chmod a+r /etc/apt/keyrings/docker.asc + +## Add the repository to Apt sources: +echo \ + "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \ + $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}") stable" | \ + sudo tee /etc/apt/sources.list.d/docker.list > /dev/null +sudo apt-get update + +## Install Docker +sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin +echo "Docker installed" + +## Install Dependencies +sudo apt-get install -y jq +sudo snap install yq + +# Docker login +echo "Logging in to Docker registry..." +docker login {{ docker_registry.url }} -u "{{ docker_registry.username }}" -p "{{ docker_registry.password }}" +echo "Logged in to Docker registry" + +# Prepare Docker Compose file +echo "Preparing Docker Compose..." +#### +echo """ +{{ docker_compose_file | to_nice_yaml(sort_keys=False,indent=2) }} +""" > ${HOME}/docker-compose.yml +#### +yq -i -P ${HOME}/docker-compose.yml +echo "Docker Compose file prepared" + +# Create nethermind data directory +mkdir -p ${HOME}/nethermind-data +chmod -R 777 ${HOME}/nethermind-data + +mkdir -p ${HOME}/nitro-data +chmod -R 777 ${HOME}/nitro-data + +# Start containers +echo "Starting containers..." +docker compose -f ${HOME}/docker-compose.yml pull +docker compose -f ${HOME}/docker-compose.yml up -d +echo "Containers started" diff --git a/scripts/wait_for_workflow.sh b/scripts/wait_for_workflow.sh new file mode 100644 index 000000000..09500c183 --- /dev/null +++ b/scripts/wait_for_workflow.sh @@ -0,0 +1,94 @@ +#!/bin/bash + +# Set the maximum waiting time (in minutes) and initialize the counter +max_wait_minutes="${MAX_WAIT_MINUTES}" +timeout="${TIMEOUT}" +interval="${INTERVAL}" +name_filter="${NAME_FILTER}" +counter=0 + +# Get the current time in ISO 8601 format +current_time=$(date -u +"%Y-%m-%dT%H:%M:%SZ") + +# Check if REF has the prefix "refs/heads/" and append it if not +if [[ ! "$REF" =~ ^refs/heads/ ]]; then + REF="refs/heads/$REF" +fi + +echo "ℹ️ Organization: ${ORG_NAME}" +echo "ℹ️ Repository: ${REPO_NAME}" +echo "ℹ️ Reference: $REF" +echo "ℹ️ Maximum wait time: ${max_wait_minutes} minutes" +echo "ℹ️ Timeout for the workflow to complete: ${timeout} minutes" +echo "ℹ️ Interval between checks: ${interval} seconds" +echo "ℹ️ Name filter applied: ${name_filter}" + +# If RUN_ID is not empty, use it directly +if [ -n "${RUN_ID}" ]; then + run_id="${RUN_ID}" + echo "ℹ️ Using provided Run ID: $run_id" +else + workflow_id="${WORKFLOW_ID}" # Id of the target workflow + echo "ℹ️ Workflow ID: $workflow_id" + + # Wait for the workflow to be triggered + while true; do + echo "⏳ Waiting for the workflow to be triggered..." + response=$(curl -s -H "Accept: application/vnd.github+json" -H "Authorization: token $GITHUB_TOKEN" \ + "https://api.github.com/repos/${ORG_NAME}/${REPO_NAME}/actions/workflows/${workflow_id}/runs") + if echo "$response" | grep -q "API rate limit exceeded"; then + echo "❌ API rate limit exceeded. Please try again later." + exit 1 + elif echo "$response" | grep -q "Not Found"; then + echo "❌ Invalid input provided (organization, repository, or workflow ID). Please check your inputs." + exit 1 + fi + run_id=$(echo "$response" | \ + jq -r --arg ref "$(echo "$REF" | sed 's/refs\/heads\///')" --arg current_time "$current_time" --arg expected_name "$name_filter" \ + '.workflow_runs[] | select(.head_branch == $ref and .status == "in_progress" and (if $expected_name == "" then true else .name | test($expected_name) end)) | .id' | sort -r | head -n 1) + if [ -n "$run_id" ]; then + echo "🎉 Workflow triggered! Run ID: $run_id" + break + fi + + # Increment the counter and check if the maximum waiting time is reached + counter=$((counter + 1)) + if [ $((counter * $interval)) -ge $((max_wait_minutes * 60)) ]; then + echo "❌ Maximum waiting time for the workflow to be triggered has been reached. Exiting." + exit 1 + fi + + sleep $interval + done +fi + +# Wait for the triggered workflow to complete and check its conclusion +timeout_counter=0 +while true; do + echo "⌛ Waiting for the workflow to complete..." + run_data=$(curl -s -H "Accept: application/vnd.github+json" -H "Authorization: token $GITHUB_TOKEN" \ + "https://api.github.com/repos/${ORG_NAME}/${REPO_NAME}/actions/runs/$run_id") + status=$(echo "$run_data" | jq -r '.status') + + if [ "$status" = "completed" ]; then + conclusion=$(echo "$run_data" | jq -r '.conclusion') + if [ "$conclusion" != "success" ]; then + echo "❌ The workflow has not completed successfully. Exiting." + exit 1 + else + echo "✅ The workflow completed successfully! Exiting." + echo "👀 Check workflow details at: https://github.com/${ORG_NAME}/${REPO_NAME}/actions/runs/$run_id" + echo "run_id=$run_id" >> $GITHUB_OUTPUT + break + fi + fi + + # Increment the timeout counter and check if the timeout has been reached + timeout_counter=$((timeout_counter + 1)) + if [ $((timeout_counter * interval)) -ge $((timeout * 60)) ]; then + echo "❌ Timeout waiting for the workflow to complete. Exiting." + exit 1 + fi + + sleep $interval +done diff --git a/src/Nethermind.Arbitrum/Properties/configs/arbitrum-mainnet-archive.json b/src/Nethermind.Arbitrum/Properties/configs/arbitrum-mainnet-archive.json index d7ae7a638..23167b7d3 100644 --- a/src/Nethermind.Arbitrum/Properties/configs/arbitrum-mainnet-archive.json +++ b/src/Nethermind.Arbitrum/Properties/configs/arbitrum-mainnet-archive.json @@ -59,7 +59,12 @@ "Enabled": true }, "Arbitrum": { - "BlockProcessingTimeout": 10000, + "BlockProcessingTimeout": 60000, "RebuildLocalWasm": "auto" + }, + "VerifyBlockHash": { + "Enabled": false, + "VerifyEveryNBlocks": 10000, + "ArbNodeRpcUrl": "https://arb1.arbitrum.io/rpc" } } diff --git a/src/Nethermind.Arbitrum/Properties/configs/arbitrum-mainnet.json b/src/Nethermind.Arbitrum/Properties/configs/arbitrum-mainnet.json index de1ed420e..44f329d59 100644 --- a/src/Nethermind.Arbitrum/Properties/configs/arbitrum-mainnet.json +++ b/src/Nethermind.Arbitrum/Properties/configs/arbitrum-mainnet.json @@ -63,5 +63,14 @@ }, "Merge": { "Enabled": true + }, + "Arbitrum": { + "BlockProcessingTimeout": 60000, + "RebuildLocalWasm": "auto" + }, + "VerifyBlockHash": { + "Enabled": false, + "VerifyEveryNBlocks": 10000, + "ArbNodeRpcUrl": "https://arb1.arbitrum.io/rpc" } }