Skip to content

Commit ee7d25a

Browse files
authored
Merge of #7614
2 parents 847fa3f + 8740bcd commit ee7d25a

File tree

6 files changed

+231
-49
lines changed

6 files changed

+231
-49
lines changed
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
name: docker-reproducible
2+
3+
on:
4+
push:
5+
branches:
6+
- unstable
7+
- stable
8+
tags:
9+
- v*
10+
workflow_dispatch: # allows manual triggering for testing purposes and skips publishing an image
11+
12+
env:
13+
DOCKER_REPRODUCIBLE_IMAGE_NAME: >-
14+
${{ github.repository_owner }}/lighthouse-reproducible
15+
DOCKER_PASSWORD: ${{ secrets.DH_KEY }}
16+
DOCKER_USERNAME: ${{ secrets.DH_ORG }}
17+
18+
jobs:
19+
extract-version:
20+
name: extract version
21+
runs-on: ubuntu-22.04
22+
steps:
23+
- name: Extract version
24+
run: |
25+
if [[ "${{ github.ref }}" == refs/tags/* ]]; then
26+
# It's a tag (e.g., v1.2.3)
27+
VERSION="${GITHUB_REF#refs/tags/}"
28+
elif [[ "${{ github.ref }}" == refs/heads/stable ]]; then
29+
# stable branch -> latest
30+
VERSION="latest"
31+
elif [[ "${{ github.ref }}" == refs/heads/unstable ]]; then
32+
# unstable branch -> latest-unstable
33+
VERSION="latest-unstable"
34+
else
35+
# For manual triggers from other branches and will not publish any image
36+
VERSION="test-build"
37+
fi
38+
echo "VERSION=$VERSION" >> $GITHUB_OUTPUT
39+
id: extract_version
40+
outputs:
41+
VERSION: ${{ steps.extract_version.outputs.VERSION }}
42+
43+
verify-and-build:
44+
name: verify reproducibility and build
45+
needs: extract-version
46+
strategy:
47+
matrix:
48+
arch: [amd64, arm64]
49+
include:
50+
- arch: amd64
51+
rust_target: x86_64-unknown-linux-gnu
52+
rust_image: >-
53+
rust:1.88-bullseye@sha256:8e3c421122bf4cd3b2a866af41a4dd52d87ad9e315fd2cb5100e87a7187a9816
54+
platform: linux/amd64
55+
runner: ubuntu-22.04
56+
- arch: arm64
57+
rust_target: aarch64-unknown-linux-gnu
58+
rust_image: >-
59+
rust:1.88-bullseye@sha256:8b22455a7ce2adb1355067638284ee99d21cc516fab63a96c4514beaf370aa94
60+
platform: linux/arm64
61+
runner: ubuntu-22.04-arm
62+
runs-on: ${{ matrix.runner }}
63+
steps:
64+
- uses: actions/checkout@v4
65+
66+
- name: Set up Docker Buildx
67+
uses: docker/setup-buildx-action@v3
68+
with:
69+
driver: docker
70+
71+
- name: Verify reproducible builds (${{ matrix.arch }})
72+
run: |
73+
# Build first image
74+
docker build -f Dockerfile.reproducible \
75+
--platform ${{ matrix.platform }} \
76+
--build-arg RUST_TARGET="${{ matrix.rust_target }}" \
77+
--build-arg RUST_IMAGE="${{ matrix.rust_image }}" \
78+
-t lighthouse-verify-1-${{ matrix.arch }} .
79+
80+
# Extract binary from first build
81+
docker create --name extract-1-${{ matrix.arch }} lighthouse-verify-1-${{ matrix.arch }}
82+
docker cp extract-1-${{ matrix.arch }}:/lighthouse ./lighthouse-1-${{ matrix.arch }}
83+
docker rm extract-1-${{ matrix.arch }}
84+
85+
# Clean state for second build
86+
docker buildx prune -f
87+
docker system prune -f
88+
89+
# Build second image
90+
docker build -f Dockerfile.reproducible \
91+
--platform ${{ matrix.platform }} \
92+
--build-arg RUST_TARGET="${{ matrix.rust_target }}" \
93+
--build-arg RUST_IMAGE="${{ matrix.rust_image }}" \
94+
-t lighthouse-verify-2-${{ matrix.arch }} .
95+
96+
# Extract binary from second build
97+
docker create --name extract-2-${{ matrix.arch }} lighthouse-verify-2-${{ matrix.arch }}
98+
docker cp extract-2-${{ matrix.arch }}:/lighthouse ./lighthouse-2-${{ matrix.arch }}
99+
docker rm extract-2-${{ matrix.arch }}
100+
101+
# Compare binaries
102+
echo "=== Comparing binaries ==="
103+
echo "Build 1 SHA256: $(sha256sum lighthouse-1-${{ matrix.arch }})"
104+
echo "Build 2 SHA256: $(sha256sum lighthouse-2-${{ matrix.arch }})"
105+
106+
if cmp lighthouse-1-${{ matrix.arch }} lighthouse-2-${{ matrix.arch }}; then
107+
echo "Reproducible build verified for ${{ matrix.arch }}"
108+
else
109+
echo "Reproducible build FAILED for ${{ matrix.arch }}"
110+
echo "BLOCKING RELEASE: Builds are not reproducible!"
111+
echo "First 10 differences:"
112+
cmp -l lighthouse-1-${{ matrix.arch }} lighthouse-2-${{ matrix.arch }} | head -10
113+
exit 1
114+
fi
115+
116+
# Clean up verification artifacts but keep one image for publishing
117+
rm -f lighthouse-*-${{ matrix.arch }}
118+
docker rmi lighthouse-verify-1-${{ matrix.arch }} || true
119+
120+
# Re-tag the second image for publishing (we verified it's identical to first)
121+
VERSION=${{ needs.extract-version.outputs.VERSION }}
122+
FINAL_TAG="${{ env.DOCKER_REPRODUCIBLE_IMAGE_NAME }}:${VERSION}-${{ matrix.arch }}"
123+
docker tag lighthouse-verify-2-${{ matrix.arch }} "$FINAL_TAG"
124+
125+
- name: Log in to Docker Hub
126+
if: ${{ github.event_name != 'workflow_dispatch' }}
127+
uses: docker/login-action@v3
128+
with:
129+
username: ${{ env.DOCKER_USERNAME }}
130+
password: ${{ env.DOCKER_PASSWORD }}
131+
132+
- name: Push verified image (${{ matrix.arch }})
133+
if: ${{ github.event_name != 'workflow_dispatch' }}
134+
run: |
135+
VERSION=${{ needs.extract-version.outputs.VERSION }}
136+
IMAGE_TAG="${{ env.DOCKER_REPRODUCIBLE_IMAGE_NAME }}:${VERSION}-${{ matrix.arch }}"
137+
docker push "$IMAGE_TAG"
138+
139+
- name: Clean up local images
140+
run: |
141+
docker rmi lighthouse-verify-2-${{ matrix.arch }} || true
142+
VERSION=${{ needs.extract-version.outputs.VERSION }}
143+
docker rmi "${{ env.DOCKER_REPRODUCIBLE_IMAGE_NAME }}:${VERSION}-${{ matrix.arch }}" || true
144+
145+
- name: Upload verification artifacts (on failure)
146+
if: failure()
147+
uses: actions/upload-artifact@v4
148+
with:
149+
name: verification-failure-${{ matrix.arch }}
150+
path: |
151+
lighthouse-*-${{ matrix.arch }}
152+
153+
create-manifest:
154+
name: create multi-arch manifest
155+
runs-on: ubuntu-22.04
156+
needs: [extract-version, verify-and-build]
157+
if: ${{ github.event_name != 'workflow_dispatch' }}
158+
steps:
159+
- name: Log in to Docker Hub
160+
uses: docker/login-action@v3
161+
with:
162+
username: ${{ env.DOCKER_USERNAME }}
163+
password: ${{ env.DOCKER_PASSWORD }}
164+
165+
- name: Create and push multi-arch manifest
166+
run: |
167+
IMAGE_NAME=${{ env.DOCKER_REPRODUCIBLE_IMAGE_NAME }}
168+
VERSION=${{ needs.extract-version.outputs.VERSION }}
169+
170+
# Create manifest for the version tag
171+
docker manifest create \
172+
${IMAGE_NAME}:${VERSION} \
173+
${IMAGE_NAME}:${VERSION}-amd64 \
174+
${IMAGE_NAME}:${VERSION}-arm64
175+
176+
docker manifest push ${IMAGE_NAME}:${VERSION}

Cargo.toml

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -279,13 +279,6 @@ lto = "fat"
279279
codegen-units = 1
280280
incremental = false
281281

282-
[profile.reproducible]
283-
inherits = "release"
284-
debug = false
285-
panic = "abort"
286-
codegen-units = 1
287-
overflow-checks = true
288-
289282
[profile.release-debug]
290283
inherits = "release"
291284
debug = true

Dockerfile.reproducible

Lines changed: 6 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -3,42 +3,22 @@ ARG RUST_IMAGE="rust:1.88-bullseye@sha256:8e3c421122bf4cd3b2a866af41a4dd52d87ad9
33
FROM ${RUST_IMAGE} AS builder
44

55
# Install specific version of the build dependencies
6-
RUN apt-get update && apt-get install -y libclang-dev=1:11.0-51+nmu5 cmake=3.18.4-2+deb11u1
6+
RUN apt-get update && apt-get install -y libclang-dev=1:11.0-51+nmu5 cmake=3.18.4-2+deb11u1 libjemalloc-dev=5.2.1-3
77

8-
# Add target architecture argument with default value
98
ARG RUST_TARGET="x86_64-unknown-linux-gnu"
109

1110
# Copy the project to the container
12-
COPY . /app
11+
COPY ./ /app
1312
WORKDIR /app
1413

15-
# Get the latest commit timestamp and set SOURCE_DATE_EPOCH (default it to 0 if not passed)
16-
ARG SOURCE_DATE=0
17-
18-
# Set environment variables for reproducibility
19-
ARG RUSTFLAGS="-C link-arg=-Wl,--build-id=none -C metadata='' --remap-path-prefix $(pwd)=."
20-
ENV SOURCE_DATE_EPOCH=$SOURCE_DATE \
21-
CARGO_INCREMENTAL=0 \
22-
LC_ALL=C \
23-
TZ=UTC \
24-
RUSTFLAGS="${RUSTFLAGS}"
25-
26-
# Set the default features if not provided
27-
ARG FEATURES="gnosis,slasher-lmdb,slasher-mdbx,slasher-redb,jemalloc"
28-
29-
# Set the default profile if not provided
30-
ARG PROFILE="reproducible"
31-
3214
# Build the project with the reproducible settings
33-
RUN cargo build --bin lighthouse \
34-
--features "${FEATURES}" \
35-
--profile "${PROFILE}" \
36-
--locked \
37-
--target "${RUST_TARGET}"
15+
RUN make build-reproducible
3816

39-
RUN mv /app/target/${RUST_TARGET}/${PROFILE}/lighthouse /lighthouse
17+
# Move the binary to a standard location
18+
RUN mv /app/target/${RUST_TARGET}/release/lighthouse /lighthouse
4019

4120
# Create a minimal final image with just the binary
4221
FROM gcr.io/distroless/cc-debian12:nonroot-6755e21ccd99ddead6edc8106ba03888cbeed41a
4322
COPY --from=builder /lighthouse /lighthouse
23+
4424
ENTRYPOINT [ "/lighthouse" ]

Makefile

Lines changed: 46 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -81,36 +81,67 @@ build-lcli-aarch64:
8181
build-lcli-riscv64:
8282
cross build --bin lcli --target riscv64gc-unknown-linux-gnu --features "portable" --profile "$(CROSS_PROFILE)" --locked
8383

84-
# extracts the current source date for reproducible builds
85-
SOURCE_DATE := $(shell git log -1 --pretty=%ct)
86-
87-
# Default image for x86_64
84+
# Environment variables for reproducible builds
85+
# Initialize RUSTFLAGS
86+
RUST_BUILD_FLAGS =
87+
# Remove build ID from the binary to ensure reproducibility across builds
88+
RUST_BUILD_FLAGS += -C link-arg=-Wl,--build-id=none
89+
# Remove metadata hash from symbol names to ensure reproducible builds
90+
RUST_BUILD_FLAGS += -C metadata=''
91+
92+
# Set timestamp from last git commit for reproducible builds
93+
SOURCE_DATE ?= $(shell git log -1 --pretty=%ct)
94+
95+
# Disable incremental compilation to avoid non-deterministic artifacts
96+
CARGO_INCREMENTAL_VAL = 0
97+
# Set C locale for consistent string handling and sorting
98+
LOCALE_VAL = C
99+
# Set UTC timezone for consistent time handling across builds
100+
TZ_VAL = UTC
101+
102+
# Features for reproducible builds
103+
FEATURES_REPRODUCIBLE = $(CROSS_FEATURES),jemalloc-unprefixed
104+
105+
# Derive the architecture-specific library path from RUST_TARGET
106+
JEMALLOC_LIB_ARCH = $(word 1,$(subst -, ,$(RUST_TARGET)))
107+
JEMALLOC_OVERRIDE = /usr/lib/$(JEMALLOC_LIB_ARCH)-linux-gnu/libjemalloc.a
108+
109+
# Default target architecture
110+
RUST_TARGET ?= x86_64-unknown-linux-gnu
111+
112+
# Default images for different architectures
88113
RUST_IMAGE_AMD64 ?= rust:1.88-bullseye@sha256:8e3c421122bf4cd3b2a866af41a4dd52d87ad9e315fd2cb5100e87a7187a9816
114+
RUST_IMAGE_ARM64 ?= rust:1.88-bullseye@sha256:8b22455a7ce2adb1355067638284ee99d21cc516fab63a96c4514beaf370aa94
89115

90-
# Reproducible build for x86_64
91-
build-reproducible-x86_64:
116+
.PHONY: build-reproducible
117+
build-reproducible: ## Build the lighthouse binary into `target` directory with reproducible builds
118+
SOURCE_DATE_EPOCH=$(SOURCE_DATE) \
119+
RUSTFLAGS="${RUST_BUILD_FLAGS} --remap-path-prefix $$(pwd)=." \
120+
CARGO_INCREMENTAL=${CARGO_INCREMENTAL_VAL} \
121+
LC_ALL=${LOCALE_VAL} \
122+
TZ=${TZ_VAL} \
123+
JEMALLOC_OVERRIDE=${JEMALLOC_OVERRIDE} \
124+
cargo build --bin lighthouse --features "$(FEATURES_REPRODUCIBLE)" --profile "$(PROFILE)" --locked --target $(RUST_TARGET)
125+
126+
.PHONY: build-reproducible-x86_64
127+
build-reproducible-x86_64: ## Build reproducible x86_64 Docker image
92128
DOCKER_BUILDKIT=1 docker build \
93129
--build-arg RUST_TARGET="x86_64-unknown-linux-gnu" \
94130
--build-arg RUST_IMAGE=$(RUST_IMAGE_AMD64) \
95-
--build-arg SOURCE_DATE=$(SOURCE_DATE) \
96131
-f Dockerfile.reproducible \
97132
-t lighthouse:reproducible-amd64 .
98133

99-
# Default image for arm64
100-
RUST_IMAGE_ARM64 ?= rust:1.88-bullseye@sha256:8b22455a7ce2adb1355067638284ee99d21cc516fab63a96c4514beaf370aa94
101-
102-
# Reproducible build for aarch64
103-
build-reproducible-aarch64:
134+
.PHONY: build-reproducible-aarch64
135+
build-reproducible-aarch64: ## Build reproducible aarch64 Docker image
104136
DOCKER_BUILDKIT=1 docker build \
105137
--platform linux/arm64 \
106138
--build-arg RUST_TARGET="aarch64-unknown-linux-gnu" \
107139
--build-arg RUST_IMAGE=$(RUST_IMAGE_ARM64) \
108-
--build-arg SOURCE_DATE=$(SOURCE_DATE) \
109140
-f Dockerfile.reproducible \
110141
-t lighthouse:reproducible-arm64 .
111142

112-
# Build both architectures
113-
build-reproducible-all: build-reproducible-x86_64 build-reproducible-aarch64
143+
.PHONY: build-reproducible-all
144+
build-reproducible-all: build-reproducible-x86_64 build-reproducible-aarch64 ## Build both x86_64 and aarch64 reproducible Docker images
114145

115146
# Create a `.tar.gz` containing a binary for a specific target.
116147
define tarball_release_binary

common/malloc_utils/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ jemalloc-profiling = ["tikv-jemallocator/profiling"]
2121
# Force the use of system malloc (or glibc) rather than jemalloc.
2222
# This is a no-op on Windows where jemalloc is always disabled.
2323
sysmalloc = []
24+
# Enable jemalloc with unprefixed malloc (recommended for reproducible builds)
25+
jemalloc-unprefixed = ["jemalloc", "tikv-jemallocator/unprefixed_malloc_on_supported_platforms"]
2426

2527
[dependencies]
2628
libc = "0.2.79"

testing/state_transition_vectors/Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,4 @@ test:
55
cargo test --release --features "$(TEST_FEATURES)"
66

77
clean:
8-
rm -r vectors/
8+
rm -rf vectors/

0 commit comments

Comments
 (0)