diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 176943a..a674a4a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,20 +19,35 @@ jobs: uses: ./ with: version: latest + setup: multi_chain + l2_ports: | + 506: 3052 + 6565: 3050 + 6566: 3051 - name: Verify RPCs respond run: | set -euo pipefail echo "ETH_RPC=$ETH_RPC" - echo "ZKSYNC_RPC=$ZKSYNC_RPC" + echo "ZKSYNC_RPC_506=$ZKSYNC_RPC_506" + echo "ZKSYNC_RPC_6565=$ZKSYNC_RPC_6565" + echo "ZKSYNC_RPC_6566=$ZKSYNC_RPC_6566" curl -sS -X POST "$ETH_RPC" \ -H 'content-type: application/json' \ --data '{"jsonrpc":"2.0","id":1,"method":"eth_blockNumber","params":[]}' | tee /tmp/l1.out - curl -sS -X POST "$ZKSYNC_RPC" \ + curl -sS -X POST "$ZKSYNC_RPC_506" \ -H 'content-type: application/json' \ - --data '{"jsonrpc":"2.0","id":1,"method":"eth_blockNumber","params":[]}' | tee /tmp/l2.out + --data '{"jsonrpc":"2.0","id":1,"method":"eth_blockNumber","params":[]}' | tee /tmp/l2_506.out + curl -sS -X POST "$ZKSYNC_RPC_6565" \ + -H 'content-type: application/json' \ + --data '{"jsonrpc":"2.0","id":1,"method":"eth_blockNumber","params":[]}' | tee /tmp/l2_6565.out + curl -sS -X POST "$ZKSYNC_RPC_6566" \ + -H 'content-type: application/json' \ + --data '{"jsonrpc":"2.0","id":1,"method":"eth_blockNumber","params":[]}' | tee /tmp/l2_6566.out grep -q '"result"' /tmp/l1.out - grep -q '"result"' /tmp/l2.out + grep -q '"result"' /tmp/l2_506.out + grep -q '"result"' /tmp/l2_6565.out + grep -q '"result"' /tmp/l2_6566.out - name: Upload logs (always) if: always() diff --git a/README.md b/README.md index 1badda1..62beaa0 100644 --- a/README.md +++ b/README.md @@ -7,8 +7,9 @@ Start a local **L1 (Anvil)** and **L2 (zksync_os_server)** directly from officia - Downloads official binaries + `local-chains.tar.gz` - Uses protocol-aware local chains from `./local-chains/` - Falls back to legacy assets for older tags that do not include `local-chains.tar.gz` -- Boots both **L1 (Anvil)** and **L2 (zksync_os_server)** -- Exports `ETH_RPC` and `ZKSYNC_RPC` for your subsequent test steps +- Supports **single-chain** and **multi-chain** setups +- Boots both **L1 (Anvil)** and one or more **L2 (zksync_os_server)** instances +- Exports `ETH_RPC` and `ZKSYNC_RPC` (single chain) or `ZKSYNC_RPC_` (multi chain) for your subsequent test steps ## Quick Start @@ -26,7 +27,7 @@ Example setup: version: v1.4.1 ``` -### **Minimal usage (latest stable)** +### Minimal usage (single chain, latest stable) ```yaml - name: Run ZKsync OS @@ -36,10 +37,10 @@ Example setup: protocol_version: v31.0 ``` -### **Pinned version** +### Pinned version ```yaml -- name: Run ZKsync OS +- name: Run ZKsync OS uses: dutterbutter/zksync-server-action@v0.1.0 with: version: v0.8.2 @@ -47,7 +48,7 @@ Example setup: l2_port: 3050 ``` -### **Include pre-releases** +### Include pre-releases ```yaml - name: Run ZKsync OS @@ -57,31 +58,65 @@ Example setup: include_prerelease: true ``` +### Multi-chain setup + +Starts one `zksync-os-server` instance per chain listed in `l2_ports`, all sharing a single Anvil L1. + +```yaml +- name: Run ZKsync OS (multi chain) + uses: dutterbutter/zksync-server-action@v0.1.0 + with: + version: latest + protocol_version: v31.0 + setup: multi_chain + # l2_ports defaults to: + # 6565: 3050 + # 6566: 3051 +``` + +To start only a subset of chains, or use custom ports, override `l2_ports`: + +```yaml +- name: Run ZKsync OS (multi chain, custom) + uses: dutterbutter/zksync-server-action@v0.1.0 + with: + setup: multi_chain + l2_ports: | + 6565: 3050 +``` + ## Inputs -| Name | Default | Description | -| -------------------- | ------------------------------ | --------------------------------------------------- | -| `version` | `latest` | Release tag (e.g. `v0.8.2`) or `latest` | -| `include_prerelease` | `false` | If `true` and `version=latest`, allows pre-releases | -| `l1_port` | `8545` | L1 RPC port (Anvil) | -| `l2_port` | `3050` | L2 RPC port (zksync_os_server) | -| `linux_arch` | `x86_64` | Architecture for binary (`x86_64` or `aarch64`) | -| `protocol_version` | `v31.0` | Protocol folder under `local-chains` (e.g. `v30.2`, `v31.0`) for `v0.15.0+` | -| `set_env` | `true` | Export `ETH_RPC` and `ZKSYNC_RPC` to `GITHUB_ENV` | -| `anvil_logs` | `false` | Print Anvil log (`.zks/anvil.log`) at the end | -| `zksync_logs` | `false` | Print zksync-os-server log (`.zks/zksyncos.log`) at the end | +| Name | Default | Description | +| -------------------- | ------------------------------ | --------------------------------------------------------------------------- | +| `version` | `latest` | Release tag (e.g. `v0.8.2`) or `latest` | +| `include_prerelease` | `false` | If `true` and `version=latest`, allows pre-releases | +| `l1_port` | `8545` | L1 RPC port (Anvil) | +| `l2_port` | `3050` | L2 RPC port — **single_chain only** | +| `linux_arch` | `x86_64` | Architecture for binary (`x86_64` or `aarch64`) | +| `protocol_version` | `v31.0` | Protocol folder under `local-chains` (e.g. `v30.2`, `v31.0`) | +| `setup` | `single_chain` | Setup type: `single_chain` or `multi_chain` | +| `l2_ports` | `6565: 3050` / `6566: 3051` | Chain ID → port map — **multi_chain only** (one entry per line) | +| `set_env` | `true` | Export RPC URLs to `GITHUB_ENV` | +| `boot_grace_seconds` | `3` | Seconds to wait after starting each server | +| `anvil_logs` | `false` | Print Anvil log (`.zks/anvil.log`) at the end | +| `zksync_logs` | `false` | Print zksync-os-server log at the end — **single_chain only** | +| `config_yaml` | *(none)* | Full YAML config for zksync-os-server — **single_chain only** | +| `operator_commit_sk` | *(dev default)* | Override `l1_sender.operator_commit_sk` — **single_chain only** | +| `operator_prove_sk` | *(dev default)* | Override `l1_sender.operator_prove_sk` — **single_chain only** | +| `operator_execute_sk`| *(dev default)* | Override `l1_sender.operator_execute_sk` — **single_chain only** | ## Outputs -| Name | Description | -| ------------------ | ----------------------------------- | -| `l1_rpc_url` | Local L1 RPC URL | -| `l2_rpc_url` | Local L2 RPC URL | -| `resolved_version` | Actual tag resolved (e.g. `v0.8.2`) | -| `anvil_log_path` | Path to Anvil log (`.zks/anvil.log`) | -| `zksync_log_path` | Path to zksync-os-server log (`.zks/zksyncos.log`) | +| Name | Description | +| ------------------ | --------------------------------------------------------------------------- | +| `l1_rpc_url` | Local L1 RPC URL | +| `l2_rpc_url` | Local L2 RPC URL (single_chain; maps to first chain's port in multi_chain) | +| `resolved_version` | Actual tag resolved (e.g. `v0.8.2`) | +| `anvil_log_path` | Path to Anvil log (`.zks/anvil.log`) | +| `zksync_log_path` | Path to zksync-os-server log (`.zks/zksyncos.log`) — **single_chain only** | -### Configuration inputs +## Configuration (single_chain) From `zksync-os-server` `v0.15.0+`, this action uses `local-chains.tar.gz` and: @@ -92,7 +127,7 @@ For tags before `v0.15.0`, it automatically falls back to legacy release assets You may configure the server in one of two ways: -#### Option 1: Provide a full YAML config (recommended) +### Option 1: Provide a full YAML config (recommended) | Name | Default | Description | | ------------- | -------- | -------------------------------------------------------------------------------------- | @@ -114,7 +149,7 @@ Example: --- -#### Option 2: Override individual operator keys +### Option 2: Override individual operator keys If `config_yaml` is **not** provided, the action uses the protocol default config from `local-chains` and allows selective overrides of the L1 sender operator keys: @@ -146,43 +181,43 @@ To use it, copy its contents and pass it via the `config_yaml` input. --- -## Outputs - -| Name | Description | -| ------------------ | -------------------------------------------------- | -| `l1_rpc_url` | Local L1 RPC URL | -| `l2_rpc_url` | Local L2 RPC URL | -| `resolved_version` | Actual tag resolved (e.g. `v0.8.2`) | -| `anvil_log_path` | Path to Anvil log (`.zks/anvil.log`) | -| `zksync_log_path` | Path to zksync-os-server log (`.zks/zksyncos.log`) | - ## Environment Variables -If `set_env` is true (default), these are automatically exported: +If `set_env` is `true` (default), RPC URLs are automatically exported to `GITHUB_ENV`. + +**Single chain:** ```bash ETH_RPC=http://127.0.0.1:8545 ZKSYNC_RPC=http://127.0.0.1:3050 ``` +**Multi chain** (one variable per chain): + +```bash +ETH_RPC=http://127.0.0.1:8545 +ZKSYNC_RPC_6565=http://127.0.0.1:3050 +ZKSYNC_RPC_6566=http://127.0.0.1:3051 +``` + ## Troubleshooting -* **Ports busy:** Adjust `l1_port` / `l2_port` if the runner already uses 8545 or 3050. +* **Ports busy:** Adjust `l1_port` / `l2_port` (single chain) or the ports in `l2_ports` (multi chain) if the runner already uses those ports. * **Logs:** - * `.zks/anvil.log` — Anvil output - * `.zks/zksyncos.log` — zksync-os-server output + * `.zks/zksyncos.log` — zksync-os-server output (single chain) + * `.zks/zksyncos_.log` — per-chain output (multi chain) Upload them on failure for debugging: - ```yaml - - name: Upload logs - if: always() - uses: actions/upload-artifact@v4 - with: - name: zks-logs - path: .zks/*.log - ``` +```yaml +- name: Upload logs + if: always() + uses: actions/upload-artifact@v4 + with: + name: zks-logs + path: .zks/*.log +``` You have two ways to view logs: diff --git a/action.yml b/action.yml index 246bb03..98e8de3 100644 --- a/action.yml +++ b/action.yml @@ -15,7 +15,7 @@ inputs: description: L1 (Anvil) RPC port default: '8545' l2_port: - description: L2 (ZKsync OS) RPC port + description: L2 (ZKsync OS) RPC port (single_chain only) default: '3050' linux_arch: description: Linux arch (x86_64 or aarch64) @@ -23,6 +23,15 @@ inputs: protocol_version: description: Protocol version inside local-chains (e.g. v31.0) default: v31.0 + setup: + description: 'Setup type: ''single_chain'' or ''multi_chain''' + default: 'single_chain' + l2_ports: + description: | + Chain ID to RPC port map for multi_chain setup (one entry per line, format: ": "). + default: | + 6565: 3050 + 6566: 3051 set_env: description: 'If ''true'', export ETH_RPC and ZKSYNC_RPC to GITHUB_ENV' default: 'true' @@ -36,23 +45,23 @@ inputs: description: 'If ''true'', print zksync-os-server log at the end' default: 'false' config_yaml: - description: Optional YAML config for zksync-os-server (overrides defaults). + description: Optional YAML config for zksync-os-server (overrides defaults, single_chain only). required: false operator_commit_sk: - description: Override l1_sender.operator_commit_sk + description: Override l1_sender.operator_commit_sk (single_chain only) required: false operator_prove_sk: - description: Override l1_sender.operator_prove_sk + description: Override l1_sender.operator_prove_sk (single_chain only) required: false operator_execute_sk: - description: Override l1_sender.operator_execute_sk + description: Override l1_sender.operator_execute_sk (single_chain only) required: false outputs: l1_rpc_url: description: Resolved L1 RPC URL value: '${{ steps.vars.outputs.l1 }}' l2_rpc_url: - description: Resolved L2 RPC URL + description: Resolved L2 RPC URL (single_chain; first chain port in multi_chain) value: '${{ steps.vars.outputs.l2 }}' resolved_version: description: Resolved tag actually used @@ -61,7 +70,7 @@ outputs: description: Path to the Anvil log file value: '${{ steps.vars.outputs.anvil_log }}' zksync_log_path: - description: Path to the zksync-os-server log file + description: Path to the zksync-os-server log file (single_chain only) value: '${{ steps.vars.outputs.zksync_log }}' runs: using: composite @@ -70,9 +79,21 @@ runs: shell: bash run: | echo "l1=http://127.0.0.1:${{ inputs.l1_port }}" >> "$GITHUB_OUTPUT" - echo "l2=http://127.0.0.1:${{ inputs.l2_port }}" >> "$GITHUB_OUTPUT" echo "anvil_log=.zks/anvil.log" >> "$GITHUB_OUTPUT" echo "zksync_log=.zks/zksyncos.log" >> "$GITHUB_OUTPUT" + if [[ "${{ inputs.setup }}" == "multi_chain" ]]; then + first_url="" + while IFS=: read -r chain_id port _rest; do + chain_id="${chain_id//[[:space:]]/}" + port="${port//[[:space:]]/}" + [[ -z "$chain_id" || "$chain_id" == \#* ]] && continue + echo "l2_${chain_id}=http://127.0.0.1:${port}" >> "$GITHUB_OUTPUT" + [[ -z "$first_url" ]] && first_url="http://127.0.0.1:${port}" + done <<< "${{ inputs.l2_ports }}" + echo "l2=${first_url}" >> "$GITHUB_OUTPUT" + else + echo "l2=http://127.0.0.1:${{ inputs.l2_port }}" >> "$GITHUB_OUTPUT" + fi - id: resolve uses: actions/github-script@v7 with: @@ -215,6 +236,7 @@ runs: - id: config name: Resolve zksync-os-server config + if: '${{ inputs.setup == ''single_chain'' }}' shell: bash run: | set -euo pipefail @@ -294,43 +316,101 @@ runs: cd .zks export L1_RPC="http://127.0.0.1:${{ inputs.l1_port }}" - GENESIS_PATH="$(pwd)/local-chains/${{ inputs.protocol_version }}/genesis/genesis.json" - if [[ ! -f "$GENESIS_PATH" ]]; then - GENESIS_PATH="$(pwd)/genesis/genesis.json" - fi - export GENESIS="$GENESIS_PATH" - export PORT="${{ inputs.l2_port }}" export RUST_LOG=info export RUST_BACKTRACE=1 - CONFIG_ABS="${{ steps.config.outputs.path }}" + if [[ "${{ inputs.setup }}" == "multi_chain" ]]; then + MULTI_DIR="$(pwd)/local-chains/${{ inputs.protocol_version }}/multi_chain" + if [[ ! -d "$MULTI_DIR" ]]; then + echo "Multi-chain directory not found: $MULTI_DIR" + exit 1 + fi - echo "Launching: ./zksync-os-server --config $CONFIG_ABS" - setsid bash -c "exec ./zksync-os-server --config \"$CONFIG_ABS\" >zksyncos.log 2>&1" & - echo $! > l2.pid + declare -A PORT_MAP + while IFS=: read -r chain_id port _rest; do + chain_id="${chain_id//[[:space:]]/}" + port="${port//[[:space:]]/}" + [[ -z "$chain_id" || "$chain_id" == \#* ]] && continue + PORT_MAP["$chain_id"]="$port" + done <<< "${{ inputs.l2_ports }}" - sleep "${{ inputs.boot_grace_seconds }}" + if [[ ${#PORT_MAP[@]} -eq 0 ]]; then + echo "No entries found in l2_ports input" + exit 1 + fi - PID="$(cat l2.pid)" - if ! kill -0 "$PID" 2>/dev/null; then - echo "::group::zksync-os-server exited early" - echo "PID $PID is not running." - echo "Last 200 lines of zksyncos.log:" - tail -n 200 zksyncos.log || true - echo "::endgroup::" + for chain_id in $(echo "${!PORT_MAP[@]}" | tr ' ' '\n' | sort); do + config_file="$MULTI_DIR/chain_${chain_id}.yaml" - echo "::group::try foreground run to capture error" - ./zksync-os-server --config "$CONFIG_ABS" || true - echo "::endgroup::" + if [[ ! -f "$config_file" ]]; then + echo "Config file not found for chain $chain_id: $config_file" + exit 1 + fi - exit 1 - fi + log_file="zksyncos_${chain_id}.log" + echo "Starting chain $chain_id with config: $config_file" + setsid bash -c "exec ./zksync-os-server --config \"$config_file\" >\"$log_file\" 2>&1" & + echo $! > "l2_${chain_id}.pid" + sleep "${{ inputs.boot_grace_seconds }}" + done + + for chain_id in $(echo "${!PORT_MAP[@]}" | tr ' ' '\n' | sort); do + PID="$(cat "l2_${chain_id}.pid")" + if ! kill -0 "$PID" 2>/dev/null; then + echo "::group::chain $chain_id (PID $PID) exited early" + tail -n 200 "zksyncos_${chain_id}.log" || true + echo "::endgroup::" + exit 1 + fi + echo "Chain $chain_id launched (pid: $PID)." + done + + else + GENESIS_PATH="$(pwd)/local-chains/${{ inputs.protocol_version }}/genesis/genesis.json" + if [[ ! -f "$GENESIS_PATH" ]]; then + GENESIS_PATH="$(pwd)/genesis/genesis.json" + fi + export GENESIS="$GENESIS_PATH" + export PORT="${{ inputs.l2_port }}" + + CONFIG_ABS="${{ steps.config.outputs.path }}" + + echo "Launching: ./zksync-os-server --config $CONFIG_ABS" + setsid bash -c "exec ./zksync-os-server --config \"$CONFIG_ABS\" >zksyncos.log 2>&1" & + echo $! > l2.pid + + sleep "${{ inputs.boot_grace_seconds }}" - echo "ZKsync OS launched (pid: $PID)." + PID="$(cat l2.pid)" + if ! kill -0 "$PID" 2>/dev/null; then + echo "::group::zksync-os-server exited early" + echo "PID $PID is not running." + echo "Last 200 lines of zksyncos.log:" + tail -n 200 zksyncos.log || true + echo "::endgroup::" + + echo "::group::try foreground run to capture error" + ./zksync-os-server --config "$CONFIG_ABS" || true + echo "::endgroup::" + + exit 1 + fi + + echo "ZKsync OS launched (pid: $PID)." + fi - name: Export RPC URLs if: '${{ inputs.set_env == ''true'' }}' shell: bash run: | echo "ETH_RPC=${{ steps.vars.outputs.l1 }}" >> "$GITHUB_ENV" - echo "ZKSYNC_RPC=${{ steps.vars.outputs.l2 }}" >> "$GITHUB_ENV" + if [[ "${{ inputs.setup }}" == "multi_chain" ]]; then + while IFS=: read -r chain_id port _rest; do + chain_id="${chain_id//[[:space:]]/}" + port="${port//[[:space:]]/}" + [[ -z "$chain_id" || "$chain_id" == \#* ]] && continue + echo "ZKSYNC_RPC_${chain_id}=http://127.0.0.1:${port}" >> "$GITHUB_ENV" + done <<< "${{ inputs.l2_ports }}" + else + echo "ZKSYNC_RPC=${{ steps.vars.outputs.l2 }}" >> "$GITHUB_ENV" + fi