Add ere-dockerized (#75)

This commit is contained in:
Han
2025-08-02 19:21:52 +08:00
committed by GitHub
parent 1585a77405
commit 42e7c6c416
42 changed files with 1365 additions and 941 deletions

16
docker/cli/Dockerfile Normal file
View File

@@ -0,0 +1,16 @@
ARG BASE_ZKVM_IMAGE_TAG=ere-base-zkvm:latest
FROM ${BASE_ZKVM_IMAGE_TAG}
COPY . /ere
WORKDIR /ere
ARG ZKVM
RUN cargo build --release --package ere-cli --bin ere-cli --features ${ZKVM} && \
cp /ere/target/release/ere-cli /ere/ere-cli && \
cargo clean && \
rm -rf $CARGO_HOME/registry/src $CARGO_HOME/registry/cache
ENTRYPOINT ["/ere/ere-cli"]

View File

@@ -1,5 +1,6 @@
ARG BASE_IMAGE_TAG=latest
FROM ere-base:${BASE_IMAGE_TAG}
ARG BASE_IMAGE_TAG=ere-base:latest
FROM ${BASE_IMAGE_TAG}
# The ere-base image provides Rust, Cargo (with a default nightly), and common tools.
# We operate as root for SDK installation.
@@ -17,4 +18,4 @@ RUN /tmp/install_jolt_sdk.sh && rm /tmp/install_jolt_sdk.sh # Clean up the scrip
# Verify jolt CLI is accessible.
RUN jolt --version
CMD ["/bin/bash"]
CMD ["/bin/bash"]

View File

@@ -1,5 +1,6 @@
ARG BASE_IMAGE_TAG=latest
FROM ere-base:${BASE_IMAGE_TAG}
ARG BASE_IMAGE_TAG=ere-base:latest
FROM ${BASE_IMAGE_TAG}
# The ere-base image provides Rust, Cargo, and common tools.
# We operate as root for SDK installation.
@@ -20,21 +21,10 @@ RUN /tmp/install_nexus_sdk.sh && rm /tmp/install_nexus_sdk.sh # Clean up the scr
# Define the Nexus toolchain for convenience in subsequent commands if needed, though cargo-nexus should use it.
ENV NEXUS_TOOLCHAIN_VERSION="nightly-2025-06-05"
# Set default toolchain
RUN rustup default "$NEXUS_TOOLCHAIN_VERSION"
# Verify Nexus installation
RUN echo "Verifying Nexus installation in Dockerfile (post-script)..." && cargo-nexus --version
# Copy the entire ere project context
# The WORKDIR is /app from the base image
WORKDIR /app
COPY . .
# Build
RUN echo "Build tests for ere-nexus library..." && \
cargo build --tests --release -p ere-nexus
# Run tests
RUN echo "Running tests for ere-nexus library..." && \
cargo test --release -p ere-nexus --lib -- --color always && \
echo "Running Nexus tests Success..."
CMD ["/bin/bash"]
CMD ["/bin/bash"]

View File

@@ -1,5 +1,6 @@
ARG BASE_IMAGE_TAG=latest
FROM ere-base:${BASE_IMAGE_TAG}
ARG BASE_IMAGE_TAG=ere-base:latest
FROM ${BASE_IMAGE_TAG}
# The ere-base image provides Rust, Cargo, and common tools.
# We operate as root for SDK installation.
@@ -20,13 +21,4 @@ ENV OPENVM_TOOLCHAIN_VERSION="nightly-2025-02-14"
# Verify cargo-openvm is accessible with the correct toolchain
RUN cargo "+${OPENVM_TOOLCHAIN_VERSION}" openvm --version
# Copy the entire ere project context
# The WORKDIR is /app from the base image
WORKDIR /app
COPY . .
# Run tests
RUN echo "Running tests for ere-openvm library..." && \
cargo test --release -p ere-openvm --lib -- --color always
CMD ["/bin/bash"]
CMD ["/bin/bash"]

View File

@@ -1,5 +1,6 @@
ARG BASE_IMAGE_TAG=latest
FROM ere-base:${BASE_IMAGE_TAG}
ARG BASE_IMAGE_TAG=ere-base:latest
FROM ${BASE_IMAGE_TAG}
# The ere-base image provides Rust, Cargo, and common tools.
# We operate as root for SDK installation.
@@ -19,16 +20,10 @@ RUN /tmp/install_pico_sdk.sh && rm /tmp/install_pico_sdk.sh # Clean up the scrip
# Define the Pico toolchain for convenience in subsequent commands if needed, though cargo pico should use it.
ENV PICO_TOOLCHAIN_VERSION="nightly-2024-11-27"
# Set default toolchain
RUN rustup default "$PICO_TOOLCHAIN_VERSION"
# Verify Pico installation
RUN echo "Verifying Pico installation in Dockerfile (post-script)..." && cargo "+${PICO_TOOLCHAIN_VERSION}" pico --version
RUN echo "Verifying Pico installation in Dockerfile (post-script)..." && cargo pico --version
# Copy the entire ere project context
# The WORKDIR is /app from the base image
WORKDIR /app
COPY . .
# Run tests
RUN echo "Running tests for ere-pico library..." && \
cargo "+${PICO_TOOLCHAIN_VERSION}" test --release -p ere-pico --lib -- --color always
CMD ["/bin/bash"]
CMD ["/bin/bash"]

View File

@@ -1,21 +0,0 @@
[package]
name = "risc0-cli"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
license.workspace = true
[dependencies]
tempfile.workspace = true
toml.workspace = true
tracing.workspace = true
clap.workspace = true
anyhow.workspace = true
hex.workspace = true
zkvm-interface.workspace = true
risc0-zkvm = { version = "^2.3.0", features = ["unstable"] }
borsh = "1.5"
bincode = "1.3"
[lints]
workspace = true

View File

@@ -1,14 +1,6 @@
ARG BASE_IMAGE_TAG=latest
ARG BASE_IMAGE_TAG=ere-base:latest
FROM rust:1.85 AS builder
WORKDIR /risc0-cli
# Build `risc0-cli`
COPY . .
RUN cargo build --release -p risc0-cli
FROM ere-base:${BASE_IMAGE_TAG}
FROM ${BASE_IMAGE_TAG}
# Copy and run the Risc0 SDK installer script
COPY scripts/sdk_installers/install_risc0_sdk.sh /tmp/install_risc0_sdk.sh
@@ -22,8 +14,4 @@ RUN echo "Verifying Risc0 installation in Dockerfile (post-script)..." && cargo
# Get docker for `cargo risczero build`
RUN curl -fsSL https://get.docker.com | sh
# Copy guest compiler binary
COPY --from=builder /risc0-cli/target/release/risc0-cli /risc0-cli/risc0-cli
# Set entrypoint to `risc0-cli`
ENTRYPOINT ["/risc0-cli/risc0-cli"]
CMD ["/bin/bash"]

View File

@@ -1,289 +0,0 @@
use anyhow::Context;
use clap::{Parser, Subcommand};
use risc0_zkvm::{ExecutorEnv, ProverOpts, default_executor, default_prover};
use std::{fs, path::PathBuf, process::Command};
use toml::Value as TomlValue;
use tracing::info;
use zkvm_interface::{ProgramExecutionReport, ProgramProvingReport};
#[derive(Parser)]
#[command(author, version)]
struct Cli {
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
/// Compile a guest program
Compile {
/// Path to the guest program crate directory.
guest_folder: PathBuf,
/// Output folder where compiled `guest.elf` and `image_id` will be placed.
output_folder: PathBuf,
},
/// Execute a compiled program
Execute {
/// Path to the compiled ELF file
elf_path: PathBuf,
/// Path to the serialized input bytes file
input_path: PathBuf,
/// Path where the execution report will be written
report_path: PathBuf,
},
/// Prove execution of a compiled program
Prove {
/// Path to the compiled ELF file
elf_path: PathBuf,
/// Path to the serialized input bytes file
input_path: PathBuf,
/// Path where the proof will be written
proof_path: PathBuf,
/// Path where the report will be written
report_path: PathBuf,
},
}
pub fn main() -> anyhow::Result<()> {
let args = Cli::parse();
match args.command {
Commands::Compile {
guest_folder,
output_folder,
} => compile(guest_folder, output_folder),
Commands::Prove {
elf_path,
input_path,
proof_path,
report_path,
} => prove(elf_path, input_path, proof_path, report_path),
Commands::Execute {
elf_path,
input_path,
report_path,
} => execute(elf_path, input_path, report_path),
}
}
fn compile(guest_folder: PathBuf, output_folder: PathBuf) -> anyhow::Result<()> {
let dir = guest_folder;
info!("Compiling Risc0 program at {}", dir.display());
if !dir.exists() || !dir.is_dir() {
anyhow::bail!(
"Program path does not exist or is not a directory: {}",
dir.display()
);
}
let guest_manifest_path = dir.join("Cargo.toml");
if !guest_manifest_path.exists() {
anyhow::bail!(
"Cargo.toml not found in program directory: {}. Expected at: {}",
dir.display(),
guest_manifest_path.display()
);
}
// ── read + parse Cargo.toml ───────────────────────────────────────────
let manifest_content = fs::read_to_string(&guest_manifest_path)
.with_context(|| format!("Failed to read file at {}", guest_manifest_path.display()))?;
let manifest_toml: TomlValue = manifest_content.parse::<TomlValue>().with_context(|| {
format!(
"Failed to parse guest Cargo.toml at {}",
guest_manifest_path.display()
)
})?;
let program_name = manifest_toml
.get("package")
.and_then(|p| p.get("name"))
.and_then(|n| n.as_str())
.with_context(|| {
format!(
"Could not find `[package].name` in guest Cargo.toml at {}",
guest_manifest_path.display()
)
})?;
info!("Parsed program name: {program_name}");
// ── build into a temp dir ─────────────────────────────────────────────
info!(
"Running `cargo risczero build` → dir: {}",
output_folder.display()
);
let output = Command::new("cargo")
.current_dir(&dir)
.args(["risczero", "build"])
.stderr(std::process::Stdio::inherit())
.output()
.with_context(|| {
format!(
"Failed to execute `cargo risczer build` in {}",
dir.display()
)
})?;
if !output.status.success() {
anyhow::bail!(
"Failed to execute `cargo risczero build` in {}",
dir.display()
)
}
let (image_id, elf_path) = {
let stdout = String::from_utf8_lossy(&output.stdout);
let line = stdout
.lines()
.find(|line| line.starts_with("ImageID: "))
.unwrap();
let (image_id, elf_path) = line
.trim_start_matches("ImageID: ")
.split_once(" - ")
.unwrap();
(image_id.to_string(), PathBuf::from(elf_path))
};
if !elf_path.exists() {
anyhow::bail!(
"Compiled ELF not found at expected path: {}",
elf_path.display()
);
}
let elf_bytes = fs::read(&elf_path)
.with_context(|| format!("Failed to read file at {}", elf_path.display()))?;
info!("Risc0 program compiled OK - {} bytes", elf_bytes.len());
info!("Image ID - {image_id}");
fs::copy(&elf_path, output_folder.join("guest.elf")).with_context(|| {
format!(
"Failed to copy elf file from {} to {}",
elf_path.display(),
output_folder.join("guest.elf").display()
)
})?;
fs::write(output_folder.join("image_id"), hex::decode(image_id)?).with_context(|| {
format!(
"Failed to write image id to {}",
output_folder.join("image_id").display()
)
})?;
Ok(())
}
fn execute(elf_path: PathBuf, input_path: PathBuf, report_path: PathBuf) -> anyhow::Result<()> {
info!("Starting execution for ELF at {}", elf_path.display());
// Read the ELF file
let elf = fs::read(&elf_path)
.with_context(|| format!("Failed to read ELF file at {}", elf_path.display()))?;
// Read the serialized input bytes
let input_bytes = fs::read(&input_path)
.with_context(|| format!("Failed to read input bytes at {}", input_path.display()))?;
info!("ELF size: {} bytes", elf.len());
info!("Input size: {} bytes", input_bytes.len());
// Create executor environment using write_slice to write the serialized input bytes directly
let executor = default_executor();
let env = ExecutorEnv::builder()
.write_slice(&input_bytes)
.build()
.context("Failed to build executor environment")?;
info!("Starting execution...");
let start = std::time::Instant::now();
// Execute the program
let session_info = executor
.execute(env, &elf)
.context("Failed to execute program")?;
let execution_duration = start.elapsed();
info!("Execution completed in {:?}", execution_duration);
info!("Total cycles: {}", session_info.cycles());
// Create execution report
let report = ProgramExecutionReport {
total_num_cycles: session_info.cycles() as u64,
execution_duration,
..Default::default()
};
// Serialize and write the report
let report_bytes =
bincode::serialize(&report).context("Failed to serialize execution report")?;
fs::write(&report_path, report_bytes)
.with_context(|| format!("Failed to write report to {}", report_path.display()))?;
info!("Execution report written to {}", report_path.display());
Ok(())
}
fn prove(
elf_path: PathBuf,
input_path: PathBuf,
proof_path: PathBuf,
report_path: PathBuf,
) -> anyhow::Result<()> {
info!(
"Starting proof generation for ELF at {}",
elf_path.display()
);
// Read the ELF file
let elf = fs::read(&elf_path)
.with_context(|| format!("Failed to read ELF file at {}", elf_path.display()))?;
// Read the serialized input bytes
let input_bytes = fs::read(&input_path)
.with_context(|| format!("Failed to read input bytes at {}", input_path.display()))?;
info!("ELF size: {} bytes", elf.len());
info!("Input size: {} bytes", input_bytes.len());
// Create prover environment using write_slice to write the serialized input bytes directly
let prover = default_prover();
let env = ExecutorEnv::builder()
.write_slice(&input_bytes)
.build()
.context("Failed to build executor environment")?;
info!("Starting proof generation...");
let now = std::time::Instant::now();
// Generate proof
let prove_info = prover
.prove_with_opts(env, &elf, &ProverOpts::succinct())
.context("Failed to generate proof")?;
let proving_time = now.elapsed();
info!("Proof generation completed in {:?}", proving_time);
// Serialize and write the proof
let proof_bytes = borsh::to_vec(&prove_info.receipt).context("Failed to serialize proof")?;
fs::write(&proof_path, proof_bytes)
.with_context(|| format!("Failed to write proof to {}", proof_path.display()))?;
let report_bytes = bincode::serialize(&ProgramProvingReport::new(proving_time))
.context("Failed to serialize report")?;
fs::write(&report_path, report_bytes)
.with_context(|| format!("Failed to write report to {}", report_path.display()))?;
info!("Proof written to {}", proof_path.display());
info!("Report written to {}", report_path.display());
Ok(())
}

View File

@@ -1,16 +0,0 @@
[package]
name = "sp1-guest-compiler"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
license.workspace = true
[dependencies]
tempfile.workspace = true
toml.workspace = true
tracing.workspace = true
clap.workspace = true
anyhow.workspace = true
[lints]
workspace = true

View File

@@ -1,23 +1,9 @@
ARG BASE_IMAGE_TAG=latest
ARG BASE_IMAGE_TAG=ere-base:latest
# Build guest-compiler binary
FROM rust:1.85 AS builder
RUN apt-get update && apt-get install -y build-essential libclang-dev
WORKDIR /guest-compiler
COPY . .
RUN cargo build --release -p sp1-guest-compiler
# Build zkVM builder image
FROM ere-base:${BASE_IMAGE_TAG}
FROM ${BASE_IMAGE_TAG}
ARG USERNAME=ere_user
# Ensure Cargo/Rustup environment variables are set from the base image for SDK script
# TODO: These should be inherited from ere-base.
ENV RUSTUP_HOME=${RUSTUP_HOME:-/usr/local/rustup} \
CARGO_HOME=${CARGO_HOME:-/usr/local/cargo} \
PATH=${PATH:-/usr/local/cargo/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin}
# Copy the SP1 SDK installer script
COPY scripts/sdk_installers/install_sp1_sdk.sh /tmp/install_sp1_sdk.sh
RUN chmod +x /tmp/install_sp1_sdk.sh
@@ -41,11 +27,7 @@ ENV PATH="${SP1UP_HOME}/bin:${SP1_HOME}/bin:$PATH"
# Verify SP1 installation (optional here, as script does it, but good for sanity)
RUN cargo prove --version
# Copy guest compiler binary
COPY --from=builder /guest-compiler/target/release/sp1-guest-compiler /guest-compiler/guest-compiler
WORKDIR /guest-compiler
CMD ["/bin/bash"]
CMD ["/bin/bash"]
# TODO: Maybe we use root to install it in ere_user and then switch back to ere_user for security
# USER ${USERNAME} # Switch to non-root user again
# USER ${USERNAME} # Switch to non-root user again

View File

@@ -1,103 +0,0 @@
use std::{fs, path::PathBuf, process::Command};
use anyhow::Context;
use clap::Parser;
use toml::Value as TomlValue;
use tracing::info;
#[derive(Parser)]
#[command(author, version)]
struct Cli {
/// Path to the guest program crate directory.
guest_folder: PathBuf,
/// Compiled ELF output folder where guest.elf will be placed.
elf_output_folder: PathBuf,
}
pub fn main() -> anyhow::Result<()> {
let args = Cli::parse();
let dir = args.guest_folder;
info!("Compiling SP1 program at {}", dir.display());
if !dir.exists() || !dir.is_dir() {
anyhow::bail!(
"Program path does not exist or is not a directory: {}",
dir.display()
);
}
let guest_manifest_path = dir.join("Cargo.toml");
if !guest_manifest_path.exists() {
anyhow::bail!(
"Cargo.toml not found in program directory: {}. Expected at: {}",
dir.display(),
guest_manifest_path.display()
);
}
// ── read + parse Cargo.toml ───────────────────────────────────────────
let manifest_content = fs::read_to_string(&guest_manifest_path)
.with_context(|| format!("Failed to read file at {}", guest_manifest_path.display()))?;
let manifest_toml: TomlValue = manifest_content.parse::<TomlValue>().with_context(|| {
format!(
"Failed to parse guest Cargo.toml at {}",
guest_manifest_path.display()
)
})?;
let program_name = manifest_toml
.get("package")
.and_then(|p| p.get("name"))
.and_then(|n| n.as_str())
.with_context(|| {
format!(
"Could not find `[package].name` in guest Cargo.toml at {}",
guest_manifest_path.display()
)
})?;
info!("Parsed program name: {program_name}");
// ── build into a temp dir ─────────────────────────────────────────────
info!(
"Running `cargo prove build` → dir: {}",
args.elf_output_folder.display()
);
let status = Command::new("cargo")
.current_dir(&dir)
.args([
"prove",
"build",
"--output-directory",
args.elf_output_folder.to_str().unwrap(),
"--elf-name",
"guest.elf",
])
.stdout(std::process::Stdio::inherit())
.stderr(std::process::Stdio::inherit())
.status()
.with_context(|| format!("Failed to execute `cargo prove build` in {}", dir.display()))?;
if !status.success() {
anyhow::bail!("Failed to execute `cargo prove build` in {}", dir.display())
}
let elf_path = args.elf_output_folder.join("guest.elf");
if !elf_path.exists() {
anyhow::bail!(
"Compiled ELF not found at expected path: {}",
elf_path.display()
);
}
let elf_bytes = fs::read(&elf_path)
.with_context(|| format!("Failed to read file at {}", elf_path.display()))?;
info!("SP1 program compiled OK - {} bytes", elf_bytes.len());
Ok(())
}

View File

@@ -1,18 +1,18 @@
ARG BASE_IMAGE_TAG=latest
FROM ere-base:${BASE_IMAGE_TAG}
ARG BASE_IMAGE_TAG=ere-base:latest
FROM ${BASE_IMAGE_TAG}
# The ere-base image provides Rust, Cargo, and common tools.
# ZisK requires Ubuntu 22.04 or higher (ere-base uses 22.04 by default).
# ZisK requires Ubuntu 22.04 or higher (ere-base uses 24.04 by default).
# We operate as root for SDK and dependency installation.
# Install ZisK system dependencies (for Ubuntu)
# Taken from https://0xpolygonhermez.github.io/zisk/getting_started/installation.html
RUN apt-get update && apt-get install -y --no-install-recommends \
xz-utils \
jq \
# build-essential is in ere-base
# jq is in ere-base
# curl is in ere-base
# git is in ere-base
# build-essential is in ere-base
qemu-system \
libomp-dev \
libgmp-dev \
@@ -28,7 +28,8 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
openmpi-bin \
openmpi-common \
libclang-dev \
clang
clang && \
apt-get clean && rm -rf /var/lib/apt/lists/*
RUN wget https://developer.download.nvidia.com/compute/cuda/repos/$(. /etc/os-release && echo "${ID}${VERSION_ID}" | tr -d '.')/$(uname -i)/cuda-keyring_1.1-1_all.deb && \
dpkg -i cuda-keyring_1.1-1_all.deb && \
@@ -37,14 +38,21 @@ RUN wget https://developer.download.nvidia.com/compute/cuda/repos/$(. /etc/os-re
apt install -y cuda-toolkit && \
apt-get clean && rm -rf /var/lib/apt/lists/*
# If current environment is in CI or not.
ARG CI
# Copy the ZisK SDK installer script from the workspace context
COPY scripts/sdk_installers/install_zisk_sdk.sh /tmp/install_zisk_sdk.sh
RUN chmod +x /tmp/install_zisk_sdk.sh
# Run the ZisK SDK installation script using ziskup.
# This script installs the 'zisk' Rust toolchain and cargo-zisk.
# TODO: Download the proving key if the CI runner has enough disk space.
RUN SETUP_KEY=verify /tmp/install_zisk_sdk.sh && rm /tmp/install_zisk_sdk.sh # Clean up the script
# This script installs the 'zisk' Rust toolchain and `cargo-zisk`
#
# If argument `CI` is set, we only install verifying key, this is used by github
# CI runner which only has small disk space (proving key requires ~35 GB).
RUN if [ -n "$CI" ]; then export SETUP_KEY=verify; fi && \
/tmp/install_zisk_sdk.sh && \
rm /tmp/install_zisk_sdk.sh # Clean up the script
# The 'zisk' Rust toolchain is now installed.
# cargo-zisk is installed in /root/.zisk/bin.
@@ -56,16 +64,4 @@ ENV PATH="${PATH}:${ZISK_BIN_DIR}"
# Verify cargo-zisk is accessible
RUN echo "Verifying Zisk installation in Dockerfile ..." && cargo-zisk --version
# Copy the entire ere project context
# The WORKDIR is /app from the base image
WORKDIR /app
COPY . .
# Run only compile and execution test, because proving requires ~31 GiB disk
# space for the provingKey.
# TODO: Run all tests if the CI runner has enough disk space to install the proving key.
RUN echo "Running tests for ere-zisk library..." && \
rm -rf ~/.zisk/provingKey && \
cargo test --release -p ere-zisk --lib -- --color always compile::tests execute_tests
CMD ["/bin/bash"]
CMD ["/bin/bash"]