tmp: use zisk-sdk of pre-develop-0.16.0

This commit is contained in:
han0110
2026-02-19 15:51:10 +00:00
parent e7ed714394
commit 8c555d2343
21 changed files with 4051 additions and 2288 deletions

View File

@@ -19,4 +19,5 @@ jobs:
cuda: true
cuda_archs: '120'
cluster: true
test_threads: 1
skip_prove_test: true

4490
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -48,7 +48,6 @@ license = "MIT OR Apache-2.0"
anyhow = "1.0.98"
auto_impl = "1.3.0"
bincode = { version = "2.0.1", default-features = false }
blake3 = "1.8.2"
bytemuck = "1.25.0"
cargo_metadata = "0.19.0"
ciborium = { version = "0.2.2", default-features = false }
@@ -61,11 +60,13 @@ fnv = { version = "1.0.7", default-features = false }
futures-util = "0.3"
http = "1"
indexmap = "2.10.0"
mpi = "0.8.0"
parking_lot = "0.12.5"
paste = "1.0.15"
postcard = { version = "1.0.8", default-features = false }
prost = "0.13"
prost-build = "0.13"
prost = "0.14"
prost-build = "0.14"
prost-types = "0.14"
rand = "0.9.2"
rkyv = { version = "0.8.12", default-features = false }
serde = { version = "1.0.219", default-features = false }
@@ -79,11 +80,14 @@ thiserror = "2.0.12"
tokio = "1.0"
toml = "0.8.23"
tonic = "0.14"
tonic-build = "0.14"
tonic-prost = "0.14"
tonic-prost-build = "0.14"
tower-http = "0.6.6"
tracing = "0.1.41"
tracing-subscriber = "0.3.19"
twirp = "0.9.1"
twirp-build = "0.9.0"
twirp = "0.10"
twirp-build = "0.10"
uuid = "1"
wait-timeout = "0.2.1"
@@ -149,9 +153,17 @@ zkm-sdk = { git = "https://github.com/ProjectZKM/Ziren.git", tag = "v1.2.3" }
zkm-zkvm = { git = "https://github.com/ProjectZKM/Ziren.git", tag = "v1.2.3", default-features = false }
# ZisK dependencies
ziskos = { git = "https://github.com/0xPolygonHermez/zisk.git", tag = "v0.15.0" }
zisk-distributed-grpc-api = { git = "https://github.com/han0110/zisk", branch = "patch/v0.15.0-cluster" }
zisk-proofman-verifier = { git = "https://github.com/0xPolygonHermez/pil2-proofman", package = "proofman-verifier", tag = "v0.15.0" }
ziskos = { git = "https://github.com/0xPolygonHermez/zisk.git", branch = "pre-develop-0.16.0" }
zisk-rom-setup = { git = "https://github.com/0xPolygonHermez/zisk.git", branch = "pre-develop-0.16.0", package = "rom-setup" }
zisk-sdk = { git = "https://github.com/0xPolygonHermez/zisk.git", branch = "pre-develop-0.16.0" }
zisk-core = { git = "https://github.com/0xPolygonHermez/zisk.git", branch = "pre-develop-0.16.0" }
ziskemu = { git = "https://github.com/0xPolygonHermez/zisk.git", branch = "pre-develop-0.16.0" }
proofman = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" }
proofman-common = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" }
proofman-util = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" }
proofman-fields = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0", package = "fields" }
proofman-verifier = { git = "https://github.com/0xPolygonHermez/pil2-proofman", branch = "pre-develop-0.16.0" }
proofman-starks-lib-c = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" }
# Local dependencies
ere-zkvm-interface = { path = "crates/zkvm-interface" }
@@ -195,3 +207,6 @@ ark-ff = { git = "https://github.com/a16z/arkworks-algebra", branch = "dev/twist
ark-ec = { git = "https://github.com/a16z/arkworks-algebra", branch = "dev/twist-shout" }
ark-serialize = { git = "https://github.com/a16z/arkworks-algebra", branch = "dev/twist-shout" }
allocative = { git = "https://github.com/facebookexperimental/allocative", rev = "85b773d85d526d068ce94724ff7a7b81203fc95e" }
# Patch vergen of zkm-sdk to use newer git2
vergen = { git = "https://github.com/rustyhorde/vergen", branch = "legacy/v8" }

View File

@@ -1,6 +1,6 @@
// This file is @generated by prost-build.
#[derive(serde::Serialize, serde::Deserialize)]
#[derive(Clone, PartialEq, ::prost::Message)]
#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
pub struct ExecuteRequest {
#[prost(bytes = "vec", tag = "1")]
pub input_stdin: ::prost::alloc::vec::Vec<u8>,
@@ -8,7 +8,7 @@ pub struct ExecuteRequest {
pub input_proofs: ::core::option::Option<::prost::alloc::vec::Vec<u8>>,
}
#[derive(serde::Serialize, serde::Deserialize)]
#[derive(Clone, PartialEq, ::prost::Message)]
#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
pub struct ExecuteResponse {
#[prost(oneof = "execute_response::Result", tags = "1, 2")]
pub result: ::core::option::Option<execute_response::Result>,
@@ -16,7 +16,7 @@ pub struct ExecuteResponse {
/// Nested message and enum types in `ExecuteResponse`.
pub mod execute_response {
#[derive(serde::Serialize, serde::Deserialize)]
#[derive(Clone, PartialEq, ::prost::Oneof)]
#[derive(Clone, PartialEq, Eq, Hash, ::prost::Oneof)]
pub enum Result {
#[prost(message, tag = "1")]
Ok(super::ExecuteOk),
@@ -25,7 +25,7 @@ pub mod execute_response {
}
}
#[derive(serde::Serialize, serde::Deserialize)]
#[derive(Clone, PartialEq, ::prost::Message)]
#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
pub struct ExecuteOk {
#[prost(bytes = "vec", tag = "1")]
pub public_values: ::prost::alloc::vec::Vec<u8>,
@@ -33,7 +33,7 @@ pub struct ExecuteOk {
pub report: ::prost::alloc::vec::Vec<u8>,
}
#[derive(serde::Serialize, serde::Deserialize)]
#[derive(Clone, PartialEq, ::prost::Message)]
#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
pub struct ProveRequest {
#[prost(bytes = "vec", tag = "1")]
pub input_stdin: ::prost::alloc::vec::Vec<u8>,
@@ -43,7 +43,7 @@ pub struct ProveRequest {
pub proof_kind: i32,
}
#[derive(serde::Serialize, serde::Deserialize)]
#[derive(Clone, PartialEq, ::prost::Message)]
#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
pub struct ProveResponse {
#[prost(oneof = "prove_response::Result", tags = "1, 2")]
pub result: ::core::option::Option<prove_response::Result>,
@@ -51,7 +51,7 @@ pub struct ProveResponse {
/// Nested message and enum types in `ProveResponse`.
pub mod prove_response {
#[derive(serde::Serialize, serde::Deserialize)]
#[derive(Clone, PartialEq, ::prost::Oneof)]
#[derive(Clone, PartialEq, Eq, Hash, ::prost::Oneof)]
pub enum Result {
#[prost(message, tag = "1")]
Ok(super::ProveOk),
@@ -60,7 +60,7 @@ pub mod prove_response {
}
}
#[derive(serde::Serialize, serde::Deserialize)]
#[derive(Clone, PartialEq, ::prost::Message)]
#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
pub struct ProveOk {
#[prost(bytes = "vec", tag = "1")]
pub public_values: ::prost::alloc::vec::Vec<u8>,
@@ -70,7 +70,7 @@ pub struct ProveOk {
pub report: ::prost::alloc::vec::Vec<u8>,
}
#[derive(serde::Serialize, serde::Deserialize)]
#[derive(Clone, PartialEq, ::prost::Message)]
#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
pub struct VerifyRequest {
#[prost(bytes = "vec", tag = "1")]
pub proof: ::prost::alloc::vec::Vec<u8>,
@@ -78,7 +78,7 @@ pub struct VerifyRequest {
pub proof_kind: i32,
}
#[derive(serde::Serialize, serde::Deserialize)]
#[derive(Clone, PartialEq, ::prost::Message)]
#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
pub struct VerifyResponse {
#[prost(oneof = "verify_response::Result", tags = "1, 2")]
pub result: ::core::option::Option<verify_response::Result>,
@@ -86,7 +86,7 @@ pub struct VerifyResponse {
/// Nested message and enum types in `VerifyResponse`.
pub mod verify_response {
#[derive(serde::Serialize, serde::Deserialize)]
#[derive(Clone, PartialEq, ::prost::Oneof)]
#[derive(Clone, PartialEq, Eq, Hash, ::prost::Oneof)]
pub enum Result {
#[prost(message, tag = "1")]
Ok(super::VerifyOk),
@@ -95,7 +95,7 @@ pub mod verify_response {
}
}
#[derive(serde::Serialize, serde::Deserialize)]
#[derive(Clone, PartialEq, ::prost::Message)]
#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
pub struct VerifyOk {
#[prost(bytes = "vec", tag = "1")]
pub public_values: ::prost::alloc::vec::Vec<u8>,

View File

@@ -122,6 +122,7 @@ where
fn assert_output(&self, public_values: &[u8]) {
let output = P::compute(self.test_case.clone());
let digest = D::digest(P::Io::serialize_output(&output).unwrap());
assert_eq!(&*digest, public_values)
assert_eq!(&*digest, &public_values[..digest.len()]);
assert!(public_values[digest.len()..].iter().all(|byte| *byte == 0));
}
}

View File

@@ -7,29 +7,40 @@ license.workspace = true
[dependencies]
anyhow.workspace = true
blake3.workspace = true
bytemuck = { workspace = true, features = ["extern_crate_alloc"] }
bincode = { workspace = true, features = ["alloc"] }
bytemuck.workspace = true
futures-util.workspace = true
http.workspace = true
mpi = { workspace = true, optional = true }
parking_lot.workspace = true
prost = { workspace = true, optional = true }
serde.workspace = true
strum = { workspace = true, features = ["derive"] }
tempfile.workspace = true
thiserror.workspace = true
tonic = { workspace = true, optional = true }
tonic-prost = { workspace = true, optional = true }
tracing.workspace = true
uuid = { workspace = true, features = ["v4"] }
wait-timeout.workspace = true
# Local dependencies
ere-compile-utils = { workspace = true, optional = true }
ere-zkvm-interface.workspace = true
# Zisk dependencies
zisk-distributed-grpc-api = { workspace = true, optional = true }
zisk-proofman-verifier = { workspace = true, optional = true }
proofman = { workspace = true, optional = true }
proofman-common = { workspace = true, optional = true }
proofman-fields = { workspace = true, optional = true }
proofman-util = { workspace = true, optional = true }
proofman-verifier = { workspace = true, optional = true }
zisk-core = { workspace = true, optional = true }
zisk-rom-setup = { workspace = true, optional = true }
zisk-sdk = { workspace = true, optional = true, features = ["disable_distributed"] }
ziskemu = { workspace = true, optional = true }
[dev-dependencies]
tonic-prost-build = { workspace = true }
tonic-build = { workspace = true }
ere-test-utils = { workspace = true, features = ["host"] }
[build-dependencies]
@@ -39,11 +50,22 @@ ere-build-utils.workspace = true
default = ["compiler", "zkvm"]
compiler = ["dep:ere-compile-utils"]
zkvm = [
"dep:mpi",
"dep:prost",
"dep:tonic",
"dep:zisk-distributed-grpc-api",
"dep:zisk-proofman-verifier",
"dep:tonic-prost",
"dep:proofman",
"dep:proofman-common",
"dep:proofman-fields",
"dep:proofman-util",
"dep:proofman-verifier",
"dep:zisk-rom-setup",
"dep:zisk-core",
"dep:zisk-sdk",
"dep:ziskemu",
"ere-zkvm-interface/tokio",
]
cuda = ["zisk-sdk/gpu"]
[lints]
workspace = true

View File

@@ -2,7 +2,7 @@
extern crate alloc;
use core::{array::from_fn, cell::UnsafeCell, hash::Hasher, ops::Deref};
use core::{cell::UnsafeCell, hash::Hasher, ops::Deref};
use ere_platform_trait::LengthPrefixedStdin;
use fnv::FnvHasher;
use ziskos::ziskos_definitions::ziskos_config::UART_ADDR;
@@ -150,7 +150,7 @@ pub struct ZiskPlatform;
impl Platform for ZiskPlatform {
fn read_whole_input() -> impl Deref<Target = [u8]> {
LengthPrefixedStdin::new(ziskos::read_input())
LengthPrefixedStdin::new(ziskos::io::read_vec())
}
fn write_whole_output(output: &[u8]) {
@@ -159,10 +159,7 @@ impl Platform for ZiskPlatform {
"Maximum output size is 256 bytes, got {}",
output.len()
);
output.chunks(4).enumerate().for_each(|(idx, chunk)| {
let value = u32::from_le_bytes(from_fn(|i| chunk.get(i).copied().unwrap_or_default()));
ziskos::set_output(idx, value)
});
ziskos::io::write(output);
}
fn print(message: &str) {

View File

@@ -1,18 +1,17 @@
use crate::{
program::ZiskProgram,
zkvm::sdk::{RomDigest, ZiskSdk},
zkvm::sdk::{ProgramVk, ZiskSdk},
};
use anyhow::bail;
use ere_zkvm_interface::zkvm::{
CommonError, Input, ProgramExecutionReport, ProgramProvingReport, Proof, ProofKind,
ProverResource, PublicValues, zkVM, zkVMProgramDigest,
};
use mpi as _;
use std::time::Instant;
mod cluster_client;
mod error;
mod sdk;
mod server;
pub use error::Error;
@@ -98,10 +97,10 @@ impl zkVM for EreZisk {
}
impl zkVMProgramDigest for EreZisk {
type ProgramDigest = RomDigest;
type ProgramDigest = ProgramVk;
fn program_digest(&self) -> anyhow::Result<Self::ProgramDigest> {
Ok(self.sdk.rom_digest()?)
Ok(self.sdk.program_vk())
}
}
@@ -159,22 +158,22 @@ mod tests {
#[test]
fn test_prove() {
let _guard = PROVE_LOCK.lock().unwrap();
let program = basic_program();
let zkvm = EreZisk::new(program, ProverResource::Cpu).unwrap();
let _guard = PROVE_LOCK.lock().unwrap();
let test_case = BasicProgram::<BincodeLegacy>::valid_test_case();
run_zkvm_prove(&zkvm, &test_case);
}
#[test]
fn test_prove_invalid_test_case() {
let _guard = PROVE_LOCK.lock().unwrap();
let program = basic_program();
let zkvm = EreZisk::new(program, ProverResource::Cpu).unwrap();
let _guard = PROVE_LOCK.lock().unwrap();
for input in [
Input::new(),
BasicProgram::<BincodeLegacy>::invalid_test_case().input(),

View File

@@ -1,5 +1,6 @@
use crate::zkvm::sdk::RomDigest;
use crate::zkvm::sdk::ProgramVk;
use ere_zkvm_interface::zkvm::CommonError;
use proofman_common::ProofmanError;
use thiserror::Error;
#[derive(Debug, Error)]
@@ -7,29 +8,47 @@ pub enum Error {
#[error(transparent)]
CommonError(#[from] CommonError),
// Execution
#[error("Total steps not found in execution report")]
TotalStepsNotFound,
// Common
#[error("Enable `cuda` feature to use `ProverResource::Gpu`")]
CudaFeatureDisabled,
// Rom setup
#[error("Failed to find ROM digest in output")]
RomDigestNotFound,
#[error("Disable `cuda` feature to use `ProverResource::Cpu`")]
CudaFeatureEnabled,
#[error("`cargo-zisk rom-setup` failed in another thread")]
RomSetupFailedBefore,
// Emulator
#[error("ROM transpilation failed: {0}")]
Riscv2zisk(String),
// Prove
#[error("Server crashed")]
ServerCrashed,
#[error("Emulation not terminated")]
EmulatorNotTerminated,
#[error("Timeout waiting for server proving")]
TimeoutWaitingServerProving,
#[error("Emulation failure")]
EmulatorError,
#[error("Timeout waiting for server ready")]
TimeoutWaitingServerReady,
#[error("Emulator panicked: {0}")]
EmulatorPanic(String),
#[error("Unknown server status, stdout: {stdout}")]
UnknownServerStatus { stdout: String },
// SDK
#[error("Check setup failed: {0}")]
CheckSetup(#[source] ProofmanError),
#[error("Create ProofCtx failed: {0}")]
ProofCtx(#[source] ProofmanError),
#[error("Compute program VK failed: {0}")]
ComputeProgramVk(#[source] anyhow::Error),
#[error("Invalid program VK length, expected 32, got {0}")]
InvalidProgramVkLength(usize),
#[error("Initialize prover failed: {0}")]
InitProver(#[source] anyhow::Error),
#[error("Prove failed: {0}")]
Prove(#[source] anyhow::Error),
#[error("Prove panicked: {0}")]
ProvePanic(String),
// Cluster
#[error("Invalid cluster endpoint: {0}")]
@@ -44,6 +63,9 @@ pub enum Error {
#[error("Cluster error: {0}")]
ClusterError(String),
#[error("Invalid proof format: {0}")]
InvalidProofFormat(anyhow::Error),
// Verify
#[error("Invalid proof")]
InvalidProof,
@@ -57,9 +79,9 @@ pub enum Error {
#[error("Public values length {0}, but expected at least 6")]
InvalidPublicValuesLength(usize),
#[error("Unexpected ROM digest - preprocessed: {preprocessed:?}, proved: {proved:?}")]
UnexpectedRomDigest {
preprocessed: RomDigest,
proved: RomDigest,
#[error("Unexpected program VK - preprocessed: {preprocessed:?}, proved: {proved:?}")]
UnexpectedProgramVk {
preprocessed: ProgramVk,
proved: ProgramVk,
},
}

View File

@@ -1,80 +1,65 @@
use crate::zkvm::Error;
use crate::zkvm::cluster_client::ClusterClient;
use crate::zkvm::server::{ZiskServer, ZiskServerOptions};
use crate::zkvm::{
Error,
sdk::{cluster::ClusterClient, local::LocalProver},
};
use ere_zkvm_interface::zkvm::{CommonError, ProverResource, ProverResourceKind, PublicValues};
use std::borrow::Cow;
use std::path::Path;
use std::process::Command;
use proofman_common::{
MpiCtx, ParamsGPU, ProofCtx, ProofType, SetupCtx, SetupsVadcop, VerboseMode,
};
use proofman_fields::Goldilocks;
use proofman_util::VadcopFinalProof;
use proofman_verifier::verify_vadcop_final;
use std::{
env, fs,
io::BufRead,
any::Any,
env,
mem::transmute,
panic::{self, AssertUnwindSafe},
path::PathBuf,
sync::OnceLock,
time::{Duration, Instant},
sync::Arc,
time::Duration,
};
use tempfile::tempdir;
use tracing::info;
use zisk_core::{Riscv2zisk, ZiskRom};
use zisk_rom_setup::rom_merkle_setup;
use zisk_sdk::{ElfBinaryFromFile, ZISK_PUBLICS, ZiskProofWithPublicValues};
use ziskemu::{Emu, EmuOptions};
mod cluster;
mod local;
/// Merkle root of ROM trace.
pub type ProgramVk = [u8; 32];
/// Verifying key of the aggregation proof.
///
/// Extracted from `$HOME/.zisk/provingKey/zisk/vadcop_final/vadcop_final.verkey.json`.
pub const VADCOP_FINAL_VK: [u8; 32] = unsafe {
// Use `transmute` to keep the endianness because `zisk_proofman_verifier::verify` will
// use `bytemuck` to cast it to `[u64; 4]`.
transmute([
723851053263266420u64,
2272245643171245153u64,
9868173762158752255u64,
6004219199197288727u64,
5756952873125057328u64,
1254521327410429374u64,
17471446849604192873u64,
13226325674217234543u64,
])
};
/// Merkle root of ROM trace generated by `cargo-zisk rom-setup`.
pub type RomDigest = [u64; 4];
/// Prover backend - either local or cluster.
#[allow(clippy::large_enum_variant)]
pub enum ZiskProver {
Server(ZiskServer),
Local(LocalProver),
Cluster(ClusterClient),
}
pub struct ZiskSdk {
elf_path: PathBuf,
resource: ProverResource,
/// ROM digest will be setup when `ZiskSdk::prove` or `ZiskSdk::verify`
/// is called, or if env variable `ERE_ZISK_SETUP_ON_INIT` is set.
///
/// Use `Option` inside because ROM setup might fail, we can get rid of
/// it if `OnceLock::get_or_try_init` is stabilized.
rom_digest: OnceLock<Option<RomDigest>>,
/// Prover instance, either a local server or a cluster client.
rom: ZiskRom,
program_vk: ProgramVk,
prover: ZiskProver,
}
impl ZiskSdk {
/// Returns SDK for the ELF.
pub fn new(elf: Vec<u8>, resource: ProverResource) -> Result<Self, Error> {
// Save ELF to `~/.zisk/cache` along with the ROM binaries, to avoid it
// been cleaned up during a long run process.
let cache_dir_path = dot_zisk_dir_path().join("cache");
fs::create_dir_all(&cache_dir_path)
.map_err(|err| CommonError::create_dir("cache", &cache_dir_path, err))?;
// Use blake3 hash as the ELF file name, since ROM setup uses blake3 as
// unique identifier as well.
let elf_hash = blake3::hash(&elf);
let elf_path = cache_dir_path.join(format!("{elf_hash}.elf"));
fs::write(&elf_path, elf).map_err(|err| CommonError::write_file("elf", &elf_path, err))?;
let prover = match &resource {
ProverResource::Cpu | ProverResource::Gpu => ZiskProver::Server(ZiskServer::new(
&elf_path,
resource.is_gpu(),
ZiskServerOptions::from_env(),
)),
ProverResource::Cluster(config) => ZiskProver::Cluster(ClusterClient::new(config)?),
_ => Err(CommonError::unsupported_prover_resource_kind(
match &resource {
ProverResource::Cpu if cfg!(feature = "cuda") => Err(Error::CudaFeatureEnabled)?,
ProverResource::Gpu if cfg!(not(feature = "cuda")) => Err(Error::CudaFeatureDisabled)?,
ProverResource::Network(_) => Err(CommonError::unsupported_prover_resource_kind(
resource.kind(),
[
ProverResourceKind::Cpu,
@@ -82,139 +67,105 @@ impl ZiskSdk {
ProverResourceKind::Cluster,
],
))?,
_ => {}
};
let sdk = Self {
elf_path,
resource,
rom_digest: OnceLock::new(),
// Convert ELF to ZisK ROM
let rom = Riscv2zisk::new(&elf)
.run()
.map_err(|e| Error::Riscv2zisk(e.to_string()))?;
// Compute program VK
let program_vk = compute_program_vk(&elf)?;
// Initialize prover
let prover = match &resource {
ProverResource::Cpu | ProverResource::Gpu => ZiskProver::Local(LocalProver::new(elf)?),
ProverResource::Cluster(config) => ZiskProver::Cluster(ClusterClient::new(config)?),
_ => unreachable!(),
};
Ok(Self {
rom,
program_vk,
prover,
};
if env::var_os("ERE_ZISK_SETUP_ON_INIT").is_some() {
sdk.rom_digest()?;
if let ZiskProver::Server(server) = &sdk.prover {
server.ensure_ready()?;
}
}
Ok(sdk)
})
}
/// Execute the ELF with the given `input`.
pub fn execute(&self, input: &[u8]) -> Result<(PublicValues, u64), Error> {
let tempdir = tempdir().map_err(CommonError::tempdir)?;
let input_path = tempdir.path().join("input");
let output_path = tempdir.path().join("output");
pub fn program_vk(&self) -> ProgramVk {
self.program_vk
}
fs::write(&input_path, input)
.map_err(|err| CommonError::write_file("input", &input_path, err))?;
/// Execute the ELF with the given `stdin`.
pub fn execute(&self, stdin: &[u8]) -> Result<(PublicValues, u64), Error> {
let mut emu = Emu::new(&self.rom);
emu.ctx = emu.create_emu_context(stdin.to_vec());
let mut cmd = Command::new("ziskemu");
let output = cmd
.arg("--elf")
.arg(&self.elf_path)
.arg("--inputs")
.arg(input_path)
.arg("--output")
.arg(&output_path)
.arg("--stats") // Enable stats in order to get total steps.
.output()
.map_err(|err| CommonError::command(&cmd, err))?;
panic::catch_unwind(AssertUnwindSafe(|| emu.run_fast(&EmuOptions::default())))
.map_err(|err| Error::EmulatorPanic(panic_msg(err)))?;
if !output.status.success() {
return Err(CommonError::command_exit_non_zero(
&cmd,
output.status,
Some(&output),
))?;
if !emu.ctx.inst_ctx.end {
return Err(Error::EmulatorNotTerminated);
}
// Extract cycle count from the stdout.
if emu.ctx.inst_ctx.error {
return Err(Error::EmulatorError);
}
let total_num_cycles = String::from_utf8_lossy(&output.stdout)
.split_once("STEPS")
.and_then(|(_, stats)| {
stats
.split_whitespace()
.next()
.and_then(|steps| steps.replace(",", "").parse::<u64>().ok())
})
.ok_or(Error::TotalStepsNotFound)?;
let public_values = fs::read(&output_path)
.map_err(|err| CommonError::read_file("output", &output_path, err))?;
let public_values = emu.get_output_8();
let total_num_cycles = emu.number_of_steps();
Ok((public_values, total_num_cycles))
}
/// Returns the ROM digest of the ELF.
///
/// If it is not setup yet, it makes sure the global setup is done, then
/// does ROM setup of the ELF and stores the ROM digest for later usage.
pub fn rom_digest(&self) -> Result<RomDigest, Error> {
// FIXME: Use `get_or_try_init` when it is stabilized
let mut result = Ok(());
let rom_digest = *self.rom_digest.get_or_init(|| {
check_setup(self.resource.is_gpu())
.and_then(|_| rom_setup(&self.elf_path))
.map_err(|err| result = Err(err))
.ok()
});
result?;
rom_digest.ok_or(Error::RomSetupFailedBefore)
}
/// Prove the ELF with the given input.
/// Prove the ELF with the given stdin.
///
/// Returns the public values, proof, and proving time.
pub fn prove(&self, input: &[u8]) -> Result<(PublicValues, Vec<u8>, Duration), Error> {
pub fn prove(&self, stdin: &[u8]) -> Result<(PublicValues, Vec<u8>, Duration), Error> {
let (proof, proving_time) = match &self.prover {
ZiskProver::Cluster(client) => client.prove(input)?,
ZiskProver::Server(server) => {
self.rom_digest()?;
let start = Instant::now();
let proof = server.prove(input)?;
let proving_time = start.elapsed();
(proof, proving_time)
}
ZiskProver::Local(local) => local.prove(stdin)?,
ZiskProver::Cluster(client) => client.prove(stdin)?,
};
// Deserialize public values.
let (proved_rom_digest, public_values) = deserialize_public_values(&proof)?;
// Extract public values and program_vk
let (public_values, proved_program_vk) = extract_public_values_and_progam_vk(&proof)?;
// The proved ROM digest should be equal to preprocessed one.
let rom_digest = self.rom_digest()?;
if proved_rom_digest != rom_digest {
return Err(Error::UnexpectedRomDigest {
preprocessed: rom_digest,
proved: proved_rom_digest,
// The proved program VK should match the preprocessed
if proved_program_vk != self.program_vk {
return Err(Error::UnexpectedProgramVk {
preprocessed: self.program_vk,
proved: proved_program_vk,
});
}
Ok((public_values, proof, proving_time))
Ok((
public_values,
bincode::serde::encode_to_vec(&proof, bincode::config::legacy())
.map_err(|err| CommonError::serialize("proof", "bincode", err))?,
proving_time,
))
}
/// Verify the proof of the ELF, and returns public values.
pub fn verify(&self, proof: &[u8]) -> Result<PublicValues, Error> {
let proof = align_to_u64(proof)?;
let proof: ZiskProofWithPublicValues =
bincode::serde::decode_from_slice(proof, bincode::config::legacy())
.map_err(|err| CommonError::deserialize("proof", "bincode", err))?
.0;
if !zisk_proofman_verifier::verify(&proof, &VADCOP_FINAL_VK) {
Err(Error::InvalidProof)?
let vadcop_final_proof = vadcop_final_proof_aligned(&proof)?;
if !verify_vadcop_final(&vadcop_final_proof, &VADCOP_FINAL_VK) {
return Err(Error::InvalidProof);
}
// Deserialize public values.
let (proved_rom_digest, public_values) = deserialize_public_values(&proof)?;
// Extract public values and program_vk
let (public_values, proved_program_vk) = extract_public_values_and_progam_vk(&proof)?;
// The proved ROM digest should be equal to preprocessed one.
let rom_digest = self.rom_digest()?;
if proved_rom_digest != rom_digest {
return Err(Error::UnexpectedRomDigest {
preprocessed: rom_digest,
proved: proved_rom_digest,
// The proved program VK should match the preprocessed
if proved_program_vk != self.program_vk {
return Err(Error::UnexpectedProgramVk {
preprocessed: self.program_vk,
proved: proved_program_vk,
});
}
@@ -222,127 +173,94 @@ impl ZiskSdk {
}
}
/// Does global setup if it is not done yet.
fn check_setup(cuda: bool) -> Result<(), Error> {
info!("Running command `cargo-zisk check-setup --aggregation`...");
fn compute_program_vk(elf: &[u8]) -> Result<ProgramVk, Error> {
let mpi_ctx = Arc::new(MpiCtx::new());
let mut pctx = ProofCtx::create_ctx(proving_key_path(), false, VerboseMode::Info, mpi_ctx)
.map_err(Error::ProofCtx)?;
let mut params = ParamsGPU::new(false);
params.with_max_number_streams(1);
let sctx = SetupCtx::new(&pctx.global_info, &ProofType::Basic, false, &params, &[]);
let setups_vadcop = SetupsVadcop::new(&pctx.global_info, false, false, &params, &[]);
pctx.set_device_buffers(&sctx, &setups_vadcop, false, &params)
.map_err(Error::ProofCtx)?;
let cargo_zisk = if cuda {
"cargo-zisk-cuda"
} else {
"cargo-zisk"
let elf = ElfBinaryFromFile {
elf: elf.to_vec(),
name: String::new(),
with_hints: false,
};
let mut cmd = Command::new(cargo_zisk);
let output = cmd
.args(["check-setup", "--aggregation"])
.output()
.map_err(|err| CommonError::command(&cmd, err))?;
let tempdir = tempdir().map_err(CommonError::tempdir)?;
if !output.status.success() {
Err(CommonError::command_exit_non_zero(
&cmd,
output.status,
Some(&output),
))?;
}
let (_, program_vk) =
rom_merkle_setup::<Goldilocks>(&pctx, &elf, &Some(tempdir.path().to_path_buf()))
.map_err(Error::ComputeProgramVk)?;
info!("Command `cargo-zisk check-setup --aggregation` succeeded");
Ok(())
program_vk_from_slice(&program_vk)
}
/// Does ROM setup of the ELF and returns the ROM digest.
fn rom_setup(elf_path: &Path) -> Result<RomDigest, Error> {
info!("Running command `cargo-zisk rom-setup` ...");
fn extract_public_values_and_progam_vk(
proof: &ZiskProofWithPublicValues,
) -> Result<(PublicValues, ProgramVk), Error> {
let program_vk = program_vk_from_slice(&proof.get_program_vk().vk)?;
let mut cmd = Command::new("cargo-zisk");
let output = cmd
.arg("rom-setup")
.arg("--elf")
.arg(elf_path)
.output()
.map_err(|err| CommonError::command(&cmd, err))?;
let mut public_values = vec![0; ZISK_PUBLICS * 4];
proof.get_publics().read_slice(&mut public_values);
proof.get_publics().head();
if !output.status.success() {
Err(CommonError::command_exit_non_zero(
&cmd,
output.status,
Some(&output),
))?;
}
// Parse the ROM digest from the stdout.
let rom_digest = output
.stdout
.lines()
.find_map(|line| {
let line = line.ok()?;
let line = line.split_once("Root hash: [")?.1;
let line = line.strip_suffix("]")?;
line.split(", ")
.filter_map(|word| word.parse::<u64>().ok())
.collect::<Vec<_>>()
.try_into()
.ok()
})
.ok_or(Error::RomDigestNotFound)?;
info!("Command `cargo-zisk rom-setup` succeeded");
Ok(rom_digest)
Ok((public_values, program_vk))
}
/// Deserialize public values as json string sequence, and parse the `RomDigest`
/// and user set public values as `Vec<u8>`.
fn deserialize_public_values(proof: &[u8]) -> Result<(RomDigest, Vec<u8>), Error> {
let proof = align_to_u64(proof)?;
let proof = bytemuck::cast_slice::<u8, u64>(&proof);
fn program_vk_from_slice(program_vk: &[u8]) -> Result<ProgramVk, Error> {
(program_vk.len() == 32)
.then(|| program_vk.try_into().unwrap())
.ok_or_else(|| Error::InvalidProgramVkLength(program_vk.len()))
}
// The public values contain at least the the total number of public values,
// `RomDigest`, and the number of user set public values.
if proof.len() < 6 {
return Err(Error::InvalidPublicValuesLength(proof.len()));
}
// The first element is total number of public values.
// The next 4 elements of public values should be ROM digest.
let rom_digest = proof[1..5].try_into().unwrap();
// The next element should be the number of user set public values.
let num_user_public_values = proof[5] as usize;
// The rest elements should be user set public values and should be `u32`.
let public_values = proof[6..]
.iter()
.map(|v| Some(u32::try_from(*v).ok()?.to_le_bytes()))
.take(num_user_public_values)
.collect::<Option<Vec<_>>>()
.ok_or(Error::InvalidPublicValue)?
.into_iter()
.flatten()
.collect();
Ok((rom_digest, public_values))
fn vadcop_final_proof_aligned(
proof: &ZiskProofWithPublicValues,
) -> Result<VadcopFinalProof, Error> {
let mut vadcop_final_proof = proof
.get_vadcop_final_proof()
.map_err(Error::InvalidProofFormat)?;
vadcop_final_proof.proof = align_to_u64(vadcop_final_proof.proof)?;
vadcop_final_proof.public_values = align_to_u64(vadcop_final_proof.public_values)?;
Ok(vadcop_final_proof)
}
/// Returns u64-aligned bytes.
///
/// Returns an error if `data.len()` is not a multiple of 8.
fn align_to_u64(data: &[u8]) -> Result<Cow<'_, [u8]>, Error> {
fn align_to_u64(data: Vec<u8>) -> Result<Vec<u8>, Error> {
if !data.len().is_multiple_of(8) {
return Err(Error::InvalidProofSize(data.len()));
}
Ok(if data.as_ptr().cast::<u64>().is_aligned() {
Cow::Borrowed(data)
data
} else {
let mut aligned: Vec<u64> = vec![0; data.len() / 8];
bytemuck::cast_slice_mut(&mut aligned).copy_from_slice(data);
Cow::Owned(bytemuck::cast_slice(&aligned).to_vec())
bytemuck::cast_slice_mut(&mut aligned).copy_from_slice(&data);
bytemuck::cast_slice(&aligned).to_vec()
})
}
fn panic_msg(err: Box<dyn Any + Send + 'static>) -> String {
None.or_else(|| err.downcast_ref::<String>().cloned())
.or_else(|| err.downcast_ref::<&'static str>().map(ToString::to_string))
.unwrap_or_else(|| "unknown panic msg".to_string())
}
/// Returns path to `~/.zisk` directory.
pub(crate) fn dot_zisk_dir_path() -> PathBuf {
fn dot_zisk_dir_path() -> PathBuf {
PathBuf::from(env::var("HOME").expect("env `$HOME` should be set")).join(".zisk")
}
/// Returns path to `~/.zisk/cache` directory.
fn cache_path() -> PathBuf {
dot_zisk_dir_path().join("cache")
}
/// Returns path to `~/.zisk/provingKey` directory.
fn proving_key_path() -> PathBuf {
dot_zisk_dir_path().join("provingKey")
}

View File

@@ -1,16 +1,25 @@
//! Remote ZisK cluster proving.
use crate::zkvm::Error;
use crate::zkvm::{
Error,
sdk::cluster::api::{
ErrorResponse, HintsMode, InputMode, LaunchProofRequest, ProofStatusType,
SubscribeToProofRequest, SystemStatusRequest, launch_proof_response,
system_status_response, zisk_distributed_api_client::ZiskDistributedApiClient,
},
};
use ere_zkvm_interface::zkvm::{RemoteProverConfig, block_on};
use futures_util::StreamExt;
use std::time::Duration;
use tonic::transport::Channel;
use tracing::debug;
use zisk_distributed_grpc_api::{
ErrorResponse, InputMode, LaunchProofRequest, ProofStatusType, SubscribeToProofRequest,
SystemStatusRequest, launch_proof_response, system_status_response,
zisk_distributed_api_client::ZiskDistributedApiClient,
};
use zisk_sdk::ZiskProofWithPublicValues;
#[rustfmt::skip]
mod api;
#[cfg(test)]
mod test;
/// Wrapper for the ZisK cluster client.
///
@@ -27,14 +36,17 @@ impl ClusterClient {
}
/// Sync wrapper for [`Self::prove_async`].
pub fn prove(&self, input: &[u8]) -> Result<(Vec<u8>, Duration), Error> {
pub fn prove(&self, input: &[u8]) -> Result<(ZiskProofWithPublicValues, Duration), Error> {
block_on(self.prove_async(input))
}
/// Send proof request to cluster and wait for completion.
///
/// Returns the proof and proving time reported by the cluster.
async fn prove_async(&self, input: &[u8]) -> Result<(Vec<u8>, Duration), Error> {
/// Returns the proof with public values and proving time reported by the cluster.
async fn prove_async(
&self,
input: &[u8],
) -> Result<(ZiskProofWithPublicValues, Duration), Error> {
let mut client = self.client.clone();
// Check system status to get available compute capacity
@@ -80,9 +92,12 @@ impl ClusterClient {
let launch_request = LaunchProofRequest {
data_id,
compute_capacity,
input_mode: InputMode::Data.into(),
input_path: None,
minimal_compute_capacity: compute_capacity,
inputs_mode: InputMode::Data.into(),
inputs_uri: None,
input_data: Some(input.to_vec()),
hints_mode: HintsMode::None.into(),
hints_uri: None,
simulated_node: None,
};
@@ -120,16 +135,19 @@ impl ClusterClient {
match ProofStatusType::try_from(update.status) {
Ok(ProofStatusType::ProofStatusCompleted) => match update.final_proof {
Some(final_proof) => {
let proof = bytemuck::cast_slice(&final_proof.values).to_vec();
let proof_with_publics = ZiskProofWithPublicValues::new_from_vadcop_proof(
&final_proof.values,
false,
)
.map_err(Error::InvalidProofFormat)?;
let proving_time = Duration::from_millis(update.duration_ms);
debug!(
proof_size = proof.len(),
proving_time = ?proving_time,
"Proof generated successfully"
);
Ok((proof, proving_time))
Ok((proof_with_publics, proving_time))
}
None => Err(cluster_error("Missing final proof")),
},

View File

@@ -0,0 +1,381 @@
// This file is @generated by prost-build.
/// Standardized error response
#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
pub struct ErrorResponse {
/// Error code
#[prost(string, tag = "1")]
pub code: ::prost::alloc::string::String,
/// Human-readable error message
#[prost(string, tag = "2")]
pub message: ::prost::alloc::string::String,
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)]
pub struct SystemStatusRequest {}
#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
pub struct SystemStatusResponse {
#[prost(oneof = "system_status_response::Result", tags = "1, 2")]
pub result: ::core::option::Option<system_status_response::Result>,
}
/// Nested message and enum types in `SystemStatusResponse`.
pub mod system_status_response {
#[derive(Clone, PartialEq, Eq, Hash, ::prost::Oneof)]
pub enum Result {
#[prost(message, tag = "1")]
Status(super::SystemStatus),
#[prost(message, tag = "2")]
Error(super::ErrorResponse),
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)]
pub struct SystemStatus {
#[prost(uint32, tag = "1")]
pub total_workers: u32,
#[prost(uint32, tag = "2")]
pub compute_capacity: u32,
#[prost(uint32, tag = "3")]
pub idle_workers: u32,
#[prost(uint32, tag = "4")]
pub busy_workers: u32,
#[prost(uint32, tag = "5")]
pub active_jobs: u32,
}
#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
pub struct LaunchProofRequest {
#[prost(string, tag = "1")]
pub data_id: ::prost::alloc::string::String,
#[prost(uint32, tag = "2")]
pub compute_capacity: u32,
#[prost(uint32, tag = "3")]
pub minimal_compute_capacity: u32,
#[prost(enumeration = "InputMode", tag = "4")]
pub inputs_mode: i32,
#[prost(string, optional, tag = "5")]
pub inputs_uri: ::core::option::Option<::prost::alloc::string::String>,
/// Input data sent directly from client
#[prost(bytes = "vec", optional, tag = "6")]
pub input_data: ::core::option::Option<::prost::alloc::vec::Vec<u8>>,
#[prost(enumeration = "HintsMode", tag = "7")]
pub hints_mode: i32,
#[prost(string, optional, tag = "8")]
pub hints_uri: ::core::option::Option<::prost::alloc::string::String>,
/// If set, indicates this is a simulated worker
#[prost(uint32, optional, tag = "9")]
pub simulated_node: ::core::option::Option<u32>,
}
#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
pub struct LaunchProofResponse {
#[prost(oneof = "launch_proof_response::Result", tags = "1, 2")]
pub result: ::core::option::Option<launch_proof_response::Result>,
}
/// Nested message and enum types in `LaunchProofResponse`.
pub mod launch_proof_response {
#[derive(Clone, PartialEq, Eq, Hash, ::prost::Oneof)]
pub enum Result {
#[prost(string, tag = "1")]
JobId(::prost::alloc::string::String),
#[prost(message, tag = "2")]
Error(super::ErrorResponse),
}
}
/// Subscription request message
#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
pub struct SubscribeToProofRequest {
#[prost(string, tag = "1")]
pub job_id: ::prost::alloc::string::String,
}
/// Subscription status update (streamed to client)
#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
pub struct ProofStatusUpdate {
#[prost(string, tag = "1")]
pub job_id: ::prost::alloc::string::String,
#[prost(enumeration = "ProofStatusType", tag = "2")]
pub status: i32,
/// Present on success
#[prost(message, optional, tag = "3")]
pub final_proof: ::core::option::Option<FinalProof>,
/// Present on failure
#[prost(message, optional, tag = "4")]
pub error: ::core::option::Option<ErrorResponse>,
#[prost(uint64, tag = "5")]
pub duration_ms: u64,
}
#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
pub struct FinalProof {
#[prost(uint64, repeated, tag = "1")]
pub values: ::prost::alloc::vec::Vec<u64>,
#[prost(uint64, tag = "2")]
pub executed_steps: u64,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)]
#[repr(i32)]
pub enum InputMode {
/// No input provided
None = 0,
/// Input will be provided as a PATH
Path = 1,
/// Input data will be sent directly
Data = 2,
}
impl InputMode {
/// String value of the enum field names used in the ProtoBuf definition.
///
/// The values are not transformed in any way and thus are considered stable
/// (if the ProtoBuf definition does not change) and safe for programmatic use.
pub fn as_str_name(&self) -> &'static str {
match self {
Self::None => "INPUT_MODE_NONE",
Self::Path => "INPUT_MODE_PATH",
Self::Data => "INPUT_MODE_DATA",
}
}
/// Creates an enum from field names used in the ProtoBuf definition.
pub fn from_str_name(value: &str) -> ::core::option::Option<Self> {
match value {
"INPUT_MODE_NONE" => Some(Self::None),
"INPUT_MODE_PATH" => Some(Self::Path),
"INPUT_MODE_DATA" => Some(Self::Data),
_ => None,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)]
#[repr(i32)]
pub enum HintsMode {
/// No hints provided
None = 0,
/// Hints will be provided as a PATH
Path = 1,
/// Hints will be sent as a stream
Stream = 2,
}
impl HintsMode {
/// String value of the enum field names used in the ProtoBuf definition.
///
/// The values are not transformed in any way and thus are considered stable
/// (if the ProtoBuf definition does not change) and safe for programmatic use.
pub fn as_str_name(&self) -> &'static str {
match self {
Self::None => "HINTS_MODE_NONE",
Self::Path => "HINTS_MODE_PATH",
Self::Stream => "HINTS_MODE_STREAM",
}
}
/// Creates an enum from field names used in the ProtoBuf definition.
pub fn from_str_name(value: &str) -> ::core::option::Option<Self> {
match value {
"HINTS_MODE_NONE" => Some(Self::None),
"HINTS_MODE_PATH" => Some(Self::Path),
"HINTS_MODE_STREAM" => Some(Self::Stream),
_ => None,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)]
#[repr(i32)]
pub enum ProofStatusType {
ProofStatusCompleted = 0,
ProofStatusFailed = 1,
}
impl ProofStatusType {
/// String value of the enum field names used in the ProtoBuf definition.
///
/// The values are not transformed in any way and thus are considered stable
/// (if the ProtoBuf definition does not change) and safe for programmatic use.
pub fn as_str_name(&self) -> &'static str {
match self {
Self::ProofStatusCompleted => "PROOF_STATUS_COMPLETED",
Self::ProofStatusFailed => "PROOF_STATUS_FAILED",
}
}
/// Creates an enum from field names used in the ProtoBuf definition.
pub fn from_str_name(value: &str) -> ::core::option::Option<Self> {
match value {
"PROOF_STATUS_COMPLETED" => Some(Self::ProofStatusCompleted),
"PROOF_STATUS_FAILED" => Some(Self::ProofStatusFailed),
_ => None,
}
}
}
/// Generated client implementations.
pub mod zisk_distributed_api_client {
#![allow(
unused_variables,
dead_code,
missing_docs,
clippy::wildcard_imports,
clippy::let_unit_value,
)]
use tonic::codegen::*;
use tonic::codegen::http::Uri;
#[derive(Debug, Clone)]
pub struct ZiskDistributedApiClient<T> {
inner: tonic::client::Grpc<T>,
}
impl ZiskDistributedApiClient<tonic::transport::Channel> {
/// Attempt to create a new client by connecting to a given endpoint.
pub async fn connect<D>(dst: D) -> Result<Self, tonic::transport::Error>
where
D: TryInto<tonic::transport::Endpoint>,
D::Error: Into<StdError>,
{
let conn = tonic::transport::Endpoint::new(dst)?.connect().await?;
Ok(Self::new(conn))
}
}
impl<T> ZiskDistributedApiClient<T>
where
T: tonic::client::GrpcService<tonic::body::Body>,
T::Error: Into<StdError>,
T::ResponseBody: Body<Data = Bytes> + std::marker::Send + 'static,
<T::ResponseBody as Body>::Error: Into<StdError> + std::marker::Send,
{
pub fn new(inner: T) -> Self {
let inner = tonic::client::Grpc::new(inner);
Self { inner }
}
pub fn with_origin(inner: T, origin: Uri) -> Self {
let inner = tonic::client::Grpc::with_origin(inner, origin);
Self { inner }
}
pub fn with_interceptor<F>(
inner: T,
interceptor: F,
) -> ZiskDistributedApiClient<InterceptedService<T, F>>
where
F: tonic::service::Interceptor,
T::ResponseBody: Default,
T: tonic::codegen::Service<
http::Request<tonic::body::Body>,
Response = http::Response<
<T as tonic::client::GrpcService<tonic::body::Body>>::ResponseBody,
>,
>,
<T as tonic::codegen::Service<
http::Request<tonic::body::Body>,
>>::Error: Into<StdError> + std::marker::Send + std::marker::Sync,
{
ZiskDistributedApiClient::new(InterceptedService::new(inner, interceptor))
}
/// Compress requests with the given encoding.
///
/// This requires the server to support it otherwise it might respond with an
/// error.
#[must_use]
pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self {
self.inner = self.inner.send_compressed(encoding);
self
}
/// Enable decompressing responses.
#[must_use]
pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self {
self.inner = self.inner.accept_compressed(encoding);
self
}
/// Limits the maximum size of a decoded message.
///
/// Default: `4MB`
#[must_use]
pub fn max_decoding_message_size(mut self, limit: usize) -> Self {
self.inner = self.inner.max_decoding_message_size(limit);
self
}
/// Limits the maximum size of an encoded message.
///
/// Default: `usize::MAX`
#[must_use]
pub fn max_encoding_message_size(mut self, limit: usize) -> Self {
self.inner = self.inner.max_encoding_message_size(limit);
self
}
/// System status
pub async fn system_status(
&mut self,
request: impl tonic::IntoRequest<super::SystemStatusRequest>,
) -> std::result::Result<
tonic::Response<super::SystemStatusResponse>,
tonic::Status,
> {
self.inner
.ready()
.await
.map_err(|e| {
tonic::Status::unknown(
format!("Service was not ready: {}", e.into()),
)
})?;
let codec = tonic_prost::ProstCodec::default();
let path = http::uri::PathAndQuery::from_static(
"/zisk.distributed.api.v1.ZiskDistributedApi/SystemStatus",
);
let mut req = request.into_request();
req.extensions_mut()
.insert(
GrpcMethod::new(
"zisk.distributed.api.v1.ZiskDistributedApi",
"SystemStatus",
),
);
self.inner.unary(req, path, codec).await
}
/// Launch a proof job
pub async fn launch_proof(
&mut self,
request: impl tonic::IntoRequest<super::LaunchProofRequest>,
) -> std::result::Result<
tonic::Response<super::LaunchProofResponse>,
tonic::Status,
> {
self.inner
.ready()
.await
.map_err(|e| {
tonic::Status::unknown(
format!("Service was not ready: {}", e.into()),
)
})?;
let codec = tonic_prost::ProstCodec::default();
let path = http::uri::PathAndQuery::from_static(
"/zisk.distributed.api.v1.ZiskDistributedApi/LaunchProof",
);
let mut req = request.into_request();
req.extensions_mut()
.insert(
GrpcMethod::new(
"zisk.distributed.api.v1.ZiskDistributedApi",
"LaunchProof",
),
);
self.inner.unary(req, path, codec).await
}
/// Client subscription to job completion
pub async fn subscribe_to_proof(
&mut self,
request: impl tonic::IntoRequest<super::SubscribeToProofRequest>,
) -> std::result::Result<
tonic::Response<tonic::codec::Streaming<super::ProofStatusUpdate>>,
tonic::Status,
> {
self.inner
.ready()
.await
.map_err(|e| {
tonic::Status::unknown(
format!("Service was not ready: {}", e.into()),
)
})?;
let codec = tonic_prost::ProstCodec::default();
let path = http::uri::PathAndQuery::from_static(
"/zisk.distributed.api.v1.ZiskDistributedApi/SubscribeToProof",
);
let mut req = request.into_request();
req.extensions_mut()
.insert(
GrpcMethod::new(
"zisk.distributed.api.v1.ZiskDistributedApi",
"SubscribeToProof",
),
);
self.inner.server_streaming(req, path, codec).await
}
}
}

View File

@@ -0,0 +1,31 @@
use std::{env, fs, path::PathBuf};
/// To sync generated `api.rs`, run:
///
/// ```
/// cargo test --package ere-zisk --lib --release -- zkvm::cluster_client::test::api_generation --exact
/// ```
#[test]
fn api_generation() {
let tempdir = tempfile::tempdir().unwrap();
let dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
let proto_dir = dir.join("src/zkvm/sdk/cluster");
tonic_prost_build::configure()
.build_server(false)
.out_dir(tempdir.path())
.compile_protos(
&[&proto_dir.join("zisk_distributed_api.proto")],
&[&proto_dir],
)
.unwrap();
let latest = tempdir.path().join("zisk.distributed.api.v1.rs");
let current = proto_dir.join("api.rs");
// If it's in CI env, don't overwrite but only check if it's up-to-date.
if env::var_os("GITHUB_ACTIONS").is_none() {
fs::copy(&latest, &current).unwrap();
}
assert_eq!(fs::read(&latest).unwrap(), fs::read(&current).unwrap());
}

View File

@@ -0,0 +1,112 @@
syntax = "proto3";
package zisk.distributed.api.v1;
// ============================================================================
// gRPC Service Definition
// ============================================================================
service ZiskDistributedApi {
// System status
rpc SystemStatus(SystemStatusRequest) returns (SystemStatusResponse);
// Launch a proof job
rpc LaunchProof(LaunchProofRequest) returns (LaunchProofResponse);
// Client subscription to job completion
rpc SubscribeToProof(SubscribeToProofRequest) returns (stream ProofStatusUpdate);
}
// ============================================================================
// Common Types
// ============================================================================
// Standardized error response
message ErrorResponse {
string code = 1; // Error code
string message = 2; // Human-readable error message
}
// ============================================================================
// SystemStatus
// ============================================================================
message SystemStatusRequest {}
message SystemStatusResponse {
oneof result {
SystemStatus status = 1;
ErrorResponse error = 2;
}
}
message SystemStatus {
uint32 total_workers = 1;
uint32 compute_capacity = 2;
uint32 idle_workers = 3;
uint32 busy_workers = 4;
uint32 active_jobs = 5;
}
// ============================================================================
// LaunchProof
// ============================================================================
message LaunchProofRequest {
string data_id = 1;
uint32 compute_capacity = 2;
uint32 minimal_compute_capacity = 3;
InputMode inputs_mode = 4;
optional string inputs_uri = 5;
optional bytes input_data = 6; // Input data sent directly from client
HintsMode hints_mode = 7;
optional string hints_uri = 8;
optional uint32 simulated_node = 9; // If set, indicates this is a simulated worker
}
enum InputMode {
INPUT_MODE_NONE = 0; // No input provided
INPUT_MODE_PATH = 1; // Input will be provided as a PATH
INPUT_MODE_DATA = 2; // Input data will be sent directly
}
enum HintsMode {
HINTS_MODE_NONE = 0; // No hints provided
HINTS_MODE_PATH = 1; // Hints will be provided as a PATH
HINTS_MODE_STREAM = 2; // Hints will be sent as a stream
}
message LaunchProofResponse {
oneof result {
string job_id = 1;
ErrorResponse error = 2;
}
}
// ============================================================================
// SubscribeToProof
// ============================================================================
// Subscription request message
message SubscribeToProofRequest {
string job_id = 1;
}
// Subscription status update (streamed to client)
message ProofStatusUpdate {
string job_id = 1;
ProofStatusType status = 2;
optional FinalProof final_proof = 3; // Present on success
optional ErrorResponse error = 4; // Present on failure
uint64 duration_ms = 5;
}
enum ProofStatusType {
PROOF_STATUS_COMPLETED = 0;
PROOF_STATUS_FAILED = 1;
}
message FinalProof {
repeated uint64 values = 1;
uint64 executed_steps = 2;
}

View File

@@ -0,0 +1,100 @@
use crate::zkvm::{
Error,
sdk::{cache_path, panic_msg, proving_key_path},
};
use ere_zkvm_interface::zkvm::CommonError;
use parking_lot::{Mutex, MutexGuard};
use proofman::ProofMan;
use proofman_common::VerboseMode;
use proofman_fields::Goldilocks;
use std::{fs, ops::Deref};
use std::{panic, time::Duration};
use tempfile::tempdir;
use tracing::info;
use zisk_rom_setup::{assembly_files_exist, gen_assembly};
use zisk_sdk::{
Asm, ElfBinaryFromFile, ProofOpts, ProverClient, ZiskProofWithPublicValues, ZiskProver,
ZiskStdin,
};
pub struct LocalProver {
elf: Vec<u8>,
setup: Mutex<bool>,
prover: Mutex<Option<ZiskProver<Asm>>>,
}
impl LocalProver {
pub fn new(elf: Vec<u8>) -> Result<Self, Error> {
Ok(Self {
elf,
setup: Mutex::new(false),
prover: Mutex::new(None),
})
}
pub fn prove(&self, stdin: &[u8]) -> Result<(ZiskProofWithPublicValues, Duration), Error> {
self.setup()?;
let prover = self.prover().map_err(Error::InitProver)?;
let stdin = ZiskStdin::from_vec(stdin.to_vec());
let tempdir = tempdir().map_err(CommonError::tempdir)?;
let opts = ProofOpts::default().output_dir(tempdir.path().to_path_buf());
let result = panic::catch_unwind(|| prover.prove(stdin).with_proof_options(opts).run())
.map_err(|err| {
drop(prover);
self.prover.lock().take();
Error::ProvePanic(panic_msg(err))
})?
.map_err(Error::Prove)?;
Ok((
result.get_proof_with_publics().clone(),
result.get_duration(),
))
}
fn setup(&self) -> Result<(), Error> {
let mut guard = self.setup.lock();
if *guard {
return Ok(());
}
info!("Running Proofman check_setup...");
ProofMan::<Goldilocks>::check_setup(proving_key_path(), true, VerboseMode::Info)
.map_err(Error::CheckSetup)?;
*guard = true;
Ok(())
}
fn prover(&self) -> anyhow::Result<impl Deref<Target = ZiskProver<Asm>>> {
let mut guard = self.prover.lock();
if guard.is_none() {
let tempdir = tempdir().map_err(CommonError::tempdir)?;
let elf_path = tempdir.path().join("elf");
fs::write(&elf_path, &self.elf)
.map_err(|err| CommonError::write_file("elf", &elf_path, err))?;
if !assembly_files_exist(&elf_path, &cache_path(), false)? {
gen_assembly(&elf_path, &None, &None, false, false)?;
}
info!("Initializing local ZisK prover...");
let prover = ProverClient::builder().asm().build()?;
let elf_binary = ElfBinaryFromFile::new(&elf_path, false)?;
prover.setup(&elf_binary)?;
info!("Local ZisK prover initialized");
*guard = Some(prover);
}
Ok(MutexGuard::map(guard, |prover| prover.as_mut().unwrap()))
}
}

View File

@@ -1,461 +0,0 @@
//! Local ZisK server management via `cargo-zisk` commands.
use crate::zkvm::{Error, sdk::dot_zisk_dir_path};
use ere_zkvm_interface::zkvm::CommonError;
use parking_lot::Mutex;
use std::{
collections::BTreeMap,
env, fs,
io::Write,
iter,
net::{Ipv4Addr, TcpStream},
path::{Path, PathBuf},
process::{Child, Command, Stdio},
thread,
time::{Duration, Instant},
};
use strum::{EnumIter, IntoEnumIterator};
use tempfile::tempdir;
use tracing::{error, info};
use wait_timeout::ChildExt;
pub const DEFAULT_START_SERVER_TIMEOUT_SEC: u64 = 120; // 2 mins
pub const DEFAULT_SHUTDOWN_SERVER_TIMEOUT_SEC: u64 = 30; // 30 secs
pub const DEFAULT_PROVE_TIMEOUT_SEC: u64 = 3600; // 1 hour
/// ZisK server status returned from `cargo-zisk prove-client status`.
#[derive(Debug)]
pub enum ZiskServerStatus {
Idle,
Working,
}
/// Options of `cargo-zisk` commands.
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, EnumIter)]
pub enum ZiskServerOption {
Port,
UnlockMappedMemory, // Should be set if locked memory is not enough
MinimalMemory,
// GPU options
Preallocate, // Should be set only if GPU memory is enough
SharedTables,
MaxStreams,
NumberThreadsWitness,
MaxWitnessStored,
}
impl ZiskServerOption {
/// The key of the env variable to read from.
fn env_var_key(&self) -> &'static str {
match self {
Self::Port => "ERE_ZISK_PORT",
Self::UnlockMappedMemory => "ERE_ZISK_UNLOCK_MAPPED_MEMORY",
Self::MinimalMemory => "ERE_ZISK_MINIMAL_MEMORY",
Self::Preallocate => "ERE_ZISK_PREALLOCATE",
Self::SharedTables => "ERE_ZISK_SHARED_TABLES",
Self::MaxStreams => "ERE_ZISK_MAX_STREAMS",
Self::NumberThreadsWitness => "ERE_ZISK_NUMBER_THREADS_WITNESS",
Self::MaxWitnessStored => "ERE_ZISK_MAX_WITNESS_STORED",
}
}
/// Whether the option is a flag (false-by-default boolean option) or not.
///
/// When we read the option from env variable, if the option is a flag,
/// we only check if the env variable is set or not.
fn is_flag(&self) -> bool {
match self {
Self::UnlockMappedMemory
| Self::MinimalMemory
| Self::Preallocate
| Self::SharedTables => true,
Self::Port | Self::MaxStreams | Self::NumberThreadsWitness | Self::MaxWitnessStored => {
false
}
}
}
/// The option key to be appended to `cargo-zisk` command arguments.
fn key(&self) -> &'static str {
match self {
Self::Port => "--port",
Self::UnlockMappedMemory => "--unlock-mapped-memory",
// NOTE: Use snake case for `prove-client` command
// Issue for tracking: https://github.com/eth-act/ere/issues/151.
Self::MinimalMemory => "--minimal_memory",
Self::Preallocate => "--preallocate",
Self::SharedTables => "--shared-tables",
Self::MaxStreams => "--max-streams",
Self::NumberThreadsWitness => "--number-threads-witness",
Self::MaxWitnessStored => "--max-witness-stored",
}
}
}
/// Configurable options for `cargo-zisk server` and `cargo-zisk prove-client` commands.
#[derive(Clone)]
pub struct ZiskServerOptions(BTreeMap<ZiskServerOption, String>);
impl ZiskServerOptions {
/// Read options from env variables.
pub fn from_env() -> Self {
Self(
ZiskServerOption::iter()
.flat_map(|option| env::var(option.env_var_key()).ok().map(|val| (option, val)))
.collect(),
)
}
/// Returns `cargo-zisk` command arguments by given options that have been
/// set.
fn args(
&self,
options: impl IntoIterator<Item = ZiskServerOption>,
) -> impl Iterator<Item = &str> {
options
.into_iter()
.filter(|option| self.0.contains_key(option))
.flat_map(|option| {
iter::once(option.key())
.chain((!option.is_flag()).then(|| self.0[&option].as_str()))
})
}
/// Returns `cargo-zisk server` command arguments.
pub(crate) fn server_args(&self) -> impl Iterator<Item = &str> {
self.args([
ZiskServerOption::Port,
ZiskServerOption::UnlockMappedMemory,
ZiskServerOption::Preallocate,
ZiskServerOption::SharedTables,
ZiskServerOption::MaxStreams,
ZiskServerOption::NumberThreadsWitness,
ZiskServerOption::MaxWitnessStored,
])
}
/// Returns `cargo-zisk prove-client` command arguments.
pub(crate) fn prove_client_args(&self) -> impl Iterator<Item = &str> {
self.args([ZiskServerOption::Port])
}
/// Returns `cargo-zisk prove-client prove` command arguments.
pub(crate) fn prove_args(&self) -> impl Iterator<Item = &str> {
self.prove_client_args()
.chain(self.args([ZiskServerOption::MinimalMemory]))
}
}
/// Wrapper for ZisK server child process.
pub struct ZiskServer {
options: ZiskServerOptions,
elf_path: PathBuf,
cuda: bool,
child: Mutex<Option<Child>>,
}
impl Drop for ZiskServer {
fn drop(&mut self) {
self.shutdown();
}
}
impl ZiskServer {
/// Create a new ZisK server for the given ELF.
///
/// The server process is lazily started on the first call to [`prove`](ZiskServer::prove).
pub fn new(elf_path: &Path, cuda: bool, options: ZiskServerOptions) -> Self {
Self {
elf_path: elf_path.to_path_buf(),
cuda,
options,
child: Mutex::new(None),
}
}
/// Send prove request to server and wait for proof to be created.
///
/// Returns the proof.
pub fn prove(&self, input: &[u8]) -> Result<Vec<u8>, Error> {
self.ensure_ready()?;
// Prefix that ZisK server will add to the file name of the proof.
// We use constant because the file will be save to a temporary dir,
// so there will be no conflict.
const PREFIX: &str = "ere";
let tempdir = tempdir().map_err(CommonError::tempdir)?;
let input_path = tempdir.path().join("input");
let output_path = tempdir.path().join("output");
let proof_path = output_path.join(format!("{PREFIX}-vadcop_final_proof.bin"));
fs::write(&input_path, input)
.map_err(|err| CommonError::write_file("input", &input_path, err))?;
// NOTE: Use snake case for `prove-client` command
// Issue for tracking: https://github.com/eth-act/ere/issues/151.
let mut cmd = Command::new("cargo-zisk");
let output = cmd
.args(["prove-client", "prove"])
.arg("--input")
.arg(input_path)
.arg("--output_dir")
.arg(&output_path)
.args(["-p", PREFIX])
.args(["--aggregation", "--verify_proofs"])
.args(self.options.prove_args())
.output()
.map_err(|err| CommonError::command(&cmd, err))?;
if !output.status.success() {
return Err(CommonError::command_exit_non_zero(
&cmd,
output.status,
Some(&output),
))?;
}
// ZisK server will finish the `prove` requested above then respond the
// following `status`. So if the following `status` succeeds, the proof
// should also be ready.
self.status(prove_timeout()).map_err(|err| {
if matches!(err, Error::TimeoutWaitingServerReady) {
Error::TimeoutWaitingServerProving
} else if err.to_string().contains("EOF") {
Error::ServerCrashed
} else {
err
}
})?;
let proof = fs::read(&proof_path)
.map_err(|err| CommonError::read_file("proof", &proof_path, err))?;
Ok(proof)
}
/// Ensure the server is running and responsive, restarting it if needed.
pub fn ensure_ready(&self) -> Result<(), Error> {
if self.child.lock().is_some() && self.status(start_server_timeout()).is_ok() {
return Ok(());
}
const MAX_RETRY: usize = 3;
let mut attempt = 0;
loop {
self.shutdown();
match self.start() {
Ok(()) => return Ok(()),
Err(Error::TimeoutWaitingServerReady) if attempt < MAX_RETRY => {
error!("Timeout waiting server ready, restarting...");
attempt += 1;
continue;
}
Err(err) => return Err(err),
}
}
}
/// Spawn the server process and wait until it's ready.
fn start(&self) -> Result<(), Error> {
info!("Starting ZisK server...");
let (cargo_zisk, witness_lib_name) = if self.cuda {
("cargo-zisk-cuda", "libzisk_witness_cuda.so")
} else {
("cargo-zisk", "libzisk_witness.so")
};
let witness_lib_path = dot_zisk_dir_path().join("bin").join(witness_lib_name);
let mut cmd = Command::new(cargo_zisk);
cmd.arg("server")
.args(self.options.server_args())
.arg("--elf")
.arg(&self.elf_path)
.arg("--witness-lib")
.arg(witness_lib_path)
.arg("--aggregation");
let child = cmd.spawn().map_err(|err| CommonError::command(&cmd, err))?;
{
let mut guard = self.child.lock();
*guard = Some(child);
}
self.wait_until_ready()?;
Ok(())
}
/// Wait until the server status to be idle.
pub fn wait_until_ready(&self) -> Result<(), Error> {
const INTERVAL: Duration = Duration::from_secs(1);
let timeout = start_server_timeout();
info!("Waiting until server is ready...");
let start = Instant::now();
while !matches!(self.status(timeout), Ok(ZiskServerStatus::Idle)) {
if start.elapsed() > timeout {
return Err(Error::TimeoutWaitingServerReady);
}
thread::sleep(INTERVAL);
}
Ok(())
}
/// Gracefully shut down the server, falling back to force-kill on failure.
fn shutdown(&self) {
let mut guard = self.child.lock();
let Some(mut child) = guard.take() else {
return;
};
info!("Shutting down ZisK server");
let mut cmd = Command::new("cargo-zisk");
let result = cmd
.args(["prove-client", "shutdown"])
.args(self.options.prove_client_args())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.and_then(
|mut child| match child.wait_timeout(shutdown_server_timeout())? {
Some(_) => child.wait_with_output(),
None => {
child.kill().ok();
Err(std::io::Error::other("shutdown command timed out"))
}
},
);
if result.as_ref().is_ok_and(|output| output.status.success()) {
info!("Shutdown ZisK server");
} else {
error!(
"Failed to shutdown ZisK server: {}",
result
.map(|output| String::from_utf8_lossy(&output.stderr).to_string())
.unwrap_or_else(|err| err.to_string())
);
error!("Shutdown server child process and asm services manually...");
let _ = child.kill();
shutdown_asm_service(23115);
shutdown_asm_service(23116);
shutdown_asm_service(23117);
remove_shm_files();
}
}
/// Get status of server.
fn status(&self, timeout: Duration) -> Result<ZiskServerStatus, Error> {
let mut cmd = Command::new("cargo-zisk");
let mut child = cmd
.args(["prove-client", "status"])
.args(self.options.prove_client_args())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.map_err(|err| CommonError::command(&cmd, err))?;
if child
.wait_timeout(timeout)
.map_err(|err| CommonError::command(&cmd, err))?
.is_none()
{
// Timeout reached, kill the process
child.kill().ok();
return Err(Error::TimeoutWaitingServerReady);
}
let output = child
.wait_with_output()
.map_err(|err| CommonError::command(&cmd, err))?;
if !output.status.success() {
return Err(CommonError::command_exit_non_zero(
&cmd,
output.status,
Some(&output),
))?;
}
let stdout = String::from_utf8_lossy(&output.stdout);
if stdout.contains("idle") {
Ok(ZiskServerStatus::Idle)
} else if stdout.contains("working") {
Ok(ZiskServerStatus::Working)
} else {
Err(Error::UnknownServerStatus {
stdout: stdout.to_string(),
})
}
}
}
/// Send shutdown request to ZisK asm services.
fn shutdown_asm_service(port: u16) {
// According to https://github.com/0xPolygonHermez/zisk/blob/v0.15.0/emulator-asm/asm-runner/src/asm_services/mod.rs#L34.
const CMD_SHUTDOWN_REQUEST_ID: u64 = 1000000;
if let Ok(mut stream) = TcpStream::connect((Ipv4Addr::LOCALHOST, port)) {
let _ = stream.write_all(
&[CMD_SHUTDOWN_REQUEST_ID, 0, 0, 0, 0]
.into_iter()
.flat_map(|word| word.to_le_bytes())
.collect::<Vec<_>>(),
);
}
}
/// Remove shared memory created by ZisK.
fn remove_shm_files() {
let Ok(shm_dir) = fs::read_dir(Path::new("/dev/shm")) else {
return;
};
for entry in shm_dir.flatten() {
let path = entry.path();
if path
.file_name()
.and_then(|n| n.to_str())
.is_some_and(|name| name.starts_with("ZISK") || name.starts_with("sem"))
{
let _ = fs::remove_file(&path);
}
}
}
/// Returns the server start timeout, configurable via `ERE_ZISK_START_SERVER_TIMEOUT_SEC`.
fn start_server_timeout() -> Duration {
timeout(
"ERE_ZISK_START_SERVER_TIMEOUT_SEC",
DEFAULT_START_SERVER_TIMEOUT_SEC,
)
}
/// Returns the server shutdown timeout, configurable via `ERE_ZISK_SHUTDOWN_SERVER_TIMEOUT_SEC`.
fn shutdown_server_timeout() -> Duration {
timeout(
"ERE_ZISK_SHUTDOWN_SERVER_TIMEOUT_SEC",
DEFAULT_SHUTDOWN_SERVER_TIMEOUT_SEC,
)
}
/// Returns the prove timeout, configurable via `ERE_ZISK_PROVE_TIMEOUT_SEC`.
fn prove_timeout() -> Duration {
timeout("ERE_ZISK_PROVE_TIMEOUT_SEC", DEFAULT_PROVE_TIMEOUT_SEC)
}
/// Read a timeout from the given env variable key, falling back to `default`.
fn timeout(key: &str, default: u64) -> Duration {
let sec = env::var(key)
.ok()
.and_then(|timeout| timeout.parse::<u64>().ok())
.unwrap_or(default);
Duration::from_secs(sec)
}

View File

@@ -6,9 +6,6 @@ FROM $BASE_IMAGE
# 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)
# Taken from https://0xpolygonhermez.github.io/zisk/getting_started/installation.html
RUN apt-get update && apt-get install -y --no-install-recommends \
@@ -37,8 +34,6 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
gcc-riscv64-unknown-elf \
&& apt-get clean && rm -rf /var/lib/apt/lists/*
# Default to build for RTX 50 series
ARG CUDA_ARCH=sm_120
# Copy the ZisK SDK installer script from the workspace context
COPY --chmod=755 scripts/sdk_installers/install_zisk_sdk.sh /tmp/install_zisk_sdk.sh

View File

@@ -14,6 +14,9 @@ WORKDIR /ere
ARG CUDA
ARG RUSTFLAGS
# Default to build for RTX 50 series
ARG CUDA_ARCH=sm_120
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/

View File

@@ -28,69 +28,49 @@ echo "Installing ZisK Toolchain and SDK using ziskup (prebuilt binaries)..."
ensure_tool_installed "curl" "to download the ziskup installer"
ensure_tool_installed "bash" "to run the ziskup installer"
ensure_tool_installed "rustup" "for managing Rust toolchains (ZisK installs its own)"
ensure_tool_installed "cargo" "as cargo-zisk is a cargo subcommand"
ensure_tool_installed "cargo" "to pre-build lib-c"
# Step 1: Download and run the script that installs the ziskup binary itself.
# Export SETUP_KEY=proving-no-consttree to download proving key but avoid doing
# cargo-zisk check-setup.
export ZISK_VERSION="0.15.0"
export SETUP_KEY=${SETUP_KEY:=proving-no-consttree}
curl "https://raw.githubusercontent.com/0xPolygonHermez/zisk/main/ziskup/install.sh" | bash
unset SETUP_KEY
# Export SETUP_KEY=proving-no-consttree to download proving key without doing setup.
export ZISK_VERSION="0.16.0"
# export SETUP_KEY=${SETUP_KEY:=proving-no-consttree}
# curl "https://raw.githubusercontent.com/0xPolygonHermez/zisk/main/ziskup/install.sh" | bash
# unset SETUP_KEY
# Step 2: Ensure the installed cargo-zisk binary is in PATH for this script session.
export PATH="$PATH:$HOME/.zisk/bin"
# FIXME: Issue for tracking: https://github.com/eth-act/ere/issues/200.
# FIXME: Remove and download from prebuilt when released
if true; then
ZISK_DIR="$HOME/.zisk"
BUCKET_URL="https://storage.googleapis.com/zisk-setup"
KEY_FILE="zisk-provingkey-pre-$ZISK_VERSION.tar.gz"
mkdir -p "$ZISK_DIR/bin" "$ZISK_DIR/zisk/emulator-asm"
# Download and install proving key
rm -rf "$ZISK_DIR/provingKey" "$ZISK_DIR/verifyKey" "$ZISK_DIR/cache"
curl -L -#o "/tmp/$KEY_FILE" "$BUCKET_URL/$KEY_FILE"
tar -xf "/tmp/$KEY_FILE" -C "$ZISK_DIR"
rm -f "/tmp/$KEY_FILE"
# Build libziskclib.a
WORKSPACE=$(mktemp -d)
git clone https://github.com/han0110/zisk.git --depth 1 --branch patch/v0.15.0 "$WORKSPACE"
cargo build --manifest-path "$WORKSPACE/Cargo.toml" --release
cp "$WORKSPACE/target/release/cargo-zisk" "$HOME/.zisk/bin/cargo-zisk"
cp "$WORKSPACE/target/release/libzisk_witness.so" "$HOME/.zisk/bin/libzisk_witness.so"
rm -rf "$WORKSPACE"
git clone --depth 1 --branch "pre-develop-$ZISK_VERSION" https://github.com/0xPolygonHermez/zisk.git "$WORKSPACE"
cargo build --manifest-path "$WORKSPACE/Cargo.toml" --release --package ziskclib --package cargo-zisk
# Install toolchain
"$WORKSPACE/target/release/cargo-zisk" sdk install-toolchain
# Copy files
cp "$WORKSPACE/target/release/cargo-zisk" "$ZISK_DIR/bin/"
cp "$WORKSPACE/target/release/libziskclib.a" "$ZISK_DIR/bin/"
cp -r "$WORKSPACE/emulator-asm/src" "$ZISK_DIR/zisk/emulator-asm/"
cp "$WORKSPACE/emulator-asm/Makefile" "$ZISK_DIR/zisk/emulator-asm/"
cp -r "$WORKSPACE/lib-c" "$ZISK_DIR/zisk/"
# Cleanup
rm -rf "${WORKSPACE}"
fi
# Verify ZisK installation
echo "Verifying ZisK installation..."
echo "Checking for 'zisk' toolchain..."
if rustup toolchain list | grep -q "^zisk"; then
echo "ZisK Rust toolchain found."
else
echo "Error: ZisK Rust toolchain ('zisk') not found after installation!" >&2
exit 1
fi
echo "Checking for cargo-zisk CLI tool..."
if cargo-zisk --version; then
echo "cargo-zisk CLI tool verified successfully."
else
echo "Error: 'cargo-zisk --version' failed." >&2
exit 1
fi
# Step 3: Build cargo-zisk-cuda from source with `gpu` feature enabled
if [ -n "$CUDA" ]; then
WORKSPACE=$(mktemp -d)
# FIXME: Issue for tracking: https://github.com/eth-act/ere/issues/200.
# git clone https://github.com/0xPolygonHermez/zisk.git --depth 1 --tag "v$ZISK_VERSION" "$WORKSPACE"
git clone https://github.com/han0110/zisk.git --depth 1 --branch patch/v0.15.0 "$WORKSPACE"
cargo build --manifest-path "$WORKSPACE/Cargo.toml" --release --features gpu
cp "$WORKSPACE/target/release/cargo-zisk" "$HOME/.zisk/bin/cargo-zisk-cuda"
cp "$WORKSPACE/target/release/libzisk_witness.so" "$HOME/.zisk/bin/libzisk_witness_cuda.so"
rm -rf "$WORKSPACE"
echo "Checking for cargo-zisk-cuda CLI tool..."
if cargo-zisk-cuda --version; then
echo "cargo-zisk-cuda CLI tool verified successfully."
else
echo "Error: 'cargo-zisk-cuda --version' failed." >&2
exit 1
fi
fi
# Step 4: Make sure `lib-c`'s build script is ran.
# Step 2: Make sure `lib-c`'s build script is ran.
#
# `ziskos` provides guest program runtime, and `lib-c` is a dependency of `ziskos`,
# when we need to compile guest, the `build.rs` of `lib-c` will need to be ran once,
@@ -98,8 +78,8 @@ fi
# So here we make sure it's already ran, and the built thing will be stored in
# `$CARGO_HOME/git/checkouts/zisk-{hash}/{rev}/lib-c/c/build`, so could be
# re-used as long as the `ziskos` has the same version.
WORKSPACE="/tmp/build-lib-c"
cargo new "$WORKSPACE" --name build-lib-c
cargo add lib-c --git https://github.com/0xPolygonHermez/zisk.git --tag "v$ZISK_VERSION" --manifest-path "$WORKSPACE/Cargo.toml"
WORKSPACE=$(mktemp -d)
cargo init "$WORKSPACE" --name build-lib-c
cargo add lib-c --git https://github.com/0xPolygonHermez/zisk.git --branch "pre-develop-$ZISK_VERSION" --manifest-path "$WORKSPACE/Cargo.toml"
cargo build --manifest-path "$WORKSPACE/Cargo.toml"
rm -rf "$WORKSPACE"

View File

@@ -11,3 +11,6 @@ require (
github.com/usbarmory/tamago v0.0.0-20250710154000-3dd21eabac74 // indirect
github.com/x448/float16 v0.8.4 // indirect
)
// FIXME: Remove when it is upstreamed
replace github.com/eth-act/skunkworks-tama => github.com/han0110/skunkworks-tama v0.0.0-20260218141853-cc6e788414fe

View File

@@ -1,7 +1,7 @@
github.com/eth-act/skunkworks-tama v0.0.0-20251105112532-eff8e3af014b h1:Nm1FYhFjCWnKkaRZzSjGmeWdqevsunbpOnYOayWpuoM=
github.com/eth-act/skunkworks-tama v0.0.0-20251105112532-eff8e3af014b/go.mod h1:M9fXNuyicUdFmj5nBlXRTNcUbJIDLMMs4eY1QoZHM3g=
github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
github.com/han0110/skunkworks-tama v0.0.0-20260218141853-cc6e788414fe h1:lv5TcqAYJUVs6e0iGzZy4/EWUMcimTNYTeO9MXQrL+A=
github.com/han0110/skunkworks-tama v0.0.0-20260218141853-cc6e788414fe/go.mod h1:M9fXNuyicUdFmj5nBlXRTNcUbJIDLMMs4eY1QoZHM3g=
github.com/usbarmory/tamago v0.0.0-20250710154000-3dd21eabac74 h1:zH22Y68S2cpwW278H+9v4r2SWpdP+JwUk/AwVc9LOlw=
github.com/usbarmory/tamago v0.0.0-20250710154000-3dd21eabac74/go.mod h1:0Bc0GnC88LvCAoCRUcd3DBFl7cribfVbCsiMJUbXyAE=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=