risc0_zkvm/host/api/
client.rs

1// Copyright 2025 RISC Zero, Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::path::Path;
16
17use anyhow::{anyhow, bail, Context, Result};
18use bytes::Bytes;
19use prost::Message;
20use risc0_zkp::core::digest::Digest;
21
22use super::{
23    malformed_err, pb, Asset, AssetRequest, ConnectionWrapper, Connector, ParentProcessConnector,
24    SessionInfo,
25};
26use crate::{
27    get_version,
28    host::{
29        api::SegmentInfo,
30        client::{env::ProveZkrRequest, prove::get_r0vm_path},
31    },
32    receipt::{AssumptionReceipt, SegmentReceipt, SuccinctReceipt},
33    receipt_claim::UnionClaim,
34    ExecutorEnv, Journal, ProveInfo, ProverOpts, Receipt, ReceiptClaim,
35};
36
37pub(crate) enum Compat {
38    Narrow,
39    Wide,
40}
41
42/// A client implementation for interacting with a zkVM server.
43pub struct Client {
44    connector: Box<dyn Connector>,
45    compat: Compat,
46    version_override: Option<semver::Version>,
47}
48
49impl Default for Client {
50    fn default() -> Self {
51        Self::new().unwrap()
52    }
53}
54
55impl Client {
56    /// Construct a [Client] that connects to `r0vm` in a child process.
57    pub fn new() -> Result<Self> {
58        Self::new_sub_process("r0vm")
59    }
60
61    /// Construct a [Client] that connects to a sub-process which implements
62    /// the server by calling the specified `server_path`.
63    pub fn new_sub_process<P: AsRef<Path>>(server_path: P) -> Result<Self> {
64        let connector = ParentProcessConnector::new_narrow_version(server_path)?;
65        Ok(Self::with_connector(Box::new(connector)))
66    }
67
68    /// Construct a [Client] that connects to a sub-process which implements
69    /// the server by calling the specified `server_path`.
70    ///
71    /// Additionally allows for wider version mismatches, only rejecting major differences.
72    pub fn new_sub_process_compat<P: AsRef<Path>>(
73        server_path: P,
74        version_override: semver::Version,
75    ) -> Result<Self> {
76        let connector = ParentProcessConnector::new(server_path)?;
77        Ok(Self {
78            connector: Box::new(connector),
79            compat: Compat::Wide,
80            version_override: Some(version_override),
81        })
82    }
83
84    /// Construct a [Client] based on environment variables.
85    pub fn from_env() -> Result<Self> {
86        Client::new_sub_process(get_r0vm_path()?)
87    }
88
89    /// Construct a [Client] using the specified [Connector] to establish a
90    /// connection with the server.
91    pub fn with_connector(connector: Box<dyn Connector>) -> Self {
92        Self {
93            connector,
94            compat: Compat::Narrow,
95            version_override: None,
96        }
97    }
98
99    /// Prove the specified ELF binary.
100    pub fn prove(
101        &self,
102        env: &ExecutorEnv<'_>,
103        opts: &ProverOpts,
104        binary: Asset,
105    ) -> Result<ProveInfo> {
106        let mut conn = self.connect()?;
107
108        let request = pb::api::ServerRequest {
109            kind: Some(pb::api::server_request::Kind::Prove(
110                pb::api::ProveRequest {
111                    env: Some(self.make_execute_env(env, binary.try_into()?)?),
112                    opts: Some(opts.clone().into()),
113                    receipt_out: Some(pb::api::AssetRequest {
114                        kind: Some(pb::api::asset_request::Kind::Inline(())),
115                    }),
116                },
117            )),
118        };
119        conn.send(request)?;
120
121        let asset = self.prove_handler(&mut conn, env)?;
122
123        let code = conn.close()?;
124        if code != 0 {
125            bail!("Child finished with: {code}");
126        }
127
128        let prove_info_bytes = asset.as_bytes()?;
129        let prove_info_pb = pb::core::ProveInfo::decode(prove_info_bytes)?;
130        prove_info_pb.try_into()
131    }
132
133    /// Execute the specified ELF binary.
134    pub fn execute<F>(
135        &self,
136        env: &ExecutorEnv<'_>,
137        binary: Asset,
138        segments_out: AssetRequest,
139        segment_callback: F,
140    ) -> Result<SessionInfo>
141    where
142        F: FnMut(SegmentInfo, Asset) -> Result<()>,
143    {
144        let mut conn = self.connect()?;
145
146        let request = pb::api::ServerRequest {
147            kind: Some(pb::api::server_request::Kind::Execute(
148                pb::api::ExecuteRequest {
149                    env: Some(self.make_execute_env(env, binary.try_into()?)?),
150                    segments_out: Some(segments_out.try_into()?),
151                },
152            )),
153        };
154        // tracing::trace!("tx: {request:?}");
155        conn.send(request)?;
156
157        let result = self.execute_handler(segment_callback, &mut conn, env);
158
159        let code = conn.close()?;
160        if code != 0 {
161            bail!("Child finished with: {code}");
162        }
163
164        result
165    }
166
167    /// Prove the specified segment.
168    pub fn prove_segment(
169        &self,
170        opts: &ProverOpts,
171        segment: Asset,
172        receipt_out: AssetRequest,
173    ) -> Result<SegmentReceipt> {
174        let mut conn = self.connect()?;
175
176        let request = pb::api::ServerRequest {
177            kind: Some(pb::api::server_request::Kind::ProveSegment(
178                pb::api::ProveSegmentRequest {
179                    opts: Some(opts.clone().into()),
180                    segment: Some(segment.try_into()?),
181                    receipt_out: Some(receipt_out.try_into()?),
182                },
183            )),
184        };
185        // tracing::trace!("tx: {request:?}");
186        conn.send(request).context("tx request failed")?;
187
188        let reply: pb::api::ProveSegmentReply = conn.recv().context("rx reply failed")?;
189
190        let result = match reply
191            .kind
192            .ok_or_else(|| malformed_err("ProveSegmentReply.kind"))?
193        {
194            pb::api::prove_segment_reply::Kind::Ok(result) => {
195                let receipt_bytes = result
196                    .receipt
197                    .ok_or_else(|| malformed_err("ProveSegmentReply.Ok.receipt"))?
198                    .as_bytes()?;
199                let receipt_pb = pb::core::SegmentReceipt::decode(receipt_bytes)?;
200                receipt_pb.try_into()
201            }
202            pb::api::prove_segment_reply::Kind::Error(err) => Err(err.into()),
203        };
204
205        let code = conn.close()?;
206        if code != 0 {
207            bail!("Child finished with: {code}");
208        }
209
210        result
211    }
212
213    /// Prove the specified ZKR proof request.
214    #[stability::unstable]
215    pub fn prove_zkr<Claim>(
216        &self,
217        proof_request: ProveZkrRequest,
218        receipt_out: AssetRequest,
219    ) -> Result<SuccinctReceipt<Claim>>
220    where
221        Claim: risc0_binfmt::Digestible + std::fmt::Debug + Clone + serde::Serialize,
222        crate::MaybePruned<Claim>: TryFrom<pb::core::MaybePruned, Error = anyhow::Error>,
223    {
224        let mut conn = self.connect()?;
225
226        let request = pb::api::ServerRequest {
227            kind: Some(pb::api::server_request::Kind::ProveZkr(
228                pb::api::ProveZkrRequest {
229                    claim_digest: Some(proof_request.claim_digest.into()),
230                    control_id: Some(proof_request.control_id.into()),
231                    input: proof_request.input,
232                    receipt_out: Some(receipt_out.try_into()?),
233                },
234            )),
235        };
236
237        tracing::trace!("tx: {request:?}");
238        conn.send(request)?;
239
240        let reply: pb::api::ProveZkrReply = conn.recv()?;
241
242        let result = match reply
243            .kind
244            .ok_or_else(|| malformed_err("ProveZkrReply.kind"))?
245        {
246            pb::api::prove_zkr_reply::Kind::Ok(result) => {
247                let receipt_bytes = result
248                    .receipt
249                    .ok_or_else(|| malformed_err("ProveZkrReply.Ok.receipt"))?
250                    .as_bytes()?;
251                let receipt_pb = pb::core::SuccinctReceipt::decode(receipt_bytes)?;
252                receipt_pb.try_into()
253            }
254            pb::api::prove_zkr_reply::Kind::Error(err) => Err(err.into()),
255        };
256
257        let code = conn.close()?;
258        if code != 0 {
259            bail!("Child finished with: {code}");
260        }
261
262        result
263    }
264
265    /// Prove the specified keccak proof request.
266    #[stability::unstable]
267    pub fn prove_keccak<Claim>(
268        &self,
269        proof_request: crate::host::client::env::ProveKeccakRequest,
270        receipt_out: AssetRequest,
271    ) -> Result<SuccinctReceipt<Claim>>
272    where
273        Claim: risc0_binfmt::Digestible + std::fmt::Debug + Clone + serde::Serialize,
274        crate::MaybePruned<Claim>: TryFrom<pb::core::MaybePruned, Error = anyhow::Error>,
275    {
276        use crate::host::api::convert::keccak_input_to_bytes;
277
278        let mut conn = self.connect()?;
279
280        let request = pb::api::ServerRequest {
281            kind: Some(pb::api::server_request::Kind::ProveKeccak(
282                pb::api::ProveKeccakRequest {
283                    claim_digest: Some(proof_request.claim_digest.into()),
284                    po2: proof_request.po2 as u32,
285                    control_root: Some(proof_request.control_root.into()),
286                    input: keccak_input_to_bytes(&proof_request.input),
287                    receipt_out: Some(receipt_out.try_into()?),
288                },
289            )),
290        };
291
292        tracing::trace!("tx: {request:?}");
293        conn.send(request)?;
294
295        let reply: pb::api::ProveKeccakReply = conn.recv()?;
296
297        let result = match reply
298            .kind
299            .ok_or_else(|| malformed_err("ProveKeccakReply.kind"))?
300        {
301            pb::api::prove_keccak_reply::Kind::Ok(result) => {
302                let receipt_bytes = result
303                    .receipt
304                    .ok_or_else(|| malformed_err("ProveKeccakReply.Ok.receipt"))?
305                    .as_bytes()?;
306                let receipt_pb = pb::core::SuccinctReceipt::decode(receipt_bytes)?;
307                receipt_pb.try_into()
308            }
309            pb::api::prove_keccak_reply::Kind::Error(err) => Err(err.into()),
310        };
311
312        let code = conn.close()?;
313        if code != 0 {
314            bail!("Child finished with: {code}");
315        }
316
317        result
318    }
319
320    /// Run the lift program to transform a [SegmentReceipt] into a [SuccinctReceipt].
321    ///
322    /// The lift program verifies the rv32im circuit STARK proof inside the recursion circuit,
323    /// resulting in a recursion circuit STARK proof. This recursion proof has a single
324    /// constant-time verification procedure, with respect to the original segment length, and is then
325    /// used as the input to all other recursion programs (e.g. join, resolve, and identity_p254).
326    pub fn lift(
327        &self,
328        opts: &ProverOpts,
329        receipt: Asset,
330        receipt_out: AssetRequest,
331    ) -> Result<SuccinctReceipt<ReceiptClaim>> {
332        let mut conn = self.connect()?;
333
334        let request = pb::api::ServerRequest {
335            kind: Some(pb::api::server_request::Kind::Lift(pb::api::LiftRequest {
336                opts: Some(opts.clone().into()),
337                receipt: Some(receipt.try_into()?),
338                receipt_out: Some(receipt_out.try_into()?),
339            })),
340        };
341        // tracing::trace!("tx: {request:?}");
342        conn.send(request)?;
343
344        let reply: pb::api::LiftReply = conn.recv()?;
345
346        let result = match reply.kind.ok_or_else(|| malformed_err("LiftReply.kind"))? {
347            pb::api::lift_reply::Kind::Ok(result) => {
348                let receipt_bytes = result
349                    .receipt
350                    .ok_or_else(|| malformed_err("LiftReply.Ok.receipt"))?
351                    .as_bytes()?;
352                let receipt_pb = pb::core::SuccinctReceipt::decode(receipt_bytes)?;
353                receipt_pb.try_into()
354            }
355            pb::api::lift_reply::Kind::Error(err) => Err(err.into()),
356        };
357
358        let code = conn.close()?;
359        if code != 0 {
360            bail!("Child finished with: {code}");
361        }
362
363        result
364    }
365
366    /// Run the join program to compress two [SuccinctReceipt]s in the same session into one.
367    ///
368    /// By repeated application of the join program, any number of receipts for execution spans within
369    /// the same session can be compressed into a single receipt for the entire session.
370    pub fn join(
371        &self,
372        opts: &ProverOpts,
373        left_receipt: Asset,
374        right_receipt: Asset,
375        receipt_out: AssetRequest,
376    ) -> Result<SuccinctReceipt<ReceiptClaim>> {
377        let mut conn = self.connect()?;
378
379        let request = pb::api::ServerRequest {
380            kind: Some(pb::api::server_request::Kind::Join(pb::api::JoinRequest {
381                opts: Some(opts.clone().into()),
382                left_receipt: Some(left_receipt.try_into()?),
383                right_receipt: Some(right_receipt.try_into()?),
384                receipt_out: Some(receipt_out.try_into()?),
385            })),
386        };
387        // tracing::trace!("tx: {request:?}");
388        conn.send(request)?;
389
390        let reply: pb::api::JoinReply = conn.recv()?;
391
392        let result = match reply.kind.ok_or_else(|| malformed_err("JoinReply.kind"))? {
393            pb::api::join_reply::Kind::Ok(result) => {
394                let receipt_bytes = result
395                    .receipt
396                    .ok_or_else(|| malformed_err("JoinReply.Ok.receipt"))?
397                    .as_bytes()?;
398                let receipt_pb = pb::core::SuccinctReceipt::decode(receipt_bytes)?;
399                receipt_pb.try_into()
400            }
401            pb::api::join_reply::Kind::Error(err) => Err(err.into()),
402        };
403
404        let code = conn.close()?;
405        if code != 0 {
406            bail!("Child finished with: {code}");
407        }
408
409        result
410    }
411
412    /// Run the union program to compress any two [SuccinctReceipt]s into one.
413    ///
414    /// By repeated application of the union program, any number of receipts can
415    /// be compressed into a single receipt.
416    pub fn union(
417        &self,
418        opts: &ProverOpts,
419        a_receipt: Asset,
420        b_receipt: Asset,
421        receipt_out: AssetRequest,
422    ) -> Result<SuccinctReceipt<UnionClaim>> {
423        let mut conn = self.connect()?;
424
425        let request = pb::api::ServerRequest {
426            kind: Some(pb::api::server_request::Kind::Union(
427                pb::api::UnionRequest {
428                    opts: Some(opts.clone().into()),
429                    left_receipt: Some(a_receipt.try_into()?),
430                    right_receipt: Some(b_receipt.try_into()?),
431                    receipt_out: Some(receipt_out.try_into()?),
432                },
433            )),
434        };
435        // tracing::trace!("tx: {request:?}");
436        conn.send(request)?;
437
438        let reply: pb::api::UnionReply = conn.recv()?;
439
440        let result = match reply.kind.ok_or_else(|| malformed_err("UnionReply.kind"))? {
441            pb::api::union_reply::Kind::Ok(result) => {
442                let receipt_bytes = result
443                    .receipt
444                    .ok_or_else(|| malformed_err("UnionReply.Ok.receipt"))?
445                    .as_bytes()?;
446                let receipt_pb = pb::core::SuccinctReceipt::decode(receipt_bytes)?;
447                receipt_pb.try_into()
448            }
449            pb::api::union_reply::Kind::Error(err) => Err(err.into()),
450        };
451
452        let code = conn.close()?;
453        if code != 0 {
454            bail!("Child finished with: {code}");
455        }
456
457        result
458    }
459
460    /// Run the resolve program to remove an assumption from a conditional [SuccinctReceipt] upon
461    /// verifying a [SuccinctReceipt] proving the validity of the assumption.
462    ///
463    /// By applying the resolve program, a conditional receipt (i.e. a receipt for an execution
464    /// using the `env::verify` API to logically verify a receipt) can be made into an
465    /// unconditional receipt.
466    pub fn resolve(
467        &self,
468        opts: &ProverOpts,
469        conditional_receipt: Asset,
470        assumption_receipt: Asset,
471        receipt_out: AssetRequest,
472    ) -> Result<SuccinctReceipt<ReceiptClaim>> {
473        let mut conn = self.connect()?;
474
475        let request = pb::api::ServerRequest {
476            kind: Some(pb::api::server_request::Kind::Resolve(
477                pb::api::ResolveRequest {
478                    opts: Some(opts.clone().into()),
479                    conditional_receipt: Some(conditional_receipt.try_into()?),
480                    assumption_receipt: Some(assumption_receipt.try_into()?),
481                    receipt_out: Some(receipt_out.try_into()?),
482                },
483            )),
484        };
485        // tracing::trace!("tx: {request:?}");
486        conn.send(request)?;
487
488        let reply: pb::api::ResolveReply = conn.recv()?;
489
490        let result = match reply
491            .kind
492            .ok_or_else(|| malformed_err("ResolveReply.kind"))?
493        {
494            pb::api::resolve_reply::Kind::Ok(result) => {
495                let receipt_bytes = result
496                    .receipt
497                    .ok_or_else(|| malformed_err("ResolveReply.Ok.receipt"))?
498                    .as_bytes()?;
499                let receipt_pb = pb::core::SuccinctReceipt::decode(receipt_bytes)?;
500                receipt_pb.try_into()
501            }
502            pb::api::resolve_reply::Kind::Error(err) => Err(err.into()),
503        };
504
505        let code = conn.close()?;
506        if code != 0 {
507            bail!("Child finished with: {code}");
508        }
509
510        result
511    }
512
513    /// Prove the verification of a recursion receipt using the Poseidon254 hash function for FRI.
514    ///
515    /// The identity_p254 program is used as the last step in the prover pipeline before running the
516    /// Groth16 prover. In Groth16 over BN254, it is much more efficient to verify a STARK that was
517    /// produced with Poseidon over the BN254 base field compared to using Poseidon over BabyBear.
518    pub fn identity_p254(
519        &self,
520        opts: &ProverOpts,
521        receipt: Asset,
522        receipt_out: AssetRequest,
523    ) -> Result<SuccinctReceipt<ReceiptClaim>> {
524        let mut conn = self.connect()?;
525
526        let request = pb::api::ServerRequest {
527            kind: Some(pb::api::server_request::Kind::IdentityP254(
528                pb::api::IdentityP254Request {
529                    opts: Some(opts.clone().into()),
530                    receipt: Some(receipt.try_into()?),
531                    receipt_out: Some(receipt_out.try_into()?),
532                },
533            )),
534        };
535        // tracing::trace!("tx: {request:?}");
536        conn.send(request)?;
537
538        let reply: pb::api::IdentityP254Reply = conn.recv()?;
539
540        let result = match reply
541            .kind
542            .ok_or_else(|| malformed_err("IdentityP254Reply.kind"))?
543        {
544            pb::api::identity_p254_reply::Kind::Ok(result) => {
545                let receipt_bytes = result
546                    .receipt
547                    .ok_or_else(|| malformed_err("IdentityP254Reply.Ok.receipt"))?
548                    .as_bytes()?;
549                let receipt_pb = pb::core::SuccinctReceipt::decode(receipt_bytes)?;
550                receipt_pb.try_into()
551            }
552            pb::api::identity_p254_reply::Kind::Error(err) => Err(err.into()),
553        };
554
555        let code = conn.close()?;
556        if code != 0 {
557            bail!("Child finished with: {code}");
558        }
559
560        result
561    }
562
563    /// Compress a [Receipt], proving the same computation using a smaller representation.
564    ///
565    /// Proving will, by default, produce a [CompositeReceipt](crate::CompositeReceipt), which
566    /// may contain an arbitrary number of receipts assembled into segments and assumptions.
567    /// Together, these receipts collectively prove a top-level
568    /// [ReceiptClaim](crate::ReceiptClaim). This function can be used to compress all of the constituent
569    /// receipts of a [CompositeReceipt](crate::CompositeReceipt) into a single
570    /// [SuccinctReceipt](crate::SuccinctReceipt) or [Groth16Receipt](crate::Groth16Receipt) that proves the same top-level claim.
571    ///
572    /// Compression from [Groth16Receipt](crate::CompositeReceipt) to
573    /// [SuccinctReceipt](crate::SuccinctReceipt) is accomplished by iterative application of the
574    /// recursion programs including lift, join, and resolve.
575    ///
576    /// Compression from [SuccinctReceipt](crate::SuccinctReceipt) to
577    /// [Groth16Receipt](crate::Groth16Receipt) is accomplished by running a Groth16 recursive
578    /// verifier, referred to as the "STARK-to-SNARK" operation.
579    ///
580    /// NOTE: Compression to [Groth16Receipt](crate::Groth16Receipt) is currently only supported on
581    /// x86 hosts, and requires Docker to be installed. See issue
582    /// [#1749](https://github.com/risc0/risc0/issues/1749) for more information.
583    ///
584    /// If the receipt is already at least as compressed as the requested compression level (e.g.
585    /// it is already succinct or Groth16 and a succinct receipt is required) this function is a
586    /// no-op. As a result, it is idempotent.
587    pub fn compress(
588        &self,
589        opts: &ProverOpts,
590        receipt: Asset,
591        receipt_out: AssetRequest,
592    ) -> Result<Receipt> {
593        let mut conn = self.connect()?;
594
595        let request = pb::api::ServerRequest {
596            kind: Some(pb::api::server_request::Kind::Compress(
597                pb::api::CompressRequest {
598                    opts: Some(opts.clone().into()),
599                    receipt: Some(receipt.try_into()?),
600                    receipt_out: Some(receipt_out.try_into()?),
601                },
602            )),
603        };
604        // tracing::trace!("tx: {request:?}");
605        conn.send(request)?;
606
607        let reply: pb::api::CompressReply = conn.recv()?;
608
609        let result = match reply
610            .kind
611            .ok_or_else(|| malformed_err("CompressReply.kind"))?
612        {
613            pb::api::compress_reply::Kind::Ok(result) => {
614                let receipt_bytes = result
615                    .receipt
616                    .ok_or_else(|| malformed_err("CompressReply.Ok.receipt"))?
617                    .as_bytes()?;
618                let receipt_pb = pb::core::Receipt::decode(receipt_bytes)?;
619                receipt_pb.try_into()
620            }
621            pb::api::compress_reply::Kind::Error(err) => Err(err.into()),
622        };
623
624        let code = conn.close()?;
625        if code != 0 {
626            bail!("Child finished with: {code}");
627        }
628
629        result
630    }
631
632    /// Verify a [Receipt].
633    pub fn verify(&self, receipt: Asset, image_id: impl Into<Digest>) -> Result<()> {
634        let mut conn = self.connect().context("connect")?;
635        let image_id = image_id.into();
636
637        let request = pb::api::ServerRequest {
638            kind: Some(pb::api::server_request::Kind::Verify(
639                pb::api::VerifyRequest {
640                    receipt: Some(receipt.try_into().context("convert receipt asset")?),
641                    image_id: Some(image_id.into()),
642                },
643            )),
644        };
645        // tracing::trace!("tx: {request:?}");
646        conn.send(request).context("send")?;
647
648        let reply: pb::api::GenericReply = conn.recv().context("error from server")?;
649        let result = match reply
650            .kind
651            .ok_or_else(|| malformed_err("GenericReply.kind"))?
652        {
653            pb::api::generic_reply::Kind::Ok(ok) => Ok(ok),
654            pb::api::generic_reply::Kind::Error(err) => Err(err.into()),
655        };
656
657        let code = conn.close().context("close")?;
658        if code != 0 {
659            bail!("Child finished with: {code}");
660        }
661
662        result
663    }
664
665    fn connect(&self) -> Result<ConnectionWrapper> {
666        let mut conn = self.connector.connect()?;
667
668        let client_version = get_version().map_err(|err| anyhow!(err))?;
669        let client_version = self.version_override.as_ref().unwrap_or(&client_version);
670        let request = pb::api::HelloRequest {
671            version: Some(client_version.clone().into()),
672        };
673        // tracing::trace!("tx: {request:?}");
674        conn.send(request)?;
675
676        let reply: pb::api::HelloReply = conn.recv()?;
677        // tracing::trace!("rx: {reply:?}");
678        match reply.kind.ok_or_else(|| malformed_err("HelloReply.kind"))? {
679            pb::api::hello_reply::Kind::Ok(reply) => {
680                let server_version: semver::Version = reply
681                    .version
682                    .ok_or_else(|| malformed_err("HelloReply.Ok.version"))?
683                    .try_into()
684                    .map_err(|err: semver::Error| anyhow!(err))?;
685
686                if !self.compat.check(client_version, &server_version) {
687                    let msg = format!("incompatible server version: {server_version}");
688                    tracing::warn!("{msg}");
689                    bail!(msg);
690                }
691            }
692            pb::api::hello_reply::Kind::Error(err) => {
693                let code = conn.close()?;
694                tracing::debug!("Child finished with: {code}");
695                bail!(err);
696            }
697        }
698
699        Ok(conn)
700    }
701
702    fn make_execute_env(
703        &self,
704        env: &ExecutorEnv<'_>,
705        binary: pb::api::Asset,
706    ) -> Result<pb::api::ExecutorEnv> {
707        Ok(pb::api::ExecutorEnv {
708            binary: Some(binary),
709            env_vars: env.env_vars.clone(),
710            args: env.args.clone(),
711            slice_ios: env.slice_io.borrow().inner.keys().cloned().collect(),
712            read_fds: env.posix_io.borrow().read_fds(),
713            write_fds: env.posix_io.borrow().write_fds(),
714            segment_limit_po2: env.segment_limit_po2,
715            keccak_max_po2: env.keccak_max_po2,
716            session_limit: env.session_limit,
717            trace_events: (!env.trace.is_empty()).then_some(()),
718            coprocessor: env.coprocessor.is_some(),
719            pprof_out: env
720                .pprof_out
721                .as_ref()
722                .map(|x| x.to_string_lossy().into())
723                .unwrap_or_default(),
724            assumptions: env
725                .assumptions
726                .borrow()
727                .0
728                .iter()
729                .map(|a| {
730                    Ok(match a {
731                        AssumptionReceipt::Proven(inner) => pb::api::AssumptionReceipt {
732                            kind: Some(pb::api::assumption_receipt::Kind::Proven(
733                                Asset::Inline(
734                                    pb::core::InnerReceipt::try_from(inner.clone())?
735                                        .encode_to_vec()
736                                        .into(),
737                                )
738                                .try_into()?,
739                            )),
740                        },
741                        AssumptionReceipt::Unresolved(assumption) => pb::api::AssumptionReceipt {
742                            kind: Some(pb::api::assumption_receipt::Kind::Unresolved(
743                                Asset::Inline(
744                                    pb::core::Assumption::try_from(assumption.clone())?
745                                        .encode_to_vec()
746                                        .into(),
747                                )
748                                .try_into()?,
749                            )),
750                        },
751                    })
752                })
753                .collect::<Result<_>>()?,
754            segment_path: env
755                .segment_path
756                .as_ref()
757                .map(|x| x.path().to_string_lossy().into())
758                .unwrap_or_default(),
759        })
760    }
761
762    fn execute_handler<F>(
763        &self,
764        segment_callback: F,
765        conn: &mut ConnectionWrapper,
766        env: &ExecutorEnv<'_>,
767    ) -> Result<SessionInfo>
768    where
769        F: FnMut(SegmentInfo, Asset) -> Result<()>,
770    {
771        let mut segment_callback = segment_callback;
772        let mut segments = Vec::new();
773        loop {
774            let reply: pb::api::ServerReply = conn.recv()?;
775            // tracing::trace!("rx: {reply:?}");
776
777            match reply
778                .kind
779                .ok_or_else(|| malformed_err("ServerReply.kind"))?
780            {
781                pb::api::server_reply::Kind::Ok(request) => {
782                    match request
783                        .kind
784                        .ok_or_else(|| malformed_err("ServerReply.Ok.kind"))?
785                    {
786                        pb::api::client_callback::Kind::Io(io) => {
787                            let msg: pb::api::OnIoReply = self.on_io(env, io).into();
788                            // tracing::trace!("tx: {msg:?}");
789                            conn.send(msg)?;
790                        }
791                        pb::api::client_callback::Kind::SegmentDone(segment) => {
792                            let reply: pb::api::GenericReply = segment
793                                .segment
794                                .map_or_else(
795                                    || Err(malformed_err("ServerReply.Ok.SegmentDone.segment")),
796                                    |segment| {
797                                        let asset = segment
798                                            .segment
799                                            .ok_or_else(|| {
800                                                malformed_err(
801                                                    "ServerReply.Ok.SegmentDone.segment.segment",
802                                                )
803                                            })?
804                                            .try_into()?;
805                                        let info = SegmentInfo {
806                                            po2: segment.po2,
807                                            cycles: segment.cycles,
808                                        };
809                                        segments.push(info.clone());
810                                        segment_callback(info, asset)
811                                    },
812                                )
813                                .into();
814                            // tracing::trace!("tx: {reply:?}");
815                            conn.send(reply)?;
816                        }
817                        pb::api::client_callback::Kind::SessionDone(session) => {
818                            return match session.session {
819                                Some(session) => {
820                                    let receipt_claim = match session.receipt_claim {
821                                        Some(claim) => Some(
822                                            pb::core::ReceiptClaim::decode(claim.as_bytes()?)?
823                                                .try_into()?,
824                                        ),
825                                        None => None,
826                                    };
827
828                                    Ok(SessionInfo {
829                                        segments,
830                                        journal: Journal::new(session.journal),
831                                        exit_code: session
832                                            .exit_code
833                                            .ok_or_else(|| malformed_err("SessionInfo.exit_code"))?
834                                            .try_into()?,
835                                        receipt_claim,
836                                    })
837                                }
838                                None => Err(malformed_err("ServerReply.ok.SessionDone.session")),
839                            }
840                        }
841                        pb::api::client_callback::Kind::ProveDone(_) => {
842                            return Err(anyhow!("Illegal client callback"))
843                        }
844                    }
845                }
846                pb::api::server_reply::Kind::Error(err) => return Err(err.into()),
847            }
848        }
849    }
850
851    fn prove_handler(
852        &self,
853        conn: &mut ConnectionWrapper,
854        env: &ExecutorEnv<'_>,
855    ) -> Result<pb::api::Asset> {
856        loop {
857            let reply: pb::api::ServerReply = conn.recv()?;
858            // tracing::trace!("rx: {reply:?}");
859
860            match reply
861                .kind
862                .ok_or_else(|| malformed_err("ServerReply.kind"))?
863            {
864                pb::api::server_reply::Kind::Ok(request) => {
865                    match request
866                        .kind
867                        .ok_or_else(|| malformed_err("ServerReply.Ok.kind"))?
868                    {
869                        pb::api::client_callback::Kind::Io(io) => {
870                            let msg: pb::api::OnIoReply = self.on_io(env, io).into();
871                            // tracing::trace!("tx: {msg:?}");
872                            conn.send(msg)?;
873                        }
874                        pb::api::client_callback::Kind::SegmentDone(_) => {
875                            return Err(anyhow!("Illegal client callback"))
876                        }
877                        pb::api::client_callback::Kind::SessionDone(_) => {
878                            return Err(anyhow!("Illegal client callback"))
879                        }
880                        pb::api::client_callback::Kind::ProveDone(done) => {
881                            return done.prove_info.ok_or_else(|| {
882                                malformed_err("ServerReply.Ok.ProveDone.prove_info")
883                            })
884                        }
885                    }
886                }
887                pb::api::server_reply::Kind::Error(err) => return Err(err.into()),
888            }
889        }
890    }
891
892    fn on_io(&self, env: &ExecutorEnv<'_>, request: pb::api::OnIoRequest) -> Result<Bytes> {
893        match request
894            .kind
895            .ok_or_else(|| malformed_err("OnIoRequest.kind"))?
896        {
897            pb::api::on_io_request::Kind::Posix(posix) => {
898                let cmd = posix
899                    .cmd
900                    .ok_or_else(|| malformed_err("OnIoRequest.Posix.cmd"))?;
901                match cmd
902                    .kind
903                    .ok_or_else(|| malformed_err("OnIoRequest.Posix.cmd.kind"))?
904                {
905                    pb::api::posix_cmd::Kind::Read(nread) => {
906                        self.on_posix_read(env, posix.fd, nread as usize)
907                    }
908                    pb::api::posix_cmd::Kind::Write(from_guest) => {
909                        self.on_posix_write(env, posix.fd, from_guest.into())?;
910                        Ok(Bytes::new())
911                    }
912                }
913            }
914            pb::api::on_io_request::Kind::Slice(slice_io) => {
915                self.on_slice(env, &slice_io.name, slice_io.from_guest.into())
916            }
917            pb::api::on_io_request::Kind::Trace(event) => {
918                self.on_trace(env, event)?;
919                Ok(Bytes::new())
920            }
921            pb::api::on_io_request::Kind::Coprocessor(request) => {
922                self.on_coprocessor(env, request)?;
923                Ok(Bytes::new())
924            }
925        }
926    }
927
928    fn on_posix_read(&self, env: &ExecutorEnv<'_>, fd: u32, nread: usize) -> Result<Bytes> {
929        tracing::debug!("on_posix_read: {fd}, {nread}");
930        let mut from_host = vec![0; nread];
931        let posix_io = env.posix_io.borrow();
932        let reader = posix_io.get_reader(fd)?;
933        let nread = reader.borrow_mut().read(&mut from_host)?;
934        let slice = from_host[..nread].to_vec();
935        Ok(slice.into())
936    }
937
938    fn on_posix_write(&self, env: &ExecutorEnv<'_>, fd: u32, from_guest: Bytes) -> Result<()> {
939        tracing::debug!("on_posix_write: {fd}");
940        let posix_io = env.posix_io.borrow();
941        let writer = posix_io.get_writer(fd)?;
942        writer.borrow_mut().write_all(&from_guest)?;
943        Ok(())
944    }
945
946    fn on_slice(&self, env: &ExecutorEnv<'_>, name: &str, from_guest: Bytes) -> Result<Bytes> {
947        let table = env.slice_io.borrow();
948        let slice_io = table
949            .inner
950            .get(name)
951            .ok_or_else(|| anyhow!("Unknown I/O channel name: {name}"))?;
952        let result = slice_io.borrow_mut().handle_io(name, from_guest)?;
953        Ok(result)
954    }
955
956    fn on_trace(&self, env: &ExecutorEnv<'_>, event: pb::api::TraceEvent) -> Result<()> {
957        for trace_callback in env.trace.iter() {
958            trace_callback
959                .borrow_mut()
960                .trace_callback(event.clone().try_into()?)?;
961        }
962        Ok(())
963    }
964
965    fn on_coprocessor(
966        &self,
967        env: &ExecutorEnv<'_>,
968        coprocessor_request: pb::api::CoprocessorRequest,
969    ) -> Result<()> {
970        match coprocessor_request
971            .kind
972            .ok_or_else(|| malformed_err("OnCoprocessorRequest.kind"))?
973        {
974            pb::api::coprocessor_request::Kind::ProveZkr(proof_request) => {
975                let proof_request = proof_request.try_into()?;
976                let coprocessor = env
977                    .coprocessor
978                    .clone()
979                    .ok_or_else(|| malformed_err("OnCoprocessorRequest.ProveZkr.coprocessor"))?;
980                let mut coprocessor = coprocessor.borrow_mut();
981                coprocessor.prove_zkr(proof_request)
982            }
983            pb::api::coprocessor_request::Kind::ProveKeccak(proof_request) => {
984                let proof_request = proof_request.try_into()?;
985                let coprocessor = env
986                    .coprocessor
987                    .clone()
988                    .ok_or_else(|| malformed_err("OnCoprocessorRequest.ProveKeccak.coprocessor"))?;
989                let mut coprocessor = coprocessor.borrow_mut();
990                coprocessor.prove_keccak(proof_request)
991            }
992        }
993    }
994}
995
996impl From<Result<Bytes, anyhow::Error>> for pb::api::OnIoReply {
997    fn from(result: Result<Bytes, anyhow::Error>) -> Self {
998        Self {
999            kind: Some(match result {
1000                Ok(bytes) => pb::api::on_io_reply::Kind::Ok(bytes.into()),
1001                Err(err) => pb::api::on_io_reply::Kind::Error(err.into()),
1002            }),
1003        }
1004    }
1005}
1006
1007impl Compat {
1008    pub(crate) fn check(&self, requested: &semver::Version, server: &semver::Version) -> bool {
1009        match self {
1010            Compat::Narrow => {
1011                if requested.pre.is_empty() {
1012                    requested.major == server.major && requested.minor == server.minor
1013                } else {
1014                    requested == server
1015                }
1016            }
1017            Compat::Wide => requested.major == server.major,
1018        }
1019    }
1020}
1021
1022#[cfg(test)]
1023mod tests {
1024    use semver::Version;
1025
1026    use super::Compat;
1027
1028    #[test]
1029    fn check_version_narrow() {
1030        fn test(requested: &str, server: &str) -> bool {
1031            Compat::Narrow.check(
1032                &Version::parse(requested).unwrap(),
1033                &Version::parse(server).unwrap(),
1034            )
1035        }
1036
1037        assert!(test("0.18.0", "0.18.0"));
1038        assert!(test("0.18.0", "0.18.1"));
1039        assert!(test("0.18.1", "0.18.1"));
1040        assert!(test("0.18.1", "0.18.2"));
1041        assert!(test("0.18.1", "0.18.0"));
1042        assert!(!test("0.18.0", "0.19.0"));
1043
1044        assert!(test("1.0.0", "1.0.0"));
1045        assert!(test("1.0.0", "1.0.1"));
1046        assert!(test("1.1.0", "1.1.0"));
1047        assert!(test("1.1.0", "1.1.1"));
1048        assert!(!test("1.0.0", "0.18.0"));
1049        assert!(!test("1.0.0", "2.0.0"));
1050        assert!(!test("1.1.0", "1.0.0"));
1051        assert!(test("1.0.3", "1.0.1"));
1052
1053        assert!(test("0.19.0-alpha.1", "0.19.0-alpha.1"));
1054        assert!(!test("0.19.0-alpha.1", "0.19.0-alpha.2"));
1055    }
1056
1057    #[test]
1058    fn check_wide_version() {
1059        fn test(requested: &str, server: &str) -> bool {
1060            Compat::Wide.check(
1061                &Version::parse(requested).unwrap(),
1062                &Version::parse(server).unwrap(),
1063            )
1064        }
1065
1066        assert!(test("0.1.0", "0.1.0"));
1067        assert!(test("0.1.0", "0.1.1"));
1068        assert!(test("0.1.0", "0.2.0"));
1069        assert!(test("0.1.0-rc.1", "0.2.0"));
1070        assert!(test("1.1.0", "1.0.0"));
1071        assert!(!test("1.0.0", "2.0.0"));
1072    }
1073}