From 1fed8c6e604a058a76fe07e459ce7a470e309836 Mon Sep 17 00:00:00 2001
From: bakhtin
Date: Fri, 7 Nov 2025 16:24:28 +0000
Subject: [PATCH] Actually reproducible builds
Signed-off-by: bakhtin
---
.github/workflows/release.yaml | 44 +++++++++----------
.github/workflows/reprotest.yml | 78 +++++++++++++++++++++++++++++++++
Makefile | 35 +++++++++++----
crates/rbuilder/Cargo.toml | 1 +
docker/Dockerfile.reproducible | 18 ++++++++
rust-toolchain.toml | 1 -
6 files changed, 144 insertions(+), 33 deletions(-)
create mode 100644 .github/workflows/reprotest.yml
create mode 100644 docker/Dockerfile.reproducible
diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml
index 5cce5fce0..2e09ce65c 100644
--- a/.github/workflows/release.yaml
+++ b/.github/workflows/release.yaml
@@ -33,7 +33,7 @@ on:
jobs:
extract-version:
name: Extract version
- runs-on: warp-ubuntu-2404-x64-16x
+ runs-on: warp-ubuntu-2404-x64-2x
outputs:
VERSION: ${{ steps.extract_version.outputs.VERSION }}
steps:
@@ -82,62 +82,60 @@ jobs:
- ${{ github.event.inputs.features || '' }}
steps:
- - name: Install dependencies
+ - uses: actions/checkout@v4
+
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v3
+
+ - name: Install rust
run: |
- sudo apt-get update
- sudo apt-get install -y \
- build-essential \
- cmake \
- curl \
- git \
- libclang-dev \
- libssl-dev \
- pkg-config \
- protobuf-compiler
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
- - uses: actions/checkout@v4
-
- - name: Build
+ - name: Build reproducible binary with Docker
run: |
- FEATURES=${{ matrix.features }} make build
- VERSION=${{ needs.extract-version.outputs.VERSION }} make build-deb
+ RUST_TOOLCHAIN=$(rustc --version | cut -d' ' -f2)
+ docker build \
+ --build-arg "RUST_TOOLCHAIN=${RUST_TOOLCHAIN}" \
+ --build-arg "FEATURES=${{ matrix.features }}" \
+ --build-arg "VERSION=${{ needs.extract-version.outputs.VERSION }}" \
+ -f docker/Dockerfile.reproducible -t rbuilder:release \
+ --output type=local,dest=./target .
- name: Upload rbuilder artifact
uses: actions/upload-artifact@v4
with:
name: rbuilder-${{ needs.extract-version.outputs.VERSION }}-${{ matrix.configs.target }}${{ matrix.features && '-' }}${{ matrix.features }}
- path: target/${{ matrix.configs.target }}/${{ matrix.configs.profile }}/rbuilder
+ path: target/${{ matrix.configs.profile }}/rbuilder
- name: Upload rbuilder-operator artifact
uses: actions/upload-artifact@v4
with:
name: rbuilder-operator-${{ needs.extract-version.outputs.VERSION }}-${{ matrix.configs.target }}${{ matrix.features && '-' }}${{ matrix.features }}
- path: target/${{ matrix.configs.target }}/${{ matrix.configs.profile }}/rbuilder-operator
+ path: target/${{ matrix.configs.profile }}/rbuilder-operator
- name: Upload reth-rbuilder artifact
uses: actions/upload-artifact@v4
with:
name: reth-rbuilder-${{ needs.extract-version.outputs.VERSION }}-${{ matrix.configs.target }}${{ matrix.features && '-' }}${{ matrix.features }}
- path: target/${{ matrix.configs.target }}/${{ matrix.configs.profile }}/reth-rbuilder
+ path: target/${{ matrix.configs.profile }}/reth-rbuilder
- name: Upload bid-scraper artifact
uses: actions/upload-artifact@v4
with:
name: bid-scraper-${{ needs.extract-version.outputs.VERSION }}-${{ matrix.configs.target }}${{ matrix.features && '-' }}${{ matrix.features }}
- path: target/${{ matrix.configs.target }}/${{ matrix.configs.profile }}/bid-scraper
+ path: target/${{ matrix.configs.profile }}/bid-scraper
- name: Upload rbuilder-rebalancer artifact
uses: actions/upload-artifact@v4
with:
name: rbuilder-rebalancer-${{ needs.extract-version.outputs.VERSION }}-${{ matrix.configs.target }}${{ matrix.features && '-' }}${{ matrix.features }}
- path: target/${{ matrix.configs.target }}/${{ matrix.configs.profile }}/rbuilder-rebalancer
+ path: target/${{ matrix.configs.profile }}/rbuilder-rebalancer
- name: Upload *.deb packages
uses: actions/upload-artifact@v4
with:
name: deb-${{ needs.extract-version.outputs.VERSION }}-${{ matrix.configs.target }}${{ matrix.features && '-' }}${{ matrix.features }}
- path: target/${{ matrix.configs.target }}/debian/*.deb
+ path: target/debian/*.deb
draft-release:
diff --git a/.github/workflows/reprotest.yml b/.github/workflows/reprotest.yml
new file mode 100644
index 000000000..36cd2ef7d
--- /dev/null
+++ b/.github/workflows/reprotest.yml
@@ -0,0 +1,78 @@
+name: reproducible-build-test
+
+on:
+ workflow_dispatch: {}
+ schedule:
+ - cron: "0 1 */2 * *"
+
+jobs:
+ build:
+ name: build reproducible binaries
+ runs-on: ${{ matrix.runner }}
+ strategy:
+ matrix:
+ include:
+ - runner: warp-ubuntu-2404-x64-32x
+ machine: machine-1
+ - runner: warp-ubuntu-2204-x64-32x
+ machine: machine-2
+ steps:
+ - uses: actions/checkout@v5
+
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v3
+
+ - name: Install rust
+ run: |
+ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
+
+ - name: Build reproducible binary with Docker
+ run: |
+ RUST_TOOLCHAIN=$(rustc --version | cut -d' ' -f2)
+ docker build \
+ --build-arg "RUST_TOOLCHAIN=${RUST_TOOLCHAIN}" \
+ -f docker/Dockerfile.reproducible -t rbuilder:release \
+ --output type=local,dest=./target .
+
+ - name: Calculate SHA256
+ id: sha256
+ run: |
+ sha256sum target/reproducible/{rbuilder-operator,rbuilder-rebalancer} > checksums.sha256
+ echo "Binaries SHA256 on ${{ matrix.machine }}: $(cat checksums.sha256)"
+ - name: Upload the hash
+ uses: actions/upload-artifact@v4
+ with:
+ name: checksums-${{ matrix.machine }}
+ path: |
+ checksums.sha256
+ retention-days: 1
+
+ compare:
+ name: compare reproducible binaries
+ needs: build
+ runs-on: ubuntu-latest
+ steps:
+ - name: Download artifacts from machine-1
+ uses: actions/download-artifact@v4
+ with:
+ name: checksums-machine-1
+ path: machine-1/
+ - name: Download artifacts from machine-2
+ uses: actions/download-artifact@v4
+ with:
+ name: checksums-machine-2
+ path: machine-2/
+ - name: Compare SHA256 hashes
+ run: |
+ echo "=== SHA256 Comparison ==="
+ echo "Machine 1 hashes:"
+ cat machine-1/checksums.sha256
+ echo "Machine 2 hashes:"
+ cat machine-2/checksums.sha256
+
+ if cmp -s machine-1/checksums.sha256 machine-2/checksums.sha256; then
+ echo "✅ SUCCESS: Binaries are identical (reproducible build verified)"
+ else
+ echo "❌ FAILURE: Binaries differ (reproducible build failed)"
+ exit 1
+ fi
diff --git a/Makefile b/Makefile
index 3193229ee..ecb9a382c 100644
--- a/Makefile
+++ b/Makefile
@@ -61,7 +61,8 @@ ifeq ($(IS_X86_64),1)
BUILD_ENV = SOURCE_DATE_EPOCH=$(SOURCE_DATE) \
RUSTFLAGS="${RUST_BUILD_FLAGS}" \
LC_ALL=${LOCALE_VAL} \
- TZ=${TZ_VAL}
+ TZ=${TZ_VAL} \
+ JEMALLOC_OVERRIDE=/usr/lib/x86_64-linux-gnu/libjemalloc.a
else
# Non-x86_64: Use release profile without reproducible build flags
BUILD_PROFILE = release
@@ -72,7 +73,7 @@ endif
.PHONY: build
build: ## Build (release version)
- $(BUILD_ENV) cargo build --features "$(FEATURES)" --locked $(if $(BUILD_TARGET),--target $(BUILD_TARGET)) --profile $(BUILD_PROFILE) --workspace
+ $(BUILD_ENV) cargo build --features "$(FEATURES) jemalloc-unprefixed" --locked $(if $(BUILD_TARGET),--target $(BUILD_TARGET)) --profile $(BUILD_PROFILE) --workspace
.PHONY: build-bid-scraper
build-bid-scraper: ## Build the bid-scraper binary (release version)
@@ -80,11 +81,11 @@ build-bid-scraper: ## Build the bid-scraper binary (release version)
.PHONY: build-rbuilder-operator
build-rbuilder-operator: ## Build the rbuilder-operator binary (release version)
- $(BUILD_ENV) cargo build --features "$(FEATURES)" --locked $(if $(BUILD_TARGET),--target $(BUILD_TARGET)) --bin rbuilder-operator --profile $(BUILD_PROFILE)
+ $(BUILD_ENV) cargo build --features "$(FEATURES) jemalloc-unprefixed" --locked $(if $(BUILD_TARGET),--target $(BUILD_TARGET)) --bin rbuilder-operator --profile $(BUILD_PROFILE)
.PHONY: build-rbuilder-rebalancer
build-rbuilder-rebalancer: ## Build the rbuilder-rebalancer binary (release version)
- $(BUILD_ENV) cargo build --features "$(FEATURES)" --locked $(if $(BUILD_TARGET),--target $(BUILD_TARGET)) --bin rbuilder-rebalancer --profile $(BUILD_PROFILE)
+ $(BUILD_ENV) cargo build --features "$(FEATURES) jemalloc-unprefixed" --locked $(if $(BUILD_TARGET),--target $(BUILD_TARGET)) --bin rbuilder-rebalancer --profile $(BUILD_PROFILE)
.PHONY: build-dev
build-dev: ## Build (debug version)
@@ -94,8 +95,9 @@ build-dev: ## Build (debug version)
docker-image-rbuilder: ## Build a rbuilder Docker image
docker build --platform linux/amd64 --target rbuilder-runtime --build-arg FEATURES="$(FEATURES)" -t rbuilder -f docker/Dockerfile.rbuilder .
-.PHONE: docker-image-rbuilder-operator
- docker build --platform linux/amd64 --target rbuilder-runtime --build-arg FEATURES="$(FEATURES)" -t rbuilder -f docker/Dockerfile.rbuilder-operator .
+.PHONY: docker-image-rbuilder-operator
+docker-image-rbuilder-operator: ## Build a rbuilder-operator Docker image
+ docker build --platform linux/amd64 --target rbuilder-runtime --build-arg FEATURES="$(FEATURES) jemalloc-unprefixed" -t rbuilder-operator -f docker/Dockerfile.rbuilder-operator .
.PHONY: docker-image-test-relay
docker-image-test-relay: ## Build a test relay Docker image
@@ -103,26 +105,41 @@ docker-image-test-relay: ## Build a test relay Docker image
##@ Debian Packages
+# Define binary paths for smart dependencies
+BID_SCRAPER_BIN := target/$(if $(BUILD_TARGET),$(BUILD_TARGET)/)$(BUILD_PROFILE)/bid-scraper
+RBUILDER_OPERATOR_BIN := target/$(if $(BUILD_TARGET),$(BUILD_TARGET)/)$(BUILD_PROFILE)/rbuilder-operator
+RBUILDER_REBALANCER_BIN := target/$(if $(BUILD_TARGET),$(BUILD_TARGET)/)$(BUILD_PROFILE)/rbuilder-rebalancer
+
.PHONY: install-cargo-deb
install-cargo-deb:
@command -v cargo-deb >/dev/null 2>&1 || cargo install cargo-deb@3.6.0 --locked
+# Build individual binaries only if they don't exist - delegate to existing build targets
+$(BID_SCRAPER_BIN): build-bid-scraper
+ @# Binary built by build-bid-scraper target
+
+$(RBUILDER_OPERATOR_BIN): build-rbuilder-operator
+ @# Binary built by build-rbuilder-operator target
+
+$(RBUILDER_REBALANCER_BIN): build-rbuilder-rebalancer
+ @# Binary built by build-rbuilder-rebalancer target
+
.PHONY: build-deb-bid-scraper
-build-deb-bid-scraper: install-cargo-deb build-bid-scraper ## Build bid-scraper Debian package
+build-deb-bid-scraper: install-cargo-deb $(BID_SCRAPER_BIN) ## Build bid-scraper Debian package
cargo deb --profile $(BUILD_PROFILE) --no-build --no-dbgsym --no-strip \
-p bid-scraper \
$(if $(BUILD_TARGET),--target $(BUILD_TARGET)) \
$(if $(VERSION),--deb-version "1~$(VERSION)")
.PHONY: build-deb-rbuilder-operator
-build-deb-rbuilder-operator: install-cargo-deb build-rbuilder-operator ## Build rbuilder-operator Debian package
+build-deb-rbuilder-operator: install-cargo-deb $(RBUILDER_OPERATOR_BIN) ## Build rbuilder-operator Debian package
cargo deb --profile $(BUILD_PROFILE) --no-build --no-dbgsym --no-strip \
-p rbuilder-operator \
$(if $(BUILD_TARGET),--target $(BUILD_TARGET)) \
$(if $(VERSION),--deb-version "1~$(VERSION)")
.PHONY: build-deb-rbuilder-rebalancer
-build-deb-rbuilder-rebalancer: install-cargo-deb build-rbuilder-rebalancer ## Build rbuilder-rebalancer Debian package
+build-deb-rbuilder-rebalancer: install-cargo-deb $(RBUILDER_REBALANCER_BIN) ## Build rbuilder-rebalancer Debian package
cargo deb --profile $(BUILD_PROFILE) --no-build --no-dbgsym --no-strip \
-p rbuilder-rebalancer \
$(if $(BUILD_TARGET),--target $(BUILD_TARGET)) \
diff --git a/crates/rbuilder/Cargo.toml b/crates/rbuilder/Cargo.toml
index 011e5f61d..843c3d282 100644
--- a/crates/rbuilder/Cargo.toml
+++ b/crates/rbuilder/Cargo.toml
@@ -154,6 +154,7 @@ criterion = { workspace = true, features = ["html_reports", "async_tokio"] }
# TODO: remove?
optimism = []
redact-sensitive = []
+jemalloc-unprefixed = ["tikv-jemallocator/unprefixed_malloc_on_supported_platforms"]
[[bench]]
name = "bench_main"
diff --git a/docker/Dockerfile.reproducible b/docker/Dockerfile.reproducible
new file mode 100644
index 000000000..54f513332
--- /dev/null
+++ b/docker/Dockerfile.reproducible
@@ -0,0 +1,18 @@
+ARG RUST_TOOLCHAIN=1.89.0
+FROM docker.io/rust:$RUST_TOOLCHAIN-trixie AS builder
+
+ARG FEATURES VERSION
+# Switch to snapshot repository
+RUN sed -i '/^# http/{N;s|^# \(http[^ ]*\)\nURIs: .*|# \1\nURIs: \1|}' /etc/apt/sources.list.d/debian.sources
+RUN apt-get -o Acquire::Check-Valid-Until=false update && \
+ apt-get install -y \
+ libjemalloc-dev \
+ libclang-dev \
+ protobuf-compiler \
+ cmake
+WORKDIR /build
+COPY . .
+RUN SOURCE_DATE=1730000000 make build && make build-deb
+
+FROM scratch AS artifacts
+COPY --from=builder /build/target/x86_64-unknown-linux-gnu/ /
diff --git a/rust-toolchain.toml b/rust-toolchain.toml
index b1fa8153a..73cb934de 100644
--- a/rust-toolchain.toml
+++ b/rust-toolchain.toml
@@ -1,4 +1,3 @@
[toolchain]
channel = "stable"
-version = "1.88.0"
components = ["rustfmt", "clippy"]