Skip to content

Commit 743230e

Browse files
authored
Merge pull request #9 from chong-he/repro
Test reproducible
2 parents 93b8f46 + 7ce0066 commit 743230e

File tree

6 files changed

+229
-50
lines changed

6 files changed

+229
-50
lines changed
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
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+
${{ secrets.DH_ORG }}/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+
platform: linux/amd64
53+
runner: ubuntu-22.04
54+
- arch: arm64
55+
rust_target: aarch64-unknown-linux-gnu
56+
platform: linux/arm64
57+
runner: ubuntu-22.04-arm
58+
runs-on: ${{ matrix.runner }}
59+
steps:
60+
- uses: actions/checkout@v4
61+
62+
- name: Set up Docker Buildx
63+
uses: docker/setup-buildx-action@v3
64+
with:
65+
driver: docker
66+
67+
- name: Verify reproducible builds (${{ matrix.arch }})
68+
run: |
69+
# Build first image
70+
docker build -f Dockerfile.reproducible \
71+
--platform ${{ matrix.platform }} \
72+
--build-arg RUST_TARGET="${{ matrix.rust_target }}" \
73+
-t lighthouse-verify-1-${{ matrix.arch }} .
74+
75+
# Extract binary from first build
76+
docker create --name extract-1-${{ matrix.arch }} lighthouse-verify-1-${{ matrix.arch }}
77+
docker cp extract-1-${{ matrix.arch }}:/lighthouse ./lighthouse-1-${{ matrix.arch }}
78+
docker rm extract-1-${{ matrix.arch }}
79+
80+
# Clean state for second build
81+
docker buildx prune -f
82+
docker system prune -f
83+
84+
# Build second image
85+
docker build -f Dockerfile.reproducible \
86+
--platform ${{ matrix.platform }} \
87+
--build-arg RUST_TARGET="${{ matrix.rust_target }}" \
88+
-t lighthouse-verify-2-${{ matrix.arch }} .
89+
90+
# Extract binary from second build
91+
docker create --name extract-2-${{ matrix.arch }} lighthouse-verify-2-${{ matrix.arch }}
92+
docker cp extract-2-${{ matrix.arch }}:/lighthouse ./lighthouse-2-${{ matrix.arch }}
93+
docker rm extract-2-${{ matrix.arch }}
94+
95+
# Compare binaries
96+
echo "=== Comparing binaries ==="
97+
echo "Build 1 SHA256: $(sha256sum lighthouse-1-${{ matrix.arch }})"
98+
echo "Build 2 SHA256: $(sha256sum lighthouse-2-${{ matrix.arch }})"
99+
100+
if cmp lighthouse-1-${{ matrix.arch }} lighthouse-2-${{ matrix.arch }}; then
101+
echo "Reproducible build verified for ${{ matrix.arch }}"
102+
else
103+
echo "Reproducible build FAILED for ${{ matrix.arch }}"
104+
echo "BLOCKING RELEASE: Builds are not reproducible!"
105+
echo "First 10 differences:"
106+
cmp -l lighthouse-1-${{ matrix.arch }} lighthouse-2-${{ matrix.arch }} | head -10
107+
exit 1
108+
fi
109+
110+
# Clean up verification artifacts but keep one image for publishing
111+
rm -f lighthouse-*-${{ matrix.arch }}
112+
docker rmi lighthouse-verify-1-${{ matrix.arch }} || true
113+
114+
# Re-tag the second image for publishing (we verified it's identical to first)
115+
VERSION=${{ needs.extract-version.outputs.VERSION }}
116+
FINAL_TAG="${{ env.DOCKER_REPRODUCIBLE_IMAGE_NAME }}:${VERSION}-${{ matrix.arch }}"
117+
docker tag lighthouse-verify-2-${{ matrix.arch }} "$FINAL_TAG"
118+
119+
- name: Log in to Docker Hub
120+
if: ${{ github.event_name != 'workflow_dispatch' }}
121+
uses: docker/login-action@v3
122+
with:
123+
username: ${{ env.DOCKER_USERNAME }}
124+
password: ${{ env.DOCKER_PASSWORD }}
125+
126+
- name: Push verified image (${{ matrix.arch }})
127+
if: ${{ github.event_name != 'workflow_dispatch' }}
128+
run: |
129+
VERSION=${{ needs.extract-version.outputs.VERSION }}
130+
IMAGE_TAG="${{ env.DOCKER_REPRODUCIBLE_IMAGE_NAME }}:${VERSION}-${{ matrix.arch }}"
131+
docker push "$IMAGE_TAG"
132+
133+
- name: Clean up local images
134+
run: |
135+
docker rmi lighthouse-verify-2-${{ matrix.arch }} || true
136+
VERSION=${{ needs.extract-version.outputs.VERSION }}
137+
docker rmi "${{ env.DOCKER_REPRODUCIBLE_IMAGE_NAME }}:${VERSION}-${{ matrix.arch }}" || true
138+
139+
- name: Upload verification artifacts (on failure)
140+
if: failure()
141+
uses: actions/upload-artifact@v4
142+
with:
143+
name: verification-failure-${{ matrix.arch }}
144+
path: |
145+
lighthouse-*-${{ matrix.arch }}
146+
147+
create-manifest:
148+
name: create multi-arch manifest
149+
runs-on: ubuntu-22.04
150+
needs: [extract-version, verify-and-build]
151+
if: ${{ github.event_name != 'workflow_dispatch' }}
152+
steps:
153+
- name: Log in to Docker Hub
154+
uses: docker/login-action@v3
155+
with:
156+
username: ${{ env.DOCKER_USERNAME }}
157+
password: ${{ env.DOCKER_PASSWORD }}
158+
159+
- name: Create and push multi-arch manifest
160+
run: |
161+
IMAGE_NAME=${{ env.DOCKER_REPRODUCIBLE_IMAGE_NAME }}
162+
VERSION=${{ needs.extract-version.outputs.VERSION }}
163+
164+
# Create manifest for the version tag
165+
docker manifest create \
166+
${IMAGE_NAME}:${VERSION} \
167+
${IMAGE_NAME}:${VERSION}-amd64 \
168+
${IMAGE_NAME}:${VERSION}-arm64
169+
170+
docker manifest push ${IMAGE_NAME}:${VERSION}

Cargo.toml

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -289,13 +289,6 @@ lto = "fat"
289289
codegen-units = 1
290290
incremental = false
291291

292-
[profile.reproducible]
293-
inherits = "release"
294-
debug = false
295-
panic = "abort"
296-
codegen-units = 1
297-
overflow-checks = true
298-
299292
[profile.release-debug]
300293
inherits = "release"
301294
debug = true

Dockerfile.reproducible

Lines changed: 8 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,24 @@
1-
# Define the Rust image as an argument with a default to x86_64 Rust 1.88 image based on Debian Bullseye
2-
ARG RUST_IMAGE="rust:1.88-bullseye@sha256:8e3c421122bf4cd3b2a866af41a4dd52d87ad9e315fd2cb5100e87a7187a9816"
1+
# Define the Rust image as an argument
2+
ARG RUST_IMAGE="rust:1.88.0-bullseye"
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: 48 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -81,36 +81,70 @@ 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)
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+
# Default profile
103+
PROFILE ?= release
104+
105+
# Features for reproducible builds
106+
FEATURES_REPRODUCIBLE = $(CROSS_FEATURES),jemalloc-unprefixed
107+
108+
# Derive the architecture-specific library path from RUST_TARGET
109+
JEMALLOC_LIB_ARCH = $(word 1,$(subst -, ,$(RUST_TARGET)))
110+
JEMALLOC_OVERRIDE = /usr/lib/$(JEMALLOC_LIB_ARCH)-linux-gnu/libjemalloc.a
111+
112+
# Default target architecture
113+
RUST_TARGET ?= x86_64-unknown-linux-gnu
86114

87-
# Default image for x86_64
115+
# Default images for different architectures
88116
RUST_IMAGE_AMD64 ?= rust:1.88-bullseye@sha256:8e3c421122bf4cd3b2a866af41a4dd52d87ad9e315fd2cb5100e87a7187a9816
117+
RUST_IMAGE_ARM64 ?= rust:1.88-bullseye@sha256:8b22455a7ce2adb1355067638284ee99d21cc516fab63a96c4514beaf370aa94
89118

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

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:
137+
.PHONY: build-reproducible-aarch64
138+
build-reproducible-aarch64: ## Build reproducible aarch64 Docker image
104139
DOCKER_BUILDKIT=1 docker build \
105140
--platform linux/arm64 \
106141
--build-arg RUST_TARGET="aarch64-unknown-linux-gnu" \
107142
--build-arg RUST_IMAGE=$(RUST_IMAGE_ARM64) \
108-
--build-arg SOURCE_DATE=$(SOURCE_DATE) \
109143
-f Dockerfile.reproducible \
110144
-t lighthouse:reproducible-arm64 .
111145

112-
# Build both architectures
113-
build-reproducible-all: build-reproducible-x86_64 build-reproducible-aarch64
146+
.PHONY: build-reproducible-all
147+
build-reproducible-all: build-reproducible-x86_64 build-reproducible-aarch64 ## Build both x86_64 and aarch64 reproducible Docker images
114148

115149
# Create a `.tar.gz` containing a binary for a specific target.
116150
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)