penumbra_sdk_funding/component/
rpc.rs

1use cnidarium::Storage;
2use penumbra_sdk_proto::core::component::funding::v1::{
3    self as pb, funding_service_server::FundingService,
4};
5use penumbra_sdk_sct::{component::clock::EpochRead, Nullifier};
6
7use super::liquidity_tournament::nullifier::NullifierRead;
8
9pub struct Server {
10    storage: Storage,
11}
12
13impl Server {
14    pub fn new(storage: Storage) -> Self {
15        Self { storage }
16    }
17}
18
19#[tonic::async_trait]
20impl FundingService for Server {
21    async fn lqt_check_nullifier(
22        &self,
23        request: tonic::Request<pb::LqtCheckNullifierRequest>,
24    ) -> Result<tonic::Response<pb::LqtCheckNullifierResponse>, tonic::Status> {
25        // Retrieve latest state snapshot.
26        let state = self.storage.latest_snapshot();
27
28        let req_inner = request.into_inner();
29        let nullifier_proto = req_inner
30            .nullifier
31            .ok_or_else(|| tonic::Status::invalid_argument("missing nullifier"))?;
32
33        // Proto to domain type conversion.
34        let nullifier: Nullifier = nullifier_proto
35            .try_into()
36            .map_err(|e| tonic::Status::invalid_argument(format!("invalid nullifier: {e}")))?;
37
38        // If `epoch_index` is omitted (defaults to zero in protobuf), query the current epoch;
39        // Otherwise use the provided epoch.
40        let epoch_index = if req_inner.epoch_index == 0 {
41            let current_epoch = state.get_current_epoch().await.map_err(|e| {
42                tonic::Status::internal(format!("failed to retrieve current epoch: {e}"))
43            })?;
44            current_epoch.index
45        } else {
46            req_inner.epoch_index
47        };
48
49        // Perform a state nullifier lookup.
50        let maybe_tx_id = state
51            .get_lqt_spent_nullifier_by_epoch(nullifier, epoch_index)
52            .await;
53
54        if let Some(tx_id) = maybe_tx_id {
55            // Nullifier was spent and user has already voted.
56            Ok(tonic::Response::new(pb::LqtCheckNullifierResponse {
57                transaction: Some(tx_id.into()),
58                already_voted: true,
59                epoch_index,
60            }))
61        } else {
62            // Nullifier was not spent and user has not voted yet.
63            Ok(tonic::Response::new(pb::LqtCheckNullifierResponse {
64                transaction: None,
65                already_voted: false,
66                epoch_index,
67            }))
68        }
69    }
70}