risc0_zkvm/host/client/prove/
bonsai.rs1use std::time::Duration;
16
17use anyhow::{anyhow, bail, ensure, Context, Result};
18use bonsai_sdk::blocking::Client;
19
20use super::Prover;
21use crate::{
22 compute_image_id, AssumptionReceipt, ExecutorEnv, InnerAssumptionReceipt, InnerReceipt,
23 ProveInfo, ProverOpts, Receipt, ReceiptKind, SessionStats, VerifierContext, VERSION,
24};
25
26pub struct BonsaiProver {
31 name: String,
32}
33
34impl BonsaiProver {
35 pub fn new(name: &str) -> Self {
37 Self {
38 name: name.to_string(),
39 }
40 }
41}
42
43fn get_inner_assumption_receipt(assumption: &AssumptionReceipt) -> Result<&InnerAssumptionReceipt> {
45 match assumption {
46 AssumptionReceipt::Proven(receipt) => {
47 if !matches!(receipt, InnerAssumptionReceipt::Succinct(_)) {
48 bail!(
49 "Bonsai only supports succinct assumption receipts. \
50 Use `ProverOpts::succinct()` when proving any assumptions."
51 );
52 };
53 Ok(receipt)
54 }
55 AssumptionReceipt::Unresolved(_) => {
56 bail!("only proven assumptions can be uploaded to Bonsai.")
57 }
58 }
59}
60
61impl Prover for BonsaiProver {
62 fn get_name(&self) -> String {
63 self.name.clone()
64 }
65
66 fn prove_with_ctx(
67 &self,
68 env: ExecutorEnv<'_>,
69 ctx: &VerifierContext,
70 elf: &[u8],
71 opts: &ProverOpts,
72 ) -> Result<ProveInfo> {
73 let client = Client::from_env(VERSION)?;
74
75 let image_id = compute_image_id(elf)?;
77 let image_id_hex = hex::encode(image_id);
78 client.upload_img(&image_id_hex, elf.to_vec())?;
79
80 let input_id = client.upload_input(env.input)?;
82
83 let mut receipts_ids = vec![];
85 for assumption in env.assumptions.borrow().0.iter() {
86 let inner_receipt = get_inner_assumption_receipt(assumption)?;
87 let serialized_receipt = bincode::serialize(inner_receipt)?;
88 let receipt_id = client.upload_receipt(serialized_receipt)?;
89 receipts_ids.push(receipt_id);
90 }
91
92 let session = client.create_session_with_limit(
96 image_id_hex,
97 input_id,
98 receipts_ids,
99 false,
100 env.session_limit,
101 )?;
102 tracing::debug!("Bonsai proving SessionID: {}", session.uuid);
103
104 let polling_interval = if let Ok(ms) = std::env::var("BONSAI_POLL_INTERVAL_MS") {
106 Duration::from_millis(ms.parse().context("invalid bonsai poll interval")?)
107 } else {
108 Duration::from_secs(1)
109 };
110 let succinct_prove_info = loop {
111 let res = session.status(&client)?;
114 if res.status == "RUNNING" {
115 std::thread::sleep(polling_interval);
116 continue;
117 }
118 if res.status == "SUCCEEDED" {
119 let receipt_url = res
121 .receipt_url
122 .ok_or_else(|| anyhow!("API error, missing receipt on completed session"))?;
123
124 let stats = res
125 .stats
126 .context("Missing stats object on Bonsai status res")?;
127 tracing::debug!(
128 "Bonsai usage: cycles: {} total_cycles: {}",
129 stats.cycles,
130 stats.total_cycles
131 );
132
133 let receipt_buf = client.download(&receipt_url)?;
134 let receipt: Receipt = bincode::deserialize(&receipt_buf)?;
135
136 if opts.prove_guest_errors {
137 receipt.verify_integrity_with_context(ctx)?;
138 } else {
139 receipt.verify_with_context(ctx, image_id)?;
140 }
141 break ProveInfo {
142 receipt,
143 work_receipt: None,
144 stats: SessionStats {
145 segments: stats.segments,
146 total_cycles: stats.total_cycles,
147 user_cycles: stats.cycles,
148 paging_cycles: 0,
150 reserved_cycles: 0,
151 },
152 };
153 } else {
154 bail!(
155 "Bonsai prover workflow [{}] exited: {} err: {}",
156 session.uuid,
157 res.status,
158 res.error_msg
159 .unwrap_or("Bonsai workflow missing error_msg".into()),
160 );
161 }
162 };
163 match opts.receipt_kind {
164 ReceiptKind::Composite | ReceiptKind::Succinct => {
166 return Ok(succinct_prove_info);
167 }
168 ReceiptKind::Groth16 => {}
170 }
171
172 let snark_session = client.create_snark(session.uuid)?;
174 let snark_receipt_url = loop {
175 let res = snark_session.status(&client)?;
176 match res.status.as_str() {
177 "RUNNING" => {
178 std::thread::sleep(polling_interval);
179 continue;
180 }
181 "SUCCEEDED" => {
182 break res.output.with_context(|| {
183 format!(
184 "Bonsai prover workflow [{}] reported success, but provided no receipt",
185 snark_session.uuid
186 )
187 })?;
188 }
189 _ => {
190 bail!(
191 "Bonsai prover workflow [{}] exited: {} err: {}",
192 snark_session.uuid,
193 res.status,
194 res.error_msg
195 .unwrap_or("Bonsai workflow missing error_msg".into()),
196 );
197 }
198 }
199 };
200
201 let receipt_buf = client.download(&snark_receipt_url)?;
209 let groth16_receipt: Receipt = bincode::deserialize(&receipt_buf)?;
210 groth16_receipt
211 .verify_integrity_with_context(ctx)
212 .context("failed to verify Groth16Receipt returned by Bonsai")?;
213
214 Ok(ProveInfo {
216 receipt: groth16_receipt,
217 work_receipt: None,
218 stats: succinct_prove_info.stats,
219 })
220 }
221
222 fn compress(&self, opts: &ProverOpts, receipt: &Receipt) -> Result<Receipt> {
223 match (&receipt.inner, opts.receipt_kind) {
224 (InnerReceipt::Composite(_), ReceiptKind::Composite)
226 | (InnerReceipt::Succinct(_), ReceiptKind::Composite | ReceiptKind::Succinct)
227 | (
228 InnerReceipt::Groth16(_),
229 ReceiptKind::Composite | ReceiptKind::Succinct | ReceiptKind::Groth16,
230 ) => Ok(receipt.clone()),
231 (InnerReceipt::Fake { .. }, _) => {
233 ensure!(
234 opts.dev_mode(),
235 "dev mode must be enabled to compress fake receipts"
236 );
237 Ok(receipt.clone())
238 }
239 (_, ReceiptKind::Succinct) => {
242 bail!("BonsaiProver does not support compression on existing receipts");
243 }
244 (_, ReceiptKind::Groth16) => {
245 bail!([
247 "BonsaiProver does not support compression on existing receipts",
248 "Set receipt_kind on ProverOpts in initial prove request to get a Groth16Receipt."
249 ].join(""));
250 }
251 }
252 }
253}