Publish Testnode #46
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Publish Testnode | |
| # On a version tag (or manual dispatch), publish a testnode image for every | |
| # variant x nitro-contracts-version we support. Each image's chain state is | |
| # GENERATED inline in CI by running `init` (no pre-built snapshot release | |
| # bundles, nothing to publish out of band). The intended (variant x version) | |
| # matrix and the init flags per combo come from runtime.mjs resolvePublishMatrix. | |
| on: | |
| push: | |
| tags: | |
| - "v*" | |
| workflow_dispatch: | |
| inputs: | |
| version: | |
| description: "Image version tag (default: tag on push, else apps/cli package version)" | |
| required: false | |
| default: "" | |
| type: string | |
| variant: | |
| description: "Variant to publish (or all)" | |
| required: true | |
| default: "all" | |
| type: choice | |
| options: | |
| - all | |
| - l2 | |
| - l2-timeboost | |
| - l3-eth | |
| - l3-custom-6 | |
| - l3-custom-16 | |
| - l3-custom-18 | |
| - l3-custom-20 | |
| nitro-contracts-version: | |
| description: "Nitro contracts version (or all)" | |
| required: true | |
| default: "all" | |
| type: choice | |
| options: | |
| - all | |
| - v2.1 | |
| - v3.2 | |
| jobs: | |
| resolve-publish-matrix: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| matrix: ${{ steps.matrix.outputs.matrix }} | |
| version: ${{ steps.ver.outputs.version }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Resolve image version | |
| id: ver | |
| run: | | |
| set -euo pipefail | |
| if [ "${{ github.ref_type }}" = "tag" ]; then | |
| V="${{ github.ref_name }}" | |
| elif [ -n "${{ inputs.version }}" ]; then | |
| V="${{ inputs.version }}" | |
| else | |
| V="v$(node -p "require('./apps/cli/package.json').version")" | |
| fi | |
| echo "version=$V" >> "$GITHUB_OUTPUT" | |
| echo "resolved image version: $V" | |
| - name: Build publish matrix | |
| id: matrix | |
| env: | |
| # On tag push: every variant x every supported version. On dispatch: | |
| # the chosen filters. resolvePublishMatrix only emits supported combos | |
| # (e.g. no v2.1 for L2-only variants). | |
| VARIANT_FILTER: ${{ github.ref_type == 'tag' && 'all' || (github.event_name == 'workflow_dispatch' && inputs.variant || 'all') }} | |
| VERSION_FILTER: ${{ github.ref_type == 'tag' && 'all' || (github.event_name == 'workflow_dispatch' && inputs.nitro-contracts-version || 'all') }} | |
| run: >- | |
| node --input-type=module -e | |
| "import { resolvePublishMatrix } from './packages/testnode/src/runtime.mjs'; | |
| const include = resolvePublishMatrix(process.env.VARIANT_FILTER, process.env.VERSION_FILTER); | |
| if (!include.length) throw new Error('empty publish matrix'); | |
| console.error('publish matrix:', JSON.stringify(include)); | |
| const { appendFileSync } = await import('node:fs'); | |
| appendFileSync(process.env.GITHUB_OUTPUT, 'matrix=' + JSON.stringify({ include }) + '\\n');" | |
| publish-testnode-image: | |
| needs: resolve-publish-matrix | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 75 | |
| strategy: | |
| fail-fast: false | |
| # Each job runs a full init (4 docker containers + image builds). Cap | |
| # concurrency so a release fan-out stays under runner/concurrency limits | |
| # and avoids resource contention. | |
| max-parallel: 4 | |
| matrix: ${{ fromJson(needs.resolve-publish-matrix.outputs.matrix) }} | |
| permissions: | |
| contents: read | |
| packages: write | |
| env: | |
| # init resolves token-bridge-contracts to <repo-parent>/token-bridge-contracts; | |
| # pin the location and the commit the contract-deployer Dockerfiles use. | |
| TOKEN_BRIDGE_LOCAL_DIR: ${{ github.workspace }}/../token-bridge-contracts | |
| TOKEN_BRIDGE_COMMIT: 5975d8f7360816341be7f94fd333ef240f4aec23 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 20 | |
| - name: Setup pnpm | |
| uses: pnpm/action-setup@v4 | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| - name: Install Foundry (forge for token-bridge build) | |
| uses: foundry-rs/foundry-toolchain@v1 | |
| - name: Install yarn (classic) | |
| run: npm install -g yarn@1.22.22 | |
| - name: Prepare token-bridge-contracts host checkout | |
| run: | | |
| set -euxo pipefail | |
| mkdir -p "$TOKEN_BRIDGE_LOCAL_DIR" | |
| cd "$TOKEN_BRIDGE_LOCAL_DIR" | |
| git init . | |
| git remote add origin https://github.com/OffchainLabs/token-bridge-contracts.git | |
| git fetch --depth 1 origin "$TOKEN_BRIDGE_COMMIT" | |
| git checkout --detach FETCH_HEAD | |
| yarn install --frozen-lockfile | |
| yarn build | |
| test -f node_modules/ts-node/dist/bin.js | |
| test -f scripts/deployment/deployTokenBridgeCreator.ts | |
| # Generate this combo's snapshot inline via init. init is flaky in CI | |
| # (intermittently exits 0 at wait-l1 before capturing, ~50%), so success | |
| # is driven off the manifest existing, with up to 3 attempts and a reset | |
| # between. --skip-post-capture-verify because the in-place verify-restart's | |
| # fixed 120s RPC budget is too tight for a cold runner. | |
| - name: Generate snapshot (init, retry until captured) | |
| env: | |
| SNAPSHOT_ID: ${{ matrix.snapshotId }} | |
| NC_VERSION: ${{ matrix.contractsVersion }} | |
| DECIMALS: ${{ matrix.feeTokenDecimals }} | |
| TIMEBOOST: ${{ matrix.timeboostEnabled }} | |
| run: | | |
| set -uo pipefail | |
| MANIFEST="config/snapshots/$SNAPSHOT_ID/manifest.json" | |
| ANVIL_STATE="config/snapshots/$SNAPSHOT_ID/anvil-state/state.json" | |
| EXTRA="" | |
| if [ -n "${DECIMALS:-}" ] && [ "${DECIMALS}" != "null" ]; then | |
| EXTRA="$EXTRA --fee-token-decimals $DECIMALS" | |
| fi | |
| if [ "${TIMEBOOST:-}" = "true" ]; then | |
| EXTRA="$EXTRA --timeboost-enabled" | |
| fi | |
| max=3 | |
| for attempt in $(seq 1 "$max"); do | |
| echo "::group::init attempt $attempt/$max ($SNAPSHOT_ID)" | |
| # shellcheck disable=SC2086 | |
| # Bound each attempt so an unforeseen hang can't consume the whole | |
| # job; 1500s comfortably exceeds a healthy ~10-15min init and keeps | |
| # 3 attempts within timeout-minutes: 75. The retry loop resets with | |
| # `docker compose ... down -v` below so a timed-out attempt recovers. | |
| timeout 1500 pnpm dev init \ | |
| --nitro-contracts-version "$NC_VERSION" \ | |
| $EXTRA \ | |
| --capture-id "$SNAPSHOT_ID" \ | |
| --rebuild \ | |
| --skip-post-capture-verify || echo "init exited/timed out on attempt $attempt" | |
| echo "::endgroup::" | |
| if [ -f "$MANIFEST" ] && [ -s "$ANVIL_STATE" ]; then | |
| echo "snapshot captured on attempt $attempt" | |
| break | |
| fi | |
| if [ -f "$MANIFEST" ]; then | |
| echo "attempt $attempt produced a manifest without non-empty Anvil state" | |
| fi | |
| echo "attempt $attempt produced an incomplete snapshot; resetting and retrying" | |
| docker compose -f docker/docker-compose.yaml -p arbitrum-testnode down -v || true | |
| done | |
| test -f "$MANIFEST" | |
| test -s "$ANVIL_STATE" | |
| - name: Dump docker state on failure | |
| if: failure() | |
| run: | | |
| docker ps -a || true | |
| docker compose -f docker/docker-compose.yaml -p arbitrum-testnode logs --tail=100 || true | |
| - name: Prepare testnode context | |
| run: >- | |
| node scripts/ci/prepare-testnode-context.mjs | |
| --variant "${{ matrix.variant }}" | |
| --snapshot-id "${{ matrix.snapshotId }}" | |
| --nitro-contracts-version "${{ matrix.contractsVersion }}" | |
| - name: Lowercase owner | |
| id: lower | |
| run: echo "owner=$(echo '${{ github.repository_owner }}' | tr '[:upper:]' '[:lower:]')" >> "$GITHUB_OUTPUT" | |
| - name: Contracts tag | |
| id: nc | |
| run: echo "tag=nc$(echo '${{ matrix.contractsVersion }}' | sed 's/^v//')" >> "$GITHUB_OUTPUT" | |
| - name: Log in to GHCR | |
| uses: docker/login-action@v3 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Build and push testnode image | |
| uses: docker/build-push-action@v6 | |
| with: | |
| context: . | |
| file: docker/testnode.Dockerfile | |
| push: true | |
| tags: ghcr.io/${{ steps.lower.outputs.owner }}/arbitrum-testnode-ci:${{ needs.resolve-publish-matrix.outputs.version }}-${{ steps.nc.outputs.tag }}-${{ matrix.variant }} |