miden-client 0.16.0-alpha.1

Client library that facilitates interaction with the Miden network
Documentation
use alloc::string::String;
use alloc::sync::Arc;
use core::time::Duration;

use miden_protocol::transaction::{ProvenTransaction, TransactionInputs};
use miden_protocol::utils::serde::{Deserializable, DeserializationError, Serializable};
use miden_protocol::vm::FutureMaybeSend;
use miden_tx::TransactionProverError;
use tokio::sync::Mutex;

use super::api_client::ApiClient;
use super::{RemoteProverClientError, generated as proto};

// REMOTE TRANSACTION PROVER
// ================================================================================================

/// A [`RemoteTransactionProver`] is a transaction prover that sends witness data to a remote
/// gRPC server and receives a proven transaction.
///
/// When compiled for the `wasm32-unknown-unknown` target, it uses the `tonic_web_wasm_client`
/// transport. Otherwise, it uses the built-in `tonic::transport` for native platforms.
///
/// The transport layer connection is established lazily when the first transaction is proven.
#[derive(Clone)]
pub struct RemoteTransactionProver {
    /// Lazily initialized gRPC client, populated on the first proving request.
    client: Arc<Mutex<Option<ApiClient>>>,

    /// Endpoint of the remote prover in the format `{protocol}://{hostname}:{port}`.
    endpoint: String,

    /// Timeout applied to each request sent to the remote prover.
    timeout: Duration,
}

impl RemoteTransactionProver {
    /// Creates a new [`RemoteTransactionProver`] with the specified gRPC server endpoint. The
    /// endpoint should be in the format `{protocol}://{hostname}:{port}`.
    pub fn new(endpoint: impl Into<String>) -> Self {
        RemoteTransactionProver {
            endpoint: endpoint.into(),
            client: Arc::new(Mutex::new(None)),
            timeout: Duration::from_secs(10),
        }
    }

    /// Configures the timeout for requests to the remote prover server.
    #[must_use]
    pub fn with_timeout(mut self, timeout: Duration) -> Self {
        self.timeout = timeout;
        self
    }

    /// Establishes a connection to the remote transaction prover server. The connection is
    /// maintained for the lifetime of the prover. If the connection is already established, this
    /// method does nothing.
    async fn connect(&self) -> Result<(), RemoteProverClientError> {
        let mut client = self.client.lock().await;
        if client.is_some() {
            return Ok(());
        }

        *client = Some(ApiClient::new_client(self.endpoint.clone(), self.timeout).await?);

        Ok(())
    }

    /// Proves the given transaction inputs on the remote prover, returning the resulting
    /// [`ProvenTransaction`].
    pub fn prove(
        &self,
        tx_inputs: &TransactionInputs,
    ) -> impl FutureMaybeSend<Result<ProvenTransaction, TransactionProverError>> {
        async move {
            self.connect().await.map_err(|err| {
                TransactionProverError::other_with_source(
                    "failed to connect to the remote prover",
                    err,
                )
            })?;

            let mut client = self
                .client
                .lock()
                .await
                .as_ref()
                .ok_or_else(|| TransactionProverError::other("client should be connected"))?
                .clone();

            let request = tonic::Request::new(tx_inputs.into());

            let response = client.prove(request).await.map_err(|err| {
                TransactionProverError::other_with_source("failed to prove transaction", err)
            })?;

            ProvenTransaction::try_from(response.into_inner()).map_err(|_| {
                TransactionProverError::other(
                    "failed to deserialize received response from remote transaction prover",
                )
            })
        }
    }
}

// CONVERSIONS
// ================================================================================================

impl TryFrom<proto::Proof> for ProvenTransaction {
    type Error = DeserializationError;

    fn try_from(response: proto::Proof) -> Result<Self, Self::Error> {
        ProvenTransaction::read_from_bytes(&response.payload)
    }
}

impl From<&TransactionInputs> for proto::ProofRequest {
    fn from(tx_inputs: &TransactionInputs) -> Self {
        proto::ProofRequest {
            proof_type: proto::ProofType::Transaction.into(),
            payload: tx_inputs.to_bytes(),
        }
    }
}