Skip to content

Commit

Permalink
wip: api client
Browse files Browse the repository at this point in the history
  • Loading branch information
SantiagoPittella committed Sep 18, 2024
1 parent 8e2efa2 commit db8b207
Show file tree
Hide file tree
Showing 4 changed files with 203 additions and 25 deletions.
38 changes: 38 additions & 0 deletions bin/miden-tx-prover/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ use std::{
use miette::IntoDiagnostic;
use protox::prost::Message;

const TONIC_CLIENT_PROTO_OUT_DIR: &str = "src/remote_prover/generated";

/// Generates Rust protobuf bindings from .proto files in the root directory.
///
/// This is done only if BUILD_PROTO environment variable is set to `1` to avoid running the script
Expand All @@ -19,6 +21,13 @@ fn main() -> miette::Result<()> {
// return Ok(());
// }

compile_tonic_server_proto()?;
compile_tonic_client_proto()?;

Ok(())
}

fn compile_tonic_server_proto() -> miette::Result<()> {
let crate_root: PathBuf =
env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR should be set").into();
let dst_dir = crate_root.join("src").join("server").join("generated");
Expand Down Expand Up @@ -88,3 +97,32 @@ fn generate_mod_rs(directory: impl AsRef<Path>) -> std::io::Result<()> {

fs::write(mod_filepath, contents)
}

// Compiles the protobuf files into a file descriptor used to generate Rust types
fn compile_tonic_client_proto() -> miette::Result<()> {
let crate_root: PathBuf =
env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR should be set").into();
let proto_dir = crate_root.join("proto");

// Compute the compiler's target file path.
let out = env::var("OUT_DIR").into_diagnostic()?;
let file_descriptor_path = PathBuf::from(out).join("file_descriptor_set.bin");

// Compile the proto file
let protos = &[proto_dir.join("api.proto")];
let includes = &[proto_dir];
let file_descriptors = protox::compile(protos, includes)?;
fs::write(&file_descriptor_path, file_descriptors.encode_to_vec()).into_diagnostic()?;

let prost_config = prost_build::Config::new();

tonic_build::configure()
.build_server(false)
.file_descriptor_set_path(&file_descriptor_path)
.skip_protoc_run()
.out_dir(TONIC_CLIENT_PROTO_OUT_DIR)
.compile_with_config(prost_config, protos, includes)
.into_diagnostic()?;

Ok(())
}
11 changes: 10 additions & 1 deletion bin/miden-tx-prover/src/domain/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use miden_objects::transaction::ProvenTransaction;
use miden_tx::utils::Serializable;
use miden_tx::{utils::{Deserializable, Serializable}, TransactionProverError};

use crate::ProveTransactionResponse;

Expand All @@ -8,3 +8,12 @@ impl From<ProvenTransaction> for ProveTransactionResponse {
ProveTransactionResponse { proven_transaction: value.to_bytes() }
}
}

impl TryFrom<ProveTransactionResponse> for ProvenTransaction {
type Error = TransactionProverError;

fn try_from(response: ProveTransactionResponse) -> Result<Self, Self::Error> {
ProvenTransaction::read_from_bytes(&response.proven_transaction)
.map_err(|_err| TransactionProverError::DeserializationError)
}
}
122 changes: 122 additions & 0 deletions bin/miden-tx-prover/src/remote_prover/generated/api.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
// This file is @generated by prost-build.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ProveTransactionRequest {
#[prost(bytes = "vec", tag = "1")]
pub transaction_witness: ::prost::alloc::vec::Vec<u8>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ProveTransactionResponse {
#[prost(bytes = "vec", tag = "1")]
pub proven_transaction: ::prost::alloc::vec::Vec<u8>,
}
/// Generated client implementations.
pub mod api_client {
#![allow(unused_variables, dead_code, missing_docs, clippy::let_unit_value)]
use tonic::codegen::*;
use tonic::codegen::http::Uri;
#[derive(Debug, Clone)]
pub struct ApiClient<T> {
inner: tonic::client::Grpc<T>,
}
impl ApiClient<tonic::transport::Channel> {
/// Attempt to create a new client by connecting to a given endpoint.
pub async fn connect<D>(dst: D) -> Result<Self, tonic::transport::Error>
where
D: TryInto<tonic::transport::Endpoint>,
D::Error: Into<StdError>,
{
let conn = tonic::transport::Endpoint::new(dst)?.connect().await?;
Ok(Self::new(conn))
}
}
impl<T> ApiClient<T>
where
T: tonic::client::GrpcService<tonic::body::BoxBody>,
T::Error: Into<StdError>,
T::ResponseBody: Body<Data = Bytes> + Send + 'static,
<T::ResponseBody as Body>::Error: Into<StdError> + Send,
{
pub fn new(inner: T) -> Self {
let inner = tonic::client::Grpc::new(inner);
Self { inner }
}
pub fn with_origin(inner: T, origin: Uri) -> Self {
let inner = tonic::client::Grpc::with_origin(inner, origin);
Self { inner }
}
pub fn with_interceptor<F>(
inner: T,
interceptor: F,
) -> ApiClient<InterceptedService<T, F>>
where
F: tonic::service::Interceptor,
T::ResponseBody: Default,
T: tonic::codegen::Service<
http::Request<tonic::body::BoxBody>,
Response = http::Response<
<T as tonic::client::GrpcService<tonic::body::BoxBody>>::ResponseBody,
>,
>,
<T as tonic::codegen::Service<
http::Request<tonic::body::BoxBody>,
>>::Error: Into<StdError> + Send + Sync,
{
ApiClient::new(InterceptedService::new(inner, interceptor))
}
/// Compress requests with the given encoding.
///
/// This requires the server to support it otherwise it might respond with an
/// error.
#[must_use]
pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self {
self.inner = self.inner.send_compressed(encoding);
self
}
/// Enable decompressing responses.
#[must_use]
pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self {
self.inner = self.inner.accept_compressed(encoding);
self
}
/// Limits the maximum size of a decoded message.
///
/// Default: `4MB`
#[must_use]
pub fn max_decoding_message_size(mut self, limit: usize) -> Self {
self.inner = self.inner.max_decoding_message_size(limit);
self
}
/// Limits the maximum size of an encoded message.
///
/// Default: `usize::MAX`
#[must_use]
pub fn max_encoding_message_size(mut self, limit: usize) -> Self {
self.inner = self.inner.max_encoding_message_size(limit);
self
}
pub async fn prove_transaction(
&mut self,
request: impl tonic::IntoRequest<super::ProveTransactionRequest>,
) -> std::result::Result<
tonic::Response<super::ProveTransactionResponse>,
tonic::Status,
> {
self.inner
.ready()
.await
.map_err(|e| {
tonic::Status::new(
tonic::Code::Unknown,
format!("Service was not ready: {}", e.into()),
)
})?;
let codec = tonic::codec::ProstCodec::default();
let path = http::uri::PathAndQuery::from_static("/api.Api/ProveTransaction");
let mut req = request.into_request();
req.extensions_mut().insert(GrpcMethod::new("api.Api", "ProveTransaction"));
self.inner.unary(req, path, codec).await
}
}
}
57 changes: 33 additions & 24 deletions bin/miden-tx-prover/src/remote_prover/remote_tx_prover.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,47 @@ use miden_tx::{
use tokio::sync::Mutex;

use crate::{ProveTransactionRequest, ProveTransactionResponse};
use crate::server::generated::api::api_client::ApiClient;
use tonic::transport::Channel;
use tokio::time::Duration;

#[derive(Debug)]
enum RpcError {
ConnectionError(String),
}

#[derive(Clone)]
pub struct RemoteTransactionProver {
pub url: String,
pub client: Arc<Mutex<reqwest::Client>>,
rpc_api: Option<ApiClient<Channel>>,
endpoint: String,
timeout_ms: u64,
}

impl RemoteTransactionProver {
pub fn new(url: String) -> Self {
pub fn new(endpoint: String, timeout_ms: u64) -> Self {
Self {
url,
client: Arc::new(Mutex::new(reqwest::Client::new())),
rpc_api: None,
endpoint,
timeout_ms,
}
}

async fn rpc_api(&mut self) -> Result<&mut ApiClient<Channel>, RpcError> {
if self.rpc_api.is_some() {
Ok(self.rpc_api.as_mut().unwrap())
} else {
let endpoint = tonic::transport::Endpoint::try_from(self.endpoint.clone())
.map_err(|err| RpcError::ConnectionError(err.to_string()))?
.timeout(Duration::from_millis(self.timeout_ms));
let rpc_api = ApiClient::connect(endpoint)
.await
.map_err(|err| RpcError::ConnectionError(err.to_string()))?;
Ok(self.rpc_api.insert(rpc_api))
}
}

pub(crate) async fn prove(
&self,
&mut self,
transaction: impl Into<TransactionWitness>,
) -> Result<ProvenTransaction, TransactionProverError> {
let tx_witness: TransactionWitness = transaction.into();
Expand All @@ -32,25 +56,10 @@ impl RemoteTransactionProver {
transaction_witness: tx_witness.to_bytes(),
};

// Send the POST request
let client = self.client.lock().await;
let response = client
.post(&format!("{}/prove", self.url))
.header("Content-Type", "application/json")
.body(tx_witness_request.into())
.send()
.await
.map_err(|_| TransactionProverError::HttpRequestError)?;

// Check if the response status is success
if response.status().is_success() {
let ProveTransactionResponse { proven_transaction } =
response.try_into().map_err(|_| TransactionProverError::DeserializationError)?;
let rpc_api = self.rpc_api().await.unwrap();
let proven_transaction: ProvenTransaction = rpc_api.prove_transaction(tx_witness_request).await.unwrap().into_inner().try_into().unwrap();

Ok(ProvenTransaction::read_from_bytes(&proven_transaction).unwrap())
} else {
Err(TransactionProverError::HttpRequestError)
}
Ok(proven_transaction)
}
}

Expand Down

0 comments on commit db8b207

Please sign in to comment.