nym_bandwidth_controller/
controller.rs1use crate::error::BandwidthControllerError;
5use crate::requests::{BandwidthControllerRequest, BandwidthControllerRequestSender};
6use crate::traits::CredentialPublicDataFetcher;
7use crate::{
8 BandwidthTicketProvider, PreparedCredential, PreparedCredentialMetadata, UPGRADE_MODE_JWT_TYPE,
9};
10use async_trait::async_trait;
11use log::error;
12use nym_credential_storage::models::RetrievedTicketbook;
13use nym_credential_storage::storage::Storage;
14use nym_credentials::ecash::bandwidth::CredentialSpendingData;
15use nym_credentials_interface::{
16 AnnotatedCoinIndexSignature, AnnotatedExpirationDateSignature, TicketType, VerificationKeyAuth,
17};
18use nym_crypto::asymmetric::ed25519;
19use nym_ecash_time::{Date, OffsetDateTime};
20use nym_task::ShutdownToken;
21use nym_validator_client::nym_api::EpochId;
22use tokio::sync::mpsc::{self, UnboundedReceiver, UnboundedSender};
23
24pub struct BandwidthController<St> {
25 storage: St,
26
27 request_channel: (
29 UnboundedSender<BandwidthControllerRequest>,
30 UnboundedReceiver<BandwidthControllerRequest>,
31 ),
32
33 public_data_fetcher: Option<Box<dyn CredentialPublicDataFetcher>>,
35}
36
37impl<St: Storage> BandwidthController<St> {
38 pub fn new(storage: St) -> Self {
39 let request_channel = mpsc::unbounded_channel();
40 BandwidthController {
41 storage,
42 request_channel,
43 public_data_fetcher: None,
44 }
45 }
46
47 #[must_use]
48 pub fn with_credential_public_data_fetcher(
49 mut self,
50 fetcher: impl CredentialPublicDataFetcher + 'static,
51 ) -> Self {
52 self.public_data_fetcher = Some(Box::new(fetcher));
53 self
54 }
55
56 pub fn get_request_sender(&self) -> BandwidthControllerRequestSender {
59 BandwidthControllerRequestSender::new(self.request_channel.0.clone())
60 }
61
62 pub async fn get_next_usable_ticketbook(
64 &self,
65 ticketbook_type: TicketType,
66 tickets: u32,
67 ) -> Result<Option<RetrievedTicketbook>, BandwidthControllerError> {
68 self.storage
69 .get_next_unspent_usable_ticketbook(ticketbook_type.to_string(), tickets)
70 .await
71 .map_err(BandwidthControllerError::credential_storage_error)
72 }
73
74 pub async fn attempt_revert_ticket_usage(
75 &self,
76 info: PreparedCredentialMetadata,
77 ) -> Result<bool, BandwidthControllerError> {
78 self.storage
79 .attempt_revert_ticketbook_withdrawal(
80 info.ticketbook_id,
81 info.tickets_withdrawn,
82 info.used_tickets,
83 )
84 .await
85 .map_err(BandwidthControllerError::credential_storage_error)
86 }
87
88 pub async fn get_aggregate_verification_key(
89 &self,
90 epoch_id: EpochId,
91 ) -> Result<Option<VerificationKeyAuth>, BandwidthControllerError> {
92 self.storage
93 .get_master_verification_key(epoch_id)
94 .await
95 .map_err(BandwidthControllerError::credential_storage_error)
96 }
97
98 pub async fn get_coin_index_signatures(
99 &self,
100 epoch_id: EpochId,
101 ) -> Result<Option<Vec<AnnotatedCoinIndexSignature>>, BandwidthControllerError> {
102 self.storage
103 .get_coin_index_signatures(epoch_id)
104 .await
105 .map_err(BandwidthControllerError::credential_storage_error)
106 }
107
108 pub async fn get_expiration_date_signatures(
109 &self,
110 epoch_id: EpochId,
111 expiration_date: Date,
112 ) -> Result<Option<Vec<AnnotatedExpirationDateSignature>>, BandwidthControllerError> {
113 self.storage
114 .get_expiration_date_signatures(expiration_date, epoch_id)
115 .await
116 .map_err(BandwidthControllerError::credential_storage_error)
117 }
118
119 async fn ensure_master_verification_key(
122 &self,
123 epoch_id: EpochId,
124 ) -> Result<VerificationKeyAuth, BandwidthControllerError> {
125 if let Some(key) = self.get_aggregate_verification_key(epoch_id).await? {
126 return Ok(key);
127 }
128 let Some(fetcher) = &self.public_data_fetcher else {
129 return Err(BandwidthControllerError::MissingVerificationKey { epoch_id });
130 };
131 let key = fetcher
132 .fetch_master_verification_key(epoch_id)
133 .await
134 .map_err(BandwidthControllerError::fetcher_error)?;
135 self.storage
136 .insert_master_verification_key(&key)
137 .await
138 .map_err(BandwidthControllerError::credential_storage_error)?;
139 Ok(key.key)
140 }
141
142 async fn ensure_coin_index_signatures(
145 &self,
146 epoch_id: EpochId,
147 ) -> Result<Vec<AnnotatedCoinIndexSignature>, BandwidthControllerError> {
148 if let Some(signatures) = self.get_coin_index_signatures(epoch_id).await? {
149 return Ok(signatures);
150 }
151 let Some(fetcher) = &self.public_data_fetcher else {
152 return Err(BandwidthControllerError::MissingCoinIndexSignatures { epoch_id });
153 };
154 let signatures = fetcher
155 .fetch_coin_index_signatures(epoch_id)
156 .await
157 .map_err(BandwidthControllerError::fetcher_error)?;
158 self.storage
159 .insert_coin_index_signatures(&signatures)
160 .await
161 .map_err(BandwidthControllerError::credential_storage_error)?;
162 Ok(signatures.signatures)
163 }
164
165 async fn ensure_expiration_date_signatures(
168 &self,
169 epoch_id: EpochId,
170 expiration_date: Date,
171 ) -> Result<Vec<AnnotatedExpirationDateSignature>, BandwidthControllerError> {
172 if let Some(signatures) = self
173 .get_expiration_date_signatures(epoch_id, expiration_date)
174 .await?
175 {
176 return Ok(signatures);
177 }
178 let Some(fetcher) = &self.public_data_fetcher else {
179 return Err(BandwidthControllerError::MissingExpirationDateSignatures { epoch_id });
180 };
181 let signatures = fetcher
182 .fetch_expiration_date_signatures(expiration_date, epoch_id)
183 .await
184 .map_err(BandwidthControllerError::fetcher_error)?;
185 self.storage
186 .insert_expiration_date_signatures(&signatures)
187 .await
188 .map_err(BandwidthControllerError::credential_storage_error)?;
189 Ok(signatures.signatures)
190 }
191
192 pub async fn ensure_global_data(
195 &self,
196 epoch_id: EpochId,
197 expiration_date: Date,
198 ) -> Result<(), BandwidthControllerError> {
199 self.ensure_master_verification_key(epoch_id).await?;
200 self.ensure_coin_index_signatures(epoch_id).await?;
201 self.ensure_expiration_date_signatures(epoch_id, expiration_date)
202 .await?;
203 Ok(())
204 }
205
206 async fn prepare_ecash_ticket_inner(
207 &self,
208 provider_pk: [u8; 32],
209 spend_time: OffsetDateTime,
210 tickets_to_spend: u32,
211 mut retrieved_ticketbook: RetrievedTicketbook,
212 ) -> Result<CredentialSpendingData, BandwidthControllerError> {
213 let epoch_id = retrieved_ticketbook.ticketbook.epoch_id();
214 let expiration_date = retrieved_ticketbook.ticketbook.expiration_date();
215
216 let verification_key = self.ensure_master_verification_key(epoch_id).await?;
217 let expiration_signatures = self
218 .ensure_expiration_date_signatures(epoch_id, expiration_date)
219 .await?;
220 let coin_indices_signatures = self.ensure_coin_index_signatures(epoch_id).await?;
221
222 let pay_info = retrieved_ticketbook
223 .ticketbook
224 .generate_pay_info(provider_pk, spend_time);
225
226 let spend_request = retrieved_ticketbook.ticketbook.prepare_for_spending(
227 &verification_key,
228 pay_info.into(),
229 &coin_indices_signatures,
230 &expiration_signatures,
231 tickets_to_spend as u64,
232 )?;
233 Ok(spend_request)
234 }
235
236 pub async fn prepare_ecash_ticket(
237 &self,
238 ticketbook_type: TicketType,
239 provider_pk: [u8; 32],
240 tickets_to_spend: u32,
241 spend_time: OffsetDateTime,
242 ) -> Result<Option<PreparedCredential>, BandwidthControllerError> {
243 let Some(retrieved_ticketbook) = self
244 .get_next_usable_ticketbook(ticketbook_type, tickets_to_spend)
245 .await?
246 else {
247 return Ok(None);
248 };
249
250 let ticketbook_id = retrieved_ticketbook.ticketbook_id;
251 let epoch_id = retrieved_ticketbook.ticketbook.epoch_id();
252
253 let used_tickets =
254 retrieved_ticketbook.ticketbook.spent_tickets() as u32 + tickets_to_spend;
255 let metadata = PreparedCredentialMetadata {
256 ticketbook_id,
257 tickets_withdrawn: tickets_to_spend,
258 used_tickets,
259 };
260
261 match self
262 .prepare_ecash_ticket_inner(
263 provider_pk,
264 spend_time,
265 tickets_to_spend,
266 retrieved_ticketbook,
267 )
268 .await
269 {
270 Ok(data) => Ok(Some(PreparedCredential {
271 data,
272 epoch_id,
273 metadata,
274 })),
275 Err(err) => {
276 error!("failed to prepare credential spending request. attempting to revert withdrawal...");
277 self.attempt_revert_ticket_usage(metadata).await?;
278 Err(err)
279 }
280 }
281 }
282
283 async fn get_upgrade_mode_token(&self) -> Result<Option<String>, BandwidthControllerError> {
284 let Some(emergency_credential) = self
285 .storage
286 .get_emergency_credential(UPGRADE_MODE_JWT_TYPE)
287 .await
288 .map_err(BandwidthControllerError::credential_storage_error)?
289 else {
290 return Ok(None);
291 };
292 let token = String::from_utf8(emergency_credential.data.content)
294 .map_err(|_| BandwidthControllerError::MalformedUpgradeModeToken)?;
295 Ok(Some(token))
296 }
297
298 pub async fn run(mut self, shutdown_token: ShutdownToken) {
301 loop {
302 tokio::select! {
303 biased;
304 _ = shutdown_token.cancelled() => {
305 log::debug!("bandwidth controller received cancellation request; shutting down");
306 break;
307 }
308 request = self.request_channel.1.recv() => match request {
309 Some(request) => self.handle_request(request).await,
310 None => {
311 log::warn!("bandwidth controller request channel closed; this should never happened as we are owning a sender; shutting down");
312 break;
313 }
314 }
315 }
316 }
317
318 self.storage.close().await;
319 }
320
321 async fn handle_request(&mut self, request: BandwidthControllerRequest) {
322 match request {
323 BandwidthControllerRequest::EcashTicket(return_sender, request) => {
324 let credential_result = self
325 .prepare_ecash_ticket(
326 request.ticket_type,
327 request.gateway_id.to_bytes(),
328 request.tickets_to_spend,
329 request.spend_time,
330 )
331 .await;
332 return_sender.send(credential_result)
333 }
334 BandwidthControllerRequest::UpgradeModeToken(return_sender) => {
335 return_sender.send(self.get_upgrade_mode_token().await)
336 }
337 BandwidthControllerRequest::AttemptRevertSpending(return_sender, metadata) => {
338 return_sender.send(self.attempt_revert_ticket_usage(metadata).await)
339 }
340 }
341 }
342}
343
344#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
346#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
347impl<St: Storage> BandwidthTicketProvider for BandwidthController<St> {
348 async fn get_ecash_ticket(
349 &self,
350 ticket_type: TicketType,
351 gateway_id: ed25519::PublicKey,
352 tickets_to_spend: u32,
353 spend_time: OffsetDateTime,
354 ) -> Result<Option<PreparedCredential>, BandwidthControllerError> {
355 self.prepare_ecash_ticket(
356 ticket_type,
357 gateway_id.to_bytes(),
358 tickets_to_spend,
359 spend_time,
360 )
361 .await
362 }
363
364 async fn get_upgrade_mode_token(&self) -> Result<Option<String>, BandwidthControllerError> {
365 self.get_upgrade_mode_token().await
366 }
367
368 async fn attempt_revert_spending(
369 &self,
370 metadata: PreparedCredentialMetadata,
371 ) -> Result<bool, BandwidthControllerError> {
372 self.attempt_revert_ticket_usage(metadata).await
373 }
374
375 async fn close(&self) {
376 self.storage.close().await
377 }
378}