Skip to content

Commit

Permalink
staking: 🔂 QueryService::get_validator_info(..)
Browse files Browse the repository at this point in the history
this introduces an RPC endpoint to fetch a single validator's
information via pcli.
  • Loading branch information
cratelyn committed Apr 29, 2024
1 parent 4f97e4f commit 9980547
Show file tree
Hide file tree
Showing 5 changed files with 354 additions and 3 deletions.
39 changes: 36 additions & 3 deletions crates/core/component/stake/src/component/rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand All @@ -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<GetValidatorInfoRequest>,
) -> Result<tonic::Response<GetValidatorInfoResponse>, 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<Box<dyn futures::Stream<Item = Result<ValidatorInfoResponse, tonic::Status>> + Send>>;

Expand Down
114 changes: 114 additions & 0 deletions crates/proto/src/gen/penumbra.core.component.stake.v1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<super::super::super::keys::v1::IdentityKey>,
}
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<ValidatorInfo>,
}
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)]
Expand Down Expand Up @@ -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<super::GetValidatorInfoRequest>,
) -> std::result::Result<
tonic::Response<super::GetValidatorInfoResponse>,
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,
Expand Down Expand Up @@ -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<super::GetValidatorInfoRequest>,
) -> std::result::Result<
tonic::Response<super::GetValidatorInfoResponse>,
tonic::Status,
>;
/// Server streaming response type for the ValidatorInfo method.
type ValidatorInfoStream: tonic::codegen::tokio_stream::Stream<
Item = std::result::Result<super::ValidatorInfoResponse, tonic::Status>,
Expand Down Expand Up @@ -1123,6 +1190,53 @@ pub mod query_service_server {
fn call(&mut self, req: http::Request<B>) -> 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<T: QueryService>(pub Arc<T>);
impl<
T: QueryService,
> tonic::server::UnaryService<super::GetValidatorInfoRequest>
for GetValidatorInfoSvc<T> {
type Response = super::GetValidatorInfoResponse;
type Future = BoxFuture<
tonic::Response<Self::Response>,
tonic::Status,
>;
fn call(
&mut self,
request: tonic::Request<super::GetValidatorInfoRequest>,
) -> Self::Future {
let inner = Arc::clone(&self.0);
let fut = async move {
<T as QueryService>::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<T: QueryService>(pub Arc<T>);
Expand Down
192 changes: 192 additions & 0 deletions crates/proto/src/gen/penumbra.core.component.stake.v1.serde.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
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<D>(deserializer: D) -> std::result::Result<Self, D::Error>
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<D>(deserializer: D) -> std::result::Result<GeneratedField, D::Error>
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<E>(self, value: &str) -> std::result::Result<GeneratedField, E>
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<V>(self, mut map_: V) -> std::result::Result<GetValidatorInfoRequest, V::Error>
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::<serde::de::IgnoredAny>()?;
}
}
}
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<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
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<D>(deserializer: D) -> std::result::Result<Self, D::Error>
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<D>(deserializer: D) -> std::result::Result<GeneratedField, D::Error>
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<E>(self, value: &str) -> std::result::Result<GeneratedField, E>
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<V>(self, mut map_: V) -> std::result::Result<GetValidatorInfoResponse, V::Error>
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::<serde::de::IgnoredAny>()?;
}
}
}
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<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
Expand Down
Binary file modified crates/proto/src/gen/proto_descriptor.bin.no_lfs
Binary file not shown.
Loading

0 comments on commit 9980547

Please sign in to comment.