From 998054760832d4c559fdb6d8b489b2628d930ffd Mon Sep 17 00:00:00 2001 From: katelyn martin Date: Wed, 24 Apr 2024 00:33:26 -0400 Subject: [PATCH] =?UTF-8?q?staking:=20=F0=9F=94=82=20`QueryService::get=5F?= =?UTF-8?q?validator=5Finfo(..)`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit this introduces an RPC endpoint to fetch a single validator's information via pcli. --- .../core/component/stake/src/component/rpc.rs | 39 +++- .../gen/penumbra.core.component.stake.v1.rs | 114 +++++++++++ .../penumbra.core.component.stake.v1.serde.rs | 192 ++++++++++++++++++ .../proto/src/gen/proto_descriptor.bin.no_lfs | Bin 399895 -> 400625 bytes .../core/component/stake/v1/stake.proto | 12 ++ 5 files changed, 354 insertions(+), 3 deletions(-) diff --git a/crates/core/component/stake/src/component/rpc.rs b/crates/core/component/stake/src/component/rpc.rs index c826680ca7..1cc32f2858 100644 --- a/crates/core/component/stake/src/component/rpc.rs +++ b/crates/core/component/stake/src/component/rpc.rs @@ -5,12 +5,13 @@ use futures::StreamExt; use penumbra_proto::{ core::component::stake::v1::{ query_service_server::QueryService, CurrentValidatorRateRequest, - CurrentValidatorRateResponse, ValidatorInfoRequest, ValidatorInfoResponse, - ValidatorPenaltyRequest, ValidatorPenaltyResponse, ValidatorStatusRequest, - ValidatorStatusResponse, + CurrentValidatorRateResponse, GetValidatorInfoRequest, GetValidatorInfoResponse, + ValidatorInfoRequest, ValidatorInfoResponse, ValidatorPenaltyRequest, + ValidatorPenaltyResponse, ValidatorStatusRequest, ValidatorStatusResponse, }, DomainType, }; +use tap::{TapFallible, TapOptional}; use tonic::Status; use tracing::{error_span, instrument, Instrument, Span}; @@ -30,6 +31,38 @@ impl Server { #[tonic::async_trait] impl QueryService for Server { + #[instrument(skip(self, request))] + async fn get_validator_info( + &self, + request: tonic::Request, + ) -> Result, tonic::Status> { + let state = self.storage.latest_snapshot(); + let GetValidatorInfoRequest { identity_key } = request.into_inner(); + + // Take the identity key from the inbound request. + let identity_key = identity_key + .ok_or_else(|| Status::invalid_argument("an identity key must be provided"))? + .try_into() + .tap_err(|error| tracing::debug!(?error, "request contained an invalid identity key")) + .map_err(|_| Status::invalid_argument("invalid identity key"))?; + + // Look up the information for the validator with the given identity key. + let info = state + .get_validator_info(&identity_key) + .await + .tap_err(|error| tracing::error!(?error, %identity_key, "failed to get validator info")) + .map_err(|_| Status::invalid_argument("failed to get validator info"))? + .tap_none(|| tracing::debug!(%identity_key, "validator info was not found")) + .ok_or_else(|| Status::not_found("validator info was not found"))?; + + // Construct the outbound response. + let resp = GetValidatorInfoResponse { + validator_info: Some(info.to_proto()), + }; + + Ok(tonic::Response::new(resp)) + } + type ValidatorInfoStream = Pin> + Send>>; diff --git a/crates/proto/src/gen/penumbra.core.component.stake.v1.rs b/crates/proto/src/gen/penumbra.core.component.stake.v1.rs index b2c42d8050..c28e60c9f9 100644 --- a/crates/proto/src/gen/penumbra.core.component.stake.v1.rs +++ b/crates/proto/src/gen/penumbra.core.component.stake.v1.rs @@ -592,6 +592,34 @@ impl ::prost::Name for Penalty { ::prost::alloc::format!("penumbra.core.component.stake.v1.{}", Self::NAME) } } +/// Requests information about a specific validator. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetValidatorInfoRequest { + /// The identity key of the validator. + #[prost(message, optional, tag = "2")] + pub identity_key: ::core::option::Option, +} +impl ::prost::Name for GetValidatorInfoRequest { + const NAME: &'static str = "GetValidatorInfoRequest"; + const PACKAGE: &'static str = "penumbra.core.component.stake.v1"; + fn full_name() -> ::prost::alloc::string::String { + ::prost::alloc::format!("penumbra.core.component.stake.v1.{}", Self::NAME) + } +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetValidatorInfoResponse { + #[prost(message, optional, tag = "1")] + pub validator_info: ::core::option::Option, +} +impl ::prost::Name for GetValidatorInfoResponse { + const NAME: &'static str = "GetValidatorInfoResponse"; + const PACKAGE: &'static str = "penumbra.core.component.stake.v1"; + fn full_name() -> ::prost::alloc::string::String { + ::prost::alloc::format!("penumbra.core.component.stake.v1.{}", Self::NAME) + } +} /// Requests information on the chain's validators. #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -876,6 +904,37 @@ pub mod query_service_client { self.inner = self.inner.max_encoding_message_size(limit); self } + /// Queries for information about a specific validator. + pub async fn get_validator_info( + &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.stake.v1.QueryService/GetValidatorInfo", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert( + GrpcMethod::new( + "penumbra.core.component.stake.v1.QueryService", + "GetValidatorInfo", + ), + ); + self.inner.unary(req, path, codec).await + } /// Queries the current validator set, with filtering. pub async fn validator_info( &mut self, @@ -1007,6 +1066,14 @@ pub mod query_service_server { /// Generated trait containing gRPC methods that should be implemented for use with QueryServiceServer. #[async_trait] pub trait QueryService: Send + Sync + 'static { + /// Queries for information about a specific validator. + async fn get_validator_info( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; /// Server streaming response type for the ValidatorInfo method. type ValidatorInfoStream: tonic::codegen::tokio_stream::Stream< Item = std::result::Result, @@ -1123,6 +1190,53 @@ pub mod query_service_server { fn call(&mut self, req: http::Request) -> Self::Future { let inner = self.inner.clone(); match req.uri().path() { + "/penumbra.core.component.stake.v1.QueryService/GetValidatorInfo" => { + #[allow(non_camel_case_types)] + struct GetValidatorInfoSvc(pub Arc); + impl< + T: QueryService, + > tonic::server::UnaryService + for GetValidatorInfoSvc { + type Response = super::GetValidatorInfoResponse; + 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 { + ::get_validator_info(&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 = GetValidatorInfoSvc(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.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } "/penumbra.core.component.stake.v1.QueryService/ValidatorInfo" => { #[allow(non_camel_case_types)] struct ValidatorInfoSvc(pub Arc); diff --git a/crates/proto/src/gen/penumbra.core.component.stake.v1.serde.rs b/crates/proto/src/gen/penumbra.core.component.stake.v1.serde.rs index 622765c973..414fae2b37 100644 --- a/crates/proto/src/gen/penumbra.core.component.stake.v1.serde.rs +++ b/crates/proto/src/gen/penumbra.core.component.stake.v1.serde.rs @@ -1521,6 +1521,198 @@ impl<'de> serde::Deserialize<'de> for GenesisContent { deserializer.deserialize_struct("penumbra.core.component.stake.v1.GenesisContent", FIELDS, GeneratedVisitor) } } +impl serde::Serialize for GetValidatorInfoRequest { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if self.identity_key.is_some() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("penumbra.core.component.stake.v1.GetValidatorInfoRequest", len)?; + if let Some(v) = self.identity_key.as_ref() { + struct_ser.serialize_field("identityKey", v)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for GetValidatorInfoRequest { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "identity_key", + "identityKey", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + IdentityKey, + __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 { + "identityKey" | "identity_key" => Ok(GeneratedField::IdentityKey), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GetValidatorInfoRequest; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct penumbra.core.component.stake.v1.GetValidatorInfoRequest") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut identity_key__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::IdentityKey => { + if identity_key__.is_some() { + return Err(serde::de::Error::duplicate_field("identityKey")); + } + identity_key__ = map_.next_value()?; + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(GetValidatorInfoRequest { + identity_key: identity_key__, + }) + } + } + deserializer.deserialize_struct("penumbra.core.component.stake.v1.GetValidatorInfoRequest", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for GetValidatorInfoResponse { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if self.validator_info.is_some() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("penumbra.core.component.stake.v1.GetValidatorInfoResponse", len)?; + if let Some(v) = self.validator_info.as_ref() { + struct_ser.serialize_field("validatorInfo", v)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for GetValidatorInfoResponse { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "validator_info", + "validatorInfo", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + ValidatorInfo, + __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 { + "validatorInfo" | "validator_info" => Ok(GeneratedField::ValidatorInfo), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GetValidatorInfoResponse; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct penumbra.core.component.stake.v1.GetValidatorInfoResponse") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut validator_info__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::ValidatorInfo => { + if validator_info__.is_some() { + return Err(serde::de::Error::duplicate_field("validatorInfo")); + } + validator_info__ = map_.next_value()?; + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(GetValidatorInfoResponse { + validator_info: validator_info__, + }) + } + } + deserializer.deserialize_struct("penumbra.core.component.stake.v1.GetValidatorInfoResponse", FIELDS, GeneratedVisitor) + } +} impl serde::Serialize for Penalty { #[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 c02673c36431de4c9e629ec1f01daaab45840573..76d9ed9711e7195fb707be50af11f7a80150fae1 100644 GIT binary patch delta 2688 zcmZ{mTWnNS6ozN7eR?{x0m0LDW;*xYTUstsDQz`^5JkLDE&*er*rraCDgj#xkwjBW z2m}Zzt0h8ofUn-M3#{rT#nCZ_yD}X+yY;V*Rj|G-v+MnAduK;yduEUCVzE;5R37IEPPD_mB$koI zzLYGRHmcR;5X4UFBMG^xSMZ~-3%odiv}sXT?Ms_weUmRinHJ40YObLm_JN=c-X?+c zK-fjTG`rzCUn2E^Sh3zqg_=Q?s@F9ykvo$!FOfTwGcQdv%)7^0jx{UjZ4YcAbynCh zUm|r@#2b8x)LHTTTWXaetI)9@ioieU`m)TXY}fAWt{(keQa+)7cPzX9f4VzINvjZN zJQTsSTBaGpt@MO?wTh{d)+JGb#x0G#tKVhJreOMw!#ut9*KpusDi8_$9^mV zACpkbFGA#F5zKIbRuS{}5j|t_pg@ImF_4JeN7bf4j+F(ho0`>1V8f2Rp8^L-f0L9= zg<*v5r(i-o3v8IN2T0FJ$rZg2d927bhd?6q0Oc3@5?&8bBx<}y9QzOjj(A=ph&)8W zr12VI=waV06ud&ZULg^C*!Kzv>S4Cp%PojH_E8Fa;$fo*Jxalp!A2R|M`VV_f&v!O zZ7d{W`^al7B(QztHMZEXk5S-+M=eI=F~Y@hV~d%3oM^df#krWXMCP+$L7^DP5`%$6>{;q9bvp(P>{+T?WUvXxK1YEsJZu7?=O|ca zI-g){zuzM$U?JVgAQ9W|_XrZye*Zp`j(vdwUwYIeA}_p5!E%F5GWMd6g#s3`)VPI2 z@I@+fhY=Fki;S(seWo1yG6lZY$4X>nK?;GFDHu^3(byD22lPaVd~Fk+(bfUKt&oTu zpir?n0TRdoO1O6b8DyDyNydG>7}MyRAzwHrjfV~rA3$F}H}0wA_k$TdLZgN)_*UQRv`qqLyRmq3hCWtG}y z5V*?gc)Ma7wD?qp^&b^-N=6#6hT^g477vMxTYmA7VBg~6R~i5|Cx{Yy=~fZfFICHAW5x_U7JUGl zo1jp{n8Pjr6BLW%{bUCi`bi$aq!z6*@?7S<>|XC5E%s0)HqB2B4h3kE!tNkK0yN2k zxY&T8ZtK6Q<-+=mc?oV4<$Fi+OK_Wl?nGdhL&+&n^(GOWozg8ea!EzTjQ&*4I*tCsff9o+rN zw!z&Wzq_mQ+XAg8b?Jb&=0x>39EqaWdy%r^7-xjshq`-Pd&?`|<81(8a zBI%AumMy}1M{L*}Sy8+zXqy@emnC@b3NIOn$&^!TEW&$N^!7(y(1M_Ws3>7w5MIV2 ztP3LBZPAFI+Nm}waub1fUpW8bXx24s9sF?b;75B-{`&0-eYrvA6A9$p7k*AXpsNck4KG zQPAVY2g2b3TP#AmC|XVDtVQag=X@&PunWI$0(aKM^qnp$I1C3 zI-+_gkCWeEj;NkDbArf>sSCnpfU8Ul5ZV(o)EJ%^7_ld)yxPQO0`C+#Uq-PRNKcX9 zWMVTMd)l562(iF$XFzCA+cN?p^|ZauY~Y)0w_n8a4bL4!l-)oko+8l)E$WN($9Bht5$MiSN^5q@)j$<9O#|ngU zjNP!V&}S^HQ;#Z-b9_@?xmG=Aa^W2(YR>fNk*7sU4-{poGiNRxKZh3w zG^%(w5ocYII?oq3iav}p_Blco z#u|VE2MuI5%v71l&;M~eu?~~dJVyOo}^hl+$LY`#CLQwOSHnY0EBXu zf^aK91kdtT`iv39ijKF-?wxrOxPl6iii`p|6L{BhvJiMz z3IC$u8@2%DReo#n#TK}?IeoTWc6H-BHa%xI4TN&eZW@TpIo|X}V?^;9QBJSjF0#6@ zL*{!5W{B})IK)7siKk4fg`H njDEf>mlq3WwDGgu0FU+!irZFrv~L{RSC;s=m3`&dp1=PGcnlj- diff --git a/proto/penumbra/penumbra/core/component/stake/v1/stake.proto b/proto/penumbra/penumbra/core/component/stake/v1/stake.proto index a7ab5884da..68797db746 100644 --- a/proto/penumbra/penumbra/core/component/stake/v1/stake.proto +++ b/proto/penumbra/penumbra/core/component/stake/v1/stake.proto @@ -238,6 +238,8 @@ message Penalty { // Query operations for the staking component. service QueryService { + // Queries for information about a specific validator. + rpc GetValidatorInfo(GetValidatorInfoRequest) returns (GetValidatorInfoResponse); // Queries the current validator set, with filtering. rpc ValidatorInfo(ValidatorInfoRequest) returns (stream ValidatorInfoResponse); rpc ValidatorStatus(ValidatorStatusRequest) returns (ValidatorStatusResponse); @@ -245,6 +247,16 @@ service QueryService { rpc CurrentValidatorRate(CurrentValidatorRateRequest) returns (CurrentValidatorRateResponse); } +// Requests information about a specific validator. +message GetValidatorInfoRequest { + // The identity key of the validator. + core.keys.v1.IdentityKey identity_key = 2; +} + +message GetValidatorInfoResponse { + core.component.stake.v1.ValidatorInfo validator_info = 1; +} + // Requests information on the chain's validators. message ValidatorInfoRequest { // Whether or not to return inactive validators