Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 63 additions & 2 deletions .github/workflows/release-testnode-image.yml
Original file line number Diff line number Diff line change
Expand Up @@ -105,11 +105,14 @@ jobs:
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.
# Prepare the same pinned Token Bridge checkout for host deployment and
# the baked image's named build context.
TOKEN_BRIDGE_LOCAL_DIR: ${{ github.workspace }}/../token-bridge-contracts
TOKEN_BRIDGE_REF: 5975d8f7360816341be7f94fd333ef240f4aec23
TOKEN_BRIDGE_COMMIT: 5975d8f7360816341be7f94fd333ef240f4aec23
TOKEN_BRIDGE_DOCKER_CONTEXT: https://github.com/OffchainLabs/token-bridge-contracts.git#5975d8f7360816341be7f94fd333ef240f4aec23
NITRO_CONTRACTS_LOCAL_DIR: ${{ github.workspace }}/../nitro-contracts
NITRO_CONTRACTS_REF: v3.2.0
NITRO_CONTRACTS_COMMIT: 2695e7b3e3f460531e2b77fed48a60561c54d90e
steps:
- name: Checkout
Expand Down Expand Up @@ -141,6 +144,7 @@ jobs:
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
git submodule update --init --recursive --depth 1
yarn install --frozen-lockfile
yarn build
test -f node_modules/ts-node/dist/bin.js
Expand Down Expand Up @@ -352,7 +356,16 @@ jobs:
uses: docker/build-push-action@v6
with:
context: .
build-contexts: |
tokenbridge=${{ env.TOKEN_BRIDGE_DOCKER_CONTEXT }}
file: docker/testnode.Dockerfile
build-args: |
BUNDLE_VERSION=${{ needs.resolve-publish-matrix.outputs.version }}
BUNDLE_VARIANT=${{ matrix.variant }}
NITRO_CONTRACTS_REF=${{ env.NITRO_CONTRACTS_REF }}
NITRO_CONTRACTS_COMMIT=${{ env.NITRO_CONTRACTS_COMMIT }}
TOKENBRIDGE_REF=${{ env.TOKEN_BRIDGE_REF }}
TOKENBRIDGE_COMMIT=${{ env.TOKEN_BRIDGE_COMMIT }}
push: true
# One build, two pushes -- a second build could diverge from the first.
tags: |
Expand All @@ -363,3 +376,51 @@ jobs:
# the saving is. Rows overwrite each other's entry; reads still hit.
cache-from: type=gha
cache-to: type=gha,mode=max

# Moves `latest-<variant>` onto this release, in both registries so the alias
# means the same thing wherever it is pulled from.
publish-latest-bundle:
if: ${{ github.ref_type == 'tag' }}
needs: [resolve-publish-matrix, publish-testnode-image]
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Install crane
env:
CRANE_VERSION: v0.21.9
run: |
set -euo pipefail
url="https://github.com/google/go-containerregistry/releases/download/${CRANE_VERSION}/go-containerregistry_Linux_x86_64.tar.gz"
curl -fsSL "$url" -o /tmp/crane.tar.gz
tar -xzf /tmp/crane.tar.gz -C /usr/local/bin crane

- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}

- name: Lowercase owner
id: owner
run: echo "name=$(echo '${{ github.repository_owner }}' | tr '[:upper:]' '[:lower:]')" >> "$GITHUB_OUTPUT"

- name: Publish latest bundle aliases
env:
MATRIX: ${{ needs.resolve-publish-matrix.outputs.matrix }}
VERSION: ${{ needs.resolve-publish-matrix.outputs.version }}
run: >-
node scripts/ci/publish-latest-aliases.mjs
--repository "ghcr.io/${{ steps.owner.outputs.name }}/arbitrum-litro"
--repository "offchainlabs/arbitrum-litro"
23 changes: 21 additions & 2 deletions .github/workflows/test-action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@ on:
default: "v0.1.6"
type: string

# testnode.Dockerfile takes the Token Bridge workspace from a named context whose
# default is `scratch`, so a build that omits it produces an empty /workspace and
# fails at the git commit. Defined once here because every job that builds the
# image needs the same pin.
env:
TOKEN_BRIDGE_CONTEXT: https://github.com/OffchainLabs/token-bridge-contracts.git#5975d8f7360816341be7f94fd333ef240f4aec23

jobs:
test-l3-eth-action:
runs-on: ubuntu-latest
Expand Down Expand Up @@ -38,7 +45,14 @@ jobs:
run: node scripts/ci/prepare-testnode-context.mjs --variant l3-eth --snapshot-id default

- name: Build local testnode image
run: docker build -f docker/testnode.Dockerfile -t local/arbitrum-testnode:${{ github.sha }}-nc3.2-l3-eth .
run: >-
docker build
--build-context tokenbridge=${{ env.TOKEN_BRIDGE_CONTEXT }}
--build-arg BUNDLE_VERSION=${{ github.sha }}
--build-arg BUNDLE_VARIANT=l3-eth
-f docker/testnode.Dockerfile
-t local/arbitrum-testnode:${{ github.sha }}-nc3.2-l3-eth
.

- name: Run action
id: action
Expand Down Expand Up @@ -104,7 +118,12 @@ jobs:
run: node scripts/ci/prepare-testnode-context.mjs --variant l3-eth --snapshot-id default

- name: Build local testnode image
run: docker build -f docker/testnode.Dockerfile -t local/arbitrum-testnode:${{ github.sha }}-nc3.2-l3-eth .
run: >-
docker build
--build-context tokenbridge=${{ env.TOKEN_BRIDGE_CONTEXT }}
-f docker/testnode.Dockerfile
-t local/arbitrum-testnode:${{ github.sha }}-nc3.2-l3-eth
.

# Read the pin from the Dockerfile the image above was built with (its ARG
# default, since the build passes no --build-arg) so this job cannot drift
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ config/anvil-state
config/runs/
config/snapshots/
.testnode-context/
.cache/
scratch/
.tmp/
config/l1-l2-admin/
Expand Down
67 changes: 35 additions & 32 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ Minimal usage:
pnpm dev start
```

By default, `start` uses the CLI package version as the image version and resolves the `l3-eth` variant image:
By default, `start` resolves the latest published `l3-eth` bundle:

```text
offchainlabs/arbitrum-litro:v0.2.10-nc3.2-l3-eth
offchainlabs/arbitrum-litro:latest-l3-eth
```

Config-driven usage can pin a different image version:
Expand All @@ -39,7 +39,7 @@ Optional config fields:

| Field | Default | Description |
|-------|---------|-------------|
| `version` | CLI package version | Testnode image release version override |
| `version` | `latest` | Published bundle version override |
| `l3Enabled` | `true` | Boot the L3-enabled testnode |
| `feeTokenDecimals` | — | Custom L3 fee token decimals (`6`, `16`, `18`, `20`) |
| `nitroContractsVersion` | `v3.2` | Nitro contracts version tag component |
Expand Down Expand Up @@ -224,9 +224,9 @@ pnpm dev status # Show service and init state
## Custom snapshots

Downstream repos can bake their **own** testnode images: boot the base stack, run a
setup script against it (deploy contracts, seed activity, drop extra files into the
config dir), snapshot the result, and build a runnable Docker image. The stock CLI and
GitHub Action then boot those custom images.
setup script against it (deploy contracts, seed activity, or add exported config),
then commit the customized state as a runnable Docker image. The stock CLI and GitHub
Action then boot those custom images.

### Setup-command environment contract

Expand All @@ -238,39 +238,37 @@ receives these environment variables:
| `ARBITRUM_TESTNODE_L1_RPC_URL` | L1 (Anvil) RPC endpoint (`http://127.0.0.1:8545`) |
| `ARBITRUM_TESTNODE_L2_RPC_URL` | L2 (Nitro) RPC endpoint (`http://127.0.0.1:8547`) |
| `ARBITRUM_TESTNODE_L3_RPC_URL` | L3 (Orbit) RPC endpoint (`http://127.0.0.1:8549`) |
| `ARBITRUM_TESTNODE_CONFIG_DIR` | Config dir; files written here ride along into the snapshot and config export |
| `ARBITRUM_TESTNODE_CONFIG_DIR` | Exported config dir; files written here ride along into the customized image |
| `ARBITRUM_TESTNODE_DEPLOYMENT_JSON` | Path to the exported `deployment.json` in the config dir |

A non-zero exit from the setup command aborts the bake with a clear error.

### One-shot local bake

`testnode bake` boots the base stack (by default it restores the installed base
snapshot; `--rebuild` runs a full init instead), runs the setup command, captures a
snapshot, and builds the image:
`testnode bake` boots the latest published bundle, runs the setup command, stops the
stack cleanly so Anvil and Nitro flush their state, and commits the container as the
custom image:

```bash
pnpm dev bake \
--setup-command "./scripts/deploy-and-seed.sh" \
--image-ref ghcr.io/acme/arbitrum-testnode:governance \
--snapshot-id custom \
--push # optional; docker login is your responsibility
```

New builds use the stable Nitro contracts v3.2.0 release by default. For local
development, point `NITRO_CONTRACTS_LOCAL_DIR` at a Nitro 3.x checkout; a sibling
`../nitro-contracts` checkout is detected automatically:
The bundle composes the runtime, initialized chain state, Nitro contracts, and Token
Bridge contracts. Consumers never clone or rebuild either contracts repository.
Override the published base with `--image-version` or `--base-image-ref`.

Local base development still supports prepared contracts workspaces through
`NITRO_CONTRACTS_LOCAL_DIR` and `TOKEN_BRIDGE_LOCAL_DIR`:

```bash
NITRO_CONTRACTS_LOCAL_DIR=../nitro-contracts pnpm dev init --rebuild
NITRO_CONTRACTS_LOCAL_DIR=../nitro-contracts pnpm dev bake --rebuild \
--setup-command "./scripts/deploy-and-seed.sh" \
--image-ref ghcr.io/acme/arbitrum-testnode:governance
```

The checkout determines the contracts family; `init` and `bake` no longer expose
separate Nitro version or branch selectors. New builds require Nitro 3.x. Existing
published v2.1 images remain available through `start` and the run action.
Those source settings are used only by `init` and release production. New releases
default to Nitro contracts v3.2.0 and the compatible pinned Token Bridge commit.

To bake straight from an existing snapshot (no setup step), use the à-la-carte
subcommand:
Expand All @@ -279,6 +277,9 @@ subcommand:
pnpm dev snapshot bake --id custom --image-ref ghcr.io/acme/arbitrum-testnode:governance --push
```

This path also layers the snapshot onto the latest published bundle; it does not
compile contract sources.

### CI bake via the composite action

The `bake` subdirectory action wraps the same flow. Registry login is the consumer's
Expand All @@ -296,12 +297,12 @@ job — log in before invoking it:
setup-command: ./scripts/deploy-and-seed.sh
image-ref: ghcr.io/acme/arbitrum-testnode:governance
push: true
github-token: ${{ secrets.GITHUB_TOKEN }} # base snapshot download
github-token: ${{ secrets.GITHUB_TOKEN }} # published bundle pull
```

By default the action installs a base snapshot release (via `github-token`) and
restores it; set `rebuild: true` to run a full init instead. Rebuilds also accept
`nitro-contracts-ref` (default `v3.2.0`) and `fee-token-decimals`.
By default the action uses the latest composed bundle. Set `bundle-version` to pin a
release or `bundle-image-ref` to use another already-published bundle. There is no
consumer rebuild path.

### Booting a custom image

Expand Down Expand Up @@ -366,9 +367,9 @@ pnpm release 0.2.11 --push # ...and push, starting the publish
```

The `Publish Testnode` workflow publishes automatically when a `v*` tag is pushed.
Tag-triggered publishes use the `default` entry in `config/testnodes.json`, with the
Git tag as the image version. The workflow can also be run manually to publish one
variant or `all`. Each build is pushed to two registries under the same tag suffix:
The Git tag becomes the image version, and every current v3.2 variant is published.
The workflow can also be run manually to publish one variant or `all`. Each build is
pushed to two registries under the same tag suffix:

```text
ghcr.io/<owner>/arbitrum-litro:<version>-nc<contracts-version>-<variant>
Expand All @@ -382,13 +383,15 @@ GHCR package is private and requires a token. Publishing requires the
replace a Docker Hub tag that already exists unless the manual run sets
`overwrite`.

After every variant succeeds, a tag-triggered release also updates the corresponding
`latest-<variant>` aliases, in both registries. These canonical aliases deliberately
omit a contracts version: consumers follow the composed bundle, while its exact Nitro
and Token Bridge refs and commits remain recorded as OCI labels.

Releases up to `v0.2.10` live in a separate GHCR package,
`ghcr.io/<owner>/arbitrum-testnode-ci`, which still serves those tags. Resolving one
needs `image-repository` plus a token, since that package is private.

The `snapshot-version` workflow input provides the snapshot release tag used for every selected variant.
For automatic tag publishes, the snapshot release tag comes from `config/testnodes.json`.

Publish the default testnode image automatically:

```bash
Expand All @@ -402,13 +405,13 @@ Publish one variant image from GitHub Actions:
workflow: Publish Testnode
version: v0.2.3
variant: l3-eth
snapshot-version: v0.1.6
```

Publish every current catalog entry by setting `variant` to `all`. Existing v2.1
images remain resolvable but are not rebuilt by new releases.

The default Timeboost publish target is `l2-timeboost`, which expects the `l2-timeboost` snapshot ID in the selected snapshot release. It can be published directly with `variant: l2-timeboost` or through the `name: timeboost` entry in `config/testnodes.json`.
The default Timeboost publish target is `l2-timeboost`; publish it directly with
`variant: l2-timeboost` or as part of `all`.

## Init Sequence

Expand Down
38 changes: 30 additions & 8 deletions action.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
name: Run Arbitrum Testnode
description: "Boot a snapshot-backed Arbitrum testnode in GitHub CI and export its config files"
description: "Boot a published Arbitrum testnode bundle in GitHub CI and export its config files"
inputs:
version:
required: false
default: ""
description: "Pinned release version; required unless image-ref is set"
default: "latest"
description: "Published bundle version; defaults to latest"
github-token:
required: false
description: "Token for private GHCR images; not needed for the public Docker Hub default"
Expand Down Expand Up @@ -95,6 +95,15 @@ outputs:
nitro-contracts-version:
description: "Resolved nitro contracts version"
value: ${{ steps.resolve.outputs.nitro-contracts-version }}
bundle-version:
description: "Published bundle version recorded in the image"
value: ${{ steps.bundle.outputs.bundle-version }}
nitro-contracts-commit:
description: "Nitro contracts commit composed into the bundle"
value: ${{ steps.bundle.outputs.nitro-contracts-commit }}
token-bridge-commit:
description: "Token Bridge contracts commit composed into the bundle"
value: ${{ steps.bundle.outputs.token-bridge-commit }}
runs:
using: composite
steps:
Expand All @@ -117,13 +126,11 @@ runs:
# Registry behavior follows the base ref -- the image actually fetched. With
# nitro-image set, the booted ref is a locally built rebase of that base.
- name: Log in to GHCR
if: ${{ startsWith(steps.resolve.outputs.base-image-ref, 'ghcr.io/') }}
if: >-
${{ inputs.github-token != '' &&
startsWith(steps.resolve.outputs.base-image-ref, 'ghcr.io/') }}
shell: bash
run: |
if [ -z "${{ inputs.github-token }}" ]; then
echo "github-token is required for ghcr.io images" >&2
exit 1
fi
echo "${{ inputs.github-token }}" | docker login ghcr.io -u "${{ github.actor }}" --password-stdin

- name: Pull testnode image
Expand All @@ -142,6 +149,21 @@ runs:
NITRO_IMAGE: ${{ steps.resolve.outputs.nitro-image }}
run: node "${{ github.action_path }}/packages/action/src/rebase.mjs"

- name: Read bundle provenance
id: bundle
shell: bash
env:
IMAGE_REF: ${{ steps.resolve.outputs.image-ref }}
run: |
set -euo pipefail
label() {
docker inspect --format '{{json .Config.Labels}}' "$IMAGE_REF" | \
jq -r --arg key "$1" '.[$key] // ""'
}
echo "bundle-version=$(label io.arbitrum.testnode.bundle.version)" >> "$GITHUB_OUTPUT"
echo "nitro-contracts-commit=$(label io.arbitrum.testnode.nitro-contracts.commit)" >> "$GITHUB_OUTPUT"
echo "token-bridge-commit=$(label io.arbitrum.testnode.token-bridge.commit)" >> "$GITHUB_OUTPUT"

- name: Boot testnode image
shell: bash
env:
Expand Down
Loading
Loading