diff --git a/Cargo.lock b/Cargo.lock index de3f1e249d..329f698219 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5469,6 +5469,7 @@ dependencies = [ "decaf377-fmd", "decaf377-ka", "decaf377-rdsa", + "futures", "hex", "ibc-types", "im", diff --git a/crates/core/component/shielded-pool/Cargo.toml b/crates/core/component/shielded-pool/Cargo.toml index c399dd96af..bf573f4fb9 100644 --- a/crates/core/component/shielded-pool/Cargo.toml +++ b/crates/core/component/shielded-pool/Cargo.toml @@ -46,6 +46,7 @@ decaf377 = {workspace = true, features = ["r1cs"], default-features = true} decaf377-fmd = {workspace = true} decaf377-ka = {workspace = true} decaf377-rdsa = {workspace = true} +futures = {workspace = true} hex = {workspace = true} ibc-types = {workspace = true, default-features = false} im = {workspace = true} diff --git a/crates/core/component/shielded-pool/src/component/rpc.rs b/crates/core/component/shielded-pool/src/component/rpc.rs index bc27938767..ef77083ca1 100644 --- a/crates/core/component/shielded-pool/src/component/rpc.rs +++ b/crates/core/component/shielded-pool/src/component/rpc.rs @@ -1,7 +1,10 @@ +use std::pin::Pin; + use cnidarium::Storage; use penumbra_asset::asset; use penumbra_proto::core::component::shielded_pool::v1::{ query_service_server::QueryService, AssetMetadataByIdRequest, AssetMetadataByIdResponse, + AssetMetadataByIdsRequest, AssetMetadataByIdsResponse, }; use tonic::Status; @@ -22,6 +25,10 @@ impl Server { #[tonic::async_trait] impl QueryService for Server { + type AssetMetadataByIdsStream = Pin< + Box> + Send>, + >; + #[instrument(skip(self, request))] async fn asset_metadata_by_id( &self, @@ -53,4 +60,11 @@ impl QueryService for Server { Ok(tonic::Response::new(rsp)) } + + async fn asset_metadata_by_ids( + &self, + _request: tonic::Request, + ) -> Result, tonic::Status> { + unimplemented!("asset_metadata_by_ids not yet implemented") + } } diff --git a/crates/proto/src/gen/penumbra.core.component.shielded_pool.v1.rs b/crates/proto/src/gen/penumbra.core.component.shielded_pool.v1.rs index b8272ed531..b2c3ff37c5 100644 --- a/crates/proto/src/gen/penumbra.core.component.shielded_pool.v1.rs +++ b/crates/proto/src/gen/penumbra.core.component.shielded_pool.v1.rs @@ -561,6 +561,40 @@ impl ::prost::Name for AssetMetadataByIdResponse { ) } } +/// Requests information on an asset by multiple asset ids +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct AssetMetadataByIdsRequest { + /// The asset IDs to request information on. Note that node is neither required + /// nor expected to stream responses in the same order as this array. + #[prost(message, repeated, tag = "1")] + pub asset_id: ::prost::alloc::vec::Vec, +} +impl ::prost::Name for AssetMetadataByIdsRequest { + const NAME: &'static str = "AssetMetadataByIdsRequest"; + const PACKAGE: &'static str = "penumbra.core.component.shielded_pool.v1"; + fn full_name() -> ::prost::alloc::string::String { + ::prost::alloc::format!( + "penumbra.core.component.shielded_pool.v1.{}", Self::NAME + ) + } +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct AssetMetadataByIdsResponse { + /// A single asset metadata streamed from the node. + #[prost(message, optional, tag = "1")] + pub denom_metadata: ::core::option::Option, +} +impl ::prost::Name for AssetMetadataByIdsResponse { + const NAME: &'static str = "AssetMetadataByIdsResponse"; + const PACKAGE: &'static str = "penumbra.core.component.shielded_pool.v1"; + fn full_name() -> ::prost::alloc::string::String { + ::prost::alloc::format!( + "penumbra.core.component.shielded_pool.v1.{}", Self::NAME + ) + } +} /// Generated client implementations. #[cfg(feature = "rpc")] pub mod query_service_client { @@ -678,6 +712,41 @@ pub mod query_service_client { ); self.inner.unary(req, path, codec).await } + /// Requests a stream of asset metadata, given an array of asset IDs. Responses + /// may be streamed in a different order from that of the asset IDs in the + /// request, and asset IDs unknown to the node will not receive any response + /// objects -- that is, the number of responses may be smaller than the length + /// of the asset IDs array. + pub async fn asset_metadata_by_ids( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response>, + 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( + "/penumbra.core.component.shielded_pool.v1.QueryService/AssetMetadataByIds", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert( + GrpcMethod::new( + "penumbra.core.component.shielded_pool.v1.QueryService", + "AssetMetadataByIds", + ), + ); + self.inner.server_streaming(req, path, codec).await + } } } /// Generated server implementations. @@ -695,6 +764,27 @@ pub mod query_service_server { tonic::Response, tonic::Status, >; + /// Server streaming response type for the AssetMetadataByIds method. + type AssetMetadataByIdsStream: tonic::codegen::tokio_stream::Stream< + Item = std::result::Result< + super::AssetMetadataByIdsResponse, + tonic::Status, + >, + > + + Send + + 'static; + /// Requests a stream of asset metadata, given an array of asset IDs. Responses + /// may be streamed in a different order from that of the asset IDs in the + /// request, and asset IDs unknown to the node will not receive any response + /// objects -- that is, the number of responses may be smaller than the length + /// of the asset IDs array. + async fn asset_metadata_by_ids( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; } /// Query operations for the shielded pool component. #[derive(Debug)] @@ -823,6 +913,55 @@ pub mod query_service_server { }; Box::pin(fut) } + "/penumbra.core.component.shielded_pool.v1.QueryService/AssetMetadataByIds" => { + #[allow(non_camel_case_types)] + struct AssetMetadataByIdsSvc(pub Arc); + impl< + T: QueryService, + > tonic::server::ServerStreamingService< + super::AssetMetadataByIdsRequest, + > for AssetMetadataByIdsSvc { + type Response = super::AssetMetadataByIdsResponse; + type ResponseStream = T::AssetMetadataByIdsStream; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::asset_metadata_by_ids(&inner, request) + .await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let inner = inner.0; + let method = AssetMetadataByIdsSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.server_streaming(method, req).await; + Ok(res) + }; + Box::pin(fut) + } _ => { Box::pin(async move { Ok( diff --git a/crates/proto/src/gen/penumbra.core.component.shielded_pool.v1.serde.rs b/crates/proto/src/gen/penumbra.core.component.shielded_pool.v1.serde.rs index 04f1e6056c..67ce5950df 100644 --- a/crates/proto/src/gen/penumbra.core.component.shielded_pool.v1.serde.rs +++ b/crates/proto/src/gen/penumbra.core.component.shielded_pool.v1.serde.rs @@ -190,6 +190,198 @@ impl<'de> serde::Deserialize<'de> for AssetMetadataByIdResponse { deserializer.deserialize_struct("penumbra.core.component.shielded_pool.v1.AssetMetadataByIdResponse", FIELDS, GeneratedVisitor) } } +impl serde::Serialize for AssetMetadataByIdsRequest { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if !self.asset_id.is_empty() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("penumbra.core.component.shielded_pool.v1.AssetMetadataByIdsRequest", len)?; + if !self.asset_id.is_empty() { + struct_ser.serialize_field("assetId", &self.asset_id)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for AssetMetadataByIdsRequest { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "asset_id", + "assetId", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + AssetId, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "assetId" | "asset_id" => Ok(GeneratedField::AssetId), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = AssetMetadataByIdsRequest; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct penumbra.core.component.shielded_pool.v1.AssetMetadataByIdsRequest") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut asset_id__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::AssetId => { + if asset_id__.is_some() { + return Err(serde::de::Error::duplicate_field("assetId")); + } + asset_id__ = Some(map_.next_value()?); + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(AssetMetadataByIdsRequest { + asset_id: asset_id__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("penumbra.core.component.shielded_pool.v1.AssetMetadataByIdsRequest", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for AssetMetadataByIdsResponse { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if self.denom_metadata.is_some() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("penumbra.core.component.shielded_pool.v1.AssetMetadataByIdsResponse", len)?; + if let Some(v) = self.denom_metadata.as_ref() { + struct_ser.serialize_field("denomMetadata", v)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for AssetMetadataByIdsResponse { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "denom_metadata", + "denomMetadata", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + DenomMetadata, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "denomMetadata" | "denom_metadata" => Ok(GeneratedField::DenomMetadata), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = AssetMetadataByIdsResponse; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct penumbra.core.component.shielded_pool.v1.AssetMetadataByIdsResponse") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut denom_metadata__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::DenomMetadata => { + if denom_metadata__.is_some() { + return Err(serde::de::Error::duplicate_field("denomMetadata")); + } + denom_metadata__ = map_.next_value()?; + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(AssetMetadataByIdsResponse { + denom_metadata: denom_metadata__, + }) + } + } + deserializer.deserialize_struct("penumbra.core.component.shielded_pool.v1.AssetMetadataByIdsResponse", FIELDS, GeneratedVisitor) + } +} impl serde::Serialize for EventOutput { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result diff --git a/crates/proto/src/gen/proto_descriptor.bin.no_lfs b/crates/proto/src/gen/proto_descriptor.bin.no_lfs index e2f91d75e6..f537de9326 100644 Binary files a/crates/proto/src/gen/proto_descriptor.bin.no_lfs and b/crates/proto/src/gen/proto_descriptor.bin.no_lfs differ diff --git a/proto/penumbra/penumbra/core/component/shielded_pool/v1/shielded_pool.proto b/proto/penumbra/penumbra/core/component/shielded_pool/v1/shielded_pool.proto index 951205572e..979b0c35f7 100644 --- a/proto/penumbra/penumbra/core/component/shielded_pool/v1/shielded_pool.proto +++ b/proto/penumbra/penumbra/core/component/shielded_pool/v1/shielded_pool.proto @@ -194,6 +194,13 @@ message OutputPlan { // Query operations for the shielded pool component. service QueryService { rpc AssetMetadataById(AssetMetadataByIdRequest) returns (AssetMetadataByIdResponse); + + // Requests a stream of asset metadata, given an array of asset IDs. Responses + // may be streamed in a different order from that of the asset IDs in the + // request, and asset IDs unknown to the node will not receive any response + // objects -- that is, the number of responses may be smaller than the length + // of the asset IDs array. + rpc AssetMetadataByIds(AssetMetadataByIdsRequest) returns (stream AssetMetadataByIdsResponse); } // Requests information on an asset by asset id @@ -208,3 +215,15 @@ message AssetMetadataByIdResponse { // If the requested asset was unknown, this field will not be present. core.asset.v1.Metadata denom_metadata = 1; } + +// Requests information on an asset by multiple asset ids +message AssetMetadataByIdsRequest { + // The asset IDs to request information on. Note that node is neither required + // nor expected to stream responses in the same order as this array. + repeated core.asset.v1.AssetId asset_id = 1; +} + +message AssetMetadataByIdsResponse { + // A single asset metadata streamed from the node. + core.asset.v1.Metadata denom_metadata = 1; +}