Skip to content

Commit

Permalink
feat: Add estimate subcommand (#93)
Browse files Browse the repository at this point in the history
Closes #78 

Uses the risc0 emulation tools to step through the ELF and return the
user cycles, total cycles and segment count.
  • Loading branch information
eureka-cpu authored Dec 12, 2024
1 parent 5832e47 commit 7bbbcd6
Show file tree
Hide file tree
Showing 13 changed files with 374 additions and 5 deletions.
2 changes: 1 addition & 1 deletion .github/docker/Dockerfile.build
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,4 @@ RUN /go/bin/yamlfmt -lint .github/workflows/*.yaml .github/workflows/*.yml .gith

RUN cargo check
RUN cargo +nightly fmt --all -- --check
RUN cargo test
RUN cargo test
34 changes: 34 additions & 0 deletions .github/workflows/integration-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: Integration Tests
on:
pull_request:
branches:
- '**'

concurrency:
group: "integration-tests"
cancel-in-progress: true

permissions: read-all

jobs:
integration-tests:
name: Setup Toolchain and Test
runs-on: ubuntu-latest-m
permissions:
id-token: "write"
contents: "read"
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Install Nix With Bonsol Binary Cache
uses: DeterminateSystems/nix-installer-action@main
with:
extra-conf: |
extra-substituters = https://bonsol.cachix.org
extra-trusted-public-keys = bonsol.cachix.org-1:yz7vi1rCPW1BpqoszdJvf08HZxQ/5gPTPxft4NnT74A=
- name: Setup Toolchain, Build and Test
run: |
nix develop --command bash -c "
cargo build &&
cargo test --features integration -- --nocapture
"
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
### Changed
* `bonsol` cli option requirements and error messages updated for added clarity

### Added
* `bonsol estimate` for estimating execution cost of bonsol programs.

### Fixed
* **Breaking**: `execute_v1` interface instruction now uses the new `InputRef` to improve CU usage.
* Adds a callback struct to use the input_hash and committed_outputs from the callback program ergonomically.
Expand Down
47 changes: 46 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 9 additions & 2 deletions cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,16 @@ path = "src/main.rs"
[features]
mac = ["risc0-zkvm/metal"]
linux = ["risc0-zkvm/cuda"]
integration = []

[dependencies]
anyhow = "1.0.86"
atty = "0.2.14"
bincode = "1.3.3"
bonsol-interface.workspace = true
bonsol-prover = { path = "../prover" }
bonsol-sdk = { path = "../sdk" }
bytemuck = "1.15.0"
hex = "0.4.3"
byte-unit = "4.0.19"
bytes = "1.4.0"
Expand All @@ -34,7 +37,9 @@ reqwest = { version = "0.11.26", features = [
"native-tls-vendored",
] }
risc0-binfmt = { workspace = true }
risc0-zkvm = { workspace = true, features = ["prove"] }
risc0-zkvm = { workspace = true, default-features = false, features = ["prove", "std"] }
risc0-zkvm-platform = { git = "https://github.com/anagrambuild/risc0", branch = "v1.0.1-bonsai-fix" }
risc0-circuit-rv32im = { git = "https://github.com/anagrambuild/risc0", branch = "v1.0.1-bonsai-fix" }
serde = { version = "1.0.197", features = ["derive"] }
serde_json = "1.0.104"
sha2 = "0.10.6"
Expand All @@ -46,4 +51,6 @@ tera = "1.17.1"
thiserror = "1.0.65"
tokio = { version = "1.38.0", features = ["full"] }

bonsol-interface.workspace = true
[dev-dependencies]
assert_cmd = "2.0.16"
predicates = "3.1.2"
17 changes: 17 additions & 0 deletions cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,20 @@ todo

### Prove
todo

### Estimate

You can estimate the number of cycles and segments using risc0 emulation to step through an ELF by passing the `estimate` command the path to a manifest.json and an inputs file (if required).

```
bonsol -k ./keypair.json -u http://localhost:8899 estimate \
--manifest-path program/manifest.json \
--input-file program/inputs.json \
--max-cycles 16777216 # this is the default
# Example Output:
#
# User cycles: 3380
# Total cycles: 65536
# Segments: 1
```
34 changes: 34 additions & 0 deletions cli/src/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,7 @@ pub enum Command {
)]
auto_confirm: bool,
},
#[command(about = "Build a ZK program")]
Build {
#[arg(
help = "The path to a ZK program folder containing a Cargo.toml",
Expand All @@ -235,6 +236,25 @@ pub enum Command {
)]
zk_program_path: String,
},
#[command(about = "Estimate the execution cost of a ZK RISC0 program")]
Estimate {
#[arg(
help = "The path to the program's manifest file (manifest.json)",
short = 'm',
long
)]
manifest_path: String,

#[arg(help = "The path to the program input file", short = 'i', long)]
input_file: Option<String>,

#[arg(
help = "Set the maximum number of cycles [default: 16777216u64]",
short = 'c',
long
)]
max_cycles: Option<u64>,
},
Execute {
#[arg(short = 'f', long)]
execution_request_file: Option<String>,
Expand Down Expand Up @@ -296,6 +316,11 @@ pub enum ParsedCommand {
Build {
zk_program_path: String,
},
Estimate {
manifest_path: String,
input_file: Option<String>,
max_cycles: Option<u64>,
},
Execute {
execution_request_file: Option<String>,

Expand Down Expand Up @@ -351,6 +376,15 @@ impl TryFrom<Command> for ParsedCommand {
),
}),
Command::Build { zk_program_path } => Ok(ParsedCommand::Build { zk_program_path }),
Command::Estimate {
manifest_path,
input_file,
max_cycles,
} => Ok(ParsedCommand::Estimate {
manifest_path,
input_file,
max_cycles,
}),
Command::Execute {
execution_request_file,
program_id,
Expand Down
74 changes: 74 additions & 0 deletions cli/src/estimate.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
//! Bare bones upper bound estimator that uses the rv32im
//! emulation utils for fast lookups in the opcode list
//! to extract the cycle count from an elf.
use anyhow::Result;
use risc0_binfmt::{MemoryImage, Program};
use risc0_zkvm::{ExecutorEnv, ExecutorImpl, Session, GUEST_MAX_MEM};
use risc0_zkvm_platform::PAGE_SIZE;

pub fn estimate<E: MkImage>(elf: E, env: ExecutorEnv) -> Result<()> {
let session = get_session(elf, env)?;
println!(
"User cycles: {}\nTotal cycles: {}\nSegments: {}",
session.user_cycles,
session.total_cycles,
session.segments.len()
);

Ok(())
}

/// Get the total number of cycles by stepping through the ELF using emulation
/// tools from the risc0_circuit_rv32im module.
pub fn get_session<E: MkImage>(elf: E, env: ExecutorEnv) -> Result<Session> {
Ok(ExecutorImpl::new(env, elf.mk_image()?)?.run()?)
}

/// Helper trait for loading an image from an elf.
pub trait MkImage {
fn mk_image(self) -> Result<MemoryImage>;
}
impl<'a> MkImage for &'a [u8] {
fn mk_image(self) -> Result<MemoryImage> {
let program = Program::load_elf(self, GUEST_MAX_MEM as u32)?;
MemoryImage::new(&program, PAGE_SIZE as u32)
}
}

#[cfg(test)]
mod estimate_tests {
use anyhow::Result;
use risc0_binfmt::MemoryImage;
use risc0_circuit_rv32im::prove::emu::{
exec::DEFAULT_SEGMENT_LIMIT_PO2,
testutil::{basic as basic_test_program, DEFAULT_SESSION_LIMIT},
};
use risc0_zkvm::{ExecutorEnv, PAGE_SIZE};

use super::MkImage;
use crate::estimate;

impl MkImage for MemoryImage {
fn mk_image(self) -> Result<MemoryImage> {
Ok(self)
}
}

#[test]
fn estimate_basic() {
let program = basic_test_program();
let mut env = &mut ExecutorEnv::builder();
env = env
.segment_limit_po2(DEFAULT_SEGMENT_LIMIT_PO2 as u32)
.session_limit(DEFAULT_SESSION_LIMIT);
let image = MemoryImage::new(&program, PAGE_SIZE as u32)
.expect("failed to create image from basic program");
let res = estimate::get_session(image, env.build().unwrap());

assert_eq!(
res.ok().and_then(|session| Some(session.total_cycles)),
Some(16384)
);
}
}
Loading

0 comments on commit 7bbbcd6

Please sign in to comment.