Skip to content

Commit cba6e28

Browse files
authored
feat: merge-train/fairies (#22936)
See [merge-train-readme.md](https://github.com/AztecProtocol/aztec-packages/blob/next/.github/workflows/merge-train-readme.md). This is a merge-train.
2 parents 1888c71 + 50a6a6c commit cba6e28

7 files changed

Lines changed: 356 additions & 49 deletions

File tree

.github/ci3_labels_to_env.sh

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,11 @@ function main {
3737
echo "NO_FAIL_FAST=1" >> $GITHUB_ENV
3838
fi
3939

40+
# Handle skip-compat-e2e label (escape hatch for backwards compat test failures on release PRs)
41+
if has_label "ci-skip-compat-e2e"; then
42+
echo "SKIP_COMPAT_E2E=1" >> $GITHUB_ENV
43+
fi
44+
4045
# Determine CI mode based on event, labels, and target branch
4146
local ci_mode
4247
if [ "${GITHUB_EVENT_NAME:-}" == "merge_group" ] || has_label "ci-merge-queue"; then

.github/workflows/ci3.yml

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -445,3 +445,108 @@ jobs:
445445
AWS_SHUTDOWN_TIME: 180
446446
run: |
447447
./.github/ci3.sh network-tests-kind
448+
449+
# Backwards compatibility e2e tests.
450+
# Runs e2e tests with contract artifacts from every prior stable release to validate
451+
# that new client code works with old contract artifacts ("new pxe / old contracts").
452+
# Blocking for stable/RC releases: ci-release-publish requires this job to pass before
453+
# publishing. Observational for nightlies: runs, but continue-on-error keeps the workflow
454+
# green and ci-release-publish's condition publishes nightlies regardless of the result.
455+
# Escape hatch: ci-skip-compat-e2e label makes failures non-blocking on release PRs.
456+
ci-compat-e2e:
457+
runs-on: ubuntu-latest
458+
permissions:
459+
id-token: write
460+
contents: read
461+
needs: [ci]
462+
if: |
463+
always()
464+
&& (needs.ci.result == 'success' || needs.ci.result == 'skipped')
465+
&& github.event.pull_request.head.repo.fork != true
466+
&& github.event.pull_request.draft == false
467+
&& (
468+
(startsWith(github.ref, 'refs/tags/v') && !contains(github.ref_name, '-commit.'))
469+
|| contains(github.event.pull_request.labels.*.name, 'ci-compat-e2e')
470+
|| contains(github.event.pull_request.labels.*.name, 'ci-release-pr')
471+
)
472+
# Non-blocking for nightlies and when ci-skip-compat-e2e escape hatch is applied.
473+
continue-on-error: ${{ contains(github.ref_name, '-nightly.') || contains(github.event.pull_request.labels.*.name, 'ci-skip-compat-e2e') }}
474+
steps:
475+
- name: Checkout
476+
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
477+
with:
478+
ref: ${{ github.event.pull_request.head.sha || github.sha }}
479+
480+
- name: Configure AWS credentials (OIDC)
481+
uses: aws-actions/configure-aws-credentials@v4
482+
with:
483+
role-to-assume: ${{ secrets.AWS_OIDC_ROLE_ARN }}
484+
aws-region: us-east-2
485+
role-session-name: ci3-compat-e2e-${{ github.run_id }}
486+
role-duration-seconds: 21600 # 6h – covers AWS_SHUTDOWN_TIME (300 min) + 60 min buffer
487+
488+
- name: Run Backwards Compatibility E2E Tests
489+
timeout-minutes: 330
490+
env:
491+
GITHUB_TOKEN: ${{ secrets.AZTEC_BOT_GITHUB_TOKEN }}
492+
BUILD_INSTANCE_SSH_KEY: ${{ secrets.BUILD_INSTANCE_SSH_KEY }}
493+
GCP_SA_KEY: ${{ secrets.GCP_SA_KEY }}
494+
DOCKERHUB_PASSWORD: ${{ secrets.DOCKERHUB_PASSWORD }}
495+
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
496+
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
497+
CI3_INSTANCE_PROFILE_NAME: ${{ secrets.CI3_INSTANCE_PROFILE_NAME }}
498+
CI3_SECURITY_GROUP_ID: ${{ secrets.CI3_SECURITY_GROUP_ID }}
499+
RUN_ID: ${{ github.run_id }}
500+
AWS_SHUTDOWN_TIME: 300
501+
run: ./.github/ci3.sh compat-e2e
502+
503+
# Publishes the release (npm, Docker, GitHub release, aztec-up scripts, etc.).
504+
# Gated on ci-compat-e2e: a compat regression blocks stable/RC publishing. Nightlies
505+
# publish regardless — compat-e2e runs there observationally. Dev `-commit.` tags from
506+
# the ci-release-pr flow never reach this job (they are not real releases).
507+
ci-release-publish:
508+
runs-on: ubuntu-latest
509+
environment: master
510+
permissions:
511+
id-token: write
512+
contents: read
513+
needs: [ci, ci-compat-e2e]
514+
if: |
515+
startsWith(github.ref, 'refs/tags/v')
516+
&& !contains(github.ref_name, '-commit.')
517+
&& needs.ci.result == 'success'
518+
&& (
519+
contains(github.ref_name, '-nightly.')
520+
|| needs.ci-compat-e2e.result == 'success'
521+
)
522+
steps:
523+
- name: Checkout
524+
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
525+
with:
526+
ref: ${{ github.sha }}
527+
528+
- name: Configure AWS credentials (OIDC)
529+
uses: aws-actions/configure-aws-credentials@v4
530+
with:
531+
role-to-assume: ${{ secrets.AWS_OIDC_ROLE_ARN }}
532+
aws-region: us-east-2
533+
role-session-name: ci3-release-publish-${{ github.run_id }}
534+
role-duration-seconds: 21600
535+
536+
- name: Run Release Publish
537+
env:
538+
GITHUB_TOKEN: ${{ secrets.AZTEC_BOT_GITHUB_TOKEN }}
539+
BUILD_INSTANCE_SSH_KEY: ${{ secrets.BUILD_INSTANCE_SSH_KEY }}
540+
GCP_SA_KEY: ${{ secrets.GCP_SA_KEY }}
541+
GCP_PROJECT_ID: ${{ secrets.GCP_PROJECT_ID }}
542+
DOCKERHUB_PASSWORD: ${{ secrets.DOCKERHUB_PASSWORD }}
543+
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
544+
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
545+
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
546+
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}
547+
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
548+
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
549+
CI3_INSTANCE_PROFILE_NAME: ${{ secrets.CI3_INSTANCE_PROFILE_NAME }}
550+
CI3_SECURITY_GROUP_ID: ${{ secrets.CI3_SECURITY_GROUP_ID }}
551+
RUN_ID: ${{ github.run_id }}
552+
run: ./.github/ci3.sh release-publish

bootstrap.sh

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -823,6 +823,17 @@ case "$cmd" in
823823
# RELEASES #
824824
############
825825
"ci-release")
826+
# Verification build for a release tag. Does NOT publish — publishing happens in
827+
# ci-release-publish, gated on ci-compat-e2e so a compat regression blocks the release.
828+
export CI=1
829+
export USE_TEST_CACHE=1
830+
if ! semver check $REF_NAME; then
831+
exit 1
832+
fi
833+
build
834+
;;
835+
"ci-release-publish")
836+
# Actual publish step. `build` cache-hits against ci-release's build of the same commit.
826837
export CI=1
827838
export USE_TEST_CACHE=1
828839
if ! semver check $REF_NAME; then
@@ -902,6 +913,82 @@ case "$cmd" in
902913
build
903914
yarn-project/end-to-end/bootstrap.sh avm_check_circuit
904915
;;
916+
#############################################
917+
# BACKWARDS COMPATIBILITY E2E TESTS #
918+
#############################################
919+
"ci-compat-e2e")
920+
# Runs e2e tests with contract artifacts from every prior stable release since 4.2.0 (version where we committed to
921+
# backwards compatibility). This Validates that old contract artifacts work on current release.
922+
export CI=1
923+
export USE_TEST_CACHE=0
924+
export CI_FULL=0
925+
export NO_FAIL_FAST=1
926+
927+
build
928+
929+
# TODO: bump when v5 commits to backwards-compatible contract artifacts.
930+
# compat_major: major version that has compat guarantees today.
931+
# compat_min_version: earliest stable tag of that major to test against
932+
# (artifacts before this are incompatible due to oracle interface changes).
933+
compat_major="4"
934+
compat_min_version="4.2.0"
935+
936+
# Get current major version.
937+
current_version=$(jq -r '."."' .release-please-manifest.json)
938+
major=$(semver major "$current_version")
939+
if [ "$major" != "$compat_major" ]; then
940+
echo "Compat e2e tests only apply to v${compat_major}. Current major: v${major}. Skipping."
941+
exit 0
942+
fi
943+
min_version="$compat_min_version"
944+
945+
# Fetch tags (EC2 clone may not have them). Fail loud: a silent fetch failure plus an empty
946+
# tag list would publish a real release with zero compat coverage.
947+
if ! git fetch origin 'refs/tags/v*:refs/tags/v*'; then
948+
echo "ERROR: failed to fetch release tags." >&2
949+
exit 1
950+
fi
951+
952+
# Discover stable tags for this major version (no prerelease suffixes).
953+
versions=()
954+
while IFS= read -r tag; do
955+
ver=${tag#v}
956+
# Include only versions >= min_version (sort -V puts smaller first).
957+
if [ "$(printf '%s\n%s' "$min_version" "$ver" | sort -V | head -1)" = "$min_version" ]; then
958+
versions+=("$ver")
959+
fi
960+
done < <(git tag -l "v${major}.*" | grep -E "^v[0-9]+\.[0-9]+\.[0-9]+$" | sort -V)
961+
962+
# Exclude the current tag when running on a release tag push.
963+
if [[ "${REF_NAME:-}" =~ ^v([0-9]+\.[0-9]+\.[0-9]+)$ ]]; then
964+
current_tag="${BASH_REMATCH[1]}"
965+
filtered=()
966+
for v in "${versions[@]}"; do
967+
[ "$v" != "$current_tag" ] && filtered+=("$v")
968+
done
969+
versions=("${filtered[@]}")
970+
fi
971+
972+
if [ ${#versions[@]} -eq 0 ]; then
973+
echo "No prior stable versions found for v${major}.x (>= $min_version). Skipping compat tests."
974+
exit 0
975+
fi
976+
977+
echo_header "Backwards compatibility e2e tests"
978+
echo "Testing against ${#versions[@]} prior stable version(s): ${versions[*]}"
979+
980+
# Pre-populate the legacy contract cache on the host. Test containers run with --net=none, so the
981+
# jest resolver's on-demand npm install would fail with EAI_AGAIN. Install here where we have network.
982+
for ver in "${versions[@]}"; do
983+
node yarn-project/end-to-end/src/install_legacy_contracts.cjs "$ver"
984+
done
985+
986+
# Generate compat test commands for all versions and run them in parallel.
987+
for ver in "${versions[@]}"; do
988+
yarn-project/end-to-end/bootstrap.sh compat_test_cmds "$ver"
989+
done | filter_test_cmds | parallelize
990+
;;
991+
905992
##########################################
906993
# ROLLUP UPGRADE DEPLOYMENT #
907994
##########################################

ci.sh

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ function print_usage {
3535
echo_cmd "network-teardown" "Spin up an EC2 instance to teardown a network deployment."
3636
echo_cmd "network-tests-kind" "Spin up an EC2 instance to run a KIND-based spartan test."
3737
echo_cmd "deploy-rollup-upgrade" "Spin up an EC2 instance to deploy a rollup upgrade."
38+
echo_cmd "compat-e2e" "Spin up an EC2 instance and run backwards compat e2e tests."
3839
echo_cmd "release" "Spin up an EC2 instance and run bootstrap release."
3940
echo_cmd "shell-new" "Spin up an EC2 instance, clone the repo, and drop into a shell."
4041
echo_cmd "shell" "Drop into a shell in the current running build instance container."
@@ -302,16 +303,42 @@ case "$cmd" in
302303
bootstrap_ec2 "./bootstrap.sh ci-deploy-rollup-upgrade $*"
303304
;;
304305

306+
##############################
307+
# BACKWARDS COMPATIBILITY #
308+
##############################
309+
compat-e2e)
310+
# Spin up an EC2 instance and run backwards compatibility e2e tests
311+
# against contract artifacts from prior stable releases.
312+
export CI_DASHBOARD="releases"
313+
export JOB_ID="x-compat-e2e"
314+
export AWS_SHUTDOWN_TIME=300
315+
bootstrap_ec2 "./bootstrap.sh ci-compat-e2e"
316+
;;
317+
305318
############
306319
# RELEASES #
307320
############
308321
release)
309-
# Spin up ec2 instance and run the release flow.
322+
# Spin up ec2 instance and run the release-tag verification build (no publish).
310323
export CI_DASHBOARD="releases"
311324
multi_job_run \
312325
'x-release amd64 ci-release' \
313326
'a-release arm64 ci-release'
314327
;;
328+
release-publish)
329+
# Spin up ec2 instance and run the actual publish flow. Gated in ci3.yml on ci + ci-compat-e2e.
330+
export CI_DASHBOARD="releases"
331+
export DENOISE=1
332+
export DENOISE_WIDTH=32
333+
run() {
334+
PARENT_LOG_ID=$RUN_ID JOB_ID=$1 INSTANCE_POSTFIX=$1 ARCH=$2 exec denoise "bootstrap_ec2 './bootstrap.sh ci-release-publish'"
335+
}
336+
export -f run
337+
338+
parallel --termseq 'TERM,10000' --tagstring '{= $_=~s/run (\w+).*/$1/; =}' --line-buffered --halt now,fail=1 ::: \
339+
'run x-release-publish amd64' \
340+
'run a-release-publish arm64' | DUP=1 cache_log "Release Publish CI run" $RUN_ID
341+
;;
315342

316343
##################
317344
# SHELL SESSIONS #

yarn-project/end-to-end/bootstrap.sh

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,37 @@ function avm_check_circuit {
246246
avm_check_circuit_cmds | parallelize
247247
}
248248

249+
# Generates e2e test commands using contract artifacts from a prior release version.
250+
# Only includes simple (jest-based) tests since compose/docker tests don't use the legacy jest resolver.
251+
# Excludes prover, block_building, and epochs tests (not relevant for contract artifact compat; epochs
252+
# tests are known-flaky and provide no additional backwards-compat coverage).
253+
function compat_test_cmds {
254+
local version=${1:?version is required}
255+
local run_test_script="yarn-project/end-to-end/scripts/run_test.sh"
256+
local prefix="$hash:ISOLATE=1"
257+
local compat_env="CONTRACT_ARTIFACTS_VERSION=$version"
258+
259+
local tests=(
260+
src/e2e_!(prover|block_building|epochs)/*.test.ts
261+
src/e2e_p2p/reqresp/*.test.ts
262+
src/e2e_!(block_building|prover_*).test.ts
263+
)
264+
for test in "${tests[@]}"; do
265+
local name=${test#*e2e_}
266+
name=e2e_${name%.test.ts}
267+
268+
if [[ "$test" == *.parallel.test.ts ]]; then
269+
while IFS= read -r test_name; do
270+
local safe_test_name=$(echo "$test_name" | sed 's/ /_/g')
271+
local full_name="compat_${version}_${name}_${safe_test_name}"
272+
echo "$prefix:NAME=$full_name $compat_env $run_test_script simple $test \"$test_name\""
273+
done < <(extract_test_names "$test")
274+
else
275+
echo "$prefix:NAME=compat_${version}_${name} $compat_env $run_test_script simple $test"
276+
fi
277+
done
278+
}
279+
249280
case "$cmd" in
250281
"")
251282
build
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
#!/usr/bin/env node
2+
// Installs pinned legacy @aztec/* contract-artifact packages into .legacy-contracts/<version>/.
3+
//
4+
// Called from two places:
5+
// - bootstrap.sh ci-compat-e2e: pre-populates the cache on the host before hermetic test
6+
// containers launch. The containers run with --net=none (see ci3/docker_isolate), so on-demand
7+
// installs from inside them fail with EAI_AGAIN.
8+
// - legacy-jest-resolver.cjs: on-demand install for local dev, where jest runs with network.
9+
//
10+
// Idempotent: no-op when all packages are already present.
11+
/* eslint-disable @typescript-eslint/no-require-imports */
12+
13+
const path = require('path');
14+
const fs = require('fs');
15+
const { execFileSync } = require('child_process');
16+
17+
const REDIRECTED = ['@aztec/noir-contracts.js', '@aztec/noir-test-contracts.js', '@aztec/accounts'];
18+
19+
const e2eRoot = path.resolve(__dirname, '..');
20+
21+
function cacheRoot(version) {
22+
return path.join(e2eRoot, '.legacy-contracts', version);
23+
}
24+
25+
function pkgJsonPath(version, pkg) {
26+
return path.join(cacheRoot(version), 'node_modules', pkg, 'package.json');
27+
}
28+
29+
function installLegacyContracts(version) {
30+
if (!version) {
31+
throw new Error('installLegacyContracts: version is required');
32+
}
33+
if (REDIRECTED.every(pkg => fs.existsSync(pkgJsonPath(version, pkg)))) {
34+
return;
35+
}
36+
37+
const cacheDir = cacheRoot(version);
38+
fs.mkdirSync(cacheDir, { recursive: true });
39+
40+
// Seed a standalone package.json so `npm install --prefix` treats cacheRoot as its own project. Without this, npm
41+
// walks up and finds the yarn-project workspace root, which breaks on `workspace:` protocol deps and risks
42+
// clobbering the monorepo's node_modules.
43+
const seed = path.join(cacheDir, 'package.json');
44+
if (!fs.existsSync(seed)) {
45+
fs.writeFileSync(seed, JSON.stringify({ name: 'legacy-contracts-cache', private: true }));
46+
}
47+
48+
const specs = REDIRECTED.map(pkg => `${pkg}@${version}`);
49+
process.stderr.write(`[legacy-contracts] installing ${specs.join(' ')} into ${cacheDir}\n`);
50+
// --prefix: install into cacheRoot instead of cwd, so the cache is isolated from the monorepo.
51+
// --no-save: don't write the installed packages back to the seeded package.json.
52+
// --ignore-scripts: skip lifecycle scripts (preinstall/postinstall) of the legacy packages and their transitive
53+
// deps; we only want the files on disk, not to run any build steps.
54+
// --legacy-peer-deps: tolerate peer-dependency mismatches between the pinned legacy @aztec/* graph and whatever
55+
// current versions npm would otherwise try to reconcile.
56+
execFileSync(
57+
'npm',
58+
['install', '--prefix', cacheDir, '--no-save', '--ignore-scripts', '--legacy-peer-deps', ...specs],
59+
{ stdio: 'inherit' },
60+
);
61+
62+
// Verify versions on disk match the requested version.
63+
for (const pkg of REDIRECTED) {
64+
const onDisk = JSON.parse(fs.readFileSync(pkgJsonPath(version, pkg), 'utf8')).version;
65+
if (onDisk !== version) {
66+
throw new Error(`[legacy-contracts] ${pkg} on disk is ${onDisk}, expected ${version}`);
67+
}
68+
}
69+
}
70+
71+
module.exports = { installLegacyContracts, REDIRECTED, cacheRoot };
72+
73+
if (require.main === module) {
74+
installLegacyContracts(process.argv[2]);
75+
}

0 commit comments

Comments
 (0)