fix(ci): commit-independent toolchain hash; generate-once VK cache - #25111
Merged
Conversation
The labs-aztec-toolchain hash hashed the bytes of the built binaries, but inject_version stamps the current commit into bb/bb-avm on non-release builds, so byte-identical trees produced a different toolchain hash on every commit. Every noir-contracts cache key mixes that hash in, so every commit recompiled all ~84 contracts and mass-regenerated their VKs in parallel into a fresh shared vk_cache. The toolchain hash now composes the providers' source content hashes (the same inputs that decide whether the binaries rebuild) plus the presence of the optional binaries. That mass regeneration exposed a latent race: get_or_generate_cached_app_vk checked existence then wrote the final path directly, with no locking — and distinct contracts can carry byte-identical functions (the simulated account contracts), so concurrent bb processes double-generated the same entry and could read a partially written VK. One corrupted account-contract VK baked into the yarn-project artifact and the aztec-up test image caches, making bridge_and_claim fail deterministically on every retry of PR #24306. VK cache entries now take a per-entry flock (generate-once: one process generates, the rest block then read) and are written via temp file + rename.
nchamo
self-requested a review
August 5, 2026 15:01
github-merge-queue
Bot
removed this pull request from the merge queue due to failed status checks
Aug 5, 2026
fcarreiro
reviewed
Aug 5, 2026
| done | ||
| hash_str $(git hash-object "${files[@]}") | ||
| hash_str \ | ||
| $("$ROOT"/barretenberg/cpp/bootstrap.sh hash) \ |
Contributor
There was a problem hiding this comment.
I will come back to this PR when I get back from OOO next week, but this part is wrong. It makes the labs repo depend on the foundation repo.
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The incident
PR #24306 failed
aztec-up/scripts/run_test.sh bridge_and_claimdeterministically — the identicalBBApiException: Failed to verify the generated proof!~2.8s into the only ClientIVC prove of the test, across multiple "retries", while the same test was 60/60 green on next. The retries could never help: the failure was frozen inside cached build artifacts (aztec-up-test-image-…andyarn-project-….tar.gz), which are content-addressed by tree hash and therefore reused by every run of the same tree.Tracing how a wrong artifact could sit under a correct content key led to two independent defects that compose:
Defect 1: the toolchain hash changes on every commit (regression, #25078 / #25057 / #25047)
labs-aztec-toolchain/bootstrap.sh hashcomputed the toolchain identity ashash_str $(git hash-object <built binaries>). Butinject_version(barretenberg/cpp/bootstrap.sh) stampsgit rev-parse --short HEADinto bb/bb-avm bytes on any non-release build — so the binary bytes encode the commit, and byte-identical source trees produce a different toolchain hash on every commit.Observed directly: two CI runs of byte-identical trees (commits
5041b6bb/fafdd723) had toolchain hashesf2e3f37015b9cdfavsd714a9fc28a62cff, zero overlap between their 84contract-*.tar.gzcache names, and bb logged~/.bb/5041b6b/vk_cachevs~/.bb/fafdd72/vk_cache— the injected commit even keys the runtime VK cache directory.Since every noir-contracts cache key mixes in
AZTEC_TOOLCHAIN_HASH, the practical effect is: every commit of every PR recompiles all ~84 contracts and mass-regenerates their VKs in parallel into a brand-new, empty, shared VK cache. Before this scheme (pre-Aug-3), contract keys were built from source rebuild patterns over bb/noir/transpiler — identical trees reused artifacts.It is also conceptually circular: the binaries themselves rebuild if and only if their source hash changes (their artifact names are the source hash — both runs above shared
barretenberg-clang20-11cccdad4e78d0a2.zst), so deriving downstream rebuild keys from the binary bytes answers "did the toolchain change?" with information that is only available after deciding exactly that.Fix: the toolchain hash now composes the providers' source content hashes —
barretenberg/cpp/bootstrap.sh hash+noir/bootstrap.sh hash— plus the presence of the optional binaries (bb-avm/acvm, whose availability is part of the toolchain's identity but whose content is already covered by the provider hashes). Rebuild decisions at every level of the graph now key on the same inputs. A future labs-repo provisioning mode (build_labs, currently stubbed) should use the released toolchain's version string as its fixed identity.Defect 2: the shared VK cache is not generate-once (latent since ~June)
get_or_generate_cached_app_vk(barretenberg/cpp/src/barretenberg/api/aztec_process.cpp) didexists() → read_file, and on miss wrote the VK directly to the final path — no locking, no temp-file+rename. The cache directory is shared by all concurrent bb processes (the noir-projects builds run one per contract under GNU parallel), and distinct contracts can carry byte-identical functions: the simulated account contracts stub out signature verification, sosimulated_schnorr_accountandsimulated_ecdsa_accountproduce the same bytecode hash → the same cache path, wanted simultaneously by two unrelated processes.The failing run's logs show the race firing three times (same VK hash "Generating…" concurrently from two contracts), plus other processes reading entries moments after a different process generated them. Two simultaneous writers on one path — or a reader consuming a half-written file — silently embeds a truncated VK into a contract artifact. Account-contract VKs are exactly what ClientIVC private-kernel recursion verifies, matching the observed deterministic proof-verification failure; the corrupted artifact then flowed into the yarn-project tarball and the aztec-up test image, both content-keyed, freezing the failure for every subsequent run of that tree.
Fix: each cache entry now takes an exclusive
flockon a<entry>.locksidecar for the duration of check-generate-write — one process generates while the rest block, then read the completed entry (generate-once, which is also the economical behavior: previously colliding processes each did the full ChonkComputeVk). Writes go via temp file +rename, so even a reader without the advisory lock (the Windows fallback, whereflockis unavailable) can never observe a partial entry; a lost rename race there is benign and resolved in favor of the completed equivalent entry. The file is already excluded from WASM builds.Why the fixes fix it
Validation
bash -non the bootstrap change.aztec-up-test-image-3747b48e27303118.zst) were separately rebuilt from a from-source local build and force-uploaded; this PR prevents recurrence.