penumbra_sdk_sct/component/
rpc.rs

1use cnidarium::Storage;
2use pbjson_types::Timestamp;
3use penumbra_sdk_proto::core::component::sct::v1::query_service_server::QueryService;
4use penumbra_sdk_proto::core::component::sct::v1::{
5    AnchorByHeightRequest, AnchorByHeightResponse, EpochByHeightRequest, EpochByHeightResponse,
6    TimestampByHeightRequest, TimestampByHeightResponse,
7};
8use tonic::Status;
9use tracing::instrument;
10
11use super::clock::EpochRead;
12use super::tree::SctRead;
13
14// TODO: Hide this and only expose a Router?
15pub struct Server {
16    storage: Storage,
17}
18
19impl Server {
20    pub fn new(storage: Storage) -> Self {
21        Self { storage }
22    }
23}
24
25#[tonic::async_trait]
26impl QueryService for Server {
27    #[instrument(skip(self, request))]
28    async fn epoch_by_height(
29        &self,
30        request: tonic::Request<EpochByHeightRequest>,
31    ) -> Result<tonic::Response<EpochByHeightResponse>, Status> {
32        let state = self.storage.latest_snapshot();
33
34        let epoch = state
35            .get_epoch_by_height(request.get_ref().height)
36            .await
37            .map_err(|e| tonic::Status::unknown(format!("could not get epoch for height: {e}")))?;
38
39        Ok(tonic::Response::new(EpochByHeightResponse {
40            epoch: Some(epoch.into()),
41        }))
42    }
43
44    #[instrument(skip(self, request))]
45    async fn anchor_by_height(
46        &self,
47        request: tonic::Request<AnchorByHeightRequest>,
48    ) -> Result<tonic::Response<AnchorByHeightResponse>, Status> {
49        let state = self.storage.latest_snapshot();
50
51        let height = request.get_ref().height;
52        let anchor = state.get_anchor_by_height(height).await.map_err(|e| {
53            tonic::Status::unknown(format!("could not get anchor for height {height}: {e}"))
54        })?;
55
56        Ok(tonic::Response::new(AnchorByHeightResponse {
57            anchor: anchor.map(Into::into),
58        }))
59    }
60
61    #[instrument(skip(self, request))]
62    async fn timestamp_by_height(
63        &self,
64        request: tonic::Request<TimestampByHeightRequest>,
65    ) -> Result<tonic::Response<TimestampByHeightResponse>, Status> {
66        let state = self.storage.latest_snapshot();
67
68        let height = request.get_ref().height;
69        let block_time = state.get_block_timestamp(height).await.map_err(|e| {
70            tonic::Status::unknown(format!("could not get timestamp for height {height}: {e}"))
71        })?;
72        let timestamp = chrono::DateTime::parse_from_rfc3339(block_time.to_rfc3339().as_str())
73            .expect("timestamp should roundtrip to string");
74
75        Ok(tonic::Response::new(TimestampByHeightResponse {
76            timestamp: Some(Timestamp {
77                seconds: timestamp.timestamp(),
78                nanos: timestamp.timestamp_subsec_nanos() as i32,
79            }),
80        }))
81    }
82}