diff --git a/.dockerignore b/.dockerignore index 1dc94b7a25f..bba9deb506c 100644 --- a/.dockerignore +++ b/.dockerignore @@ -4,6 +4,14 @@ **/tmp/ +# Heavy test/data assets not needed by the build (saves ~1.4 GB context). +.git/ +fixtures/blockchain/ +fixtures/blobs/ +fixtures/cache/ +fixtures/rsp/ +fixtures/hive/ + # These are backup files generated by rustfmt **/*.rs.bk @@ -20,6 +28,7 @@ docs/ hive/ ethereum-package/ tooling/ef_tests/blockchain/vectors +tooling/ef_tests/blockchain/vectors_zkevm tooling/ef_tests/state/vectors tooling/sync/multisync_logs/ dev_ethrex_l1/ diff --git a/.github/actions/build-docker/action.yml b/.github/actions/build-docker/action.yml index 166f91e4b64..607a58d09e6 100644 --- a/.github/actions/build-docker/action.yml +++ b/.github/actions/build-docker/action.yml @@ -116,7 +116,11 @@ runs: type=registry,ref=${{ inputs.registry }}/${{ github.repository }}:cache-${{ steps.cache-config.outputs.cache_scope }}-${{ inputs.variant }}-${{ steps.cache-config.outputs.platform_safe }} type=registry,ref=${{ inputs.registry }}/${{ github.repository }}:cache-main-${{ inputs.variant }}-${{ steps.cache-config.outputs.platform_safe }} cache-to: ${{ inputs.cache_write == 'true' && format('type=registry,ref={0}/{1}:cache-{2}-{3}-{4},mode=max', inputs.registry, github.repository, steps.cache-config.outputs.cache_scope, inputs.variant, steps.cache-config.outputs.platform_safe) || format('type=gha,mode=max,scope={0}-{1}-{2}', steps.cache-config.outputs.cache_scope, inputs.variant, steps.cache-config.outputs.platform_safe) }} - build-args: ${{ inputs.build_args }} + build-args: | + ${{ inputs.build_args }} + GIT_SHA=${{ github.sha }} + GIT_BRANCH=${{ github.head_ref || github.ref_name }} + VERSION=${{ github.ref_type == 'tag' && github.ref_name || format('dev-{0}', github.sha) }} platforms: ${{ inputs.platforms }} # Since we're exporting the image as a tar, we need to load it manually as well diff --git a/.github/actions/snapsync-run/action.yml b/.github/actions/snapsync-run/action.yml index d4eb440813c..24b08af6cc7 100644 --- a/.github/actions/snapsync-run/action.yml +++ b/.github/actions/snapsync-run/action.yml @@ -50,8 +50,14 @@ runs: IMAGE_TAG: ${{ inputs.ethrex_tag }} run: | echo "Building ethrex with profile: ${BUILD_PROFILE}" + GIT_SHA=$(git rev-parse HEAD 2>/dev/null || echo unknown) + GIT_BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo unknown) + VERSION=$(git describe --tags --always --dirty 2>/dev/null || echo dev) docker build \ --build-arg PROFILE="${BUILD_PROFILE}" \ + --build-arg GIT_SHA="${GIT_SHA}" \ + --build-arg GIT_BRANCH="${GIT_BRANCH}" \ + --build-arg VERSION="${VERSION}" \ -t ethrex-local:${IMAGE_TAG} \ -f Dockerfile . diff --git a/Dockerfile b/Dockerfile index 20be01ed9f3..cf26b137d57 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,100 +1,143 @@ -FROM rust:1.91 AS chef - -RUN apt-get update && apt-get install -y \ - build-essential \ - libclang-dev \ - libc6 \ - libssl-dev \ - ca-certificates \ - && rm -rf /var/lib/apt/lists/* -RUN cargo install cargo-chef +# syntax=docker/dockerfile:1.10 + +# --- Chef base --- +# Slim rust image + apt deps needed to compile native crates (rocksdb, openssl-sys, bindgen). +FROM rust:1.91-slim-bookworm AS chef + +RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ + --mount=type=cache,target=/var/lib/apt,sharing=locked \ + apt-get update && apt-get install -y --no-install-recommends \ + build-essential \ + libclang-dev \ + libssl-dev \ + pkg-config \ + ca-certificates \ + curl \ + git + +# Force cargo to fetch git deps via the git CLI instead of libgit2. The bundled +# libgit2 hangs on some hosts/networks inside containers; the CLI also supports +# single-commit fetches for rev-pinned deps. +ENV CARGO_NET_GIT_FETCH_WITH_CLI=true + +# Install cargo-chef via prebuilt binary (cargo-binstall) โ€” avoids ~2 min source build. +# cargo-binstall pinned for reproducibility; bump deliberately. +ARG CARGO_BINSTALL_VERSION=v1.19.1 +RUN --mount=type=cache,target=/usr/local/cargo/registry \ + --mount=type=cache,target=/usr/local/cargo/git \ + curl -fsSL https://github.com/cargo-bins/cargo-binstall/releases/download/${CARGO_BINSTALL_VERSION}/cargo-binstall-$(uname -m)-unknown-linux-musl.tgz \ + | tar -xz -C /usr/local/cargo/bin \ + && cargo binstall --no-confirm cargo-chef WORKDIR /ethrex -# --- Planner Stage --- -# Copy all source code to calculate the dependency recipe. -# This layer is fast and will be invalidated on any source change. +# --- Planner --- +# Compute the dependency recipe. Fast, invalidated on any source change. FROM chef AS planner -COPY benches ./benches -COPY crates ./crates -COPY metrics ./metrics -COPY cmd ./cmd -COPY test ./test -COPY tooling ./tooling -COPY Cargo.* . -COPY .cargo/ ./.cargo +COPY --link benches ./benches +COPY --link crates ./crates +COPY --link metrics ./metrics +COPY --link cmd ./cmd +COPY --link test ./test +COPY --link tooling/repl ./tooling/repl +COPY --link tooling/monitor ./tooling/monitor +COPY --link Cargo.toml Cargo.lock ./ +COPY --link .cargo ./.cargo RUN cargo chef prepare --recipe-path recipe.json -# --- Builder Stage --- -# Build the dependencies. This is the most time-consuming step. -# This layer will be cached and only re-run if the recipe.json from the -# previous stage has changed, which only happens when dependencies change. +# --- Builder --- +# Cook deps first (cached unless recipe.json changes), then build the app. FROM chef AS builder -# Build configuration -# PROFILE: Cargo profile to use (release, release-with-debug-assertions, etc.) -# BUILD_FLAGS: Additional cargo flags (features, etc.) -ARG PROFILE="release" +ARG PROFILE=release ARG BUILD_FLAGS="" - -COPY --from=planner /ethrex/recipe.json recipe.json -RUN cargo chef cook --release --recipe-path recipe.json $BUILD_FLAGS - -RUN if [ "$(uname -m)" = aarch64 ]; \ - then \ - SOLC_URL=https://github.com/ethereum/solidity/releases/download/v0.8.31/solc-static-linux-arm;\ - else \ - SOLC_URL=https://github.com/ethereum/solidity/releases/download/v0.8.31/solc-static-linux; \ - fi \ - && curl -L -o /usr/bin/solc $SOLC_URL \ +ARG TARGETARCH + +# vergen-git2 reads .git unless these env vars are set. Pass via build args +# so we don't ship the 1 GB .git directory into the build context. +ARG GIT_BRANCH=unknown +ARG GIT_SHA=unknown +ENV VERGEN_GIT_BRANCH=$GIT_BRANCH \ + VERGEN_GIT_SHA=$GIT_SHA \ + VERGEN_IDEMPOTENT=1 + +COPY --from=planner --link /ethrex/recipe.json recipe.json + +RUN --mount=type=cache,target=/usr/local/cargo/registry \ + --mount=type=cache,target=/usr/local/cargo/git \ + --mount=type=cache,target=/ethrex/target,id=ethrex-target-${TARGETARCH} \ + cargo chef cook --profile $PROFILE --recipe-path recipe.json $BUILD_FLAGS + +# Fetch solc using buildx's TARGETARCH (no shell uname). +RUN case "$TARGETARCH" in \ + arm64) SOLC_URL=https://github.com/ethereum/solidity/releases/download/v0.8.31/solc-static-linux-arm ;; \ + amd64) SOLC_URL=https://github.com/ethereum/solidity/releases/download/v0.8.31/solc-static-linux ;; \ + *) echo "unsupported TARGETARCH=$TARGETARCH" >&2; exit 1 ;; \ + esac \ + && curl -fsSL -o /usr/bin/solc "$SOLC_URL" \ && chmod +x /usr/bin/solc -COPY benches ./benches -COPY crates ./crates -COPY cmd ./cmd -COPY metrics ./metrics -COPY tooling ./tooling -COPY fixtures/genesis ./fixtures/genesis -COPY .git ./.git -COPY Cargo.* ./ -COPY fixtures ./fixtures -COPY .cargo/ ./.cargo +COPY --link benches ./benches +COPY --link crates ./crates +COPY --link cmd ./cmd +COPY --link metrics ./metrics +COPY --link test ./test +COPY --link tooling/repl ./tooling/repl +COPY --link tooling/monitor ./tooling/monitor +COPY --link Cargo.toml Cargo.lock ./ +COPY --link .cargo ./.cargo +# Only these subdirs are referenced by include_str!/include_bytes! in workspace +# crates; the rest of fixtures/ is test data not needed at build time. +COPY --link fixtures/genesis ./fixtures/genesis +COPY --link fixtures/keys ./fixtures/keys ENV COMPILE_CONTRACTS=true -RUN cargo build --profile $PROFILE $BUILD_FLAGS +# Combine build + extract in one RUN so the target cache mount is still mounted. +RUN --mount=type=cache,target=/usr/local/cargo/registry \ + --mount=type=cache,target=/usr/local/cargo/git \ + --mount=type=cache,target=/ethrex/target,id=ethrex-target-${TARGETARCH} \ + cargo build --profile $PROFILE $BUILD_FLAGS \ + && mkdir -p /ethrex/bin \ + && cp /ethrex/target/${PROFILE}/ethrex /ethrex/bin/ethrex -RUN mkdir -p /ethrex/bin && \ - cp /ethrex/target/${PROFILE}/ethrex /ethrex/bin/ethrex -# --- Final Image --- -# Copy the ethrex binary into a minimalist image to reduce bloat size. -# This image must have glibc and libssl +# --- Runtime --- +# ubuntu:24.04 keeps glibc + libssl3 available. Network genesis/bootnodes are +# embedded into the binary via include_str!, so no extra files are needed. FROM ubuntu:24.04 -WORKDIR /usr/local/bin -RUN apt-get update && apt-get install -y --no-install-recommends libssl3 +ARG GIT_SHA=unknown +ARG VERSION=dev + +LABEL org.opencontainers.image.title="ethrex" \ + org.opencontainers.image.description="Rust Ethereum execution client" \ + org.opencontainers.image.source="https://github.com/lambdaclass/ethrex" \ + org.opencontainers.image.licenses="MIT OR Apache-2.0" \ + org.opencontainers.image.revision="${GIT_SHA}" \ + org.opencontainers.image.version="${VERSION}" -COPY cmd/ethrex/networks ./cmd/ethrex/networks -COPY --from=builder /ethrex/bin/ethrex . +RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ + --mount=type=cache,target=/var/lib/apt,sharing=locked \ + apt-get update && apt-get install -y --no-install-recommends \ + libssl3 \ + ca-certificates + +WORKDIR /usr/local/bin + +COPY --from=builder --link /ethrex/bin/ethrex /usr/local/bin/ethrex # Common ports: # - 8545: RPC # - 8551: EngineAPI -# - 30303: Discovery +# - 30303: Discovery (tcp+udp) # - 9090: Metrics # - 1729: L2 RPC # - 3900: L2 Proof Coordinator -EXPOSE 8545 -EXPOSE 8551 -EXPOSE 30303/tcp -EXPOSE 30303/udp -EXPOSE 9090 -EXPOSE 1729 -EXPOSE 3900 - -ENTRYPOINT [ "./ethrex" ] +EXPOSE 8545 8551 9090 1729 3900 30303/tcp 30303/udp + +ENTRYPOINT ["ethrex"] diff --git a/Makefile b/Makefile index 2e5e759a912..e6d8100a271 100644 --- a/Makefile +++ b/Makefile @@ -42,15 +42,29 @@ clean: clean-vectors ## ๐Ÿงน Remove build artifacts cargo clean rm -rf hive +# Docker image tag (override with `make build-image TAG=foo`). +TAG ?= local +IMAGE := ethrex:$(TAG) + +# Git metadata baked into the image via Dockerfile ARGs. Falls back to "unknown" +# / "dev" if not in a git checkout. +GIT_SHA := $(shell git rev-parse HEAD 2>/dev/null || echo unknown) +GIT_BRANCH := $(shell git rev-parse --abbrev-ref HEAD 2>/dev/null || echo unknown) +VERSION := $(shell git describe --tags --always --dirty 2>/dev/null || echo dev) + STAMP_FILE := .docker_build_stamp -$(STAMP_FILE): $(shell find crates cmd -type f -name '*.rs') Cargo.toml Dockerfile - docker build -t ethrex:local . +$(STAMP_FILE): $(shell find crates cmd tooling/repl tooling/monitor -type f -name '*.rs') Cargo.toml Dockerfile + docker build \ + --build-arg GIT_SHA=$(GIT_SHA) \ + --build-arg GIT_BRANCH=$(GIT_BRANCH) \ + --build-arg VERSION=$(VERSION) \ + -t $(IMAGE) . touch $(STAMP_FILE) -build-image: $(STAMP_FILE) ## ๐Ÿณ Build the Docker image +build-image: $(STAMP_FILE) ## ๐Ÿณ Build the Docker image (override tag with TAG=foo) run-image: build-image ## ๐Ÿƒ Run the Docker image - docker run --rm -p 127.0.0.1:8545:8545 ethrex:main --http.addr 0.0.0.0 + docker run --rm -p 127.0.0.1:8545:8545 $(IMAGE) --http.addr 0.0.0.0 dev: ## ๐Ÿƒ Run the ethrex client in DEV_MODE with the InMemory Engine cargo run $(PROFILING_CFG) --release -- \ @@ -84,7 +98,7 @@ localnet: build-image checkout-ethereum-package ## ๐ŸŒ Start kurtosis network trap 'printf "\nStopping localnet...\n"; $(MAKE) stop-localnet || true; exit 0' INT TERM HUP QUIT; \ cp metrics/provisioning/grafana/dashboards/common_dashboards/ethrex_l1_perf.json ethereum-package/src/grafana/ethrex_l1_perf.json; \ kurtosis run --enclave $(ENCLAVE) ethereum-package --args-file $(KURTOSIS_CONFIG_FILE); \ - CID=$$(docker ps -q --filter ancestor=ethrex:local | head -n1); \ + CID=$$(docker ps -q --filter ancestor=$(IMAGE) | head -n1); \ if [ -n "$$CID" ]; then docker logs -f $$CID || true; else echo "No ethrex container found; skipping logs."; fi stop-localnet: ## ๐Ÿ›‘ Stop local network diff --git a/tooling/sync/docker_monitor.py b/tooling/sync/docker_monitor.py index fc4be68c06e..687b1b707a9 100644 --- a/tooling/sync/docker_monitor.py +++ b/tooling/sync/docker_monitor.py @@ -386,11 +386,29 @@ def build_docker_image(profile: str, image_tag: str, ethrex_dir: str) -> bool: """ print(f"๐Ÿ”จ Building Docker image with profile '{profile}'...") print(f" Image tag: {image_tag}") + + def _git(args: list[str], default: str) -> str: + try: + out = subprocess.run( + ["git", "-C", ethrex_dir, *args], + capture_output=True, text=True, check=True, + ).stdout.strip() + return out or default + except (subprocess.CalledProcessError, FileNotFoundError): + return default + + git_sha = _git(["rev-parse", "HEAD"], "unknown") + git_branch = _git(["rev-parse", "--abbrev-ref", "HEAD"], "unknown") + version = _git(["describe", "--tags", "--always", "--dirty"], "dev") + try: subprocess.run( [ "docker", "build", "--build-arg", f"PROFILE={profile}", + "--build-arg", f"GIT_SHA={git_sha}", + "--build-arg", f"GIT_BRANCH={git_branch}", + "--build-arg", f"VERSION={version}", "-t", image_tag, "-f", f"{ethrex_dir}/Dockerfile", ethrex_dir