nym_credential_proxy_lib/shared_state/
mod.rs

1// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
2// SPDX-License-Identifier: GPL-3.0-only
3
4use crate::deposits_buffer::{BufferedDeposit, DepositsBuffer};
5use crate::error::CredentialProxyError;
6use crate::shared_state::ecash_state::EcashState;
7use crate::shared_state::nyxd_client::ChainClient;
8use crate::storage::CredentialProxyStorage;
9use nym_compact_ecash::{Base58, PublicKeyUser, VerificationKeyAuth};
10use nym_credential_proxy_requests::api::v1::ticketbook::models::{
11    AggregatedCoinIndicesSignaturesResponse, AggregatedExpirationDateSignaturesResponse,
12    GlobalDataParams, MasterVerificationKeyResponse,
13};
14use nym_credentials::{AggregatedCoinIndicesSignatures, AggregatedExpirationDateSignatures};
15use nym_ecash_contract_common::deposit::DepositId;
16use nym_validator_client::EcashApiClient;
17use nym_validator_client::nym_api::EpochId;
18use nym_validator_client::nyxd::Coin;
19use nym_validator_client::nyxd::contract_traits::dkg_query_client::Epoch;
20use std::sync::Arc;
21use std::time::Duration;
22use time::{Date, OffsetDateTime};
23use tokio::sync::RwLockReadGuard;
24use tokio::time::Instant;
25use tracing::{debug, error, warn};
26use uuid::Uuid;
27
28pub mod ecash_state;
29pub mod nyxd_client;
30pub mod required_deposit_cache;
31
32#[derive(Clone)]
33pub struct CredentialProxyState {
34    inner: Arc<CredentialProxyStateInner>,
35}
36
37impl CredentialProxyState {
38    pub fn new(
39        storage: CredentialProxyStorage,
40        client: ChainClient,
41        deposits_buffer: DepositsBuffer,
42        ecash_state: EcashState,
43    ) -> Self {
44        CredentialProxyState {
45            inner: Arc::new(CredentialProxyStateInner {
46                storage,
47                client,
48                deposits_buffer,
49                ecash_state,
50            }),
51        }
52    }
53
54    pub fn storage(&self) -> &CredentialProxyStorage {
55        &self.inner.storage
56    }
57
58    pub async fn deposit_amount(&self) -> Result<Coin, CredentialProxyError> {
59        self.ecash_state().deposit_amount(self.client()).await
60    }
61
62    pub fn client(&self) -> &ChainClient {
63        &self.inner.client
64    }
65
66    pub fn deposits_buffer(&self) -> &DepositsBuffer {
67        &self.inner.deposits_buffer
68    }
69
70    pub fn ecash_state(&self) -> &EcashState {
71        &self.inner.ecash_state
72    }
73
74    pub async fn ensure_credentials_issuable(&self) -> Result<(), CredentialProxyError> {
75        self.ecash_state()
76            .ensure_credentials_issuable(self.client())
77            .await
78    }
79
80    pub async fn get_deposit(
81        &self,
82        request_uuid: Uuid,
83        requested_on: OffsetDateTime,
84        client_pubkey: PublicKeyUser,
85    ) -> Result<BufferedDeposit, CredentialProxyError> {
86        let start = Instant::now();
87        let deposit = self
88            .deposits_buffer()
89            .get_valid_deposit(request_uuid, requested_on, client_pubkey)
90            .await;
91
92        let time_taken = start.elapsed();
93        let formatted = humantime::format_duration(time_taken);
94        if time_taken > Duration::from_secs(10) {
95            warn!(
96                "attempting to get buffered deposit took {formatted}. perhaps the buffer is too small or the process/chain is overloaded?"
97            )
98        } else {
99            debug!("attempting to get buffered deposit took {formatted}")
100        };
101
102        deposit
103    }
104
105    pub async fn insert_deposit_usage_error(&self, deposit_id: DepositId, error: String) {
106        if let Err(err) = self
107            .storage()
108            .insert_deposit_usage_error(deposit_id, error)
109            .await
110        {
111            error!(
112                "failed to insert information about deposit (id: {deposit_id}) usage failure: {err}"
113            )
114        }
115    }
116
117    pub async fn current_epoch_id(&self) -> Result<EpochId, CredentialProxyError> {
118        self.ecash_state().current_epoch_id(self.client()).await
119    }
120
121    pub async fn current_epoch(&self) -> Result<Epoch, CredentialProxyError> {
122        self.ecash_state().current_epoch(self.client()).await
123    }
124
125    pub async fn global_data(
126        &self,
127        global_data: GlobalDataParams,
128        epoch_id: EpochId,
129        expiration_date: Date,
130    ) -> Result<
131        (
132            Option<MasterVerificationKeyResponse>,
133            Option<AggregatedExpirationDateSignaturesResponse>,
134            Option<AggregatedCoinIndicesSignaturesResponse>,
135        ),
136        CredentialProxyError,
137    > {
138        let master_verification_key = if global_data.include_master_verification_key {
139            debug!("including master verification key in the response");
140            Some(
141                self.master_verification_key(Some(epoch_id))
142                    .await
143                    .map(|key| MasterVerificationKeyResponse {
144                        epoch_id,
145                        bs58_encoded_key: key.to_bs58(),
146                    })
147                    .inspect_err(|err| warn!("request failure: {err}"))?,
148            )
149        } else {
150            None
151        };
152
153        let aggregated_expiration_date_signatures =
154            if global_data.include_expiration_date_signatures {
155                debug!("including expiration date signatures in the response");
156                Some(
157                    self.master_expiration_date_signatures(epoch_id, expiration_date)
158                        .await
159                        .map(|signatures| AggregatedExpirationDateSignaturesResponse {
160                            signatures: signatures.clone(),
161                        })
162                        .inspect_err(|err| warn!("request failure: {err}"))?,
163                )
164            } else {
165                None
166            };
167
168        let aggregated_coin_index_signatures = if global_data.include_coin_index_signatures {
169            debug!("including coin index signatures in the response");
170            Some(
171                self.master_coin_index_signatures(Some(epoch_id))
172                    .await
173                    .map(|signatures| AggregatedCoinIndicesSignaturesResponse {
174                        signatures: signatures.clone(),
175                    })
176                    .inspect_err(|err| warn!("request failure: {err}"))?,
177            )
178        } else {
179            None
180        };
181
182        Ok((
183            master_verification_key,
184            aggregated_expiration_date_signatures,
185            aggregated_coin_index_signatures,
186        ))
187    }
188
189    pub async fn master_verification_key(
190        &self,
191        epoch_id: Option<EpochId>,
192    ) -> Result<RwLockReadGuard<'_, VerificationKeyAuth>, CredentialProxyError> {
193        self.ecash_state()
194            .master_verification_key(self.client(), self.storage(), epoch_id)
195            .await
196    }
197
198    pub async fn master_coin_index_signatures(
199        &self,
200        epoch_id: Option<EpochId>,
201    ) -> Result<RwLockReadGuard<'_, AggregatedCoinIndicesSignatures>, CredentialProxyError> {
202        self.ecash_state()
203            .master_coin_index_signatures(self.client(), self.storage(), epoch_id)
204            .await
205    }
206
207    pub async fn master_expiration_date_signatures(
208        &self,
209        epoch_id: EpochId,
210        expiration_date: Date,
211    ) -> Result<RwLockReadGuard<'_, AggregatedExpirationDateSignatures>, CredentialProxyError> {
212        self.ecash_state()
213            .master_expiration_date_signatures(
214                self.client(),
215                self.storage(),
216                epoch_id,
217                expiration_date,
218            )
219            .await
220    }
221
222    pub async fn ecash_clients(
223        &self,
224        epoch_id: EpochId,
225    ) -> Result<RwLockReadGuard<'_, Vec<EcashApiClient>>, CredentialProxyError> {
226        self.ecash_state()
227            .ecash_clients(self.client(), epoch_id)
228            .await
229    }
230
231    pub async fn ecash_threshold(&self, epoch_id: EpochId) -> Result<u64, CredentialProxyError> {
232        self.ecash_state()
233            .ecash_threshold(self.client(), epoch_id)
234            .await
235    }
236}
237
238struct CredentialProxyStateInner {
239    storage: CredentialProxyStorage,
240
241    client: ChainClient,
242
243    deposits_buffer: DepositsBuffer,
244
245    ecash_state: EcashState,
246}