Introduce CompilerKind and refactor ere-dockerized (#221)

This commit is contained in:
Han
2025-11-25 10:59:48 +09:00
committed by GitHub
parent 19c441983a
commit daea3f4fff
51 changed files with 1315 additions and 1042 deletions

View File

@@ -59,7 +59,7 @@ jobs:
uses: tj-actions/changed-files@v46
with:
files: |
docker/base/Dockerfile.base
docker/Dockerfile.base
docker/${{ inputs.zkvm }}/**
scripts/sdk_installers/install_${{ inputs.zkvm }}_sdk.sh
@@ -97,7 +97,7 @@ jobs:
uses: docker/build-push-action@v6
with:
context: .
file: docker/base/Dockerfile.base
file: docker/Dockerfile.base
push: true
tags: ${{ steps.image_meta.outputs.base_image }}
@@ -123,7 +123,7 @@ jobs:
tags: ${{ steps.image_meta.outputs.compiler_zkvm_image }}
build-args: |
BASE_ZKVM_IMAGE=${{ steps.image_meta.outputs.base_zkvm_image }}
- name: Build ere-server-${{ inputs.zkvm }} image
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
uses: docker/build-push-action@v6
@@ -290,4 +290,4 @@ jobs:
- name: Run cargo test for ere-${{ inputs.zkvm }} via ere-dockerized
run: |
cargo test --release --package ere-dockerized \
-- ${{ inputs.zkvm }} ${{ inputs.skip_prove_test && '--skip prove' || '' }}
-- ${{ inputs.zkvm }} ${{ inputs.skip_prove_test && '--skip prove' || '' }} --test-threads=1

58
Cargo.lock generated
View File

@@ -3622,7 +3622,7 @@ dependencies = [
[[package]]
name = "ere-airbender"
version = "0.0.14"
version = "0.0.15"
dependencies = [
"anyhow",
"bincode 2.0.1",
@@ -3639,14 +3639,14 @@ dependencies = [
[[package]]
name = "ere-build-utils"
version = "0.0.14"
version = "0.0.15"
dependencies = [
"cargo_metadata 0.19.2",
]
[[package]]
name = "ere-compile-utils"
version = "0.0.14"
version = "0.0.15"
dependencies = [
"anyhow",
"cargo_metadata 0.19.2",
@@ -3656,7 +3656,7 @@ dependencies = [
[[package]]
name = "ere-compiler"
version = "0.0.14"
version = "0.0.15"
dependencies = [
"anyhow",
"bincode 2.0.1",
@@ -3678,13 +3678,15 @@ dependencies = [
[[package]]
name = "ere-dockerized"
version = "0.0.14"
version = "0.0.15"
dependencies = [
"anyhow",
"ere-build-utils",
"ere-compiler",
"ere-server",
"ere-test-utils",
"ere-zkvm-interface",
"paste",
"serde",
"tempfile",
"thiserror 2.0.12",
@@ -3694,7 +3696,7 @@ dependencies = [
[[package]]
name = "ere-io-serde"
version = "0.0.14"
version = "0.0.15"
dependencies = [
"bincode 2.0.1",
"serde",
@@ -3702,7 +3704,7 @@ dependencies = [
[[package]]
name = "ere-jolt"
version = "0.0.14"
version = "0.0.15"
dependencies = [
"anyhow",
"ark-serialize 0.5.0",
@@ -3720,7 +3722,7 @@ dependencies = [
[[package]]
name = "ere-miden"
version = "0.0.14"
version = "0.0.15"
dependencies = [
"anyhow",
"ere-build-utils",
@@ -3738,7 +3740,7 @@ dependencies = [
[[package]]
name = "ere-nexus"
version = "0.0.14"
version = "0.0.15"
dependencies = [
"anyhow",
"bincode 2.0.1",
@@ -3757,7 +3759,7 @@ dependencies = [
[[package]]
name = "ere-openvm"
version = "0.0.14"
version = "0.0.15"
dependencies = [
"anyhow",
"ere-build-utils",
@@ -3778,7 +3780,7 @@ dependencies = [
[[package]]
name = "ere-pico"
version = "0.0.14"
version = "0.0.15"
dependencies = [
"anyhow",
"bincode 2.0.1",
@@ -3796,7 +3798,7 @@ dependencies = [
[[package]]
name = "ere-platform-airbender"
version = "0.0.14"
version = "0.0.15"
dependencies = [
"ere-platform-trait",
"riscv_common",
@@ -3804,7 +3806,7 @@ dependencies = [
[[package]]
name = "ere-platform-jolt"
version = "0.0.14"
version = "0.0.15"
dependencies = [
"ere-platform-trait",
"jolt-sdk",
@@ -3813,7 +3815,7 @@ dependencies = [
[[package]]
name = "ere-platform-nexus"
version = "0.0.14"
version = "0.0.15"
dependencies = [
"ere-platform-trait",
"nexus-rt",
@@ -3821,7 +3823,7 @@ dependencies = [
[[package]]
name = "ere-platform-openvm"
version = "0.0.14"
version = "0.0.15"
dependencies = [
"ere-platform-trait",
"openvm",
@@ -3829,7 +3831,7 @@ dependencies = [
[[package]]
name = "ere-platform-pico"
version = "0.0.14"
version = "0.0.15"
dependencies = [
"ere-platform-trait",
"pico-sdk",
@@ -3837,7 +3839,7 @@ dependencies = [
[[package]]
name = "ere-platform-risc0"
version = "0.0.14"
version = "0.0.15"
dependencies = [
"ere-platform-trait",
"risc0-zkvm",
@@ -3845,7 +3847,7 @@ dependencies = [
[[package]]
name = "ere-platform-sp1"
version = "0.0.14"
version = "0.0.15"
dependencies = [
"ere-platform-trait",
"sp1-zkvm",
@@ -3853,14 +3855,14 @@ dependencies = [
[[package]]
name = "ere-platform-trait"
version = "0.0.14"
version = "0.0.15"
dependencies = [
"digest 0.10.7",
]
[[package]]
name = "ere-platform-ziren"
version = "0.0.14"
version = "0.0.15"
dependencies = [
"ere-platform-trait",
"zkm-zkvm",
@@ -3868,7 +3870,7 @@ dependencies = [
[[package]]
name = "ere-platform-zisk"
version = "0.0.14"
version = "0.0.15"
dependencies = [
"ere-platform-trait",
"ziskos",
@@ -3876,7 +3878,7 @@ dependencies = [
[[package]]
name = "ere-risc0"
version = "0.0.14"
version = "0.0.15"
dependencies = [
"anyhow",
"borsh",
@@ -3896,7 +3898,7 @@ dependencies = [
[[package]]
name = "ere-server"
version = "0.0.14"
version = "0.0.15"
dependencies = [
"anyhow",
"bincode 2.0.1",
@@ -3926,7 +3928,7 @@ dependencies = [
[[package]]
name = "ere-sp1"
version = "0.0.14"
version = "0.0.15"
dependencies = [
"anyhow",
"bincode 2.0.1",
@@ -3943,7 +3945,7 @@ dependencies = [
[[package]]
name = "ere-test-utils"
version = "0.0.14"
version = "0.0.15"
dependencies = [
"ere-io-serde",
"ere-platform-trait",
@@ -3955,7 +3957,7 @@ dependencies = [
[[package]]
name = "ere-ziren"
version = "0.0.14"
version = "0.0.15"
dependencies = [
"anyhow",
"bincode 2.0.1",
@@ -3971,7 +3973,7 @@ dependencies = [
[[package]]
name = "ere-zisk"
version = "0.0.14"
version = "0.0.15"
dependencies = [
"anyhow",
"blake3",
@@ -3989,7 +3991,7 @@ dependencies = [
[[package]]
name = "ere-zkvm-interface"
version = "0.0.14"
version = "0.0.15"
dependencies = [
"anyhow",
"auto_impl",

View File

@@ -24,8 +24,8 @@ members = [
"crates/zkvm/zisk",
"crates/zkvm/zisk/platform",
# Dockerized zkVM
"crates/dockerized",
"crates/dockerized/compiler",
"crates/dockerized/dockerized",
"crates/dockerized/server",
# Utils
"crates/io-serde",
@@ -36,7 +36,7 @@ members = [
resolver = "2"
[workspace.package]
version = "0.0.14"
version = "0.0.15"
edition = "2024"
rust-version = "1.85"
license = "MIT OR Apache-2.0"
@@ -56,6 +56,7 @@ dashmap = "6.1.0"
digest = { version = "0.10.7", default-features = false }
eyre = "0.6.12"
indexmap = "2.10.0"
paste = "1.0.15"
postcard = { version = "1.0.8", default-features = false }
prost = "0.13"
prost-build = "0.13"
@@ -155,8 +156,8 @@ ere-platform-sp1 = { path = "crates/zkvm/sp1/platform" }
ere-platform-ziren = { path = "crates/zkvm/ziren/platform" }
ere-platform-zisk = { path = "crates/zkvm/zisk/platform" }
ere-dockerized = { path = "crates/dockerized" }
ere-compiler = { path = "crates/dockerized/compiler" }
ere-dockerized = { path = "crates/dockerized/dockerized" }
ere-server = { path = "crates/dockerized/server" }
ere-io-serde = { path = "crates/io-serde" }

View File

@@ -130,7 +130,7 @@ ere-dockerized = { git = "https://github.com/eth-act/ere.git", tag = "v0.0.12" }
```rust
// main.rs
use ere_dockerized::{EreDockerizedCompiler, EreDockerizedzkVM, ErezkVM};
use ere_dockerized::{CompilerKind, DockerizedCompiler, DockerizedzkVM, zkVMKind};
use ere_zkvm_interface::{
compiler::Compiler,
zkvm::{ProofKind, ProverResourceType, zkVM},
@@ -140,11 +140,15 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
let guest_directory = std::path::Path::new("workspace/guest");
// Compile guest
let compiler = EreDockerizedCompiler::new(ErezkVM::SP1, std::path::Path::new("workspace"));
let compiler = DockerizedCompiler::new(
zkVMKind::SP1,
CompilerKind::RustCustomized,
std::path::Path::new("workspace"),
);
let program = compiler.compile(guest_directory)?;
// Create zkVM instance
let zkvm = EreDockerizedzkVM::new(ErezkVM::SP1, program, ProverResourceType::Cpu)?;
let zkvm = DockerizedzkVM::new(zkVMKind::SP1, program, ProverResourceType::Cpu)?;
// Serialize input
let input = 42u32.to_le_bytes();

View File

@@ -73,12 +73,12 @@ impl CommonError {
pub fn read_file(id: impl AsRef<str>, path: impl AsRef<Path>, err: io::Error) -> Self {
let (id, path) = (id.as_ref(), path.as_ref().display());
Self::io(format!("Failed to write {id} to {path}"), err)
Self::io(format!("Failed to read {id} from {path}"), err)
}
pub fn write_file(id: impl AsRef<str>, path: impl AsRef<Path>, err: io::Error) -> Self {
let (id, path) = (id.as_ref(), path.as_ref().display());
Self::io(format!("Failed to read {id} from {path}"), err)
Self::io(format!("Failed to write {id} to {path}"), err)
}
pub fn deserialize(

View File

@@ -16,8 +16,12 @@ tracing.workspace = true
# Local dependencies
ere-zkvm-interface = { workspace = true, features = ["clap"] }
ere-server.workspace = true
ere-compiler.workspace = true
[dev-dependencies]
paste.workspace = true
# Local dependencies
ere-test-utils = { workspace = true, features = ["host"] }
[build-dependencies]

View File

@@ -45,7 +45,7 @@ fn generate_zkvm_sdk_version_impl() {
.map(detect_sdk_version);
let zkvm_sdk_version_impl = format!(
r#"impl crate::ErezkVM {{
r#"impl crate::zkVMKind {{
pub fn sdk_version(&self) -> &'static str {{
match self {{
Self::Airbender => "{airbender_version}",

View File

@@ -0,0 +1,48 @@
use std::{
fmt::{self, Display, Formatter},
str::FromStr,
};
/// Compiler kind to use to compile the guest.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum CompilerKind {
/// Stock Rust compiler
Rust,
/// Rust compiler with customized toolchain
RustCustomized,
/// Go compiler with customized toolchain
GoCustomized,
/// Miden assembly compiler
MidenAsm,
}
impl CompilerKind {
pub fn as_str(&self) -> &'static str {
match self {
Self::Rust => "rust",
Self::RustCustomized => "rust-customized",
Self::GoCustomized => "go-customized",
Self::MidenAsm => "miden-asm",
}
}
}
impl FromStr for CompilerKind {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(match s {
"rust" => Self::Rust,
"rust-customized" => Self::RustCustomized,
"go-customized" => Self::GoCustomized,
"miden-asm" => Self::MidenAsm,
_ => return Err(format!("Unsupported compiler kind {s}")),
})
}
}
impl Display for CompilerKind {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_str())
}
}

View File

@@ -1,8 +1,9 @@
use anyhow::{Context, Error};
use anyhow::{Context, Error, bail};
use clap::Parser;
use ere_compiler::CompilerKind;
use ere_zkvm_interface::compiler::Compiler;
use serde::Serialize;
use std::{env, fs::File, path::PathBuf};
use std::{fs::File, path::PathBuf};
use tracing_subscriber::EnvFilter;
// Compile-time check to ensure exactly one zkVM feature is enabled for `ere-compiler`
@@ -26,6 +27,9 @@ const _: () = {
#[derive(Parser)]
#[command(author, version)]
struct Args {
/// Compiler kind to use
#[arg(long, value_parser = <CompilerKind as std::str::FromStr>::from_str)]
compiler_kind: CompilerKind,
/// Path to the guest program
#[arg(long)]
guest_path: PathBuf,
@@ -41,7 +45,7 @@ fn main() -> Result<(), Error> {
let args = Args::parse();
let program = compile(args.guest_path)?;
let program = compile(args.guest_path, args.compiler_kind)?;
let mut output = File::create(args.output_path).with_context(|| "Failed to create output")?;
bincode::serde::encode_into_std_write(&program, &mut output, bincode::config::legacy())
@@ -50,62 +54,140 @@ fn main() -> Result<(), Error> {
Ok(())
}
fn compile(guest_path: PathBuf) -> Result<impl Serialize, Error> {
fn compile(guest_path: PathBuf, compiler_kind: CompilerKind) -> Result<impl Serialize, Error> {
#[cfg(feature = "airbender")]
let result = ere_airbender::compiler::RustRv32ima.compile(&guest_path);
let result = {
use ere_airbender::compiler::*;
match compiler_kind {
CompilerKind::Rust | CompilerKind::RustCustomized => RustRv32ima.compile(&guest_path),
_ => bail!(unsupported_compiler_kind_err(
compiler_kind,
[CompilerKind::Rust, CompilerKind::RustCustomized]
)),
}
};
#[cfg(feature = "jolt")]
let result = if use_stock_rust() {
ere_jolt::compiler::RustRv64imac.compile(&guest_path)
} else {
ere_jolt::compiler::RustRv64imacCustomized.compile(&guest_path)
let result = {
use ere_jolt::compiler::*;
match compiler_kind {
CompilerKind::Rust => RustRv64imac.compile(&guest_path),
CompilerKind::RustCustomized => RustRv64imacCustomized.compile(&guest_path),
_ => bail!(unsupported_compiler_kind_err(
compiler_kind,
[CompilerKind::Rust, CompilerKind::RustCustomized]
)),
}
};
#[cfg(feature = "miden")]
let result = ere_miden::compiler::MidenAsm.compile(&guest_path);
let result = {
use ere_miden::compiler::*;
match compiler_kind {
CompilerKind::MidenAsm => MidenAsm.compile(&guest_path),
_ => bail!(unsupported_compiler_kind_err(
compiler_kind,
[CompilerKind::MidenAsm]
)),
}
};
#[cfg(feature = "nexus")]
let result = ere_nexus::compiler::RustRv32i.compile(&guest_path);
let result = {
use ere_nexus::compiler::*;
match compiler_kind {
CompilerKind::Rust | CompilerKind::RustCustomized => RustRv32i.compile(&guest_path),
_ => bail!(unsupported_compiler_kind_err(
compiler_kind,
[CompilerKind::Rust, CompilerKind::RustCustomized]
)),
}
};
#[cfg(feature = "openvm")]
let result = if use_stock_rust() {
ere_openvm::compiler::RustRv32ima.compile(&guest_path)
} else {
ere_openvm::compiler::RustRv32imaCustomized.compile(&guest_path)
let result = {
use ere_openvm::compiler::*;
match compiler_kind {
CompilerKind::Rust => RustRv32ima.compile(&guest_path),
CompilerKind::RustCustomized => RustRv32imaCustomized.compile(&guest_path),
_ => bail!(unsupported_compiler_kind_err(
compiler_kind,
[CompilerKind::Rust, CompilerKind::RustCustomized]
)),
}
};
#[cfg(feature = "pico")]
let result = if use_stock_rust() {
ere_pico::compiler::RustRv32ima.compile(&guest_path)
} else {
ere_pico::compiler::RustRv32imaCustomized.compile(&guest_path)
let result = {
use ere_pico::compiler::*;
match compiler_kind {
CompilerKind::Rust => RustRv32ima.compile(&guest_path),
CompilerKind::RustCustomized => RustRv32imaCustomized.compile(&guest_path),
_ => bail!(unsupported_compiler_kind_err(
compiler_kind,
[CompilerKind::Rust, CompilerKind::RustCustomized]
)),
}
};
#[cfg(feature = "risc0")]
let result = if use_stock_rust() {
ere_risc0::compiler::RustRv32ima.compile(&guest_path)
} else {
ere_risc0::compiler::RustRv32imaCustomized.compile(&guest_path)
let result = {
use ere_risc0::compiler::*;
match compiler_kind {
CompilerKind::Rust => RustRv32ima.compile(&guest_path),
CompilerKind::RustCustomized => RustRv32imaCustomized.compile(&guest_path),
_ => bail!(unsupported_compiler_kind_err(
compiler_kind,
[CompilerKind::Rust, CompilerKind::RustCustomized]
)),
}
};
#[cfg(feature = "sp1")]
let result = if use_stock_rust() {
ere_sp1::compiler::RustRv32ima.compile(&guest_path)
} else {
ere_sp1::compiler::RustRv32imaCustomized.compile(&guest_path)
let result = {
use ere_sp1::compiler::*;
match compiler_kind {
CompilerKind::Rust => RustRv32ima.compile(&guest_path),
CompilerKind::RustCustomized => RustRv32imaCustomized.compile(&guest_path),
_ => bail!(unsupported_compiler_kind_err(
compiler_kind,
[CompilerKind::Rust, CompilerKind::RustCustomized]
)),
}
};
#[cfg(feature = "ziren")]
let result = ere_ziren::compiler::RustMips32r2Customized.compile(&guest_path);
let result = {
use ere_ziren::compiler::*;
match compiler_kind {
CompilerKind::RustCustomized => RustMips32r2Customized.compile(&guest_path),
_ => bail!(unsupported_compiler_kind_err(
compiler_kind,
[CompilerKind::RustCustomized]
)),
}
};
#[cfg(feature = "zisk")]
let result = ere_zisk::compiler::RustRv64imaCustomized.compile(&guest_path);
let result = {
use ere_zisk::compiler::*;
match compiler_kind {
CompilerKind::RustCustomized => RustRv64imaCustomized.compile(&guest_path),
CompilerKind::GoCustomized => GoCustomized.compile(&guest_path),
_ => bail!(unsupported_compiler_kind_err(
compiler_kind,
[CompilerKind::RustCustomized, CompilerKind::GoCustomized]
)),
}
};
result.with_context(|| "Failed to compile program")
}
#[allow(dead_code)]
/// Returns whether to use stock Rust compiler instead of customized compiler.
fn use_stock_rust() -> bool {
env::var_os("ERE_RUST_TOOLCHAIN").is_some()
fn unsupported_compiler_kind_err(
compiler_kind: CompilerKind,
supported: impl IntoIterator<Item = CompilerKind>,
) -> anyhow::Error {
let supported = supported.into_iter().collect::<Vec<_>>();
anyhow::anyhow!("Unsupported compiler kind {compiler_kind:?}, expect one of {supported:?}",)
}

View File

@@ -1,54 +0,0 @@
use ere_server::client::{self, TwirpErrorResponse};
use std::{io, path::PathBuf};
use thiserror::Error;
impl From<client::Error> for Error {
fn from(value: client::Error) -> Self {
match value {
client::Error::zkVM(err) => Self::zkVM(err),
client::Error::ConnectionTimeout => Self::ConnectionTimeout,
client::Error::Rpc(err) => Self::Rpc(err),
}
}
}
#[derive(Debug, Error)]
#[allow(non_camel_case_types)]
pub enum Error {
#[error(
"Guest directory must be in mounting directory, mounting_directory: {mounting_directory}, guest_directory: {guest_directory}"
)]
GuestNotInMountingDirecty {
mounting_directory: PathBuf,
guest_directory: PathBuf,
},
#[error("{context}: {source}")]
Io {
#[source]
source: io::Error,
context: String,
},
#[error("Failed to execute `docker image`: {0}")]
DockerImageCmd(io::Error),
#[error("Failed to execute `docker build`: {0}")]
DockerBuildCmd(io::Error),
#[error("Failed to execute `docker run`: {0}")]
DockerRunCmd(io::Error),
#[error("Failed to execute `docker container`: {0}")]
DockerContainerCmd(io::Error),
#[error("zkVM method error: {0}")]
zkVM(String),
#[error("Connection to zkVM server timeout after 5 minutes")]
ConnectionTimeout,
#[error("RPC to zkVM server error: {0}")]
Rpc(TwirpErrorResponse),
}
impl Error {
pub fn io(source: io::Error, context: impl ToString) -> Self {
Self::Io {
source,
context: context.to_string(),
}
}
}

View File

@@ -1,770 +0,0 @@
//! # Ere Dockerized
//!
//! A Docker-based wrapper for other zkVM crates `ere-{zkvm}`.
//!
//! This crate provides a unified interface to dockerize the `Compiler` and
//! `zkVM` implementation of other zkVM crates `ere-{zkvm}`, it requires only
//! `docker` to be installed, but no zkVM specific SDK.
//!
//! ## Docker image building
//!
//! It builds 4 Docker images in sequence if they don't exist:
//! 1. `ere-base:{version}` - Base image with common dependencies
//! 2. `ere-base-{zkvm}:{version}` - zkVM-specific base image with the zkVM SDK
//! 3. `ere-compiler-{zkvm}:{version}` - Compiler image with the `ere-compiler`
//! binary built with the selected zkVM feature
//! 4. `ere-server-{zkvm}:{version}` - Server image with the `ere-server` binary
//! built with the selected zkVM feature
//!
//! When [`ProverResourceType::Gpu`] is selected, the image with GPU support
//! will be built and tagged with specific suffix.
//!
//! To force rebuild all images, set the environment variable
//! `ERE_FORCE_REBUILD_DOCKER_IMAGE` to non-empty value.
//!
//! ## Example
//!
//! ```rust,no_run
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
//! use ere_dockerized::{EreDockerizedCompiler, EreDockerizedzkVM, ErezkVM};
//! use ere_zkvm_interface::{
//! compiler::Compiler,
//! zkvm::{ProofKind, ProverResourceType, zkVM},
//! };
//! use std::path::Path;
//!
//! // The zkVM we plan to use
//! let zkvm = ErezkVM::SP1;
//!
//! // Compile a guest program
//! let compiler = EreDockerizedCompiler::new(zkvm, "mounting/directory")?;
//! let guest_path = Path::new("relative/path/to/guest/program");
//! let program = compiler.compile(&guest_path)?;
//!
//! // Create zkVM instance
//! let resource = ProverResourceType::Cpu;
//! let zkvm = EreDockerizedzkVM::new(zkvm, program, resource)?;
//!
//! // Serialize input
//! let input = 42u32.to_le_bytes();
//!
//! // Execute program
//! let (public_values, execution_report) = zkvm.execute(&input)?;
//! println!("Execution cycles: {}", execution_report.total_num_cycles);
//!
//! // Generate proof
//! let (public_values, proof, proving_report) = zkvm.prove(&input, ProofKind::Compressed)?;
//! println!("Proof generated in: {:?}", proving_report.proving_time);
//!
//! // Verify proof
//! let public_values = zkvm.verify(&proof)?;
//! println!("Proof verified successfully!");
//! # Ok(())
//! # }
//! ```
#![cfg_attr(not(test), warn(unused_crate_dependencies))]
use crate::{
cuda::cuda_arch,
docker::{DockerBuildCmd, DockerRunCmd, docker_image_exists, stop_docker_container},
};
use ere_server::client::{Url, zkVMClient};
use ere_zkvm_interface::{
compiler::Compiler,
zkvm::{
ProgramExecutionReport, ProgramProvingReport, Proof, ProofKind, ProverResourceType,
PublicValues, zkVM,
},
};
use serde::{Deserialize, Serialize};
use std::{
env,
fmt::{self, Display, Formatter},
fs, iter,
path::{Path, PathBuf},
str::FromStr,
};
use tempfile::TempDir;
use tracing::error;
mod cuda;
mod docker;
mod error;
pub use error::Error;
include!(concat!(env!("OUT_DIR"), "/crate_version.rs"));
include!(concat!(env!("OUT_DIR"), "/zkvm_sdk_version_impl.rs"));
/// Offset of port used for `ere-server` for [`ErezkVM`]s.
const ERE_SERVER_PORT_OFFSET: u16 = 4174;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ErezkVM {
Airbender,
Jolt,
Miden,
Nexus,
OpenVM,
Pico,
Risc0,
SP1,
Ziren,
Zisk,
}
impl ErezkVM {
pub fn as_str(&self) -> &'static str {
match self {
Self::Airbender => "airbender",
Self::Jolt => "jolt",
Self::Miden => "miden",
Self::Nexus => "nexus",
Self::OpenVM => "openvm",
Self::Pico => "pico",
Self::Risc0 => "risc0",
Self::SP1 => "sp1",
Self::Ziren => "ziren",
Self::Zisk => "zisk",
}
}
/// Tag of images in format of `{version}{suffix}`.
fn image_tag(&self, version: &str, gpu: bool) -> String {
let suffix = match (gpu, self) {
// Only the following zkVMs requires CUDA setup in the base image
// when GPU support is required.
(true, Self::Airbender | Self::OpenVM | Self::Risc0 | Self::Zisk) => "-cuda",
_ => "",
};
format!("{version}{suffix}")
}
pub fn base_image(&self, version: &str, gpu: bool) -> String {
format!("ere-base:{}", self.image_tag(version, gpu))
}
pub fn base_zkvm_image(&self, version: &str, gpu: bool) -> String {
format!("ere-base-{self}:{}", self.image_tag(version, gpu))
}
pub fn compiler_zkvm_image(&self, version: &str) -> String {
format!("ere-compiler-{self}:{}", self.image_tag(version, false))
}
pub fn server_zkvm_image(&self, version: &str, gpu: bool) -> String {
format!("ere-server-{self}:{}", self.image_tag(version, gpu))
}
/// This method builds 4 Docker images in sequence:
/// 1. `ere-base:{version}`: Base image with common dependencies
/// 2. `ere-base-{zkvm}:{version}`: zkVM-specific base image with the zkVM SDK
/// 3. `ere-compiler-{zkvm}:{version}` - Compiler image with the `ere-compiler`
/// binary built with the selected zkVM feature
/// 4. `ere-server-{zkvm}:{version}` - Server image with the `ere-server` binary
/// built with the selected zkVM feature
///
/// When [`ProverResourceType::Gpu`] is selected, the image with GPU support
/// will be built and tagged with specific suffix.
///
/// Images are cached and only rebuilt if they don't exist or if the
/// `ERE_FORCE_REBUILD_DOCKER_IMAGE` environment variable is set.
pub fn build_docker_image(&self, gpu: bool) -> Result<(), Error> {
let workspace_dir = workspace_dir();
let docker_dir = workspace_dir.join("docker");
let force_rebuild = env::var_os("ERE_FORCE_REBUILD_DOCKER_IMAGE").is_some();
// Build `ere-base`
if force_rebuild || !docker_image_exists(self.base_image(CRATE_VERSION, gpu))? {
let mut cmd = DockerBuildCmd::new()
.file(docker_dir.join("base").join("Dockerfile.base"))
.tag(self.base_image(CRATE_VERSION, gpu))
.tag(self.base_image("latest", gpu));
if gpu {
cmd = cmd.build_arg("CUDA", "1");
}
cmd.exec(&workspace_dir).map_err(Error::DockerBuildCmd)?;
}
// Build `ere-base-{zkvm}`
if force_rebuild || !docker_image_exists(self.base_zkvm_image(CRATE_VERSION, gpu))? {
let mut cmd = DockerBuildCmd::new()
.file(docker_dir.join(self.as_str()).join("Dockerfile.base"))
.tag(self.base_zkvm_image(CRATE_VERSION, gpu))
.tag(self.base_zkvm_image("latest", gpu))
.build_arg("BASE_IMAGE", self.base_image(CRATE_VERSION, gpu))
.build_arg_from_env("RUSTFLAGS");
if gpu {
cmd = cmd.build_arg("CUDA", "1");
let cuda_arch = cuda_arch();
match self {
Self::Airbender | Self::OpenVM | Self::Risc0 | Self::Zisk => {
if let Some(cuda_arch) = cuda_arch {
cmd = cmd.build_arg("CUDA_ARCH", cuda_arch)
}
}
_ => {}
}
}
cmd.exec(&workspace_dir).map_err(Error::DockerBuildCmd)?;
}
// Build `ere-compiler-{zkvm}`
if force_rebuild || !docker_image_exists(self.compiler_zkvm_image(CRATE_VERSION))? {
DockerBuildCmd::new()
.file(docker_dir.join(self.as_str()).join("Dockerfile.compiler"))
.tag(self.compiler_zkvm_image(CRATE_VERSION))
.tag(self.compiler_zkvm_image("latest"))
.build_arg("BASE_ZKVM_IMAGE", self.base_zkvm_image(CRATE_VERSION, gpu))
.exec(&workspace_dir)
.map_err(Error::DockerBuildCmd)?;
}
// Build `ere-server-{zkvm}`
if force_rebuild || !docker_image_exists(self.server_zkvm_image(CRATE_VERSION, gpu))? {
let mut cmd = DockerBuildCmd::new()
.file(docker_dir.join(self.as_str()).join("Dockerfile.server"))
.tag(self.server_zkvm_image(CRATE_VERSION, gpu))
.tag(self.server_zkvm_image("latest", gpu))
.build_arg("BASE_ZKVM_IMAGE", self.base_zkvm_image(CRATE_VERSION, gpu))
.build_arg_from_env("RUSTFLAGS");
if gpu {
cmd = cmd.build_arg("CUDA", "1");
}
cmd.exec(&workspace_dir).map_err(Error::DockerBuildCmd)?;
}
Ok(())
}
fn server_port(&self) -> u16 {
ERE_SERVER_PORT_OFFSET + *self as u16
}
fn spawn_server(
&self,
program: &SerializedProgram,
resource: &ProverResourceType,
) -> Result<ServerContainer, Error> {
let port = self.server_port().to_string();
let name = format!("ere-server-{self}-{port}");
let gpu = matches!(resource, ProverResourceType::Gpu);
let mut cmd = DockerRunCmd::new(self.server_zkvm_image(CRATE_VERSION, gpu))
.rm()
.inherit_env("RUST_LOG")
.inherit_env("NO_COLOR")
.publish(&port, &port)
.name(&name);
// zkVM specific options
cmd = match self {
Self::Risc0 => cmd
.inherit_env("RISC0_SEGMENT_PO2")
.inherit_env("RISC0_KECCAK_PO2"),
// ZisK uses shared memory to exchange data between processes, it
// requires at least 8G shared memory, here we set 16G for safety.
Self::Zisk => cmd
.option("shm-size", "16G")
.option("ulimit", "memlock=-1:-1")
.inherit_env("ZISK_PORT")
.inherit_env("ZISK_CHUNK_SIZE_BITS")
.inherit_env("ZISK_UNLOCK_MAPPED_MEMORY")
.inherit_env("ZISK_MINIMAL_MEMORY")
.inherit_env("ZISK_PREALLOCATE")
.inherit_env("ZISK_SHARED_TABLES")
.inherit_env("ZISK_MAX_STREAMS")
.inherit_env("ZISK_NUMBER_THREADS_WITNESS")
.inherit_env("ZISK_MAX_WITNESS_STORED"),
_ => cmd,
};
// zkVM specific options when using GPU
if gpu {
cmd = match self {
Self::Airbender => cmd.gpus("all"),
Self::OpenVM => cmd.gpus("all"),
// SP1 runs docker command to spin up the server to do GPU
// proving, to give the client access to the prover service, we
// need to use the host networking driver.
Self::SP1 => cmd.mount_docker_socket().network("host"),
Self::Risc0 => cmd.gpus("all").inherit_env("RISC0_DEFAULT_PROVER_NUM_GPUS"),
Self::Zisk => cmd.gpus("all"),
_ => cmd,
}
}
let tempdir =
TempDir::new().map_err(|err| Error::io(err, "Failed to create temporary directory"))?;
// zkVM specific options needed for proving Groth16 proof.
cmd = match self {
// Risc0 and SP1 runs docker command to prove Groth16 proof, and
// they pass the input by mounting temporary directory. Here we
// create a temporary directory and mount it on the top level, so
// the volume could be shared, and override `TMPDIR` so we don't
// need to mount the whole `/tmp`.
Self::Risc0 => cmd
.mount_docker_socket()
.env("TMPDIR", tempdir.path().to_string_lossy())
.volume(tempdir.path(), tempdir.path()),
Self::SP1 => {
let groth16_circuit_path = home_dir().join(".sp1").join("circuits").join("groth16");
cmd.mount_docker_socket()
.env(
"SP1_GROTH16_CIRCUIT_PATH",
groth16_circuit_path.to_string_lossy(),
)
.env("TMPDIR", tempdir.path().to_string_lossy())
.volume(tempdir.path(), tempdir.path())
.volume(&groth16_circuit_path, &groth16_circuit_path)
}
_ => cmd,
};
let args = iter::empty()
.chain(["--port", &port])
.chain(resource.to_args());
cmd.spawn(args, &program.0).map_err(Error::DockerRunCmd)?;
Ok(ServerContainer { name, tempdir })
}
}
impl FromStr for ErezkVM {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(match s {
"airbender" => Self::Airbender,
"jolt" => Self::Jolt,
"miden" => Self::Miden,
"nexus" => Self::Nexus,
"openvm" => Self::OpenVM,
"pico" => Self::Pico,
"risc0" => Self::Risc0,
"sp1" => Self::SP1,
"ziren" => Self::Ziren,
"zisk" => Self::Zisk,
_ => return Err(format!("Unsupported zkvm {s}")),
})
}
}
impl Display for ErezkVM {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_str())
}
}
pub struct EreDockerizedCompiler {
zkvm: ErezkVM,
mount_directory: PathBuf,
}
impl EreDockerizedCompiler {
pub fn new(zkvm: ErezkVM, mount_directory: impl AsRef<Path>) -> Result<Self, Error> {
zkvm.build_docker_image(false)?;
Ok(Self {
zkvm,
mount_directory: mount_directory.as_ref().to_path_buf(),
})
}
pub fn zkvm(&self) -> ErezkVM {
self.zkvm
}
}
/// Wrapper for serialized program.
#[derive(Clone, Serialize, Deserialize)]
pub struct SerializedProgram(Vec<u8>);
impl Compiler for EreDockerizedCompiler {
type Error = Error;
type Program = SerializedProgram;
fn compile(&self, guest_directory: &Path) -> Result<Self::Program, Self::Error> {
let guest_relative_path = guest_directory
.strip_prefix(&self.mount_directory)
.map_err(|_| Error::GuestNotInMountingDirecty {
mounting_directory: self.mount_directory.to_path_buf(),
guest_directory: guest_directory.to_path_buf(),
})?;
let guest_path_in_docker = PathBuf::from("/guest").join(guest_relative_path);
let tempdir =
TempDir::new().map_err(|err| Error::io(err, "Failed to create temporary directory"))?;
let mut cmd = DockerRunCmd::new(self.zkvm.compiler_zkvm_image(CRATE_VERSION))
.rm()
.inherit_env("RUST_LOG")
.inherit_env("NO_COLOR")
.inherit_env("ERE_RUST_TOOLCHAIN")
.volume(&self.mount_directory, "/guest")
.volume(tempdir.path(), "/output");
cmd = match self.zkvm {
// OpenVM allows to select Rust toolchain for guest compilation.
ErezkVM::OpenVM => cmd.inherit_env("OPENVM_RUST_TOOLCHAIN"),
_ => cmd,
};
cmd.exec([
"--guest-path",
guest_path_in_docker.to_string_lossy().as_ref(),
"--output-path",
"/output/program",
])
.map_err(Error::DockerRunCmd)?;
let program_path = tempdir.path().join("program");
let program = fs::read(&program_path).map_err(|err| {
Error::io(
err,
format!(
"Failed to read compiled program at {}",
program_path.display()
),
)
})?;
Ok(SerializedProgram(program))
}
}
struct ServerContainer {
name: String,
#[allow(dead_code)]
tempdir: TempDir,
}
impl Drop for ServerContainer {
fn drop(&mut self) {
if let Err(err) = stop_docker_container(&self.name) {
error!("{err}");
}
}
}
pub struct EreDockerizedzkVM {
zkvm: ErezkVM,
program: SerializedProgram,
resource: ProverResourceType,
#[allow(dead_code)]
server_container: ServerContainer,
client: zkVMClient,
}
impl EreDockerizedzkVM {
pub fn new(
zkvm: ErezkVM,
program: SerializedProgram,
resource: ProverResourceType,
) -> Result<Self, Error> {
zkvm.build_docker_image(matches!(resource, ProverResourceType::Gpu))?;
let server_container = zkvm.spawn_server(&program, &resource)?;
let url = Url::parse(&format!("http://127.0.0.1:{}", zkvm.server_port())).unwrap();
let client = block_on(zkVMClient::new(url))?;
Ok(Self {
zkvm,
program,
resource,
server_container,
client,
})
}
pub fn zkvm(&self) -> ErezkVM {
self.zkvm
}
pub fn program(&self) -> &SerializedProgram {
&self.program
}
pub fn resource(&self) -> &ProverResourceType {
&self.resource
}
}
impl zkVM for EreDockerizedzkVM {
fn execute(&self, input: &[u8]) -> anyhow::Result<(PublicValues, ProgramExecutionReport)> {
let (public_values, report) =
block_on(self.client.execute(input.to_vec())).map_err(Error::from)?;
Ok((public_values, report))
}
fn prove(
&self,
input: &[u8],
proof_kind: ProofKind,
) -> anyhow::Result<(PublicValues, Proof, ProgramProvingReport)> {
let (public_values, proof, report) =
block_on(self.client.prove(input.to_vec(), proof_kind)).map_err(Error::from)?;
Ok((public_values, proof, report))
}
fn verify(&self, proof: &Proof) -> anyhow::Result<PublicValues> {
let public_values = block_on(self.client.verify(proof)).map_err(Error::from)?;
Ok(public_values)
}
fn name(&self) -> &'static str {
self.zkvm.as_str()
}
fn sdk_version(&self) -> &'static str {
self.zkvm.sdk_version()
}
}
fn block_on<T>(future: impl Future<Output = T>) -> T {
match tokio::runtime::Handle::try_current() {
Ok(handle) => tokio::task::block_in_place(|| handle.block_on(future)),
Err(_) => tokio::runtime::Runtime::new().unwrap().block_on(future),
}
}
fn workspace_dir() -> PathBuf {
let mut dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
dir.pop();
dir.pop();
dir.pop();
dir.canonicalize().unwrap()
}
fn home_dir() -> PathBuf {
PathBuf::from(std::env::var("HOME").expect("env `$HOME` should be set"))
}
#[cfg(test)]
mod test {
use crate::{
EreDockerizedCompiler, EreDockerizedzkVM, ErezkVM, Error, SerializedProgram, workspace_dir,
};
use ere_test_utils::{host::*, program::basic::BasicProgramInput};
use ere_zkvm_interface::{
compiler::Compiler,
zkvm::{ProofKind, ProverResourceType, zkVM},
};
use std::sync::{Mutex, MutexGuard, OnceLock};
macro_rules! test_compile {
($zkvm:ident, $program:literal) => {
use super::*;
fn program() -> &'static SerializedProgram {
static PROGRAM: OnceLock<SerializedProgram> = OnceLock::new();
PROGRAM.get_or_init(|| {
let zkvm = ErezkVM::$zkvm;
let guest_directory = testing_guest_directory(zkvm.as_str(), $program);
EreDockerizedCompiler::new(zkvm, workspace_dir())
.unwrap()
.compile(&guest_directory)
.unwrap()
})
}
#[allow(dead_code)]
fn zkvm() -> (MutexGuard<'static, ()>, EreDockerizedzkVM) {
static LOCK: Mutex<()> = Mutex::new(());
let guard = LOCK.lock().unwrap();
let zkvm = ErezkVM::$zkvm;
let zkvm = EreDockerizedzkVM::new(zkvm, program().clone(), ProverResourceType::Cpu)
.unwrap();
(guard, zkvm)
}
#[test]
fn test_compile() {
let program = program();
assert!(!program.0.is_empty(), "Program should not be empty");
}
};
}
macro_rules! test_execute {
($zkvm:ident, $valid_test_case:expr, $invalid_test_cases:expr) => {
#[test]
fn test_execute() {
let (_guard, zkvm) = zkvm();
// Valid test case
run_zkvm_execute(&zkvm, &$valid_test_case);
// Invalid test cases
for input in $invalid_test_cases {
let err = zkvm.execute(&input).unwrap_err();
assert!(matches!(err.downcast::<Error>().unwrap(), Error::zkVM(_)),);
}
drop(zkvm);
}
};
}
macro_rules! test_prove {
($zkvm:ident, $valid_test_case:expr, $invalid_test_cases:expr) => {
#[test]
fn test_prove() {
let (_guard, zkvm) = zkvm();
// Valid test case
run_zkvm_prove(&zkvm, &$valid_test_case);
// Invalid test cases
for input in $invalid_test_cases {
let err = zkvm.prove(&input, ProofKind::default()).unwrap_err();
assert!(matches!(err.downcast::<Error>().unwrap(), Error::zkVM(_)),);
}
drop(zkvm);
}
};
}
mod airbender {
test_compile!(Airbender, "basic");
test_execute!(
Airbender,
BasicProgramInput::valid().into_output_sha256(),
[Vec::new(), BasicProgramInput::invalid().serialized_input()]
);
test_prove!(
Airbender,
BasicProgramInput::valid().into_output_sha256(),
[Vec::new(), BasicProgramInput::invalid().serialized_input()]
);
}
mod jolt {
test_compile!(Jolt, "basic");
test_execute!(
Jolt,
BasicProgramInput::valid(),
[Vec::new(), BasicProgramInput::invalid().serialized_input()]
);
test_prove!(
Jolt,
BasicProgramInput::valid(),
[Vec::new(), BasicProgramInput::invalid().serialized_input()]
);
}
mod miden {
test_compile!(Miden, "fib");
}
mod nexus {
test_compile!(Nexus, "basic");
test_execute!(
Nexus,
BasicProgramInput::valid(),
[Vec::new(), BasicProgramInput::invalid().serialized_input()]
);
test_prove!(
Nexus,
BasicProgramInput::valid(),
[Vec::new(), BasicProgramInput::invalid().serialized_input()]
);
}
mod openvm {
test_compile!(OpenVM, "basic");
test_execute!(
OpenVM,
BasicProgramInput::valid().into_output_sha256(),
[Vec::new(), BasicProgramInput::invalid().serialized_input()]
);
test_prove!(
OpenVM,
BasicProgramInput::valid().into_output_sha256(),
[Vec::new(), BasicProgramInput::invalid().serialized_input()]
);
}
mod pico {
test_compile!(Pico, "basic");
test_execute!(
Pico,
BasicProgramInput::valid(),
[Vec::new(), BasicProgramInput::invalid().serialized_input()]
);
test_prove!(
Pico,
BasicProgramInput::valid(),
[Vec::new(), BasicProgramInput::invalid().serialized_input()]
);
}
mod risc0 {
test_compile!(Risc0, "basic");
test_execute!(
Risc0,
BasicProgramInput::valid(),
[Vec::new(), BasicProgramInput::invalid().serialized_input()]
);
test_prove!(
Risc0,
BasicProgramInput::valid(),
[Vec::new(), BasicProgramInput::invalid().serialized_input()]
);
}
mod sp1 {
test_compile!(SP1, "basic");
test_execute!(
SP1,
BasicProgramInput::valid(),
[Vec::new(), BasicProgramInput::invalid().serialized_input()]
);
test_prove!(
SP1,
BasicProgramInput::valid(),
[Vec::new(), BasicProgramInput::invalid().serialized_input()]
);
}
mod ziren {
test_compile!(Ziren, "basic");
test_execute!(
Ziren,
BasicProgramInput::valid(),
[Vec::new(), BasicProgramInput::invalid().serialized_input()]
);
test_prove!(
Ziren,
BasicProgramInput::valid(),
[Vec::new(), BasicProgramInput::invalid().serialized_input()]
);
}
mod zisk {
test_compile!(Zisk, "basic_rust");
test_execute!(
Zisk,
BasicProgramInput::valid().into_output_sha256(),
[Vec::new(), BasicProgramInput::invalid().serialized_input()]
);
test_prove!(
Zisk,
BasicProgramInput::valid().into_output_sha256(),
[Vec::new(), BasicProgramInput::invalid().serialized_input()]
);
}
}

View File

@@ -0,0 +1,236 @@
use crate::{
CompilerKind, base_image, base_zkvm_image, compiler_zkvm_image,
util::{
docker::{DockerBuildCmd, DockerRunCmd, docker_image_exists, force_rebuild},
workspace_dir,
},
zkVMKind,
};
use ere_zkvm_interface::{CommonError, compiler::Compiler};
use serde::{Deserialize, Serialize};
use std::{
fs,
path::{Path, PathBuf},
};
use tempfile::TempDir;
use tracing::info;
mod error;
pub use error::Error;
/// This method builds 3 Docker images in sequence:
/// 1. `ere-base:{version}` - Base image with common dependencies
/// 2. `ere-base-{zkvm}:{version}` - zkVM-specific base image with the zkVM SDK
/// 3. `ere-compiler-{zkvm}:{version}` - Compiler image with the `ere-compiler`
/// binary built with the selected zkVM feature
///
/// Images are cached and only rebuilt if they don't exist or if the
/// `ERE_FORCE_REBUILD_DOCKER_IMAGE` environment variable is set.
fn build_compiler_image(zkvm_kind: zkVMKind) -> Result<(), Error> {
let workspace_dir = workspace_dir();
let docker_dir = workspace_dir.join("docker");
let docker_zkvm_dir = docker_dir.join(zkvm_kind.as_str());
let force_rebuild = force_rebuild();
let base_image = base_image(zkvm_kind, false);
let base_zkvm_image = base_zkvm_image(zkvm_kind, false);
let compiler_zkvm_image = compiler_zkvm_image(zkvm_kind);
// Build `ere-base`
if force_rebuild || !docker_image_exists(&base_image)? {
info!("Building image {base_image}...");
DockerBuildCmd::new()
.file(docker_dir.join("Dockerfile.base"))
.tag(&base_image)
.exec(&workspace_dir)?;
}
// Build `ere-base-{zkvm_kind}`
if force_rebuild || !docker_image_exists(&base_zkvm_image)? {
info!("Building image {base_zkvm_image}...");
DockerBuildCmd::new()
.file(docker_zkvm_dir.join("Dockerfile.base"))
.tag(&base_zkvm_image)
.build_arg("BASE_IMAGE", &base_image)
.build_arg_from_env("RUSTFLAGS")
.exec(&workspace_dir)?;
}
// Build `ere-compiler-{zkvm_kind}`
if force_rebuild || !docker_image_exists(&compiler_zkvm_image)? {
info!("Building image {compiler_zkvm_image}...");
DockerBuildCmd::new()
.file(docker_zkvm_dir.join("Dockerfile.compiler"))
.tag(&compiler_zkvm_image)
.build_arg("BASE_ZKVM_IMAGE", &base_zkvm_image)
.exec(&workspace_dir)?;
}
Ok(())
}
/// Wrapper for serialized program.
#[derive(Clone, Serialize, Deserialize)]
pub struct SerializedProgram(pub(crate) Vec<u8>);
pub struct DockerizedCompiler {
zkvm_kind: zkVMKind,
compiler_kind: CompilerKind,
mount_directory: PathBuf,
}
impl DockerizedCompiler {
pub fn new(
zkvm_kind: zkVMKind,
compiler_kind: CompilerKind,
mount_directory: impl AsRef<Path>,
) -> Result<Self, Error> {
build_compiler_image(zkvm_kind)?;
Ok(Self {
zkvm_kind,
compiler_kind,
mount_directory: mount_directory.as_ref().to_path_buf(),
})
}
pub fn zkvm_kind(&self) -> zkVMKind {
self.zkvm_kind
}
pub fn compiler_kind(&self) -> CompilerKind {
self.compiler_kind
}
}
impl Compiler for DockerizedCompiler {
type Error = Error;
type Program = SerializedProgram;
fn compile(&self, guest_directory: &Path) -> Result<Self::Program, Self::Error> {
let guest_relative_path = guest_directory
.strip_prefix(&self.mount_directory)
.map_err(|_| Error::GuestNotInMountingDirecty {
mounting_directory: self.mount_directory.to_path_buf(),
guest_directory: guest_directory.to_path_buf(),
})?;
let guest_path_in_docker = PathBuf::from("/guest").join(guest_relative_path);
let tempdir = TempDir::new().map_err(CommonError::tempdir)?;
let mut cmd = DockerRunCmd::new(compiler_zkvm_image(self.zkvm_kind))
.rm()
.inherit_env("RUST_LOG")
.inherit_env("NO_COLOR")
.inherit_env("ERE_RUST_TOOLCHAIN")
.volume(&self.mount_directory, "/guest")
.volume(tempdir.path(), "/output");
cmd = match self.zkvm_kind {
// OpenVM allows to select Rust toolchain for guest compilation.
zkVMKind::OpenVM => cmd.inherit_env("OPENVM_RUST_TOOLCHAIN"),
_ => cmd,
};
cmd.exec([
"--compiler-kind",
self.compiler_kind.as_str(),
"--guest-path",
guest_path_in_docker.to_string_lossy().as_ref(),
"--output-path",
"/output/program",
])?;
let program_path = tempdir.path().join("program");
let program = fs::read(&program_path)
.map_err(|err| CommonError::read_file("program", &program_path, err))?;
Ok(SerializedProgram(program))
}
}
#[cfg(test)]
pub(crate) mod test {
use crate::{
CompilerKind,
compiler::{DockerizedCompiler, SerializedProgram},
util::workspace_dir,
zkVMKind,
};
use ere_test_utils::host::testing_guest_directory;
use ere_zkvm_interface::compiler::Compiler;
pub fn compile(
zkvm_kind: zkVMKind,
compiler_kind: CompilerKind,
program: &'static str,
) -> SerializedProgram {
DockerizedCompiler::new(zkvm_kind, compiler_kind, workspace_dir())
.unwrap()
.compile(&testing_guest_directory(zkvm_kind.as_str(), program))
.unwrap()
}
macro_rules! test_compile {
($zkvm_kind:ident, $compiler_kind:ident, $program:literal) => {
paste::paste! {
#[test]
fn [<test_compile_ $compiler_kind:snake>]() {
let zkvm_kind = crate::zkVMKind::$zkvm_kind;
let compiler_kind = crate::CompilerKind::$compiler_kind;
let program = crate::compiler::test::compile(zkvm_kind, compiler_kind, $program);
assert!(!program.0.is_empty(), "Program should not be empty");
}
}
};
}
mod airbender {
test_compile!(Airbender, Rust, "basic");
}
mod jolt {
test_compile!(Jolt, RustCustomized, "basic");
test_compile!(Jolt, Rust, "stock_nightly_no_std");
}
mod miden {
test_compile!(Miden, MidenAsm, "fib");
}
mod nexus {
test_compile!(Nexus, Rust, "basic");
}
mod openvm {
test_compile!(OpenVM, RustCustomized, "basic");
test_compile!(OpenVM, Rust, "stock_nightly_no_std");
}
mod pico {
test_compile!(Pico, RustCustomized, "basic");
test_compile!(Pico, Rust, "stock_nightly_no_std");
}
mod risc0 {
test_compile!(Risc0, RustCustomized, "basic");
test_compile!(Risc0, Rust, "stock_nightly_no_std");
}
mod sp1 {
test_compile!(SP1, RustCustomized, "basic");
test_compile!(SP1, Rust, "stock_nightly_no_std");
}
mod ziren {
test_compile!(Ziren, RustCustomized, "basic");
}
mod zisk {
test_compile!(Zisk, RustCustomized, "basic_rust");
test_compile!(Zisk, GoCustomized, "basic_go");
}
}

View File

@@ -0,0 +1,16 @@
use ere_zkvm_interface::CommonError;
use std::path::PathBuf;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum Error {
#[error(transparent)]
CommonError(#[from] CommonError),
#[error(
"Guest directory must be in mounting directory, mounting_directory: {mounting_directory}, guest_directory: {guest_directory}"
)]
GuestNotInMountingDirecty {
mounting_directory: PathBuf,
guest_directory: PathBuf,
},
}

View File

@@ -0,0 +1,179 @@
//! # Ere Dockerized
//!
//! A Docker-based wrapper for other zkVM crates `ere-{zkvm}`.
//!
//! This crate provides a unified interface to dockerize the `Compiler` and
//! `zkVM` implementation of other zkVM crates `ere-{zkvm}`, it requires only
//! `docker` to be installed, but no zkVM specific SDK.
//!
//! ## Docker image building
//!
//! It builds 4 Docker images in sequence if they don't exist:
//! 1. `ere-base:{version}` - Base image with common dependencies
//! 2. `ere-base-{zkvm}:{version}` - zkVM-specific base image with the zkVM SDK
//! 3. `ere-compiler-{zkvm}:{version}` - Compiler image with the `ere-compiler`
//! binary built with the selected zkVM feature
//! 4. `ere-server-{zkvm}:{version}` - Server image with the `ere-server` binary
//! built with the selected zkVM feature
//!
//! When [`ProverResourceType::Gpu`] is selected, the image with GPU support
//! will be built and tagged with specific suffix.
//!
//! To force rebuild all images, set the environment variable
//! `ERE_FORCE_REBUILD_DOCKER_IMAGE` to non-empty value.
//!
//! ## Example
//!
//! ```rust,no_run
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
//! use ere_dockerized::{CompilerKind, DockerizedCompiler, DockerizedzkVM, zkVMKind};
//! use ere_zkvm_interface::{
//! compiler::Compiler,
//! zkvm::{ProofKind, ProverResourceType, zkVM},
//! };
//! use std::path::Path;
//!
//! // The zkVM we plan to use
//! let zkvm_kind = zkVMKind::SP1;
//!
//! // The compiler we plan to use
//! let compiler_kind = CompilerKind::RustCustomized;
//!
//! // Compile a guest program
//! let compiler = DockerizedCompiler::new(zkvm_kind, compiler_kind, "mounting/directory")?;
//! let guest_path = Path::new("relative/path/to/guest/program");
//! let program = compiler.compile(&guest_path)?;
//!
//! // Create zkVM instance
//! let resource = ProverResourceType::Cpu;
//! let zkvm = DockerizedzkVM::new(zkvm_kind, program, resource)?;
//!
//! // Serialize input
//! let input = 42u32.to_le_bytes();
//!
//! // Execute program
//! let (public_values, execution_report) = zkvm.execute(&input)?;
//! println!("Execution cycles: {}", execution_report.total_num_cycles);
//!
//! // Generate proof
//! let (public_values, proof, proving_report) = zkvm.prove(&input, ProofKind::Compressed)?;
//! println!("Proof generated in: {:?}", proving_report.proving_time);
//!
//! // Verify proof
//! let public_values = zkvm.verify(&proof)?;
//! println!("Proof verified successfully!");
//! # Ok(())
//! # }
//! ```
#![cfg_attr(not(test), warn(unused_crate_dependencies))]
use std::{
fmt::{self, Display, Formatter},
str::FromStr,
};
mod util;
pub mod compiler;
pub mod zkvm;
pub use crate::{
compiler::{DockerizedCompiler, SerializedProgram},
zkvm::DockerizedzkVM,
};
pub use ere_compiler::CompilerKind;
include!(concat!(env!("OUT_DIR"), "/crate_version.rs"));
include!(concat!(env!("OUT_DIR"), "/zkvm_sdk_version_impl.rs"));
#[allow(non_camel_case_types)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum zkVMKind {
Airbender,
Jolt,
Miden,
Nexus,
OpenVM,
Pico,
Risc0,
SP1,
Ziren,
Zisk,
}
impl zkVMKind {
pub fn as_str(&self) -> &'static str {
match self {
Self::Airbender => "airbender",
Self::Jolt => "jolt",
Self::Miden => "miden",
Self::Nexus => "nexus",
Self::OpenVM => "openvm",
Self::Pico => "pico",
Self::Risc0 => "risc0",
Self::SP1 => "sp1",
Self::Ziren => "ziren",
Self::Zisk => "zisk",
}
}
}
impl FromStr for zkVMKind {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(match s {
"airbender" => Self::Airbender,
"jolt" => Self::Jolt,
"miden" => Self::Miden,
"nexus" => Self::Nexus,
"openvm" => Self::OpenVM,
"pico" => Self::Pico,
"risc0" => Self::Risc0,
"sp1" => Self::SP1,
"ziren" => Self::Ziren,
"zisk" => Self::Zisk,
_ => return Err(format!("Unsupported zkvm kind {s}")),
})
}
}
impl Display for zkVMKind {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_str())
}
}
/// Tag of images in format of `{version}{suffix}`.
fn image_tag(zkvm_kind: zkVMKind, gpu: bool) -> String {
let suffix = match (zkvm_kind, gpu) {
// Only the following zkVMs requires CUDA setup in the base image
// when GPU support is required.
(zkVMKind::Airbender | zkVMKind::OpenVM | zkVMKind::Risc0 | zkVMKind::Zisk, true) => {
"-cuda"
}
_ => "",
};
format!("{CRATE_VERSION}{suffix}")
}
fn base_image(zkvm_kind: zkVMKind, gpu: bool) -> String {
let image_tag = image_tag(zkvm_kind, gpu);
format!("ere-base:{image_tag}")
}
fn base_zkvm_image(zkvm_kind: zkVMKind, gpu: bool) -> String {
let image_tag = image_tag(zkvm_kind, gpu);
format!("ere-base-{zkvm_kind}:{image_tag}")
}
fn server_zkvm_image(zkvm_kind: zkVMKind, gpu: bool) -> String {
let image_tag = image_tag(zkvm_kind, gpu);
format!("ere-server-{zkvm_kind}:{image_tag}")
}
fn compiler_zkvm_image(zkvm_kind: zkVMKind) -> String {
let image_tag = image_tag(zkvm_kind, false);
format!("ere-compiler-{zkvm_kind}:{image_tag}")
}

View File

@@ -0,0 +1,15 @@
use std::path::PathBuf;
pub mod cuda;
pub mod docker;
pub fn workspace_dir() -> PathBuf {
let mut dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
dir.pop();
dir.pop();
dir.canonicalize().unwrap()
}
pub fn home_dir() -> PathBuf {
PathBuf::from(std::env::var("HOME").expect("env `$HOME` should be set"))
}

View File

@@ -1,16 +1,17 @@
use crate::error::Error;
use ere_zkvm_interface::CommonError;
use std::{
env,
fmt::{self, Display, Formatter},
io::{self, Write},
io::Write,
path::Path,
process::{Child, Command, Stdio},
};
use tracing::debug;
pub const DOCKER_SOCKET: &str = "/var/run/docker.sock";
#[derive(Clone)]
pub struct CmdOption(String, Option<String>);
struct CmdOption(String, Option<String>);
impl CmdOption {
pub fn new(key: impl AsRef<str>, value: impl AsRef<str>) -> Self {
@@ -78,7 +79,7 @@ impl DockerBuildCmd {
}
}
pub fn exec(self, context: impl AsRef<Path>) -> Result<(), io::Error> {
pub fn exec(self, context: impl AsRef<Path>) -> Result<(), CommonError> {
let mut cmd = Command::new("docker");
cmd.arg("build");
for option in self.options {
@@ -86,12 +87,14 @@ impl DockerBuildCmd {
}
cmd.arg(context.as_ref().to_string_lossy().to_string());
let status = cmd.status()?;
debug!("Docker build with command: {cmd:?}");
let status = cmd
.status()
.map_err(|err| CommonError::command(&cmd, err))?;
if !status.success() {
return Err(io::Error::other(format!(
"Command {cmd:?} failed with status: {status}",
)));
Err(CommonError::command_exit_non_zero(&cmd, status, None))?
}
Ok(())
@@ -177,7 +180,7 @@ impl DockerRunCmd {
mut self,
commands: impl IntoIterator<Item: AsRef<str>>,
stdin: &[u8],
) -> Result<Child, io::Error> {
) -> Result<Child, CommonError> {
self = self.flag("interactive");
let mut cmd = Command::new("docker");
@@ -190,15 +193,25 @@ impl DockerRunCmd {
cmd.arg(command.as_ref());
}
let mut child = cmd.stdin(Stdio::piped()).spawn()?;
debug!("Docker run with command: {cmd:?}");
let mut child = cmd
.stdin(Stdio::piped())
.spawn()
.map_err(|err| CommonError::command(&cmd, err))?;
// Write all to stdin then drop to close the pipe.
child.stdin.take().unwrap().write_all(stdin)?;
child
.stdin
.take()
.unwrap()
.write_all(stdin)
.map_err(|err| CommonError::command(&cmd, err))?;
Ok(child)
}
pub fn exec(self, commands: impl IntoIterator<Item: AsRef<str>>) -> Result<(), io::Error> {
pub fn exec(self, commands: impl IntoIterator<Item: AsRef<str>>) -> Result<(), CommonError> {
let mut cmd = Command::new("docker");
cmd.arg("run");
for option in self.options {
@@ -209,43 +222,61 @@ impl DockerRunCmd {
cmd.arg(command.as_ref());
}
let status = cmd.status()?;
debug!("Docker run with command: {cmd:?}");
let status = cmd
.status()
.map_err(|err| CommonError::command(&cmd, err))?;
if !status.success() {
return Err(io::Error::other(format!(
"Command {cmd:?} failed with status: {status}",
)));
Err(CommonError::command_exit_non_zero(&cmd, status, None))?
}
Ok(())
}
}
pub fn stop_docker_container(container_name: impl AsRef<str>) -> Result<(), Error> {
let output = Command::new("docker")
pub fn stop_docker_container(container_name: impl AsRef<str>) -> Result<(), CommonError> {
let mut cmd = Command::new("docker");
let output = cmd
.args(["container", "stop", container_name.as_ref()])
.output()
.map_err(Error::DockerContainerCmd)?;
.map_err(|err| CommonError::command(&cmd, err))?;
if String::from_utf8_lossy(&output.stdout).starts_with("Error") {
return Err(Error::DockerContainerCmd(io::Error::other(format!(
"Failed to stop container {}",
container_name.as_ref()
))));
if !output.status.success() {
Err(CommonError::command_exit_non_zero(
&cmd,
output.status,
Some(&output),
))?
}
Ok(())
}
pub fn docker_image_exists(image: impl AsRef<str>) -> Result<bool, Error> {
let output = Command::new("docker")
pub fn docker_image_exists(image: impl AsRef<str>) -> Result<bool, CommonError> {
let mut cmd = Command::new("docker");
let output = cmd
.args(["images", "--quiet", image.as_ref()])
.output()
.map_err(Error::DockerImageCmd)?;
.map_err(|err| CommonError::command(&cmd, err))?;
if !output.status.success() {
Err(CommonError::command_exit_non_zero(
&cmd,
output.status,
Some(&output),
))?
}
// If image exists, image id will be printed hence stdout will be non-empty.
Ok(!output.stdout.is_empty())
}
pub fn force_rebuild() -> bool {
env::var_os("ERE_FORCE_REBUILD_DOCKER_IMAGE").is_some()
}
fn to_string(s: impl AsRef<str>) -> String {
s.as_ref().to_string()
}

View File

@@ -0,0 +1,467 @@
use crate::{
base_image, base_zkvm_image,
compiler::SerializedProgram,
server_zkvm_image,
util::{
cuda::cuda_arch,
docker::{
DockerBuildCmd, DockerRunCmd, docker_image_exists, force_rebuild, stop_docker_container,
},
home_dir, workspace_dir,
},
zkVMKind,
};
use ere_server::client::{Url, zkVMClient};
use ere_zkvm_interface::{
CommonError,
zkvm::{
ProgramExecutionReport, ProgramProvingReport, Proof, ProofKind, ProverResourceType,
PublicValues, zkVM,
},
};
use std::iter;
use tempfile::TempDir;
use tracing::{error, info};
mod error;
pub use error::Error;
/// This method builds 3 Docker images in sequence:
/// 1. `ere-base:{version}` - Base image with common dependencies
/// 2. `ere-base-{zkvm}:{version}` - zkVM-specific base image with the zkVM SDK
/// 3. `ere-server-{zkvm}:{version}` - Server image with the `ere-server` binary
/// built with the selected zkVM feature
///
/// When [`ProverResourceType::Gpu`] is selected, the image with GPU support
/// will be built and tagged with specific suffix.
///
/// Images are cached and only rebuilt if they don't exist or if the
/// `ERE_FORCE_REBUILD_DOCKER_IMAGE` environment variable is set.
fn build_server_image(zkvm_kind: zkVMKind, gpu: bool) -> Result<(), Error> {
let workspace_dir = workspace_dir();
let docker_dir = workspace_dir.join("docker");
let docker_zkvm_dir = docker_dir.join(zkvm_kind.as_str());
let force_rebuild = force_rebuild();
let base_image = base_image(zkvm_kind, gpu);
let base_zkvm_image = base_zkvm_image(zkvm_kind, gpu);
let server_zkvm_image = server_zkvm_image(zkvm_kind, gpu);
// Build `ere-base`
if force_rebuild || !docker_image_exists(&base_image)? {
info!("Building image {base_image}...");
let mut cmd = DockerBuildCmd::new()
.file(docker_dir.join("Dockerfile.base"))
.tag(&base_image);
if gpu {
cmd = cmd.build_arg("CUDA", "1");
}
cmd.exec(&workspace_dir)?;
}
// Build `ere-base-{zkvm_kind}`
if force_rebuild || !docker_image_exists(&base_zkvm_image)? {
info!("Building image {base_zkvm_image}...");
let mut cmd = DockerBuildCmd::new()
.file(docker_zkvm_dir.join("Dockerfile.base"))
.tag(&base_zkvm_image)
.build_arg("BASE_IMAGE", &base_image)
.build_arg_from_env("RUSTFLAGS");
if gpu {
cmd = cmd.build_arg("CUDA", "1");
match zkvm_kind {
zkVMKind::Airbender | zkVMKind::OpenVM | zkVMKind::Risc0 | zkVMKind::Zisk => {
if let Some(cuda_arch) = cuda_arch() {
cmd = cmd.build_arg("CUDA_ARCH", cuda_arch)
}
}
_ => {}
}
}
cmd.exec(&workspace_dir)?;
}
// Build `ere-server-{zkvm_kind}`
if force_rebuild || !docker_image_exists(&server_zkvm_image)? {
info!("Building image {server_zkvm_image}...");
let mut cmd = DockerBuildCmd::new()
.file(docker_zkvm_dir.join("Dockerfile.server"))
.tag(&server_zkvm_image)
.build_arg("BASE_ZKVM_IMAGE", &base_zkvm_image)
.build_arg_from_env("RUSTFLAGS");
if gpu {
cmd = cmd.build_arg("CUDA", "1");
}
cmd.exec(&workspace_dir)?;
}
Ok(())
}
struct ServerContainer {
name: String,
port: u16,
#[allow(dead_code)]
tempdir: TempDir,
}
impl Drop for ServerContainer {
fn drop(&mut self) {
if let Err(err) = stop_docker_container(&self.name) {
error!("Failed to stop docker container: {err}");
}
}
}
impl ServerContainer {
/// Offset of port used for `ere-server`.
const PORT_OFFSET: u16 = 4174;
fn new(
zkvm_kind: zkVMKind,
program: &SerializedProgram,
resource: &ProverResourceType,
) -> Result<Self, Error> {
let port = Self::PORT_OFFSET + zkvm_kind as u16;
let name = format!("ere-server-{zkvm_kind}-{port}");
let gpu = matches!(resource, ProverResourceType::Gpu);
let mut cmd = DockerRunCmd::new(server_zkvm_image(zkvm_kind, gpu))
.rm()
.inherit_env("RUST_LOG")
.inherit_env("NO_COLOR")
.publish(port.to_string(), port.to_string())
.name(&name);
// zkVM specific options
cmd = match zkvm_kind {
zkVMKind::Risc0 => cmd
.inherit_env("RISC0_SEGMENT_PO2")
.inherit_env("RISC0_KECCAK_PO2"),
// ZisK uses shared memory to exchange data between processes, it
// requires at least 8G shared memory, here we set 16G for safety.
zkVMKind::Zisk => cmd
.option("shm-size", "16G")
.option("ulimit", "memlock=-1:-1")
.inherit_env("ZISK_PORT")
.inherit_env("ZISK_CHUNK_SIZE_BITS")
.inherit_env("ZISK_UNLOCK_MAPPED_MEMORY")
.inherit_env("ZISK_MINIMAL_MEMORY")
.inherit_env("ZISK_PREALLOCATE")
.inherit_env("ZISK_SHARED_TABLES")
.inherit_env("ZISK_MAX_STREAMS")
.inherit_env("ZISK_NUMBER_THREADS_WITNESS")
.inherit_env("ZISK_MAX_WITNESS_STORED"),
_ => cmd,
};
// zkVM specific options when using GPU
if gpu {
cmd = match zkvm_kind {
zkVMKind::Airbender => cmd.gpus("all"),
zkVMKind::OpenVM => cmd.gpus("all"),
// SP1 runs docker command to spin up the server to do GPU
// proving, to give the client access to the prover service, we
// need to use the host networking driver.
zkVMKind::SP1 => cmd.mount_docker_socket().network("host"),
zkVMKind::Risc0 => cmd.gpus("all").inherit_env("RISC0_DEFAULT_PROVER_NUM_GPUS"),
zkVMKind::Zisk => cmd.gpus("all"),
_ => cmd,
}
}
let tempdir = TempDir::new().map_err(CommonError::tempdir)?;
// zkVM specific options needed for proving Groth16 proof.
cmd = match zkvm_kind {
// Risc0 and SP1 runs docker command to prove Groth16 proof, and
// they pass the input by mounting temporary directory. Here we
// create a temporary directory and mount it on the top level, so
// the volume could be shared, and override `TMPDIR` so we don't
// need to mount the whole `/tmp`.
zkVMKind::Risc0 => cmd
.mount_docker_socket()
.env("TMPDIR", tempdir.path().to_string_lossy())
.volume(tempdir.path(), tempdir.path()),
zkVMKind::SP1 => {
let groth16_circuit_path = home_dir().join(".sp1").join("circuits").join("groth16");
cmd.mount_docker_socket()
.env(
"SP1_GROTH16_CIRCUIT_PATH",
groth16_circuit_path.to_string_lossy(),
)
.env("TMPDIR", tempdir.path().to_string_lossy())
.volume(tempdir.path(), tempdir.path())
.volume(&groth16_circuit_path, &groth16_circuit_path)
}
_ => cmd,
};
cmd.spawn(
iter::empty()
.chain(["--port", &port.to_string()])
.chain(resource.to_args()),
&program.0,
)?;
Ok(ServerContainer {
name,
port,
tempdir,
})
}
fn endpoint(&self) -> Url {
Url::parse(&format!("http://127.0.0.1:{}", self.port)).unwrap()
}
}
pub struct DockerizedzkVM {
zkvm_kind: zkVMKind,
program: SerializedProgram,
resource: ProverResourceType,
#[allow(dead_code)]
server_container: ServerContainer,
client: zkVMClient,
}
impl DockerizedzkVM {
pub fn new(
zkvm_kind: zkVMKind,
program: SerializedProgram,
resource: ProverResourceType,
) -> Result<Self, Error> {
build_server_image(zkvm_kind, matches!(resource, ProverResourceType::Gpu))?;
let server_container = ServerContainer::new(zkvm_kind, &program, &resource)?;
let client = block_on(zkVMClient::new(server_container.endpoint()))?;
Ok(Self {
zkvm_kind,
program,
resource,
server_container,
client,
})
}
pub fn zkvm_kind(&self) -> zkVMKind {
self.zkvm_kind
}
pub fn program(&self) -> &SerializedProgram {
&self.program
}
pub fn resource(&self) -> &ProverResourceType {
&self.resource
}
}
impl zkVM for DockerizedzkVM {
fn execute(&self, input: &[u8]) -> anyhow::Result<(PublicValues, ProgramExecutionReport)> {
let (public_values, report) =
block_on(self.client.execute(input.to_vec())).map_err(Error::from)?;
Ok((public_values, report))
}
fn prove(
&self,
input: &[u8],
proof_kind: ProofKind,
) -> anyhow::Result<(PublicValues, Proof, ProgramProvingReport)> {
let (public_values, proof, report) =
block_on(self.client.prove(input.to_vec(), proof_kind)).map_err(Error::from)?;
Ok((public_values, proof, report))
}
fn verify(&self, proof: &Proof) -> anyhow::Result<PublicValues> {
let public_values = block_on(self.client.verify(proof)).map_err(Error::from)?;
Ok(public_values)
}
fn name(&self) -> &'static str {
self.zkvm_kind.as_str()
}
fn sdk_version(&self) -> &'static str {
self.zkvm_kind.sdk_version()
}
}
fn block_on<T>(future: impl Future<Output = T>) -> T {
match tokio::runtime::Handle::try_current() {
Ok(handle) => tokio::task::block_in_place(|| handle.block_on(future)),
Err(_) => tokio::runtime::Runtime::new().unwrap().block_on(future),
}
}
#[cfg(test)]
mod test {
use crate::{
CompilerKind,
compiler::test::compile,
zkVMKind,
zkvm::{DockerizedzkVM, Error},
};
use ere_test_utils::{host::*, program::basic::BasicProgramInput};
use ere_zkvm_interface::zkvm::{ProofKind, ProverResourceType, zkVM};
fn zkvm(
zkvm_kind: zkVMKind,
compiler_kind: CompilerKind,
program: &'static str,
) -> DockerizedzkVM {
let program = compile(zkvm_kind, compiler_kind, program).clone();
DockerizedzkVM::new(zkvm_kind, program, ProverResourceType::Cpu).unwrap()
}
macro_rules! test {
($zkvm_kind:ident, $compiler_kind:ident, $program:literal, $valid_test_cases:expr, $invalid_test_cases:expr) => {
#[test]
fn test_execute() {
let zkvm = zkvm(zkVMKind::$zkvm_kind, CompilerKind::$compiler_kind, $program);
// Valid test cases
for test_case in $valid_test_cases {
run_zkvm_execute(&zkvm, &test_case);
}
// Invalid test cases
for input in $invalid_test_cases {
let err = zkvm.execute(&input).unwrap_err();
assert!(matches!(err.downcast::<Error>().unwrap(), Error::zkVM(_)));
}
}
#[test]
fn test_prove() {
let zkvm = zkvm(zkVMKind::$zkvm_kind, CompilerKind::$compiler_kind, $program);
// Valid test cases
for test_case in $valid_test_cases {
run_zkvm_prove(&zkvm, &test_case);
}
// Invalid test cases
for input in $invalid_test_cases {
let err = zkvm.prove(&input, ProofKind::default()).unwrap_err();
assert!(matches!(err.downcast::<Error>().unwrap(), Error::zkVM(_)));
}
}
};
}
mod airbender {
use super::*;
test!(
Airbender,
Rust,
"basic",
[BasicProgramInput::valid().into_output_sha256()],
[Vec::new(), BasicProgramInput::invalid().serialized_input()]
);
}
mod jolt {
use super::*;
test!(
Jolt,
RustCustomized,
"basic",
[BasicProgramInput::valid()],
[Vec::new(), BasicProgramInput::invalid().serialized_input()]
);
}
mod nexus {
use super::*;
test!(
Nexus,
Rust,
"basic",
[BasicProgramInput::valid()],
[Vec::new(), BasicProgramInput::invalid().serialized_input()]
);
}
mod openvm {
use super::*;
test!(
OpenVM,
RustCustomized,
"basic",
[BasicProgramInput::valid().into_output_sha256()],
[Vec::new(), BasicProgramInput::invalid().serialized_input()]
);
}
mod pico {
use super::*;
test!(
Pico,
RustCustomized,
"basic",
[BasicProgramInput::valid()],
[Vec::new(), BasicProgramInput::invalid().serialized_input()]
);
}
mod risc0 {
use super::*;
test!(
Risc0,
RustCustomized,
"basic",
[BasicProgramInput::valid()],
[Vec::new(), BasicProgramInput::invalid().serialized_input()]
);
}
mod sp1 {
use super::*;
test!(
SP1,
RustCustomized,
"basic",
[BasicProgramInput::valid()],
[Vec::new(), BasicProgramInput::invalid().serialized_input()]
);
}
mod ziren {
use super::*;
test!(
Ziren,
RustCustomized,
"basic",
[BasicProgramInput::valid()],
[Vec::new(), BasicProgramInput::invalid().serialized_input()]
);
}
mod zisk {
use super::*;
test!(
Zisk,
RustCustomized,
"basic_rust",
[BasicProgramInput::valid().into_output_sha256()],
[Vec::new(), BasicProgramInput::invalid().serialized_input()]
);
}
}

View File

@@ -0,0 +1,26 @@
use ere_server::client::{self, TwirpErrorResponse};
use ere_zkvm_interface::CommonError;
use thiserror::Error;
impl From<client::Error> for Error {
fn from(value: client::Error) -> Self {
match value {
client::Error::zkVM(err) => Self::zkVM(err),
client::Error::ConnectionTimeout => Self::ConnectionTimeout,
client::Error::Rpc(err) => Self::Rpc(err),
}
}
}
#[derive(Debug, Error)]
#[allow(non_camel_case_types)]
pub enum Error {
#[error(transparent)]
CommonError(#[from] CommonError),
#[error("zkVM method error: {0}")]
zkVM(String),
#[error("Connection to zkVM server timeout after 5 minutes")]
ConnectionTimeout,
#[error("RPC to zkVM server error: {0}")]
Rpc(TwirpErrorResponse),
}

View File

@@ -80,12 +80,12 @@ impl CommonError {
pub fn read_file(id: impl AsRef<str>, path: impl AsRef<Path>, err: io::Error) -> Self {
let (id, path) = (id.as_ref(), path.as_ref().display());
Self::io(format!("Failed to write {id} to {path}"), err)
Self::io(format!("Failed to read {id} from {path}"), err)
}
pub fn write_file(id: impl AsRef<str>, path: impl AsRef<Path>, err: io::Error) -> Self {
let (id, path) = (id.as_ref(), path.as_ref().display());
Self::io(format!("Failed to read {id} from {path}"), err)
Self::io(format!("Failed to write {id} to {path}"), err)
}
pub fn serialize(

View File

@@ -14,11 +14,11 @@ toml.workspace = true
# OpenVM dependencies
openvm-build.workspace = true
openvm-circuit.workspace = true
openvm-continuations.workspace = true
openvm-sdk = { workspace = true, features = ["nightly-features"] }
openvm-stark-sdk.workspace = true
openvm-transpiler.workspace = true
openvm-circuit = { workspace = true, optional = true }
openvm-continuations = { workspace = true, optional = true }
openvm-sdk = { workspace = true, features = ["nightly-features"], optional = true }
openvm-stark-sdk = { workspace = true, optional = true }
openvm-transpiler = { workspace = true, optional = true }
# Local dependencies
ere-compile-utils = { workspace = true, optional = true }
@@ -33,7 +33,13 @@ ere-build-utils.workspace = true
[features]
default = ["compiler", "zkvm"]
compiler = ["dep:ere-compile-utils"]
zkvm = []
zkvm = [
"dep:openvm-circuit",
"dep:openvm-continuations",
"dep:openvm-sdk",
"dep:openvm-stark-sdk",
"dep:openvm-transpiler",
]
cuda = ["openvm-sdk/cuda"]
[lints]

View File

@@ -1,6 +1,4 @@
use ere_compile_utils::CommonError;
use openvm_sdk::config::{AppConfig, DEFAULT_APP_LOG_BLOWUP, DEFAULT_LEAF_LOG_BLOWUP, SdkVmConfig};
use openvm_stark_sdk::config::FriParameters;
use std::{fs, path::Path};
mod error;
@@ -11,30 +9,14 @@ pub use error::Error;
pub use rust_rv32ima::RustRv32ima;
pub use rust_rv32ima_customized::RustRv32imaCustomized;
fn read_app_config(app_config_path: impl AsRef<Path>) -> Result<AppConfig<SdkVmConfig>, Error> {
Ok(if app_config_path.as_ref().exists() {
let toml = fs::read_to_string(app_config_path.as_ref())
.map_err(|err| CommonError::read_file("app_config", &app_config_path, err))?;
toml::from_str(&toml).map_err(|err| CommonError::deserialize("app_config", "toml", err))?
} else {
// The default `AppConfig` copied from https://github.com/openvm-org/openvm/blob/v1.4.1/crates/cli/src/default.rs#L35.
AppConfig {
app_fri_params: FriParameters::standard_with_100_bits_conjectured_security(
DEFAULT_APP_LOG_BLOWUP,
)
.into(),
// By default it supports RISCV32IM with IO but no precompiles.
app_vm_config: SdkVmConfig::builder()
.system(Default::default())
.rv32i(Default::default())
.rv32m(Default::default())
.io(Default::default())
.build(),
leaf_fri_params: FriParameters::standard_with_100_bits_conjectured_security(
DEFAULT_LEAF_LOG_BLOWUP,
)
.into(),
compiler_options: Default::default(),
}
})
fn read_app_config(app_config_path: impl AsRef<Path>) -> Result<Option<String>, Error> {
if !app_config_path.as_ref().exists() {
return Ok(None);
}
let value = fs::read_to_string(app_config_path.as_ref())
.map_err(|err| CommonError::read_file("app_config", &app_config_path, err))?;
toml::from_str::<toml::Value>(&value)
.map_err(|err| CommonError::deserialize("app_config", "toml", err))?;
Ok(Some(value))
}

View File

@@ -1,11 +1,10 @@
use openvm_sdk::config::{AppConfig, SdkVmConfig};
use serde::{Deserialize, Serialize};
/// OpenVM program that contains ELF of compiled guest and app config.
#[derive(Clone, Serialize, Deserialize)]
pub struct OpenVMProgram {
pub(crate) elf: Vec<u8>,
pub(crate) app_config: AppConfig<SdkVmConfig>,
pub(crate) app_config: Option<String>,
}
impl OpenVMProgram {
@@ -13,7 +12,7 @@ impl OpenVMProgram {
&self.elf
}
pub fn app_config(&self) -> &AppConfig<SdkVmConfig> {
&self.app_config
pub fn app_config(&self) -> Option<&str> {
self.app_config.as_deref()
}
}

View File

@@ -10,11 +10,11 @@ use openvm_sdk::{
CpuSdk, F, SC, StdIn,
codec::{Decode, Encode},
commit::AppExecutionCommit,
config::{AppConfig, SdkVmConfig},
config::{AppConfig, DEFAULT_APP_LOG_BLOWUP, DEFAULT_LEAF_LOG_BLOWUP, SdkVmConfig},
fs::read_object_from_file,
keygen::{AggProvingKey, AggVerifyingKey, AppProvingKey},
};
use openvm_stark_sdk::openvm_stark_backend::p3_field::PrimeField32;
use openvm_stark_sdk::{config::FriParameters, openvm_stark_backend::p3_field::PrimeField32};
use openvm_transpiler::{elf::Elf, openvm_platform::memory::MEM_SIZE};
use std::{env, path::PathBuf, sync::Arc, time::Instant};
@@ -49,7 +49,31 @@ impl EreOpenVM {
_ => {}
}
let sdk = CpuSdk::new(program.app_config().clone()).map_err(Error::SdkInit)?;
let app_config = if let Some(value) = program.app_config() {
toml::from_str(value).map_err(Error::InvalidAppConfig)?
} else {
// The default `AppConfig` copied from https://github.com/openvm-org/openvm/blob/v1.4.1/crates/cli/src/default.rs#L35.
AppConfig {
app_fri_params: FriParameters::standard_with_100_bits_conjectured_security(
DEFAULT_APP_LOG_BLOWUP,
)
.into(),
// By default it supports RISCV32IM with IO but no precompiles.
app_vm_config: SdkVmConfig::builder()
.system(Default::default())
.rv32i(Default::default())
.rv32m(Default::default())
.io(Default::default())
.build(),
leaf_fri_params: FriParameters::standard_with_100_bits_conjectured_security(
DEFAULT_LEAF_LOG_BLOWUP,
)
.into(),
compiler_options: Default::default(),
}
};
let sdk = CpuSdk::new(app_config.clone()).map_err(Error::SdkInit)?;
let elf = Elf::decode(program.elf(), MEM_SIZE as u32).map_err(Error::ElfDecode)?;
@@ -69,7 +93,7 @@ impl EreOpenVM {
.app_commit();
Ok(Self {
app_config: program.app_config,
app_config,
app_exe,
app_pk,
agg_pk,

View File

@@ -8,6 +8,9 @@ pub enum Error {
CommonError(#[from] CommonError),
// Common
#[error("Invalid AppConfig: {0}")]
InvalidAppConfig(toml::de::Error),
#[error("Initialize SDK failed: {0}")]
SdkInit(SdkError),

View File

@@ -44,13 +44,6 @@ ENV RUSTUP_HOME=/usr/local/rustup \
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain ${RUST_VERSION} --no-modify-path
# Copy the TamaGo installer script from the workspace context
COPY --chmod=755 scripts/install_tamago.sh /tmp/install_tamago.sh
# Run the TamaGo installation script.
RUN /tmp/install_tamago.sh && \
rm /tmp/install_tamago.sh
# Add a non-root user for subsequent stages or use in derived images
# This is generally best practice.
ARG USERNAME=ere_user

View File

@@ -1,16 +1,11 @@
ARG BASE_IMAGE=ere-base:latest
ARG BASE_CUDA_IMAGE=ere-base:latest-cuda
# Whether to enable CUDA feature or not.
ARG CUDA
FROM $BASE_IMAGE AS base
FROM $BASE_CUDA_IMAGE AS base_cuda
FROM base${CUDA:+_cuda}
FROM $BASE_IMAGE
# Set default toolchain to nightly
RUN rustup default nightly
# Whether to enable CUDA feature or not.
ARG CUDA
# Default to build for RTX 50 series

View File

@@ -9,7 +9,7 @@ WORKDIR /ere
RUN cargo build --release --package ere-compiler --bin ere-compiler --features airbender \
&& mkdir bin && mv target/release/ere-compiler bin/ere-compiler \
&& cargo clean && rm -rf $CARGO_HOME/registry/src $CARGO_HOME/registry/cache
&& cargo clean && rm -rf $CARGO_HOME/registry/
FROM $RUNTIME_IMAGE AS runtime_stage

View File

@@ -1,14 +1,11 @@
ARG BASE_ZKVM_IMAGE=ere-base-airbender:latest
ARG BASE_ZKVM_CUDA_IMAGE=ere-base-airbender:latest-cuda
ARG RUNTIME_IMAGE=ubuntu:24.04
ARG RUNTIME_CUDA_IMAGE=nvidia/cuda:12.9.1-runtime-ubuntu24.04
# Whether to enable CUDA feature or not.
ARG CUDA
FROM $BASE_ZKVM_IMAGE AS base
FROM $BASE_ZKVM_CUDA_IMAGE AS base_cuda
FROM base${CUDA:+_cuda} AS build_stage
FROM $BASE_ZKVM_IMAGE AS build_stage
COPY . /ere
@@ -19,7 +16,7 @@ ARG RUSTFLAGS
RUN cargo build --release --package ere-server --bin ere-server --features airbender${CUDA:+,cuda} \
&& mkdir bin && mv target/release/ere-server bin/ere-server \
&& cargo clean && rm -rf $CARGO_HOME/registry/src $CARGO_HOME/registry/cache
&& cargo clean && rm -rf $CARGO_HOME/registry/
FROM $RUNTIME_IMAGE AS runtime
FROM $RUNTIME_CUDA_IMAGE AS runtime_cuda

View File

@@ -5,9 +5,6 @@ FROM $BASE_IMAGE
# The ere-base image provides Rust, Cargo (with a default nightly), and common tools.
# We operate as root for SDK installation.
# Set default toolchain to 1.88.0
RUN rustup default 1.88.0
# Copy the Jolt SDK (CLI) installer script from the workspace context
COPY --chmod=755 scripts/sdk_installers/install_jolt_sdk.sh /tmp/install_jolt_sdk.sh

View File

@@ -9,7 +9,7 @@ WORKDIR /ere
RUN cargo build --release --package ere-compiler --bin ere-compiler --features jolt \
&& mkdir bin && mv target/release/ere-compiler bin/ere-compiler \
&& cargo clean && rm -rf $CARGO_HOME/registry/src $CARGO_HOME/registry/cache
&& cargo clean && rm -rf $CARGO_HOME/registry/
FROM $RUNTIME_IMAGE AS runtime_stage

View File

@@ -11,7 +11,7 @@ ARG RUSTFLAGS
RUN cargo build --release --package ere-server --bin ere-server --features jolt \
&& mkdir bin && mv target/release/ere-server bin/ere-server \
&& cargo clean && rm -rf $CARGO_HOME/registry/src $CARGO_HOME/registry/cache
&& cargo clean && rm -rf $CARGO_HOME/registry/
FROM $RUNTIME_IMAGE AS runtime_stage

View File

@@ -9,7 +9,7 @@ WORKDIR /ere
RUN cargo build --release --package ere-compiler --bin ere-compiler --features miden \
&& mkdir bin && mv target/release/ere-compiler bin/ere-compiler \
&& cargo clean && rm -rf $CARGO_HOME/registry/src $CARGO_HOME/registry/cache
&& cargo clean && rm -rf $CARGO_HOME/registry/
FROM $RUNTIME_IMAGE AS runtime_stage

View File

@@ -11,7 +11,7 @@ ARG RUSTFLAGS
RUN cargo build --release --package ere-server --bin ere-server --features miden \
&& mkdir bin && mv target/release/ere-server bin/ere-server \
&& cargo clean && rm -rf $CARGO_HOME/registry/src $CARGO_HOME/registry/cache
&& cargo clean && rm -rf $CARGO_HOME/registry/
FROM $RUNTIME_IMAGE AS runtime_stage

View File

@@ -9,7 +9,7 @@ WORKDIR /ere
RUN cargo build --release --package ere-compiler --bin ere-compiler --features nexus \
&& mkdir bin && mv target/release/ere-compiler bin/ere-compiler \
&& cargo clean && rm -rf $CARGO_HOME/registry/src $CARGO_HOME/registry/cache
&& cargo clean && rm -rf $CARGO_HOME/registry/
FROM $RUNTIME_IMAGE AS runtime_stage

View File

@@ -11,7 +11,7 @@ ARG RUSTFLAGS
RUN cargo build --release --package ere-server --bin ere-server --features nexus \
&& mkdir bin && mv target/release/ere-server bin/ere-server \
&& cargo clean && rm -rf $CARGO_HOME/registry/src $CARGO_HOME/registry/cache
&& cargo clean && rm -rf $CARGO_HOME/registry/
FROM $RUNTIME_IMAGE AS runtime_stage

View File

@@ -1,12 +1,6 @@
ARG BASE_IMAGE=ere-base:latest
ARG BASE_CUDA_IMAGE=ere-base:latest-cuda
# Whether to enable CUDA feature or not.
ARG CUDA
FROM $BASE_IMAGE AS base
FROM $BASE_CUDA_IMAGE AS base_cuda
FROM base${CUDA:+_cuda}
FROM $BASE_IMAGE
# The ere-base image provides Rust, Cargo, and common tools.
# We operate as root for SDK installation.
@@ -14,6 +8,7 @@ FROM base${CUDA:+_cuda}
# Set default toolchain to nightly
RUN rustup default nightly
# Whether to enable CUDA feature or not.
ARG CUDA
# Default to build for RTX 50 series

View File

@@ -9,7 +9,7 @@ WORKDIR /ere
RUN cargo build --release --package ere-compiler --bin ere-compiler --features openvm \
&& mkdir bin && mv target/release/ere-compiler bin/ere-compiler \
&& cargo clean && rm -rf $CARGO_HOME/registry/src $CARGO_HOME/registry/cache
&& cargo clean && rm -rf $CARGO_HOME/registry/
FROM $RUNTIME_IMAGE AS runtime_stage

View File

@@ -1,14 +1,11 @@
ARG BASE_ZKVM_IMAGE=ere-base-openvm:latest
ARG BASE_ZKVM_CUDA_IMAGE=ere-base-openvm:latest-cuda
ARG RUNTIME_IMAGE=ubuntu:24.04
ARG RUNTIME_CUDA_IMAGE=nvidia/cuda:12.9.1-runtime-ubuntu24.04
# Whether to enable CUDA feature or not.
ARG CUDA
FROM $BASE_ZKVM_IMAGE AS base
FROM $BASE_ZKVM_CUDA_IMAGE AS base_cuda
FROM base${CUDA:+_cuda} AS build_stage
FROM $BASE_ZKVM_IMAGE AS build_stage
COPY . /ere
@@ -19,7 +16,7 @@ ARG RUSTFLAGS
RUN cargo build --release --package ere-server --bin ere-server --features openvm${CUDA:+,cuda} \
&& mkdir bin && mv target/release/ere-server bin/ere-server \
&& cargo clean && rm -rf $CARGO_HOME/registry/src $CARGO_HOME/registry/cache
&& cargo clean && rm -rf $CARGO_HOME/registry/
FROM $RUNTIME_IMAGE AS runtime
FROM $RUNTIME_CUDA_IMAGE AS runtime_cuda

View File

@@ -9,7 +9,7 @@ WORKDIR /ere
RUN cargo build --release --package ere-compiler --bin ere-compiler --features pico \
&& mkdir bin && mv target/release/ere-compiler bin/ere-compiler \
&& cargo clean && rm -rf $CARGO_HOME/registry/src $CARGO_HOME/registry/cache
&& cargo clean && rm -rf $CARGO_HOME/registry/
FROM $RUNTIME_IMAGE AS runtime_stage

View File

@@ -11,7 +11,7 @@ ARG RUSTFLAGS
RUN cargo build --release --package ere-server --bin ere-server --features pico \
&& mkdir bin && mv target/release/ere-server bin/ere-server \
&& cargo clean && rm -rf $CARGO_HOME/registry/src $CARGO_HOME/registry/cache
&& cargo clean && rm -rf $CARGO_HOME/registry/
FROM $RUNTIME_IMAGE AS runtime_stage

View File

@@ -1,14 +1,10 @@
ARG BASE_IMAGE=ere-base:latest
ARG BASE_CUDA_IMAGE=ere-base:latest-cuda
FROM $BASE_IMAGE
# Whether to enable CUDA feature or not.
ARG CUDA
FROM $BASE_IMAGE AS base
FROM $BASE_CUDA_IMAGE AS base_cuda
FROM base${CUDA:+_cuda}
ARG CUDA
ARG RUSTFLAGS
# Install protoc with same version as https://github.com/risc0/risc0/blob/v3.0.3/bento/dockerfiles/agent.dockerfile#L24-L26.

View File

@@ -9,7 +9,7 @@ WORKDIR /ere
RUN cargo build --release --package ere-compiler --bin ere-compiler --features risc0 \
&& mkdir bin && mv target/release/ere-compiler bin/ere-compiler \
&& cargo clean && rm -rf $CARGO_HOME/registry/src $CARGO_HOME/registry/cache
&& cargo clean && rm -rf $CARGO_HOME/registry/
FROM $RUNTIME_IMAGE AS runtime_stage

View File

@@ -1,14 +1,11 @@
ARG BASE_ZKVM_IMAGE=ere-base-risc0:latest
ARG BASE_ZKVM_CUDA_IMAGE=ere-base-risc0:latest-cuda
ARG RUNTIME_IMAGE=ubuntu:24.04
ARG RUNTIME_CUDA_IMAGE=nvidia/cuda:12.9.1-runtime-ubuntu24.04
# Whether to enable CUDA feature or not.
ARG CUDA
FROM $BASE_ZKVM_IMAGE AS base
FROM $BASE_ZKVM_CUDA_IMAGE AS base_cuda
FROM base${CUDA:+_cuda} AS build_stage
FROM $BASE_ZKVM_IMAGE AS build_stage
COPY . /ere
@@ -19,7 +16,7 @@ ARG RUSTFLAGS
RUN cargo build --release --package ere-server --bin ere-server --features risc0${CUDA:+,cuda} \
&& mkdir bin && mv target/release/ere-server bin/ere-server \
&& cargo clean && rm -rf $CARGO_HOME/registry/src $CARGO_HOME/registry/cache
&& cargo clean && rm -rf $CARGO_HOME/registry/
FROM $RUNTIME_IMAGE AS runtime
FROM $RUNTIME_CUDA_IMAGE AS runtime_cuda

View File

@@ -9,7 +9,7 @@ WORKDIR /ere
RUN cargo build --release --package ere-compiler --bin ere-compiler --features sp1 \
&& mkdir bin && mv target/release/ere-compiler bin/ere-compiler \
&& cargo clean && rm -rf $CARGO_HOME/registry/src $CARGO_HOME/registry/cache
&& cargo clean && rm -rf $CARGO_HOME/registry/
FROM $RUNTIME_IMAGE AS runtime_stage

View File

@@ -11,7 +11,7 @@ ARG RUSTFLAGS
RUN cargo build --release --package ere-server --bin ere-server --features sp1 \
&& mkdir bin && mv target/release/ere-server bin/ere-server \
&& cargo clean && rm -rf $CARGO_HOME/registry/src $CARGO_HOME/registry/cache
&& cargo clean && rm -rf $CARGO_HOME/registry/
FROM $RUNTIME_IMAGE AS runtime_stage

View File

@@ -9,7 +9,7 @@ WORKDIR /ere
RUN cargo build --release --package ere-compiler --bin ere-compiler --features ziren \
&& mkdir bin && mv target/release/ere-compiler bin/ere-compiler \
&& cargo clean && rm -rf $CARGO_HOME/registry/src $CARGO_HOME/registry/cache
&& cargo clean && rm -rf $CARGO_HOME/registry/
FROM $RUNTIME_IMAGE AS runtime_stage

View File

@@ -11,7 +11,7 @@ ARG RUSTFLAGS
RUN cargo build --release --package ere-server --bin ere-server --features ziren \
&& mkdir bin && mv target/release/ere-server bin/ere-server \
&& cargo clean && rm -rf $CARGO_HOME/registry/src $CARGO_HOME/registry/cache
&& cargo clean && rm -rf $CARGO_HOME/registry/
FROM $RUNTIME_IMAGE AS runtime_stage

View File

@@ -1,17 +1,12 @@
ARG BASE_IMAGE=ere-base:latest
ARG BASE_CUDA_IMAGE=ere-base:latest-cuda
# Whether to enable CUDA feature or not.
ARG CUDA
FROM $BASE_IMAGE AS base
FROM $BASE_CUDA_IMAGE AS base_cuda
FROM base${CUDA:+_cuda}
FROM $BASE_IMAGE
# The ere-base image provides Rust, Cargo, and common tools.
# ZisK requires Ubuntu 22.04 or higher (ere-base uses 24.04 by default).
# We operate as root for SDK and dependency installation.
# Whether to enable CUDA feature or not.
ARG CUDA
# Install ZisK system dependencies (for Ubuntu)
@@ -68,4 +63,11 @@ ENV PATH=/root/.zisk/bin:$PATH
# Verify cargo-zisk is accessible
RUN echo "Verifying ZisK installation in Dockerfile ..." && cargo-zisk --version
# Copy the TamaGo installer script from the workspace context
COPY --chmod=755 scripts/install_tamago.sh /tmp/install_tamago.sh
# Run the TamaGo installation script.
RUN /tmp/install_tamago.sh && \
rm /tmp/install_tamago.sh
CMD ["/bin/bash"]

View File

@@ -9,7 +9,7 @@ WORKDIR /ere
RUN cargo build --release --package ere-compiler --bin ere-compiler --features zisk \
&& mkdir bin && mv target/release/ere-compiler bin/ere-compiler \
&& cargo clean && rm -rf $CARGO_HOME/registry/src $CARGO_HOME/registry/cache
&& cargo clean && rm -rf $CARGO_HOME/registry/
FROM $RUNTIME_IMAGE AS runtime_stage
@@ -28,6 +28,12 @@ ENV RUSTUP_HOME=/usr/local/rustup \
CARGO_HOME=/usr/local/cargo \
PATH=/usr/local/cargo/bin:$PATH
# Copy TamaGo
COPY --from=build_stage /root/.tamago /root/.tamago
# Add TamaGo to path
ENV PATH=/root/.tamago/bin:$PATH
# Copy ZisK SDK
COPY --from=build_stage /root/.zisk/toolchains /root/.zisk/toolchains

View File

@@ -1,14 +1,11 @@
ARG BASE_ZKVM_IMAGE=ere-base-zisk:latest
ARG BASE_ZKVM_CUDA_IMAGE=ere-base-zisk:latest-cuda
ARG RUNTIME_IMAGE=ubuntu:24.04
ARG RUNTIME_CUDA_IMAGE=nvidia/cuda:12.9.1-runtime-ubuntu24.04
# Whether to enable CUDA feature or not.
ARG CUDA
FROM $BASE_ZKVM_IMAGE AS base
FROM $BASE_ZKVM_CUDA_IMAGE AS base_cuda
FROM base${CUDA:+_cuda} AS build_stage
FROM $BASE_ZKVM_IMAGE AS build_stage
COPY . /ere
@@ -19,7 +16,7 @@ ARG RUSTFLAGS
RUN cargo build --release --package ere-server --bin ere-server --features zisk${CUDA:+,cuda} \
&& mkdir bin && mv target/release/ere-server bin/ere-server \
&& cargo clean && rm -rf $CARGO_HOME/registry/src $CARGO_HOME/registry/cache
&& cargo clean && rm -rf $CARGO_HOME/registry/
FROM $RUNTIME_IMAGE AS runtime
FROM $RUNTIME_CUDA_IMAGE AS runtime_cuda