1use 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
42pub 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 pub fn new() -> Result<Self> {
58 Self::new_sub_process("r0vm")
59 }
60
61 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 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 pub fn from_env() -> Result<Self> {
86 Client::new_sub_process(get_r0vm_path()?)
87 }
88
89 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 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 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 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 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 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 #[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 #[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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 conn.send(request)?;
675
676 let reply: pb::api::HelloReply = conn.recv()?;
677 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 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 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 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 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 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}