penumbra_custody/client.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
use anyhow::Result;
use futures::FutureExt;
use penumbra_proto::custody::v1::custody_service_client::CustodyServiceClient;
use penumbra_proto::custody::v1::AuthorizeResponse;
use std::{future::Future, pin::Pin};
use tonic::codegen::Bytes;
use crate::AuthorizeRequest;
/// A well-typed wrapper around the GRPC custody protocol that uses Rust domain types rather than proto types.
///
/// The custody protocol is used by a wallet client to request authorization for
/// a transaction they’ve constructed.
///
/// Modeling transaction authorization as an asynchronous RPC call encourages
/// software to be written in a way that has a compatible data flow with a “soft
/// HSM”, threshold signing, a hardware wallet, etc.
///
/// The custody protocol does not trust the client to authorize spends, so
/// custody requests must contain sufficient information for the custodian to
/// understand the transaction and determine whether or not it should be
/// authorized.
///
/// This trait is a wrapper around the proto-generated [`CustodyServiceClient`] that serves two goals:
///
/// 1. It works on domain types rather than proto-generated types, avoiding conversions;
/// 2. It's easier to write as a trait bound than the `CustodyServiceClient`,
/// which requires complex bounds on its inner type to enforce that it is a
/// tower `Service`
pub trait CustodyClient {
/// Requests authorization of the transaction with the given description.
fn authorize(
&mut self,
request: AuthorizeRequest,
) -> Pin<Box<dyn Future<Output = Result<AuthorizeResponse>> + Send + 'static>>;
}
impl<T> CustodyClient for CustodyServiceClient<T>
where
T: tonic::client::GrpcService<tonic::body::BoxBody> + Send + Clone + 'static,
T::ResponseBody: tonic::codegen::Body<Data = Bytes> + Send + 'static,
T::Future: Send + 'static,
T::Error: Into<tonic::codegen::StdError>,
<T::ResponseBody as tonic::codegen::Body>::Error: Into<tonic::codegen::StdError> + Send,
{
fn authorize(
&mut self,
request: AuthorizeRequest,
) -> Pin<Box<dyn Future<Output = Result<AuthorizeResponse>> + Send + 'static>> {
let mut self2 = self.clone();
async move {
Ok(self2
.authorize(tonic::Request::new(request.into()))
.await?
.into_inner())
}
.boxed()
}
}