penumbra_sdk_tendermint_proxy/
tendermint_proxy.rsuse crate::TendermintProxy;
use penumbra_sdk_proto::{
util::tendermint_proxy::v1::{
tendermint_proxy_service_server::TendermintProxyService, AbciQueryRequest,
AbciQueryResponse, BroadcastTxAsyncRequest, BroadcastTxAsyncResponse,
BroadcastTxSyncRequest, BroadcastTxSyncResponse, GetBlockByHeightRequest,
GetBlockByHeightResponse, GetStatusRequest, GetStatusResponse, GetTxRequest, GetTxResponse,
},
DomainType,
};
use penumbra_sdk_transaction::Transaction;
use tap::TapFallible;
use tendermint::{abci::Code, block::Height};
use tendermint_rpc::{Client, HttpClient};
use tonic::Status;
use tracing::instrument;
#[tonic::async_trait]
impl TendermintProxyService for TendermintProxy {
#[instrument(level = "info", skip_all)]
async fn get_tx(
&self,
req: tonic::Request<GetTxRequest>,
) -> Result<tonic::Response<GetTxResponse>, Status> {
let client = HttpClient::new(self.tendermint_url.as_ref()).map_err(|e| {
Status::unavailable(format!("error creating tendermint http client: {e:#?}"))
})?;
let GetTxRequest { hash, prove } = req.into_inner();
let hash = hash
.try_into()
.map_err(|e| Status::invalid_argument(format!("invalid transaction hash: {e:#?}")))?;
let rsp = client
.tx(hash, prove)
.await
.map(GetTxResponse::from)
.map_err(|e| Status::unavailable(format!("error getting tx: {e}")))?;
Transaction::decode(rsp.tx.as_ref())
.map_err(|e| Status::unavailable(format!("error decoding tx: {e}")))?;
Ok(tonic::Response::new(rsp))
}
#[instrument(
level = "info",
skip_all,
fields(req_id = tracing::field::Empty),
)]
async fn broadcast_tx_async(
&self,
req: tonic::Request<BroadcastTxAsyncRequest>,
) -> Result<tonic::Response<BroadcastTxAsyncResponse>, Status> {
let client = HttpClient::new(self.tendermint_url.as_ref()).map_err(|e| {
Status::unavailable(format!("error creating tendermint http client: {e:#?}"))
})?;
let BroadcastTxAsyncRequest { req_id, params } = req.into_inner();
tracing::Span::current().record("req_id", req_id);
client
.broadcast_tx_async(params)
.await
.map(BroadcastTxAsyncResponse::from)
.map(tonic::Response::new)
.map_err(|e| Status::unavailable(format!("error broadcasting tx async: {e}")))
}
#[instrument(
level = "info",
skip_all,
fields(req_id = tracing::field::Empty),
)]
async fn broadcast_tx_sync(
&self,
req: tonic::Request<BroadcastTxSyncRequest>,
) -> Result<tonic::Response<BroadcastTxSyncResponse>, Status> {
let client = HttpClient::new(self.tendermint_url.as_ref()).map_err(|e| {
Status::unavailable(format!("error creating tendermint http client: {e:#?}"))
})?;
let BroadcastTxSyncRequest { req_id, params } = req.into_inner();
tracing::Span::current().record("req_id", req_id);
client
.broadcast_tx_sync(params)
.await
.map(BroadcastTxSyncResponse::from)
.map(tonic::Response::new)
.map_err(|e| tonic::Status::unavailable(format!("error broadcasting tx sync: {e}")))
.tap_ok(|res| tracing::debug!("{:?}", res))
}
#[instrument(level = "info", skip_all)]
async fn get_status(
&self,
_req: tonic::Request<GetStatusRequest>,
) -> Result<tonic::Response<GetStatusResponse>, Status> {
let client = HttpClient::new(self.tendermint_url.as_ref()).map_err(|e| {
tonic::Status::unavailable(format!("error creating tendermint http client: {e:#?}"))
})?;
client
.status()
.await
.map(GetStatusResponse::from)
.map(tonic::Response::new)
.map_err(|e| tonic::Status::unavailable(format!("error querying status: {e}")))
}
#[instrument(level = "info", skip_all)]
async fn abci_query(
&self,
req: tonic::Request<AbciQueryRequest>,
) -> Result<tonic::Response<AbciQueryResponse>, Status> {
let client = HttpClient::new(self.tendermint_url.to_string().as_ref()).map_err(|e| {
tonic::Status::unavailable(format!("error creating tendermint http client: {e:#?}"))
})?;
let AbciQueryRequest {
data,
path,
height,
prove,
} = req.into_inner();
let height: Height = height
.try_into()
.map_err(|_| Status::invalid_argument("invalid height"))?;
let rsp = client
.abci_query(Some(path), data, Some(height), prove)
.await
.map_err(|e| Status::unavailable(format!("error querying abci: {e}")))
.and_then(|rsp| match rsp.code {
Code::Ok => Ok(rsp),
tendermint::abci::Code::Err(e) => {
Err(Status::unavailable(format!("error querying abci: {e}")))
}
})?;
AbciQueryResponse::try_from(rsp)
.map(tonic::Response::new)
.map_err(|error| Status::internal(format!("{error}")))
}
#[instrument(level = "info", skip_all)]
async fn get_block_by_height(
&self,
req: tonic::Request<GetBlockByHeightRequest>,
) -> Result<tonic::Response<GetBlockByHeightResponse>, Status> {
let client = HttpClient::new(self.tendermint_url.to_string().as_ref()).map_err(|e| {
tonic::Status::unavailable(format!("error creating tendermint http client: {e:#?}"))
})?;
let GetBlockByHeightRequest { height } = req.into_inner();
let height =
tendermint::block::Height::try_from(height).expect("height should be less than 2^63");
client
.block(height)
.await
.map_err(|e| tonic::Status::unavailable(format!("error querying abci: {e}")))
.and_then(|b| {
match GetBlockByHeightResponse::try_from(b) {
Ok(b) => Ok(b),
Err(e) => {
tracing::warn!(?height, error = ?e, "proxy: error deserializing GetBlockByHeightResponse");
Err(tonic::Status::internal("error deserializing GetBlockByHeightResponse"))
}
}
})
.map(tonic::Response::new)
}
}