eels: optionally consume pre-staged local fixtures - #2
Conversation
The consume-engine/consume-rlp simulators download the fixtures tarball at image-build time (uv run consume cache --input <url>). With hive's --docker.nocache that re-downloads on every run, which is slow and exposed to transient release-CDN failures. Allow a caller to provide fixtures locally instead: stage a fixtures.tar.gz in the simulator build context and pass --build-arg fixtures=/fixtures. The tarball is extracted in a throwaway build stage and only the extracted dir is copied into the final image, so the tarball never doubles the image size. Backward compatible: with no fixtures.tar.gz staged and fixtures=<release/url> (the default), the optional COPY grabs only the Dockerfile anchor, /fixtures stays empty, and consume downloads exactly as before.
There was a problem hiding this comment.
Pull request overview
This PR updates the EELS consume-engine and consume-rlp simulator images to optionally use pre-staged fixtures from the local Docker build context (instead of always downloading fixtures at image-build time), improving build reliability and avoiding repeated CDN fetches when Hive is run with --docker.nocache.
Changes:
- Refactors both simulator Dockerfiles into multi-stage builds with an optional “fixtures extraction” stage.
- Adds logic to
COPYand extract an optionally-stagedfixtures.tar.gzinto/fixtures, then copies only the extracted directory into the final image. - Moves
uv syncearlier so dependencies are installed before runningconsume cache.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| simulators/ethereum/eels/consume-rlp/Dockerfile | Adds a multi-stage build to optionally extract and bake local fixtures into the image. |
| simulators/ethereum/eels/consume-engine/Dockerfile | Same pattern as consume-rlp, enabling optional local fixtures for the engine simulator. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| # the container starts. | ||
| # If newer version of the fixtures is needed, the image needs to be rebuilt. | ||
| # Use `--docker.nocache` flag to force rebuild. | ||
| RUN uv run consume cache --input "$FIXTURES" |
There was a problem hiding this comment.
Already handled upstream, so no extra Dockerfile guard is needed here. consume cache --input /fixtures resolves the source through FixturesSource.validate_local_path:
if not any(path.glob("**/*.json")):
pytest.exit(f"Specified fixture directory '{path}' does not contain any JSON files.")pytest.exit() with no returncode exits non-zero, so an empty /fixtures fails the image build at this exact step with a clear message — Specified fixture directory '/fixtures' does not contain any JSON files. — rather than a confusing error. That is already the "fail early with a clear message" being suggested.
On the "fall back to remote fixtures" alternative: we specifically don't want that. A silent fallback would mask a fixtures-staging / cache-miss bug and reintroduce the per-run CDN download this change removes. Failing loudly at build time is the intended behavior.
| RUN mkdir -p /fixtures && \ | ||
| if [ -f /staged/fixtures.tar.gz ]; then tar -xzf /staged/fixtures.tar.gz -C /fixtures; fi |
There was a problem hiding this comment.
Good catch — will add --no-same-owner to the extraction. Impact is low in this setup (both the build and the simulator entrypoint run as root, and the tarball comes from our own CI cache rather than untrusted input), but it's cheap and makes extraction deterministic regardless of the uid/gid baked into the archive, so it's worth doing. Leaving --no-same-permissions off — more intrusive with no clear benefit here.
| # If newer version of the fixtures is needed, the image needs to be rebuilt. | ||
| # Use `--docker.nocache` flag to force rebuild. | ||
| RUN uv sync | ||
| RUN uv run consume cache --input "$FIXTURES" |
There was a problem hiding this comment.
Already handled upstream, so no extra Dockerfile guard is needed here. consume cache --input /fixtures resolves the source through FixturesSource.validate_local_path:
if not any(path.glob("**/*.json")):
pytest.exit(f"Specified fixture directory '{path}' does not contain any JSON files.")pytest.exit() with no returncode exits non-zero, so an empty /fixtures fails the image build at this exact step with a clear message — Specified fixture directory '/fixtures' does not contain any JSON files. — rather than a confusing error. That is already the "fail early with a clear message" being suggested.
On the "fall back to remote fixtures" alternative: we specifically don't want that. A silent fallback would mask a fixtures-staging / cache-miss bug and reintroduce the per-run CDN download this change removes. Failing loudly at build time is the intended behavior.
| RUN mkdir -p /fixtures && \ | ||
| if [ -f /staged/fixtures.tar.gz ]; then tar -xzf /staged/fixtures.tar.gz -C /fixtures; fi |
There was a problem hiding this comment.
Good catch — will add --no-same-owner to the extraction. Impact is low in this setup (both the build and the simulator entrypoint run as root, and the tarball comes from our own CI cache rather than untrusted input), but it's cheap and makes extraction deterministic regardless of the uid/gid baked into the archive, so it's worth doing. Leaving --no-same-permissions off — more intrusive with no clear benefit here.
…-fixtures erigontech/hive#2 merged into yperbasis/client-pool; pin to that merge commit (9afe5f15) instead of the now-merged feature branch HEAD.
…run CDN download (erigontech#21882) ## Problem `test-hive-eest`'s `eels` simulators download the fixtures tarball from the GitHub release CDN **inside the image build** (`consume cache --input <url>`). With hive's `--docker.nocache`, that download repeats on **every run** — slow, exposed to transient release-CDN 504s, and bypassing the hardened `tools/test-fixtures.sh` entirely. ## Change Feed the fixtures from cache + a hardened fetch, and hand them to the sim locally: - **Restore** the EEST tarballs from the base-branch cache warmed by `cache-warming-eest-fixtures.yml` — **restore-only** (the warmer owns saving, so no per-ref duplicates). - **Fallback** on a cache miss: the hardened `tools/test-fixtures.sh` (5-min exponential backoff). - **Stage** the matrix tarball into the eels sim build context and pass `--sim.buildarg fixtures=/fixtures`, so the sim's `consume` reads a local directory instead of downloading. The `branch` build-arg (devnet `consume`/execution-specs selection) is unchanged. ## Hive dependency (now merged) The local-fixtures eels Dockerfiles landed in **erigontech/hive#2** (merged into `yperbasis/client-pool`). The hive `ref` is pinned to that merge commit (`9afe5f15`). ## Validation End-to-end verified on a real runner via `workflow_dispatch` (run 27152648786): exact-key **cache hit** (no CDN download), staged tarball picked up by the eels build, and `consume engine` ran the full `paris+shanghai` shard — **3573 tests, 0 failed**. The fixtures-directory layout was also confirmed against `execution-specs`/`consume` locally (it accepts the extracted dir and recursively builds its index).
Why
The
eels/consume-engineandeels/consume-rlpsimulators download the fixtures tarball at image-build time viauv run consume cache --input "$FIXTURES". Because hive is invoked with--docker.nocache, that download repeats on every run, which is slow and exposed to transient GitHub release-CDN failures (e.g. HTTP 504s).What
Let a caller provide the fixtures locally instead of by URL:
fixtures.tar.gzinto the simulator build context and pass--build-arg fixtures=/fixtures.COPY --from'd into the final image, so the tarball never lands in the final image (no size doubling).consumethen reads--input /fixtures(a local directory), which it accepts and recursively indexes — verified againstexecution-specs/consume(FixturesSource.validate_local_path+generate_fixtures_index).Backward compatible
With no
fixtures.tar.gzstaged andfixtures=<release/url>(the default, and how it's invoked today), the optionalCOPYmatches only theDockerfileanchor,/fixturesstays empty,FIXTURESremains the URL/release, andconsumedownloads exactly as before. The new path activates only when both a tarball is staged andfixtures=/fixturesis passed.Consumer
This unblocks erigon's
test-hive-eest.ymlto restore the fixtures from its existingactions/cache(warmed out-of-band) and pass them in locally, removing the per-run CDN download. Onlyconsume-engineandconsume-rlpare changed (the two simulators erigon's matrix uses); the othereelssimulators are untouched and can adopt the same pattern later if needed.