Skip to main content

miden_client/remote_prover/
tx_prover.rs

1use alloc::string::String;
2use alloc::sync::Arc;
3use core::time::Duration;
4
5use miden_objects::{BuildUnchecked, DecodeMessage};
6use miden_protocol::transaction::{ProvenTransaction, TransactionInputs};
7use miden_protocol::vm::FutureMaybeSend;
8use miden_tx::TransactionProverError;
9use tokio::sync::Mutex;
10
11use super::api_client::ApiClient;
12use super::{RemoteProverClientError, generated as proto};
13
14// REMOTE TRANSACTION PROVER
15// ================================================================================================
16
17/// A [`RemoteTransactionProver`] is a transaction prover that sends witness data to a remote gRPC
18/// server and receives a proven transaction.
19///
20/// When compiled for the `wasm32-unknown-unknown` target, it uses the `tonic_web_wasm_client`
21/// transport. Otherwise, it uses the built-in `tonic::transport` for native platforms.
22///
23/// The transport layer connection is established lazily when the first transaction is proven.
24#[derive(Clone)]
25pub struct RemoteTransactionProver {
26    /// Lazily initialized gRPC client, populated on the first proving request.
27    client: Arc<Mutex<Option<ApiClient>>>,
28
29    /// Endpoint of the remote prover in the format `{protocol}://{hostname}:{port}`.
30    endpoint: String,
31
32    /// Timeout applied to each request sent to the remote prover.
33    timeout: Duration,
34}
35
36impl RemoteTransactionProver {
37    /// Creates a new [`RemoteTransactionProver`] with the specified gRPC server endpoint. The
38    /// endpoint should be in the format `{protocol}://{hostname}:{port}`.
39    pub fn new(endpoint: impl Into<String>) -> Self {
40        RemoteTransactionProver {
41            endpoint: endpoint.into(),
42            client: Arc::new(Mutex::new(None)),
43            timeout: Duration::from_secs(10),
44        }
45    }
46
47    /// Configures the timeout for requests to the remote prover server.
48    #[must_use]
49    pub fn with_timeout(mut self, timeout: Duration) -> Self {
50        self.timeout = timeout;
51        self
52    }
53
54    /// Establishes a connection to the remote transaction prover server. The connection is
55    /// maintained for the lifetime of the prover. If the connection is already established, this
56    /// method does nothing.
57    async fn connect(&self) -> Result<(), RemoteProverClientError> {
58        let mut client = self.client.lock().await;
59        if client.is_some() {
60            return Ok(());
61        }
62
63        *client = Some(ApiClient::new_client(self.endpoint.clone(), self.timeout).await?);
64
65        Ok(())
66    }
67
68    /// Proves the given transaction inputs on the remote prover, returning the resulting
69    /// [`ProvenTransaction`].
70    pub fn prove(
71        &self,
72        tx_inputs: &TransactionInputs,
73    ) -> impl FutureMaybeSend<Result<ProvenTransaction, TransactionProverError>> {
74        async move {
75            self.connect().await.map_err(|err| {
76                TransactionProverError::other_with_source(
77                    "failed to connect to the remote prover",
78                    err,
79                )
80            })?;
81
82            let mut client = self
83                .client
84                .lock()
85                .await
86                .as_ref()
87                .ok_or_else(|| TransactionProverError::other("client should be connected"))?
88                .clone();
89
90            let request = tonic::Request::new(tx_inputs.into());
91
92            let response = client.prove(request).await.map_err(|err| {
93                TransactionProverError::other_with_source("failed to prove transaction", err)
94            })?;
95
96            ProvenTransaction::try_from(response.into_inner())
97        }
98    }
99}
100
101// CONVERSIONS
102// ================================================================================================
103
104impl TryFrom<proto::Proof> for ProvenTransaction {
105    type Error = TransactionProverError;
106
107    fn try_from(response: proto::Proof) -> Result<Self, Self::Error> {
108        match response.proof {
109            Some(proto::proof::Proof::Transaction(transaction)) => transaction
110                .decode_fields()
111                .map_err(|err| {
112                    TransactionProverError::other_with_source(
113                        "failed to decode the transaction proof",
114                        err,
115                    )
116                })?
117                .build_unchecked()
118                .map_err(|err| {
119                    TransactionProverError::other_with_source(
120                        "failed to build the transaction proof",
121                        err,
122                    )
123                }),
124            Some(proto::proof::Proof::Batch(_)) => Err(TransactionProverError::other(
125                "expected a transaction proof, got a batch proof",
126            )),
127            Some(proto::proof::Proof::Block(_)) => Err(TransactionProverError::other(
128                "expected a transaction proof, got a block proof",
129            )),
130            None => Err(TransactionProverError::other("prover returned no proof")),
131        }
132    }
133}
134
135impl From<&TransactionInputs> for proto::ProofRequest {
136    fn from(tx_inputs: &TransactionInputs) -> Self {
137        proto::ProofRequest {
138            request: Some(proto::proof_request::Request::Transaction(tx_inputs.into())),
139        }
140    }
141}