diff --git a/.github/actions/install-zisk/action.yml b/.github/actions/install-zisk/action.yml index 481542da2..857fcb356 100644 --- a/.github/actions/install-zisk/action.yml +++ b/.github/actions/install-zisk/action.yml @@ -1,18 +1,16 @@ name: Install Zisk description: >- Install the system build deps, the ZisK zkVM toolchain (ziskup, CPU build), - and — unless `proving-key: false` — the fork-matching proving key needed to - RUN the Zisk host. Execute needs the key too (zisk-host's `client.setup()` - loads the circuit's const-tree files before either the execute or the prove - branch), but BUILDING the host does not, so build-only callers skip the - few-GB download + const-tree regeneration. Assumes a Rust toolchain is - already set up. + and — unless `proving-key: false` — the fork-matching proving key used for + proof generation. Build and execute-only callers can skip the few-GB + download and const-tree regeneration. Assumes a Rust toolchain is already + set up. inputs: proving-key: description: >- - Install the fork-matching proving key (required to execute or prove; - not needed to build). Set false for build-only jobs. + Install the fork-matching proving key. Required to prove, but not to + build or use the SDK's execute-only client. required: false default: "true" @@ -107,9 +105,8 @@ runs: shell: bash run: cargo build --release -p proofman-starks-lib-c working-directory: zisk - # Execute still needs a proving key present: zisk-host calls `client.setup()` - # (which the SDK runs before the execute branch), and that loads the circuit's - # const-tree files. We host the fork-matching key in a public S3 bucket + # Proof generation needs the fork-matching proving key. It lives in a + # public S3 bucket # WITHOUT the const-trees — exactly like Zisk's released # `zisk-provingkey-*.tar.gz` on `storage.googleapis.com/zisk-setup` — and # regenerate them here with `cargo-zisk-dev check-setup -a`, which is how diff --git a/.github/workflows/bench-main.yml b/.github/workflows/bench-main.yml index 65fff44c5..99b052871 100644 --- a/.github/workflows/bench-main.yml +++ b/.github/workflows/bench-main.yml @@ -295,6 +295,8 @@ jobs: - name: Install Zisk if: matrix.params.backend == 'zisk' uses: ./.github/actions/install-zisk + with: + proving-key: false - run: echo "$HOME/.local/bin" >> $GITHUB_PATH # Provision the toolchain so the staged binaries find libleanshared # (no package build). use-github-cache off: nothing to cache here, and diff --git a/.github/workflows/bench-pr.yml b/.github/workflows/bench-pr.yml index dd818d4b2..3bdaeeb52 100644 --- a/.github/workflows/bench-pr.yml +++ b/.github/workflows/bench-pr.yml @@ -557,6 +557,8 @@ jobs: - name: Install Zisk if: matrix.params.backend == 'zisk' uses: ./.github/actions/install-zisk + with: + proving-key: false # ---------- PR side ---------- # The PR side runs first: `ix bench run` selects its constants from diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 89170ace6..6ae6220a6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,8 +1,6 @@ name: CI Jobs on: - push: - branches: main pull_request: merge_group: workflow_dispatch: @@ -127,15 +125,10 @@ jobs: # jobs and with each other after `build` succeeds. Both hosts exit # non-zero when the kernel rejects a constant (sp1: EXIT_REJECTED; zisk: # `reject_failures`), so a guest that compiles but panics / faults / rejects - # fails the job. SP1 execute is pure RISC-V emulation (no key). Zisk execute - # goes through `client.setup()`, which loads the circuit const-trees — so the - # Zisk job DOES install the fork-matching proving key (~3 GB + const-tree regen; - # verified against the Zisk source: `AsmCoreProver::new` requires the key at - # client-build time, before setup). SP1 and Zisk build as independent jobs so - # they parallelize; each installs only its own toolchain via sp1up / ziskup - # (prebuilt binaries). The apt list inside the install actions is the shared - # superset both backends need (proofman's C++ links OpenMPI/OpenMP/GMP/…; - # SP1's host crates need pkg-config + libssl-dev). + # fails the job. Both execute paths are keyless: Zisk uses its SDK's + # standalone execute-only client, which retains the ASM executor without + # initializing the prover. SP1 and Zisk build as independent jobs so they + # parallelize; each installs only its own toolchain via sp1up / ziskup. sp1-build: name: SP1 host build needs: build @@ -184,11 +177,9 @@ jobs: - uses: ./.github/actions/setup-rust-toolchain with: cache-workspaces: zisk - # Proving key ON (the default): the execute step's `client.setup()` loads - # the circuit const-trees, and `AsmCoreProver::new` requires the key path - # to exist at client-build time — so an execute run needs it even though a - # build does not. - uses: ./.github/actions/install-zisk + with: + proving-key: false # Unit tests: the clap surface `ix bench run` drives, plus the closure auditor # (closure_detects_missing_dep self-skips without an IX_TEST_IXE # fixture — this gate has no Lean build to produce one). @@ -205,11 +196,10 @@ jobs: with: name: nataddcomm-ixe path: . - # Run the guest ELF in the Zisk VM over the nataddcomm env. `zisk-host` - # exits 3 (EXIT_REJECTED, via `reject_failures`) if the kernel rejects any - # constant and non-zero on any VM fault — so the tool errors on failures, - # no output parsing. The ASM executor (default) mmaps with MAP_LOCKED, so - # raise the memlock limit in this shell for the tool it spawns. + # Run the guest ELF through Zisk's keyless ASM executor. `zisk-host` exits + # 3 (EXIT_REJECTED, via `reject_failures`) if the kernel rejects any + # constant and non-zero on any VM fault. The ASM executor mmaps with + # MAP_LOCKED, so raise the memlock limit for the tool it spawns. - name: Execute zisk-guest over the nataddcomm env working-directory: zisk env: diff --git a/.github/workflows/merge-tests.yml b/.github/workflows/merge-tests.yml index 6dbbdfd79..66803f3a6 100644 --- a/.github/workflows/merge-tests.yml +++ b/.github/workflows/merge-tests.yml @@ -19,9 +19,19 @@ jobs: fail-fast: true matrix: include: - - name: Lake ignored tests + - name: Lake ignored tests (compile pipeline) kind: lake runner: warp-ubuntu-latest-x64-32x + test_args: --ignored compile-pipeline + - name: Lake ignored tests (kernel) + kind: lake + runner: warp-ubuntu-latest-x64-32x + test_args: --ignored kernel + - name: Lake ignored tests (misc) + kind: lake + runner: warp-ubuntu-latest-x64-32x + test_args: --ignored --exclude=compile-pipeline,kernel,typecheckers + zk_voting: true - name: Valgrind FFI kind: valgrind runner: warp-ubuntu-latest-x64-8x @@ -63,16 +73,14 @@ jobs: key: lake-valgrind-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('lean-toolchain') }}-${{ hashFiles('lake-manifest.json') }}-${{ github.sha }} restore-keys: lake-valgrind-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('lean-toolchain') }}-${{ hashFiles('lake-manifest.json') }}- - # The proof build uses its own incremental cache and falls back to the - # base Lake cache without weakening the trust or sorry-frontier checks. + # A generic cache on the merge-queue ref can shadow a complete `lake-tc` + # cache from main, so this deliberately has no base-cache fallback. - if: ${{ matrix.kind == 'tc' }} uses: actions/cache@v6 with: path: ./.lake key: lake-tc-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('lean-toolchain') }}-${{ hashFiles('lake-manifest.json') }}-${{ github.sha }} - restore-keys: | - lake-tc-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('lean-toolchain') }}-${{ hashFiles('lake-manifest.json') }}- - lake-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('lean-toolchain') }}-${{ hashFiles('lake-manifest.json') }}- + restore-keys: lake-tc-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('lean-toolchain') }}-${{ hashFiles('lake-manifest.json') }}- # The Rust FFI bindgen build also needs the Lean toolchain and headers. - if: ${{ matrix.kind != 'valgrind' }} @@ -88,13 +96,11 @@ jobs: build-args: "IxTests" use-github-cache: false - - name: Run general ignored Lake tests + - name: Run ignored Lake test partition if: ${{ matrix.kind == 'lake' }} - run: >- - lake test --wfail -- --ignored - --exclude=tc-pins,tc-accel-diff,tc-anon-diff,tc-init,tc-tutorial,tc-roundtrip,lean4lean + run: lake test --wfail -- ${{ matrix.test_args }} - name: Run ZK voting prover - if: ${{ matrix.kind == 'lake' }} + if: ${{ matrix.kind == 'lake' && matrix.zk_voting }} run: lake exe Apps.ZKVoting.Prover - name: Install valgrind @@ -129,13 +135,9 @@ jobs: - name: Test Ix.Tc unit and adversarial fixtures if: ${{ matrix.kind == 'tc' }} run: lake test --wfail -- tc-unit - # These suites are partitioned from the general Lake sweep so the long - # parity and proof-related tests run concurrently with it. - name: Run Ix.Tc ignored tests if: ${{ matrix.kind == 'tc' }} - run: >- - lake test --wfail -- --ignored - tc-pins tc-accel-diff tc-anon-diff tc-init tc-tutorial tc-roundtrip lean4lean + run: lake test --wfail -- --ignored typecheckers merge-tests-result: name: Merge tests diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml index 042abec93..f8401b25c 100644 --- a/.github/workflows/nix.yml +++ b/.github/workflows/nix.yml @@ -1,8 +1,6 @@ name: Nix CI on: - push: - branches: main pull_request: merge_group: workflow_dispatch: @@ -18,7 +16,7 @@ jobs: # Runs Lean tests via Nix nix-test: name: Nix Tests - runs-on: warp-ubuntu-latest-x64-8x + runs-on: warp-ubuntu-latest-x64-16x steps: - uses: actions/checkout@v7 - uses: cachix/install-nix-action@v31 @@ -32,15 +30,8 @@ jobs: # Ix CLI - run: nix build --print-build-logs --accept-flake-config - run: nix run .#ix -- --help - # One step per check for separate logs; the store is shared across - # steps, so nothing rebuilds. - - run: nix build --print-build-logs --accept-flake-config .#checks.x86_64-linux.clippy - - run: nix build --print-build-logs --accept-flake-config .#checks.x86_64-linux.nextest - - run: nix build --print-build-logs --accept-flake-config .#checks.x86_64-linux.ix-tests - # Catch-all: builds every flake check for this system (near-free — the - # heavy ones above are already in the store), so a check added without - # its own step is still verified here. - - run: nix flake check --accept-flake-config + # A single invocation lets Nix schedule independent checks concurrently. + - run: nix flake check --print-build-logs --accept-flake-config # Tests Nix devShell support on Ubuntu nix-devshell: diff --git a/README.md b/README.md index 118de948b..9677b8b3c 100644 --- a/README.md +++ b/README.md @@ -195,13 +195,15 @@ cells locally, and `!benchmark` runs them on a PR — see **Lean tests:** `lake test` - `lake test -- ` runs one or multiple primary test suites. Primary suites: `ffi`, `byte-array`, `ixon`, `claim`, `commit`, `canon`, `keccak`, `sharing`, `graph-unit`, `condense-unit` -- `lake test -- --ignored` runs only the expensive test suites: `shard-map`, `rust-canon-roundtrip`, `serial-canon-roundtrip`, `parallel-canon-roundtrip`, `graph-cross`, `condense-cross`, `compile`, `decompile`, `rust-serialize`, `rust-decompile`, `commit-io`, `aiur`, `aiur-hashes`, `ixvm` +- `lake test -- --ignored` runs all expensive test suites and runners - Most tests require at least 32 GB RAM - The `compile` and `decompile` tests require 128 GB RAM - - `aiur` and `aiur-hashes` generate ZK proofs and use significant CPU -- `lake test -- --ignored ` runs one or multiple expensive suites by name + - `ixvm` generates ZK proofs and uses significant CPU +- `lake test -- --ignored ` runs one or more expensive suites, runners, or groups by name +- Ignored groups: `compile-pipeline`, `kernel`, and `typecheckers` +- `--exclude=` excludes ignored suites, runners, or groups from a full ignored-test run - `lake test -- --include-ignored` runs both primary and expensive test suites -- `lake test -- --include-ignored ` runs all primary suites plus one or multiple expensive suites +- `lake test -- --include-ignored ` runs all primary suites plus selected expensive suites, runners, or groups - `lake test -- cli` runs CLI integration tests - `lake test -- rust-compile` runs the Rust cross-compilation diagnostic diff --git a/Tests/Main.lean b/Tests/Main.lean index 1ff8a82c5..3e1f4450e 100644 --- a/Tests/Main.lean +++ b/Tests/Main.lean @@ -129,6 +129,50 @@ def ignoredSuites : Std.HashMap String (List LSpec.TestSeq) := .ofList [ ("tc-ingress-meta", Tests.Tc.IngressMeta.suite), ] +/-- Related ignored suites and runners that can be selected or excluded together. -/ +def ignoredGroups : Std.HashMap String (List String) := .ofList [ + ("compile-pipeline", [ + "rust-canon-roundtrip", + "serial-canon-roundtrip", + "parallel-canon-roundtrip", + "graph-cross", + "condense-cross", + "compile", + "decompile", + "rust-serialize", + "ixon-corpus", + "rust-decompile", + "validate-aux", + "aux-gen-diff", + "decompile-diff", + ]), + ("kernel", [ + "kernel-ixon-roundtrip", + "kernel-tutorial", + "kernel-check-env", + "kernel-check-const", + "rust-kernel-build-primitives", + "rust-kernel-build-prim-origs", + "ixvm", + ]), + ("typecheckers", [ + "tc-anon-diff", + "tc-init", + "tc-tutorial", + "tc-roundtrip", + "tc-ingress-meta", + "tc-pins", + "tc-accel-diff", + "lean4lean", + ]), +] + +private def expandIgnoredNames (names : List String) : List String := + (names.flatMap fun name => + match ignoredGroups[name]? with + | some members => members + | none => [name]).eraseDups + /-- Primary test runners — quick suites run by default alongside `primarySuites`, but kept as deferred `IO` actions (not `TestSeq` values) so their setup — Aiur system builds, STARK proofs — does not @@ -297,14 +341,15 @@ def main (args : List String) : IO UInt32 := do let runIgnored := args.contains "--ignored" let includeIgnored := args.contains "--include-ignored" - -- `--exclude=a,b,c` drops the named ignored suites/runners from the sweep, so - -- one job can run every ignored test except the suites another job owns. - let excludeSet : List String := + -- `--exclude=a,b,c` drops named ignored suites, runners, or groups. + let rawExcludeSet : List String := match args.find? (·.startsWith "--exclude=") with | some a => (a.drop ("--exclude=".length)).toString.splitOn "," |>.filter fun s => !s.isEmpty | none => [] + let excludeSet := expandIgnoredNames rawExcludeSet let filterArgs := args.filter fun a => a != "--ignored" && a != "--include-ignored" && !a.startsWith "--exclude=" + let ignoredFilterArgs := expandIgnoredNames filterArgs -- Run primary tests unless --ignored (without --include-ignored) is specified if !runIgnored || includeIgnored then @@ -338,16 +383,16 @@ def main (args : List String) : IO UInt32 := do -- that matches nothing is an ERROR, not a silent no-op: otherwise a typo -- runs (or excludes) nothing and still reports success having executed -- nothing. - for arg in filterArgs ++ excludeSet do + for arg in ignoredFilterArgs ++ excludeSet do if !(allRunners.any fun (key, _) => key == arg) && !ignoredSuites.contains arg then IO.eprintln s!"error: no ignored suite or runner named '{arg}'" return 1 let suites := excludeSet.foldl (fun m k => m.erase k) ignoredSuites let runners := allRunners.filter fun (key, _) => !excludeSet.contains key - let mut result ← LSpec.lspecIO suites filterArgs - let filtered := if filterArgs.isEmpty then runners - else filterArgs.filterMap fun arg => runners.find? fun (key, _) => key == arg + let mut result ← LSpec.lspecIO suites ignoredFilterArgs + let filtered := if ignoredFilterArgs.isEmpty then runners + else ignoredFilterArgs.filterMap fun arg => runners.find? fun (key, _) => key == arg for (_, action) in filtered do let r ← action if r != 0 then result := r diff --git a/flake.nix b/flake.nix index 4883aa140..d5cc07874 100644 --- a/flake.nix +++ b/flake.nix @@ -255,6 +255,7 @@ lake2nix.mkPackage ( lakeTestBuildArgs // { + lakeArtifacts = ixLib; name = "IxTests"; installArtifacts = true; } diff --git a/zisk/Cargo.lock b/zisk/Cargo.lock index 37c307b8e..aeff25c92 100644 --- a/zisk/Cargo.lock +++ b/zisk/Cargo.lock @@ -786,7 +786,7 @@ checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" [[package]] name = "bignat" version = "0.1.0" -source = "git+https://github.com/argumentcomputer/lean-ffi.git?rev=839b405de708b80504fda39abe1402114b4135a5#839b405de708b80504fda39abe1402114b4135a5" +source = "git+https://github.com/argumentcomputer/lean-ffi.git?rev=a6e781bc55cec99b99fa7a4bee105c5000cec967#a6e781bc55cec99b99fa7a4bee105c5000cec967" dependencies = [ "num-bigint", ] diff --git a/zisk/host/src/main.rs b/zisk/host/src/main.rs index 2802f7980..b54b3792d 100644 --- a/zisk/host/src/main.rs +++ b/zisk/host/src/main.rs @@ -44,14 +44,15 @@ use ix_kernel::anon_work::{ use ixon::env::Env as IxonEnv; use zisk_host::{AGG_PROGRAM, SHARD_PROGRAM}; use zisk_sdk::{ - EmbeddedClient, EmbeddedClientBuilder, EmbeddedOpts, ProverClient, - VerboseMode, VerifyConstraintsExtension, ZiskStdin, + EmbeddedClient, EmbeddedClientBuilder, EmbeddedExecuteOnlyClient, + EmbeddedOpts, GuestProgram, ProverClient, VerboseMode, + VerifyConstraintsExtension, ZiskStdin, }; #[derive(Parser, Debug)] #[command(author, version, about, long_about = None)] struct Args { - /// Run shards in the VM only — no proof generated. + /// Run shards in the VM only — no proof or proving key required. #[arg(long, conflicts_with = "verify_constraints")] execute: bool, @@ -598,8 +599,7 @@ fn program_vk(proof: &[u8]) -> Vec { /// side of vk pinning. The allowed set the agg guest verifies children /// against must come from here, never from the proofs being folded: deriving /// it from the (untrusted) proofs would let any proof admit its own program. -/// Requires the program's ROM setup to have run (`client.setup(...)`), which -/// writes the verkey file this reads. +/// Computed directly from the embedded ELF, without a proving key. fn guest_vk_bytes(program: &zisk_sdk::GuestProgram) -> Result> { let vk = program .vk() @@ -806,7 +806,64 @@ fn check_input_coherence( Ok(failures) } -fn build_client(gpu: bool, asm: bool) -> Result { +enum HostClient { + Prover(EmbeddedClient), + ExecuteOnly(EmbeddedExecuteOnlyClient), +} + +struct ExecutionStats { + publics: ShardPublics, + steps: u64, + time_ms: u64, +} + +impl HostClient { + async fn setup(&self, program: &GuestProgram) -> Result<()> { + match self { + Self::Prover(client) => { + client.setup(program).run()?.await?; + }, + Self::ExecuteOnly(client) => client.setup(program, false)?, + } + Ok(()) + } + + async fn execute( + &self, + program: &GuestProgram, + stdin: ZiskStdin, + ) -> Result { + let mut buf = [0u8; SHARD_PUBLICS_LEN]; + let (steps, time_ms) = match self { + Self::Prover(client) => { + let result = client.execute(program, stdin).run()?.await?; + result.get_public_values_slice(&mut buf); + (result.get_execution_steps(), result.get_execution_time()) + }, + Self::ExecuteOnly(client) => { + let result = client.execute(program, stdin, None)?; + result.get_public_values_slice(&mut buf); + (result.get_execution_steps(), result.get_execution_time()) + }, + }; + Ok(ExecutionStats { publics: ShardPublics::decode(&buf), steps, time_ms }) + } + + fn prover(&self) -> Result<&EmbeddedClient> { + match self { + Self::Prover(client) => Ok(client), + Self::ExecuteOnly(_) => { + bail!("proof operation requested from execute-only client") + }, + } + } +} + +fn build_client( + gpu: bool, + asm: bool, + execute_only: bool, +) -> Result { // Executor choice. The default is the Assembly executor (`asm = true`, // i.e. no `--emulator`): it is markedly faster at trace generation and is // the prerequisite for the hints stream. It historically broke under our @@ -833,10 +890,15 @@ fn build_client(gpu: bool, asm: bool) -> Result { if asm { builder = builder.assembly(); } + // The SDK's standalone executor skips proving-key, Std, SetupCtx, and + // ProofMan initialization while retaining the selected VM backend. + if execute_only { + return Ok(HostClient::ExecuteOnly(builder.execute_only().build()?)); + } if gpu { builder = builder.gpu(); } - Ok(builder.build()?) + Ok(HostClient::Prover(builder.build()?)) } /// Check a single constant chosen by Lean NAME (one iteration of `--consts`). @@ -847,7 +909,7 @@ fn build_client(gpu: bool, asm: bool) -> Result { /// (write stdin for ziskemu), `--execute` (cycles), and plain prove (single /// leaf, subject-bound + verified). No aggregation — it's one leaf. async fn run_constant( - client: &EmbeddedClient, + client: &HostClient, plan: &InputPlan, name: &str, args: &Args, @@ -940,13 +1002,11 @@ async fn run_constant( // ---- Execute mode: cycles only, no proof. ---- if args.execute { let t0 = Instant::now(); - let result = client.execute(&SHARD_PROGRAM, stdin).run()?.await?; + let result = client.execute(&SHARD_PROGRAM, stdin).await?; let execute_secs = t0.elapsed().as_secs_f64(); tracing_texray::json_sink::record_manual("zisk/execute", execute_secs); - let mut buf = [0u8; SHARD_PUBLICS_LEN]; - result.get_public_values_slice(&mut buf); - let publics = ShardPublics::decode(&buf); - let cycles = result.get_execution_steps(); + let publics = result.publics; + let cycles = result.steps; println!("cycles: {cycles}, failures: {}", publics.failures); if let Some(path) = &args.json { let tput = throughput(cover.len(), execute_secs); @@ -978,6 +1038,7 @@ async fn run_constant( } // ---- Prove mode: single leaf, bind subject to the env, verify. ---- + let client = client.prover()?; let result = client.prove(&SHARD_PROGRAM, stdin).run()?.await?; let mut buf = [0u8; SHARD_PUBLICS_LEN]; result.get_public_values_slice(&mut buf); @@ -1126,7 +1187,7 @@ fn discharge( /// Manifests without a bisection tree (written before it existed) are /// rejected when aggregation is needed — regenerate with the current planner. async fn run_shard_plan( - client: &EmbeddedClient, + client: &HostClient, plan: &InputPlan, manifest_path: &std::path::Path, args: &Args, @@ -1368,12 +1429,10 @@ async fn run_shard_plan( // Windowed RAM high-water: reset before each shard so the per-shard // peaks are independent; the env row's peak-rss is their max. tracing_texray::rss_sampler::reset_peak_tree_rss(); - let result = client.execute(&SHARD_PROGRAM, stdin).run()?.await?; - let mut buf = [0u8; SHARD_PUBLICS_LEN]; - result.get_public_values_slice(&mut buf); - let publics = ShardPublics::decode(&buf); - let cycles = result.get_execution_steps(); - let exec_secs = result.get_execution_time() as f64 / 1000.0; + let result = client.execute(&SHARD_PROGRAM, stdin).await?; + let publics = result.publics; + let cycles = result.steps; + let exec_secs = result.time_ms as f64 / 1000.0; let peak = peak_rss_bytes(); total_steps += cycles; max_shard_cycles = max_shard_cycles.max(cycles); @@ -1451,6 +1510,7 @@ async fn run_shard_plan( } // ---- Prove mode: one closure-injected leaf per shard. ---- + let client = client.prover()?; let mut leaf_proofs: Vec> = Vec::with_capacity(selected.len()); // Per-leaf claim preimages (sorted, deduped): the certified target set and // the assumption set behind the leaf's committed roots. Supplied to the agg @@ -1990,12 +2050,12 @@ async fn run() -> Result<()> { let grand_target_count: usize = plans.iter().map(|p| p.target_count).sum(); let total_leaves: usize = plans.iter().map(|p| p.shards.len()).sum(); - let client = build_client(args.gpu, !args.emulator)?; + let client = build_client(args.gpu, !args.emulator, args.execute)?; // Dump mode never runs the VM; it needs ROM setup (and thus the proving // key) only to derive the shard vk for store filtering. Skipping setup // otherwise makes input dumping fast and key-free. if args.dump_input.is_none() || args.store_dir.is_some() { - client.setup(&SHARD_PROGRAM).run()?.await?; + client.setup(&SHARD_PROGRAM).await?; } // Skip agg-guest setup unless we'll produce more than one leaf proof. // The shard-plan path sets up the agg program itself, after its leaves. @@ -2011,7 +2071,7 @@ async fn run() -> Result<()> { && args.shard_plan.is_none() && total_leaves > 1; if need_agg && args.emulator { - client.setup(&AGG_PROGRAM).run()?.await?; + client.setup(&AGG_PROGRAM).await?; } // ---- Manifest-driven sharding (the offline profiler/partitioner plan). ---- @@ -2040,13 +2100,11 @@ async fn run() -> Result<()> { let num_shards = plan.shards.len(); for (i, &(start, end)) in plan.shards.iter().enumerate() { let stdin = leaf_stdin(start, end, &plan.env_bytes, &[]); - let result = client.execute(&SHARD_PROGRAM, stdin).run()?.await?; - let mut buf = [0u8; SHARD_PUBLICS_LEN]; - result.get_public_values_slice(&mut buf); - let publics = ShardPublics::decode(&buf); - let cycles = result.get_execution_steps(); + let result = client.execute(&SHARD_PROGRAM, stdin).await?; + let publics = result.publics; + let cycles = result.steps; total_steps += cycles; - total_exec_ms += result.get_execution_time(); + total_exec_ms += result.time_ms; println!( " [{} shard {}/{num_shards}] range [{start}, {end}), failures={}, cycles={cycles}", plan.label, @@ -2075,6 +2133,8 @@ async fn run() -> Result<()> { return Ok(()); } + let client = client.prover()?; + if args.verify_constraints { // Guaranteed single input by the guard above. let plan = &plans[0];