nym_bandwidth_controller/
lib.rs1#![warn(clippy::expect_used)]
5#![warn(clippy::unwrap_used)]
6#![warn(clippy::todo)]
7#![warn(clippy::dbg_macro)]
8
9use crate::error::BandwidthControllerError;
10use crate::utils::{
11 get_aggregate_verification_key, get_coin_index_signatures, get_expiration_date_signatures,
12 ApiClientsWrapper,
13};
14use log::error;
15use nym_credential_storage::models::{EmergencyCredential, RetrievedTicketbook};
16use nym_credential_storage::storage::Storage;
17use nym_credentials::ecash::bandwidth::CredentialSpendingData;
18use nym_credentials_interface::{
19 AnnotatedCoinIndexSignature, AnnotatedExpirationDateSignature, TicketType, VerificationKeyAuth,
20};
21use nym_ecash_time::Date;
22use nym_validator_client::nym_api::EpochId;
23use nym_validator_client::nyxd::contract_traits::DkgQueryClient;
24
25pub use event::BandwidthStatusMessage;
26pub use traits::{BandwidthTicketProvider, DEFAULT_TICKETS_TO_SPEND};
27
28pub mod acquire;
29pub mod error;
30mod event;
31pub mod mock;
32mod traits;
33mod utils;
34
35#[derive(Debug)]
36pub struct BandwidthController<C, St> {
37 storage: St,
38 client: C,
39}
40
41pub struct PreparedCredential {
42 pub data: CredentialSpendingData,
44
45 pub epoch_id: EpochId,
48
49 pub metadata: PreparedCredentialMetadata,
51}
52
53#[derive(Copy, Clone)]
54pub struct PreparedCredentialMetadata {
55 pub ticketbook_id: i64,
57
58 pub tickets_withdrawn: u32,
60
61 pub used_tickets: u32,
63}
64
65impl<C, St: Storage> BandwidthController<C, St> {
66 pub fn new(storage: St, client: C) -> Self {
67 BandwidthController { storage, client }
68 }
69
70 pub async fn get_next_usable_ticketbook(
72 &self,
73 ticketbook_type: TicketType,
74 tickets: u32,
75 ) -> Result<RetrievedTicketbook, BandwidthControllerError>
76 where
77 <St as Storage>::StorageError: Send + Sync + 'static,
78 {
79 let Some(ticketbook) = self
80 .storage
81 .get_next_unspent_usable_ticketbook(ticketbook_type.to_string(), tickets)
82 .await
83 .map_err(BandwidthControllerError::credential_storage_error)?
84 else {
85 return Err(BandwidthControllerError::NoCredentialsAvailable);
86 };
87
88 Ok(ticketbook)
89 }
90
91 pub async fn attempt_revert_ticket_usage(
92 &self,
93 info: PreparedCredentialMetadata,
94 ) -> Result<bool, BandwidthControllerError>
95 where
96 <St as Storage>::StorageError: Send + Sync + 'static,
97 {
98 self.storage
99 .attempt_revert_ticketbook_withdrawal(
100 info.ticketbook_id,
101 info.used_tickets,
102 info.tickets_withdrawn,
103 )
104 .await
105 .map_err(BandwidthControllerError::credential_storage_error)
106 }
107
108 async fn get_aggregate_verification_key(
109 &self,
110 epoch_id: EpochId,
111 ecash_apis: &mut ApiClientsWrapper<'_, C>,
112 ) -> Result<VerificationKeyAuth, BandwidthControllerError>
113 where
114 C: DkgQueryClient + Sync + Send,
115 <St as Storage>::StorageError: Send + Sync + 'static,
116 {
117 get_aggregate_verification_key(&self.storage, epoch_id, ecash_apis).await
118 }
119
120 async fn get_coin_index_signatures(
121 &self,
122 epoch_id: EpochId,
123 ecash_apis: &mut ApiClientsWrapper<'_, C>,
124 ) -> Result<Vec<AnnotatedCoinIndexSignature>, BandwidthControllerError>
125 where
126 C: DkgQueryClient + Sync + Send,
127 <St as Storage>::StorageError: Send + Sync + 'static,
128 {
129 get_coin_index_signatures(&self.storage, epoch_id, ecash_apis).await
130 }
131
132 async fn get_expiration_date_signatures(
133 &self,
134 epoch_id: EpochId,
135 expiration_date: Date,
136 ecash_apis: &mut ApiClientsWrapper<'_, C>,
137 ) -> Result<Vec<AnnotatedExpirationDateSignature>, BandwidthControllerError>
138 where
139 C: DkgQueryClient + Sync + Send,
140 <St as Storage>::StorageError: Send + Sync + 'static,
141 {
142 get_expiration_date_signatures(&self.storage, epoch_id, expiration_date, ecash_apis).await
143 }
144
145 async fn prepare_ecash_ticket_inner(
146 &self,
147 provider_pk: [u8; 32],
148 tickets_to_spend: u32,
149 mut retrieved_ticketbook: RetrievedTicketbook,
150 ) -> Result<CredentialSpendingData, BandwidthControllerError>
151 where
152 C: DkgQueryClient + Sync + Send,
153 <St as Storage>::StorageError: Send + Sync + 'static,
154 {
155 let epoch_id = retrieved_ticketbook.ticketbook.epoch_id();
156 let expiration_date = retrieved_ticketbook.ticketbook.expiration_date();
157 let mut api_clients = ApiClientsWrapper::new(&self.client, epoch_id);
158
159 let verification_key = self
160 .get_aggregate_verification_key(epoch_id, &mut api_clients)
161 .await?;
162 let expiration_signatures = self
163 .get_expiration_date_signatures(epoch_id, expiration_date, &mut api_clients)
164 .await?;
165 let coin_indices_signatures = self
166 .get_coin_index_signatures(epoch_id, &mut api_clients)
167 .await?;
168
169 let pay_info = retrieved_ticketbook
170 .ticketbook
171 .generate_pay_info(provider_pk);
172
173 let spend_request = retrieved_ticketbook.ticketbook.prepare_for_spending(
174 &verification_key,
175 pay_info.into(),
176 &coin_indices_signatures,
177 &expiration_signatures,
178 tickets_to_spend as u64,
179 )?;
180 Ok(spend_request)
181 }
182
183 pub async fn prepare_ecash_ticket(
184 &self,
185 ticketbook_type: TicketType,
186 provider_pk: [u8; 32],
187 tickets_to_spend: u32,
188 ) -> Result<PreparedCredential, BandwidthControllerError>
189 where
190 C: DkgQueryClient + Sync + Send,
191 <St as Storage>::StorageError: Send + Sync + 'static,
192 {
193 let retrieved_ticketbook = self
194 .get_next_usable_ticketbook(ticketbook_type, tickets_to_spend)
195 .await?;
196
197 let ticketbook_id = retrieved_ticketbook.ticketbook_id;
198 let epoch_id = retrieved_ticketbook.ticketbook.epoch_id();
199
200 let used_tickets =
201 retrieved_ticketbook.ticketbook.spent_tickets() as u32 + tickets_to_spend;
202 let metadata = PreparedCredentialMetadata {
203 ticketbook_id,
204 tickets_withdrawn: tickets_to_spend,
205 used_tickets,
206 };
207
208 match self
209 .prepare_ecash_ticket_inner(provider_pk, tickets_to_spend, retrieved_ticketbook)
210 .await
211 {
212 Ok(data) => Ok(PreparedCredential {
213 data,
214 epoch_id,
215 metadata,
216 }),
217 Err(err) => {
218 error!("failed to prepare credential spending request. attempting to revert withdrawal...");
219 self.attempt_revert_ticket_usage(metadata).await?;
220 Err(err)
221 }
222 }
223 }
224
225 pub async fn get_emergency_credential(
226 &self,
227 typ: &str,
228 ) -> Result<Option<EmergencyCredential>, BandwidthControllerError>
229 where
230 <St as Storage>::StorageError: Send + Sync + 'static,
231 {
232 self.storage
233 .get_emergency_credential(typ)
234 .await
235 .map_err(BandwidthControllerError::credential_storage_error)
236 }
237}
238
239impl<C, St> Clone for BandwidthController<C, St>
240where
241 C: Clone,
242 St: Clone,
243{
244 fn clone(&self) -> Self {
245 BandwidthController {
246 storage: self.storage.clone(),
247 client: self.client.clone(),
248 }
249 }
250}