Skip to content

chore(ci): manual workflow to deploy build images and AMIs - #25606

Closed
spalladino wants to merge 3 commits into
nextfrom
spl/publish-build-images-workflow
Closed

spalladino wants to merge 3 commits into
nextfrom
spl/publish-build-images-workflow

Conversation

@spalladino

@spalladino spalladino commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Adds a manual Publish build images workflow that runs ./build-images/bootstrap.sh deploy for a given branch from CI: it builds aztecprotocol/build, devbox and sysbox on EC2 for amd64 and arm64, pushes them and the multi-arch manifests to Docker Hub, bakes new CI AMIs, and commits the new ci3/aws/ami_id_{amd64,arm64} back to that branch.

First use: publish tag 3.1 for #25602 (Foundry 1.8.5).

gh workflow run publish-build-images.yml --ref next -f branch=<branch>

How it works

  • resolve (no secrets): resolves the branch to its head SHA, reads the tag from build-images/bootstrap.sh, and fails if ci3/aws/ami_update.sh caches a different tag.
  • deploy (environment: master): first checks Docker Hub (refuses to overwrite an existing tag unless allow_overwrite; in mode=amis-only requires the tag to exist). The check lives here so "Re-run failed jobs" cannot republish a tag; after a failed AMI step, dispatch again with mode=amis-only. Then AWS via the existing OIDC role, the build-instance SSH key decoded as in .github/ci3.sh, and bootstrap.sh deploy (or update_amis). AMI ids are uploaded as an artifact on success.
  • commit-amis: validates the AMI ids and pushes a commit to the branch with the bot token. It runs no script from the branch, and the push is non-force from the resolved SHA, so it is rejected if the branch moved.
  • One publish at a time (global concurrency group). Dispatchable only on next, which is also what the master environment's branch policy allows.

Security trade-off

The target branch's own scripts run with the Docker Hub credentials, the CI AWS role and the build-instance SSH key. Today anyone who can dispatch workflows on next could use that. Follow-up: move it to a dedicated environment with required reviewers.

Needs checking before the first run

  • Security group sg-0ccd4e5df0dcca0c9 (used in SSH mode) allows port 22 from GitHub-hosted runners.
  • The OIDC role has ec2:CreateImage and ec2:DescribeImages; ami_update.sh loops on the image waiter until the 120-minute job timeout if it does not.
  • The master environment's DOCKERHUB_PASSWORD belongs to aztecprotocolci, which build-images/bootstrap.sh hardcodes.
  • build_ec2 / ami_update.sh as ported in chore: bump Foundry to 1.8.5 #25602 have not run yet; watch the first dispatch.

Follow-ups

  • Dedicated environment with required reviewers.
  • Ports to v6 and the private repo.

@charlielye charlielye left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think we can just run ./build-images/bootstrap.sh deploy
the result will update two files with the new ami ids.
we'd then want to push those changes back to branch we ran on.

@spalladino spalladino changed the title chore(ci): manual workflow to publish build images chore(ci): manual workflow to deploy build images and AMIs Oct 6, 2026
@spalladino
spalladino requested a review from charlielye October 6, 2026 20:07
@spalladino

Copy link
Copy Markdown
Contributor Author

Thanks for the review @charlielye, updated

@spalladino spalladino closed this Oct 7, 2026
spalladino added a commit that referenced this pull request Oct 8, 2026
## Summary

Moves the pinned Foundry toolchain from 1.4.1 to 1.8.5 (latest release,
published 2026-10-05) everywhere the repo pins it, adapts `l1-contracts`
to the behaviour changes shipped in Foundry 1.5 through 1.8, targets the
Osaka EVM that mainnet runs, and introduces build image tag `3.1` so
this toolchain can be published without disturbing `next`.

Pins updated:
- `bootstrap.sh` (`expected_abs_foundry_version`, which the toolchain
check greps from `forge --version` / `anvil --version`)
- `build-images/src/Dockerfile` (`FOUNDRY_VERSION`, the build/devbox
image)
- `scripts/setup-container.sh`

## Adaptations required by the new toolchain

- **`forge fmt` output changed (Foundry 1.5 formatter).** 48 Solidity
files under `l1-contracts` reformatted with `forge fmt` 1.8.5 so `forge
fmt --check` passes. Every change is whitespace-only: stripping all
whitespace from each file before and after yields identical token
streams (checked for all 48 files).
- **`foundry.toml`: `isolate = false`.** Foundry 1.8 runs forge tests
with `--isolate` by default, which executes each top-level call as its
own transaction. That charges cold-access and intrinsic gas per call,
which pushed the large validator-set tests (`RollupGetters`, `tmnt333`,
`flushEntryQueue` fuzz) past the gas limit, and failed the
`getCurrentEpochCommittee` gas-growth assertion. Pinning it off keeps
the semantics the suite was written against. It does not affect the
committed gas reports: a gas report (`FORGE_GAS_REPORT=true`) isolates
every call whatever this is set to, and the same report is
byte-identical with it on or off. Regenerating `gas_report.json` under
1.8.5 reproduces every committed `min`/`max`; only fuzz-driven call
counts and averages drift (the 1.5 fuzzer samples inputs differently
even with `--fuzz-seed 42`). `gas_benchmark.md` regenerated under 1.8.5
is identical to what `next` produces under 1.4.1, and
`partial_epoch_proof_gas_report.json` reproduces exactly.
- **`foundry.toml`: `evm_version = 'osaka'`.** Mainnet runs the Osaka
EVM (Fusaka activated; the current fork reported by `eth_config` is the
second BPO fork of 2026-01-07, with no next fork scheduled), and Foundry
1.7+ defaults forge and anvil to Osaka. Building with the Osaka target
produces byte-identical creation and runtime code for all 125 `src/`
contracts (solc 0.8.30 emits no Osaka-only opcodes here), the full suite
passes, the Rollup gas report matches on every `min`/`max`, and nothing
in `src/` uses the modexp precompile that EIP-7883 reprices. solc 0.8.30
still labels `osaka` experimental in its help text; it is the chain we
deploy to, so the tests should simulate it.
- **`foundry.toml`: dropped `variable_override_spacing`.** Not a
recognised `[fmt]` key in 1.8.5 (every forge invocation warned about
it); `override_spacing = false` on the next line is the key that is
honoured.
- **`test/governance/governance/tmnt331.t.sol`.**
`Governance__CallerCannotBeSelf()` declares no parameters, but the test
encoded an `address` argument into its `expectRevert` data. Older forge
matched that leniently; 1.8.5 compares revert data exactly, so the test
now encodes the selector alone.
- **`scripts/forge_broadcast.js`.** `forge script` 1.8.5 rejects
`--batch-size` (`error: unexpected argument '--batch-size' found`),
which broke `scripts/test_rollup_upgrade.sh`. The wrapper now passes
`--slow` (send the next tx only after the previous one is confirmed) in
the automining-anvil case where it previously used `--batch-size 1`, and
leaves forge's default batching elsewhere. Real-chain deploys therefore
no longer send in batches of 8; there is no flag left that reproduces
that. If strictly sequential live deploys are preferred, pass `--slow`
unconditionally.
- **`l1-contracts/bootstrap.sh build_src` and
`barretenberg/sol/bootstrap.sh build_sol` no longer run `forge
install`.** Under 1.8.5 `forge install` syncs every submodule of the
repository to `foundry.lock` and exits 1 when the lock names a revision
a shallow clone does not have. Both lock files list
`l1-contracts/lib/circuits`, `labs` and `noir/noir-repo` behind the
revisions the repository records, so the cache-miss build failed before
compiling anything (1.4.1 exits 0 and touches nothing). The `git
submodule update --init --recursive ./lib` that follows already checks
the libraries out at the recorded revisions.
- **`l1-contracts/bootstrap.sh build_verifier` ends with a full `forge
build`.** Under 1.8.5, running `forge test` after an explicit-path
`forge build` so that forge incrementally compiles only the handful of
leftover files (`shouting.t.sol`, `test/script/*`, scripts) produces
inconsistent artifacts: 3 of 3 such runs failed 177 tests with
`EvmError: Revert` at the same ~2.93M gas point in every
Rollup-deploying suite. A full build before the artifacts are uploaded
means `forge test` has nothing left to compile; that sequence passed
every time.

## Build image `3.1`

- `build-images/bootstrap.sh` `version`, `build-images/run.sh`,
`.devcontainer/dev/devcontainer.json`, `ci3/bootstrap_ec2`,
`ci3/docker_isolate`, `ci3/aws/ami_update.sh` and
`ci3/tests/signal_test` now reference `3.1`. CI runners boot from an AMI
with `3.0` already cached and `bootstrap_ec2` runs `docker run
aztecprotocol/devbox:<tag>` without pulling first, so a re-pushed `3.0`
would never reach them and would break every other PR on `next` (whose
checkout pins 1.4.1). A fresh tag is pulled on first use and leaves
`next` untouched.
- `build-images/bootstrap.sh build_ec2` and `ci3/aws/ami_update.sh` are
ported to the current `aws_request_instance` contract (state directory,
`KEY_NAME`, `aws_terminate_instance <state_dir>`); they still called the
pre-March-2026 three-argument form and failed with `state_dir: unbound
variable` before doing anything. The port was exercised end to end
against stubbed `aws`, `ssh` and `docker` commands: it exits 0, requests
and terminates four instances, pushes the manifests and writes both AMI
id files, where the scripts as they are on `next` fail with `$4: unbound
variable` before requesting anything. It has not been run against real
AWS.

**To go green, the `3.1` images (`build`, `devbox`, `sysbox`, amd64 +
arm64 + manifest) must be published to Docker Hub.** #25606 adds a
workflow that runs this branch's own deploy script; a laptop runbook is
in the PR thread as a fallback. Refreshing the AMIs
(`ci3/aws/ami_update.sh`, commit the new `ci3/aws/ami_id_*`) only speeds
up cold starts and can follow later from the CI AWS account. A mainframe
admin also needs to point `/usr/local/bin/launch_sysbox` at
`sysbox:3.1`.

## Verified locally with Foundry 1.8.5 binaries

| Check | Result |
| --- | --- |
| `forge fmt --check` | clean |
| `forge build` (bootstrap's file set) | compiles |
| `forge test` (full suite, Osaka target) | 1383 passed, 0 failed, 3
skipped (1386) |
| `scripts/test_rollup_upgrade.sh` (anvil 1.8.5 + forge script
broadcast) | completed successfully |
| `scripts/check_contract_sizes.sh` (both profiles, Osaka target) |
within EIP-170 |
| `solhint` | unchanged warnings only |
| Bytecode, Osaka vs Prague target | identical for all 125 `src/`
contracts (metadata aside) |

## Things to know

- `forge build` now emits ~245 non-fatal `forge-lint` warnings because
1.8 added many detectors. A stacked PR on this branch brings that to
zero without changing bytecode.
- The existing CI command `forge test --match-contract MerkleCheck
--ffi` matches no contract on `next` (no `MerkleCheck` contract exists
under `test/`); this predates the bump and still exits 0.
- The anvil-related skip of `uniswap_trade_on_l1_from_l2.test.ts` in
`.test_patterns.yml` is untouched.

---
*Created by
[claudebox](https://claudebox.work/v2/sessions/f38155cc1541e786/jobs/1)
· group: `slackbot` · requested by Mike (@iAmMichaelConnor) · [Slack
thread](https://aztecfoundation.slack.com/archives/D0B2N7W1WJD/p1791282577517869?thread_ts=1791282577.517869&cid=D0B2N7W1WJD)*

---------

Co-authored-by: Charlie <5764343+charlielye@users.noreply.github.com>
Co-authored-by: Santiago Palladino <santiago@aztec-labs.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants