Skip to content

Commit

Permalink
sct(component): Add anchor_by_height rpc method (#4250)
Browse files Browse the repository at this point in the history
## Describe your changes
This PR adds a `anchor_by_height` method to the
[`SctQueryService`](https://github.com/penumbra-zone/penumbra/tree/main/crates/core/component/sct/src/component/rpc.rs).

## Issue ticket number and link
Closes #4153

## Checklist before requesting a review

- [x] If this code contains consensus-breaking changes, I have added the
"consensus-breaking" label. Otherwise, I declare my belief that there
are not consensus-breaking changes, for the following reason:

> This is an added rpc method for consumer convenience. It prevents
consumers from being forced to perform raw KV queries against the chain
state.
  • Loading branch information
phasewalk1 authored Apr 23, 2024
1 parent fc7aac3 commit 863e4d3
Show file tree
Hide file tree
Showing 6 changed files with 334 additions and 1 deletion.
Binary file modified crates/cnidarium/src/gen/proto_descriptor.bin.no_lfs
Binary file not shown.
22 changes: 21 additions & 1 deletion crates/core/component/sct/src/component/rpc.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
use cnidarium::Storage;
use penumbra_proto::core::component::sct::v1::query_service_server::QueryService;
use penumbra_proto::core::component::sct::v1::{EpochByHeightRequest, EpochByHeightResponse};
use penumbra_proto::core::component::sct::v1::{
AnchorByHeightRequest, AnchorByHeightResponse, EpochByHeightRequest, EpochByHeightResponse,
};
use tonic::Status;
use tracing::instrument;

use super::clock::EpochRead;
use super::tree::SctRead;

// TODO: Hide this and only expose a Router?
pub struct Server {
Expand Down Expand Up @@ -35,4 +38,21 @@ impl QueryService for Server {
epoch: Some(epoch.into()),
}))
}

#[instrument(skip(self, request))]
async fn anchor_by_height(
&self,
request: tonic::Request<AnchorByHeightRequest>,
) -> Result<tonic::Response<AnchorByHeightResponse>, Status> {
let state = self.storage.latest_snapshot();

let height = request.get_ref().height;
let anchor = state.get_anchor_by_height(height).await.map_err(|e| {
tonic::Status::unknown(format!("could not get anchor for height {height}: {e}"))
})?;

Ok(tonic::Response::new(AnchorByHeightResponse {
anchor: anchor.map(Into::into),
}))
}
}
111 changes: 111 additions & 0 deletions crates/proto/src/gen/penumbra.core.component.sct.v1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,34 @@ impl ::prost::Name for EpochByHeightResponse {
::prost::alloc::format!("penumbra.core.component.sct.v1.{}", Self::NAME)
}
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct AnchorByHeightRequest {
#[prost(uint64, tag = "1")]
pub height: u64,
}
impl ::prost::Name for AnchorByHeightRequest {
const NAME: &'static str = "AnchorByHeightRequest";
const PACKAGE: &'static str = "penumbra.core.component.sct.v1";
fn full_name() -> ::prost::alloc::string::String {
::prost::alloc::format!("penumbra.core.component.sct.v1.{}", Self::NAME)
}
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct AnchorByHeightResponse {
#[prost(message, optional, tag = "1")]
pub anchor: ::core::option::Option<
super::super::super::super::crypto::tct::v1::MerkleRoot,
>,
}
impl ::prost::Name for AnchorByHeightResponse {
const NAME: &'static str = "AnchorByHeightResponse";
const PACKAGE: &'static str = "penumbra.core.component.sct.v1";
fn full_name() -> ::prost::alloc::string::String {
::prost::alloc::format!("penumbra.core.component.sct.v1.{}", Self::NAME)
}
}
/// Generated client implementations.
#[cfg(feature = "rpc")]
pub mod query_service_client {
Expand Down Expand Up @@ -386,6 +414,36 @@ pub mod query_service_client {
self.inner = self.inner.max_encoding_message_size(limit);
self
}
pub async fn anchor_by_height(
&mut self,
request: impl tonic::IntoRequest<super::AnchorByHeightRequest>,
) -> std::result::Result<
tonic::Response<super::AnchorByHeightResponse>,
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.sct.v1.QueryService/AnchorByHeight",
);
let mut req = request.into_request();
req.extensions_mut()
.insert(
GrpcMethod::new(
"penumbra.core.component.sct.v1.QueryService",
"AnchorByHeight",
),
);
self.inner.unary(req, path, codec).await
}
pub async fn epoch_by_height(
&mut self,
request: impl tonic::IntoRequest<super::EpochByHeightRequest>,
Expand Down Expand Up @@ -426,6 +484,13 @@ 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 {
async fn anchor_by_height(
&self,
request: tonic::Request<super::AnchorByHeightRequest>,
) -> std::result::Result<
tonic::Response<super::AnchorByHeightResponse>,
tonic::Status,
>;
async fn epoch_by_height(
&self,
request: tonic::Request<super::EpochByHeightRequest>,
Expand Down Expand Up @@ -514,6 +579,52 @@ 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.sct.v1.QueryService/AnchorByHeight" => {
#[allow(non_camel_case_types)]
struct AnchorByHeightSvc<T: QueryService>(pub Arc<T>);
impl<
T: QueryService,
> tonic::server::UnaryService<super::AnchorByHeightRequest>
for AnchorByHeightSvc<T> {
type Response = super::AnchorByHeightResponse;
type Future = BoxFuture<
tonic::Response<Self::Response>,
tonic::Status,
>;
fn call(
&mut self,
request: tonic::Request<super::AnchorByHeightRequest>,
) -> Self::Future {
let inner = Arc::clone(&self.0);
let fut = async move {
<T as QueryService>::anchor_by_height(&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 = AnchorByHeightSvc(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.sct.v1.QueryService/EpochByHeight" => {
#[allow(non_camel_case_types)]
struct EpochByHeightSvc<T: QueryService>(pub Arc<T>);
Expand Down
193 changes: 193 additions & 0 deletions crates/proto/src/gen/penumbra.core.component.sct.v1.serde.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,196 @@
impl serde::Serialize for AnchorByHeightRequest {
#[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.height != 0 {
len += 1;
}
let mut struct_ser = serializer.serialize_struct("penumbra.core.component.sct.v1.AnchorByHeightRequest", len)?;
if self.height != 0 {
#[allow(clippy::needless_borrow)]
struct_ser.serialize_field("height", ToString::to_string(&self.height).as_str())?;
}
struct_ser.end()
}
}
impl<'de> serde::Deserialize<'de> for AnchorByHeightRequest {
#[allow(deprecated)]
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
const FIELDS: &[&str] = &[
"height",
];

#[allow(clippy::enum_variant_names)]
enum GeneratedField {
Height,
__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 {
"height" => Ok(GeneratedField::Height),
_ => Ok(GeneratedField::__SkipField__),
}
}
}
deserializer.deserialize_identifier(GeneratedVisitor)
}
}
struct GeneratedVisitor;
impl<'de> serde::de::Visitor<'de> for GeneratedVisitor {
type Value = AnchorByHeightRequest;

fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter.write_str("struct penumbra.core.component.sct.v1.AnchorByHeightRequest")
}

fn visit_map<V>(self, mut map_: V) -> std::result::Result<AnchorByHeightRequest, V::Error>
where
V: serde::de::MapAccess<'de>,
{
let mut height__ = None;
while let Some(k) = map_.next_key()? {
match k {
GeneratedField::Height => {
if height__.is_some() {
return Err(serde::de::Error::duplicate_field("height"));
}
height__ =
Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0)
;
}
GeneratedField::__SkipField__ => {
let _ = map_.next_value::<serde::de::IgnoredAny>()?;
}
}
}
Ok(AnchorByHeightRequest {
height: height__.unwrap_or_default(),
})
}
}
deserializer.deserialize_struct("penumbra.core.component.sct.v1.AnchorByHeightRequest", FIELDS, GeneratedVisitor)
}
}
impl serde::Serialize for AnchorByHeightResponse {
#[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.anchor.is_some() {
len += 1;
}
let mut struct_ser = serializer.serialize_struct("penumbra.core.component.sct.v1.AnchorByHeightResponse", len)?;
if let Some(v) = self.anchor.as_ref() {
struct_ser.serialize_field("anchor", v)?;
}
struct_ser.end()
}
}
impl<'de> serde::Deserialize<'de> for AnchorByHeightResponse {
#[allow(deprecated)]
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
const FIELDS: &[&str] = &[
"anchor",
];

#[allow(clippy::enum_variant_names)]
enum GeneratedField {
Anchor,
__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 {
"anchor" => Ok(GeneratedField::Anchor),
_ => Ok(GeneratedField::__SkipField__),
}
}
}
deserializer.deserialize_identifier(GeneratedVisitor)
}
}
struct GeneratedVisitor;
impl<'de> serde::de::Visitor<'de> for GeneratedVisitor {
type Value = AnchorByHeightResponse;

fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter.write_str("struct penumbra.core.component.sct.v1.AnchorByHeightResponse")
}

fn visit_map<V>(self, mut map_: V) -> std::result::Result<AnchorByHeightResponse, V::Error>
where
V: serde::de::MapAccess<'de>,
{
let mut anchor__ = None;
while let Some(k) = map_.next_key()? {
match k {
GeneratedField::Anchor => {
if anchor__.is_some() {
return Err(serde::de::Error::duplicate_field("anchor"));
}
anchor__ = map_.next_value()?;
}
GeneratedField::__SkipField__ => {
let _ = map_.next_value::<serde::de::IgnoredAny>()?;
}
}
}
Ok(AnchorByHeightResponse {
anchor: anchor__,
})
}
}
deserializer.deserialize_struct("penumbra.core.component.sct.v1.AnchorByHeightResponse", FIELDS, GeneratedVisitor)
}
}
impl serde::Serialize for CommitmentSource {
#[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.
9 changes: 9 additions & 0 deletions proto/penumbra/penumbra/core/component/sct/v1/sct.proto
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,16 @@ message EpochByHeightResponse {
Epoch epoch = 1;
}

message AnchorByHeightRequest {
uint64 height = 1;
}

message AnchorByHeightResponse {
crypto.tct.v1.MerkleRoot anchor = 1;
}

// Query operations for the SCT component.
service QueryService {
rpc AnchorByHeight(AnchorByHeightRequest) returns (AnchorByHeightResponse);
rpc EpochByHeight(EpochByHeightRequest) returns (EpochByHeightResponse);
}

0 comments on commit 863e4d3

Please sign in to comment.