1use crate::config::BandwidthControllerConfig;
5use crate::error::BandwidthControllerError;
6use crate::readiness::{FetchFailure, ReadinessRequest, ReadinessSnapshot};
7use crate::requests::{BandwidthControllerRequest, BandwidthControllerRequestSender, ReturnSender};
8use crate::ticketbooks::AvailableTicketbooks;
9use crate::traits::{CredentialFetcher, CredentialPublicDataFetcher};
10use crate::NymCredential;
11use crate::{
12 BandwidthTicketProvider, PreparedCredential, PreparedCredentialMetadata, UPGRADE_MODE_JWT_TYPE,
13};
14
15use nym_credential_storage::models::EmergencyCredentialContent;
16use nym_credential_storage::models::RetrievedTicketbook;
17use nym_credential_storage::storage::Storage;
18use nym_credentials::ecash::bandwidth::CredentialSpendingData;
19use nym_credentials::IssuedTicketBook;
20use nym_credentials_interface::{
21 AnnotatedCoinIndexSignature, AnnotatedExpirationDateSignature, TicketType, VerificationKeyAuth,
22};
23use nym_crypto::asymmetric::ed25519;
24use nym_ecash_time::{Date, OffsetDateTime};
25use nym_task::ShutdownToken;
26use nym_validator_client::nym_api::EpochId;
27
28use async_trait::async_trait;
29use log::error;
30use std::collections::HashSet;
31use std::sync::Arc;
32use tokio::sync::mpsc::{self, UnboundedReceiver, UnboundedSender};
33
34#[cfg(not(target_arch = "wasm32"))]
35use tokio::time::{interval, MissedTickBehavior};
36
37#[cfg(target_arch = "wasm32")]
38use wasmtimer::tokio::{interval, MissedTickBehavior};
39
40use crate::in_flight::{
41 global_data::{GlobalData, GlobalDataRequest},
42 FetchJob, FetchKey, FetchResult, FetchedData, InFlightFetches,
43};
44
45pub struct BandwidthController<St> {
66 storage: St,
67
68 request_channel: (
70 UnboundedSender<BandwidthControllerRequest>,
71 UnboundedReceiver<BandwidthControllerRequest>,
72 ),
73
74 config: BandwidthControllerConfig,
75
76 public_data_fetcher: Option<Arc<dyn CredentialPublicDataFetcher>>,
78
79 credential_fetcher: Option<Arc<dyn CredentialFetcher>>,
82
83 in_flight: InFlightFetches,
86
87 pending_readiness: Vec<ReadinessRequest>,
89}
90
91impl<St: Storage> BandwidthController<St> {
92 pub fn new(storage: St) -> Self {
97 let request_channel = mpsc::unbounded_channel();
98 BandwidthController {
99 storage,
100 request_channel,
101 config: Default::default(),
102 public_data_fetcher: None,
103 credential_fetcher: None,
104 in_flight: InFlightFetches::new(),
105 pending_readiness: Vec::new(),
106 }
107 }
108 #[must_use]
109 pub fn with_config(mut self, config: BandwidthControllerConfig) -> Self {
110 self.config = config;
111 self
112 }
113
114 #[must_use]
115 pub fn with_credential_fetcher(mut self, fetcher: impl CredentialFetcher + 'static) -> Self {
116 let fetcher = Arc::new(fetcher);
117 self.credential_fetcher = Some(fetcher.clone());
118 self.public_data_fetcher = Some(fetcher);
119 self
120 }
121
122 #[must_use]
123 pub fn with_credential_public_data_fetcher(
124 mut self,
125 fetcher: impl CredentialPublicDataFetcher + 'static,
126 ) -> Self {
127 self.public_data_fetcher = Some(Arc::new(fetcher));
128 self
129 }
130
131 pub fn get_request_sender(&self) -> BandwidthControllerRequestSender {
134 BandwidthControllerRequestSender::new(self.request_channel.0.clone())
135 }
136
137 pub async fn run(mut self, shutdown_token: ShutdownToken) {
145 tracing::info!("BandwidthController started successfully");
146
147 let mut topup_interval = interval(self.config.topup_interval);
148 topup_interval.set_missed_tick_behavior(MissedTickBehavior::Delay);
149
150 loop {
151 tokio::select! {
152 biased;
153 _ = shutdown_token.cancelled() => {
154 log::debug!("bandwidth controller received cancellation request; shutting down");
155 break;
156 }
157 _ = topup_interval.tick() => {
158 let _ = self.print_info().await;
159 self.check_and_restock(self.config.managed_ticket_types.clone()).await;
160 }
161 (key, res) = self.in_flight.next_result(), if !self.in_flight.is_empty() => {
162 self.on_fetch_complete(key, res).await;
163 }
164 request = self.request_channel.1.recv() => match request {
165 Some(request) => self.handle_request(request).await,
166 None => {
167 log::warn!("bandwidth controller request channel closed; this should never happen as we own a sender; shutting down");
168 break;
169 }
170 }
171 }
172 }
173
174 self.in_flight.cancel_and_join().await;
176 if let Some(fetcher) = self.credential_fetcher {
177 fetcher.cleanup().await;
178 }
179 self.storage.close().await;
180 }
181
182 async fn handle_request(&mut self, request: BandwidthControllerRequest) {
187 match request {
188 BandwidthControllerRequest::EcashTicket(return_sender, request) => {
189 let ticket_type = request.ticket_type;
190 let credential_result = self
191 .prepare_ecash_ticket(
192 ticket_type,
193 request.gateway_id.to_bytes(),
194 request.tickets_to_spend,
195 request.spend_time,
196 )
197 .await;
198 return_sender.send(credential_result);
199 if self.config.managed_ticket_types.contains(&ticket_type) {
203 self.check_and_restock(vec![ticket_type]).await;
204 }
205 }
206 BandwidthControllerRequest::UpgradeModeToken(return_sender) => {
207 return_sender.send(self.get_upgrade_mode_token().await)
208 }
209 BandwidthControllerRequest::AttemptRevertSpending(return_sender, metadata) => {
210 return_sender.send(self.attempt_revert_ticket_usage(metadata).await)
211 }
212 BandwidthControllerRequest::SetCredentialFetcher(return_sender, fetcher) => {
213 self.handle_set_credential_fetcher(fetcher).await;
214 return_sender.send(Ok(()))
215 }
216
217 BandwidthControllerRequest::SetPublicDataFetcher(return_sender, fetcher) => {
218 self.handle_set_public_data_fetcher(fetcher).await;
219 return_sender.send(Ok(()))
220 }
221 BandwidthControllerRequest::Reset(return_sender) => {
222 return_sender.send(self.handle_reset().await)
223 }
224 BandwidthControllerRequest::ClearEmergencyCredentials(return_sender) => return_sender
225 .send(
226 self.storage
227 .clear_emergency_credentials()
228 .await
229 .map_err(BandwidthControllerError::credential_storage_error),
230 ),
231 BandwidthControllerRequest::GetAvailableTicketbooks(return_sender) => {
232 return_sender.send(self.handle_get_available_ticketbooks().await)
233 }
234 BandwidthControllerRequest::RestockTicketbooks(return_sender, ticket_types) => {
235 self.check_and_restock(ticket_types).await;
236 return_sender.send(Ok(()))
237 }
238 BandwidthControllerRequest::WaitForTicketbooks(return_sender, ticket_types) => {
239 self.handle_wait_for_ticketbooks(return_sender, ticket_types)
240 .await
241 }
242 }
243 }
244
245 async fn handle_set_credential_fetcher(&mut self, fetcher: Option<Arc<dyn CredentialFetcher>>) {
246 self.in_flight.cancel_and_join().await;
247 if let Some(old_fetcher) = self.credential_fetcher.take() {
248 old_fetcher.cleanup().await;
249 }
250 let public_data_fetcher = fetcher
252 .clone()
253 .map(|f| f as Arc<dyn CredentialPublicDataFetcher>);
254 self.public_data_fetcher = public_data_fetcher;
255 self.credential_fetcher = fetcher;
258 self.check_and_restock(self.config.managed_ticket_types.clone())
259 .await;
260 }
261
262 async fn handle_set_public_data_fetcher(
263 &mut self,
264 fetcher: Option<Arc<dyn CredentialPublicDataFetcher>>,
265 ) {
266 self.public_data_fetcher = fetcher;
267 self.prefetch_global_data().await;
270 }
271
272 async fn handle_reset(&mut self) -> Result<(), BandwidthControllerError> {
274 self.in_flight.cancel_and_join().await;
279
280 if let Some(fetcher) = &self.credential_fetcher {
281 fetcher.cleanup().await;
282 }
283 self.credential_fetcher = None;
284
285 let requests = std::mem::take(&mut self.pending_readiness);
286 requests.into_iter().for_each(|r| r.cancel());
287
288 self.storage
290 .clear_ticketbooks()
291 .await
292 .map_err(BandwidthControllerError::credential_storage_error)?;
293 self.storage
294 .clear_emergency_credentials()
295 .await
296 .map_err(BandwidthControllerError::credential_storage_error)
297 }
298
299 async fn handle_get_available_ticketbooks(
300 &self,
301 ) -> Result<AvailableTicketbooks, BandwidthControllerError> {
302 self.print_info().await?;
303 self.get_available_ticketbooks().await
304 }
305
306 async fn handle_wait_for_ticketbooks(
307 &mut self,
308 return_sender: ReturnSender<()>,
309 ticket_types: Vec<TicketType>,
310 ) {
311 let global_data = self
315 .required_global_data(&ticket_types)
316 .await
317 .unwrap_or_default();
318 let request = ReadinessRequest {
319 return_sender,
320 ticket_types,
321 global_data,
322 };
323
324 let snapshot = match self.build_readiness_snapshot(None).await {
325 Ok(snapshot) => snapshot,
326 Err(err) => {
327 tracing::warn!("could not assess ticketbook readiness: {err}");
329 self.pending_readiness.push(request);
330 return;
331 }
332 };
333 tracing::debug!("Readiness snapshot : {:#?}", snapshot);
334 if let Some(request) = request.try_resolve(&snapshot) {
335 self.pending_readiness.push(request);
336 }
337 }
338
339 pub async fn prepare_ecash_ticket(
344 &self,
345 ticketbook_type: TicketType,
346 provider_pk: [u8; 32],
347 tickets_to_spend: u32,
348 spend_time: OffsetDateTime,
349 ) -> Result<Option<PreparedCredential>, BandwidthControllerError> {
350 let Some(retrieved_ticketbook) = self
351 .get_next_usable_ticketbook(ticketbook_type, tickets_to_spend)
352 .await?
353 else {
354 return Ok(None);
355 };
356
357 let ticketbook_id = retrieved_ticketbook.ticketbook_id;
358 let epoch_id = retrieved_ticketbook.ticketbook.epoch_id();
359
360 let used_tickets =
361 retrieved_ticketbook.ticketbook.spent_tickets() as u32 + tickets_to_spend;
362 let metadata = PreparedCredentialMetadata {
363 ticketbook_id,
364 tickets_withdrawn: tickets_to_spend,
365 used_tickets,
366 };
367
368 match self
369 .prepare_ecash_ticket_inner(
370 provider_pk,
371 spend_time,
372 tickets_to_spend,
373 retrieved_ticketbook,
374 )
375 .await
376 {
377 Ok(data) => Ok(Some(PreparedCredential {
378 data,
379 epoch_id,
380 metadata,
381 })),
382 Err(err) => {
383 error!("failed to prepare credential spending request. attempting to revert withdrawal...");
384 self.attempt_revert_ticket_usage(metadata).await?;
385 Err(err)
386 }
387 }
388 }
389
390 async fn prepare_ecash_ticket_inner(
391 &self,
392 provider_pk: [u8; 32],
393 spend_time: OffsetDateTime,
394 tickets_to_spend: u32,
395 mut retrieved_ticketbook: RetrievedTicketbook,
396 ) -> Result<CredentialSpendingData, BandwidthControllerError> {
397 let epoch_id = retrieved_ticketbook.ticketbook.epoch_id();
398 let expiration_date = retrieved_ticketbook.ticketbook.expiration_date();
399
400 let verification_key = self.ensure_master_verification_key(epoch_id).await?;
401 let expiration_signatures = self
402 .ensure_expiration_date_signatures(epoch_id, expiration_date)
403 .await?;
404 let coin_indices_signatures = self.ensure_coin_index_signatures(epoch_id).await?;
405
406 let pay_info = retrieved_ticketbook
407 .ticketbook
408 .generate_pay_info(provider_pk, spend_time);
409
410 let spend_request = retrieved_ticketbook.ticketbook.prepare_for_spending(
411 &verification_key,
412 pay_info.into(),
413 &coin_indices_signatures,
414 &expiration_signatures,
415 tickets_to_spend as u64,
416 )?;
417 Ok(spend_request)
418 }
419
420 pub async fn get_next_usable_ticketbook(
423 &self,
424 ticketbook_type: TicketType,
425 tickets: u32,
426 ) -> Result<Option<RetrievedTicketbook>, BandwidthControllerError> {
427 self.storage
428 .get_next_unspent_usable_ticketbook(ticketbook_type.to_string(), tickets)
429 .await
430 .map_err(BandwidthControllerError::credential_storage_error)
431 }
432
433 async fn attempt_revert_ticket_usage(
434 &self,
435 info: PreparedCredentialMetadata,
436 ) -> Result<bool, BandwidthControllerError> {
437 self.storage
438 .attempt_revert_ticketbook_withdrawal(
439 info.ticketbook_id,
440 info.tickets_withdrawn,
441 info.used_tickets,
442 )
443 .await
444 .map_err(BandwidthControllerError::credential_storage_error)
445 }
446
447 pub async fn fetch_ticketbook(
455 &self,
456 ticketbook_type: TicketType,
457 ) -> Result<(), BandwidthControllerError> {
458 let Some(fetcher) = &self.credential_fetcher else {
459 return Err(BandwidthControllerError::MissingCredentialFetcher);
460 };
461 let credentials = fetcher
462 .fetch_ticketbooks(ticketbook_type)
463 .await
464 .map_err(BandwidthControllerError::fetcher_error)?;
465 self.store_fetched(credentials).await;
466
467 self.ensure_global_data().await;
470
471 Ok(())
472 }
473
474 async fn check_and_restock(&mut self, ticketbook_types: Vec<TicketType>) {
477 let available = match self.get_available_ticketbooks().await {
478 Ok(available) => available,
479 Err(err) => {
480 tracing::warn!("could not assess ticket stock for restocking: {err}");
481 return;
482 }
483 };
484
485 for typ in ticketbook_types {
486 if self.config.managed_ticket_types.contains(&typ) {
487 tracing::debug!("Checking credential stock for {typ} ticket");
488 if available.needs_restock(typ, &self.config) {
489 tracing::debug!("{typ} tickets need a restock");
490 self.ensure_stocked(typ);
491 }
492 } else {
493 tracing::debug!("Credential {typ} is not managed, check and restock skipped");
494 }
495 }
496 self.prefetch_global_data().await;
497 }
498
499 fn ensure_stocked(&mut self, ticket_type: TicketType) {
502 let Some(fetcher) = &self.credential_fetcher else {
503 tracing::debug!("No credential fetcher set. No restock possible");
504 return;
505 };
506 if self.in_flight.is_in_flight(ticket_type) {
507 tracing::debug!("{ticket_type} ticket restock already in flight");
509 return;
510 }
511 tracing::debug!("requesting more {ticket_type} ticketbooks");
512 self.in_flight.spawn(FetchJob::Ticketbook {
513 ticket_type,
514 fetcher: Arc::clone(fetcher),
515 });
516 }
517
518 async fn on_fetch_complete(&mut self, key: FetchKey, received: FetchResult) {
520 match key {
521 FetchKey::Ticketbook(ticket_type) => {
522 self.on_ticketbook_fetch_complete(ticket_type, received)
523 .await
524 }
525 FetchKey::GlobalData(request) => {
526 self.on_global_data_fetch_complete(request, received).await
527 }
528 }
529 }
530
531 async fn on_ticketbook_fetch_complete(
534 &mut self,
535 ticket_type: TicketType,
536 received: FetchResult,
537 ) {
538 let failure = match received {
540 Ok(Some(Ok(FetchedData::Ticketbooks(credentials)))) => {
541 self.store_fetched(credentials).await;
542 tracing::info!("fetched and stored a {ticket_type} ticketbook");
543 self.prefetch_global_data().await;
545 None
546 }
547 Ok(Some(Err(err))) => {
548 tracing::warn!("failed to fetch {ticket_type} ticketbooks: {err}");
549 Some(FetchFailure {
550 ticket_type,
551 error: err,
552 })
553 }
554 Ok(None) => {
555 tracing::debug!("fetch for {ticket_type} ticketbooks was cancelled");
557 None
558 }
559 Ok(Some(Ok(_))) | Err(_) => {
562 tracing::error!("a credential fetch task for {ticket_type} terminated abnormally");
563 None
564 }
565 };
566 self.resolve_pending_waiters(failure).await;
567 }
568
569 async fn store_fetched(&self, credentials: Vec<NymCredential>) {
571 for credential in credentials {
572 match credential {
573 NymCredential::Ticketbook(ticketbook) => self.store_ticketbook(*ticketbook).await,
574 NymCredential::UpgradeModeToken { jwt, expiration } => {
575 self.store_upgrade_token(jwt, expiration).await
576 }
577 }
578 }
579 }
580
581 async fn store_ticketbook(&self, ticketbook: IssuedTicketBook) {
583 if let Err(err) = self.storage.insert_issued_ticketbook(&ticketbook).await {
584 tracing::warn!("failed to store ticketbook: {err}");
585 }
586 }
587
588 async fn store_upgrade_token(&self, jwt: String, expiration: OffsetDateTime) {
589 let credential_content = EmergencyCredentialContent {
590 typ: UPGRADE_MODE_JWT_TYPE.into(),
591 content: jwt.into_bytes(),
592 expiration: Some(expiration),
593 };
594 if let Err(e) = self
595 .storage
596 .insert_emergency_credential(&credential_content)
597 .await
598 {
599 tracing::warn!("failed to store emergency credential: {e}");
600 }
601 }
602
603 async fn build_readiness_snapshot(
611 &self,
612 failure: Option<FetchFailure>,
613 ) -> Result<ReadinessSnapshot, BandwidthControllerError> {
614 let upgrade_mode = self.get_upgrade_mode_token().await?.is_some();
615
616 let available = self.get_available_ticketbooks().await?;
617 let stocked_types = AvailableTicketbooks::ticketbook_types()
618 .into_iter()
619 .filter(|typ| available.contains_minimal_tickets(*typ, &self.config))
620 .collect();
621
622 let available_global_data = self
623 .storage
624 .get_available_global_data()
625 .await
626 .map_err(BandwidthControllerError::credential_storage_error)?;
627
628 Ok(ReadinessSnapshot::build(
629 upgrade_mode,
630 stocked_types,
631 self.in_flight.in_flight_ticketbooks(),
632 failure,
633 available_global_data,
634 self.in_flight.in_flight_global_data(),
635 ))
636 }
637
638 async fn required_global_data(
645 &self,
646 ticket_types: &[TicketType],
647 ) -> Result<Vec<(EpochId, Date)>, BandwidthControllerError> {
648 let available = self.get_available_ticketbooks().await?;
649 Ok(ticket_types
650 .iter()
651 .filter_map(|&typ| {
652 available
653 .next_usable(typ)
654 .map(|ticketbook| (ticketbook.epoch_id, ticketbook.expiration))
655 })
656 .collect())
657 }
658
659 async fn resolve_pending_waiters(&mut self, failure: Option<FetchFailure>) {
662 if self.pending_readiness.is_empty() {
663 return;
664 }
665 let snapshot = match self.build_readiness_snapshot(failure).await {
666 Ok(snapshot) => snapshot,
667 Err(err) => {
668 tracing::warn!("could not assess ticketbook readiness: {err}");
670 return;
671 }
672 };
673
674 tracing::debug!("Readiness snapshot : {:#?}", snapshot);
675
676 let mut still_waiting = Vec::new();
677 for mut request in std::mem::take(&mut self.pending_readiness) {
678 request.global_data = self
681 .required_global_data(&request.ticket_types)
682 .await
683 .unwrap_or_default();
684 if let Some(request) = request.try_resolve(&snapshot) {
685 still_waiting.push(request);
686 }
687 }
688
689 self.pending_readiness = still_waiting;
690 }
691
692 async fn ensure_master_verification_key(
699 &self,
700 epoch_id: EpochId,
701 ) -> Result<VerificationKeyAuth, BandwidthControllerError> {
702 if let Some(key) = self
703 .storage
704 .get_master_verification_key(epoch_id)
705 .await
706 .map_err(BandwidthControllerError::credential_storage_error)?
707 {
708 return Ok(key);
709 }
710 self.fetch_and_store_global_data(GlobalDataRequest::MasterVerificationKey(epoch_id))
711 .await?;
712 self.storage
713 .get_master_verification_key(epoch_id)
714 .await
715 .map_err(BandwidthControllerError::credential_storage_error)?
716 .ok_or(BandwidthControllerError::MissingVerificationKey { epoch_id })
717 }
718
719 async fn ensure_coin_index_signatures(
722 &self,
723 epoch_id: EpochId,
724 ) -> Result<Vec<AnnotatedCoinIndexSignature>, BandwidthControllerError> {
725 if let Some(signatures) = self
726 .storage
727 .get_coin_index_signatures(epoch_id)
728 .await
729 .map_err(BandwidthControllerError::credential_storage_error)?
730 {
731 return Ok(signatures);
732 }
733 self.fetch_and_store_global_data(GlobalDataRequest::CoinIndexSignatures(epoch_id))
734 .await?;
735 self.storage
736 .get_coin_index_signatures(epoch_id)
737 .await
738 .map_err(BandwidthControllerError::credential_storage_error)?
739 .ok_or(BandwidthControllerError::MissingCoinIndexSignatures { epoch_id })
740 }
741
742 async fn ensure_expiration_date_signatures(
745 &self,
746 epoch_id: EpochId,
747 expiration_date: Date,
748 ) -> Result<Vec<AnnotatedExpirationDateSignature>, BandwidthControllerError> {
749 if let Some(signatures) = self
750 .storage
751 .get_expiration_date_signatures(expiration_date, epoch_id)
752 .await
753 .map_err(BandwidthControllerError::credential_storage_error)?
754 {
755 return Ok(signatures);
756 }
757 self.fetch_and_store_global_data(GlobalDataRequest::ExpirationDateSignatures {
758 epoch_id,
759 expiration_date,
760 })
761 .await?;
762 self.storage
763 .get_expiration_date_signatures(expiration_date, epoch_id)
764 .await
765 .map_err(BandwidthControllerError::credential_storage_error)?
766 .ok_or(BandwidthControllerError::MissingExpirationDateSignatures { epoch_id })
767 }
768
769 async fn missing_global_data(
772 &self,
773 ) -> Result<Vec<GlobalDataRequest>, BandwidthControllerError> {
774 let available_global_data = self
775 .storage
776 .get_available_global_data()
777 .await
778 .map_err(BandwidthControllerError::credential_storage_error)?;
779
780 let ticketbooks = self
781 .storage
782 .get_ticketbooks_info()
783 .await
784 .map_err(BandwidthControllerError::credential_storage_error)?;
785
786 let required = AvailableTicketbooks::try_from(ticketbooks)?
787 .filter(|ticketbook| !ticketbook.has_expired())
788 .flat_map(|ticketbook| {
789 GlobalDataRequest::for_ticketbook(ticketbook.epoch_id, ticketbook.expiration)
790 })
791 .collect::<HashSet<_>>();
792
793 let missing = required
794 .into_iter()
795 .filter(|request| {
796 let present = match *request {
797 GlobalDataRequest::MasterVerificationKey(epoch_id) => available_global_data
798 .master_verification_key_epochs
799 .contains(&epoch_id),
800 GlobalDataRequest::CoinIndexSignatures(epoch_id) => available_global_data
801 .coin_index_signature_epochs
802 .contains(&epoch_id),
803 GlobalDataRequest::ExpirationDateSignatures {
804 epoch_id,
805 expiration_date,
806 } => available_global_data
807 .expiration_date_signatures
808 .contains(&(epoch_id, expiration_date)),
809 };
810 !present
811 })
812 .collect();
813
814 Ok(missing)
815 }
816
817 async fn prefetch_global_data(&mut self) {
821 let missing = match self.missing_global_data().await {
822 Ok(missing) => missing,
823 Err(err) => {
824 tracing::warn!("could not assess global data for prefetch: {err}");
825 return;
826 }
827 };
828 for request in missing {
829 self.spawn_global_data_fetch(request);
830 }
831 }
832
833 async fn ensure_global_data(&self) {
836 let missing = match self.missing_global_data().await {
837 Ok(missing) => missing,
838 Err(err) => {
839 tracing::warn!("could not assess global data: {err}");
840 return;
841 }
842 };
843 for request in missing {
844 if let Err(err) = self.fetch_and_store_global_data(request).await {
845 tracing::warn!("failed to ensure global data ({request:?}): {err}");
846 }
847 }
848 }
849
850 fn spawn_global_data_fetch(&mut self, request: GlobalDataRequest) {
853 let Some(fetcher) = &self.public_data_fetcher else {
854 tracing::debug!("no public data fetcher set; cannot prefetch {request:?}");
855 return;
856 };
857 if self.in_flight.is_in_flight(request) {
858 return;
859 }
860 tracing::debug!("prefetching global data: {request:?}");
861 self.in_flight.spawn(FetchJob::GlobalData {
862 request,
863 fetcher: Arc::clone(fetcher),
864 });
865 }
866
867 async fn fetch_and_store_global_data(
871 &self,
872 request: GlobalDataRequest,
873 ) -> Result<(), BandwidthControllerError> {
874 let Some(fetcher) = &self.public_data_fetcher else {
875 return Ok(());
876 };
877 let data = request
878 .fetch(&**fetcher)
879 .await
880 .map_err(BandwidthControllerError::fetcher_error)?;
881 self.persist_global_data(data).await
882 }
883
884 async fn persist_global_data(&self, data: GlobalData) -> Result<(), BandwidthControllerError> {
886 match data {
887 GlobalData::MasterVerificationKey(key) => {
888 self.storage.insert_master_verification_key(&key).await
889 }
890 GlobalData::CoinIndexSignatures(signatures) => {
891 self.storage.insert_coin_index_signatures(&signatures).await
892 }
893 GlobalData::ExpirationDateSignatures(signatures) => {
894 self.storage
895 .insert_expiration_date_signatures(&signatures)
896 .await
897 }
898 }
899 .map_err(BandwidthControllerError::credential_storage_error)
900 }
901
902 async fn on_global_data_fetch_complete(
905 &mut self,
906 request: GlobalDataRequest,
907 received: FetchResult,
908 ) {
909 match received {
910 Ok(Some(Ok(FetchedData::GlobalData(data)))) => {
911 if let Err(err) = self.persist_global_data(data).await {
912 tracing::warn!("failed to persist prefetched global data ({request:?}): {err}");
913 }
914 }
915 Ok(Some(Err(err))) => {
916 tracing::warn!("failed to prefetch global data ({request:?}): {err}")
917 }
918 Ok(None) => tracing::debug!("global data prefetch cancelled: {request:?}"),
919 Ok(Some(Ok(_))) | Err(_) => {
921 tracing::error!("a global data prefetch task ({request:?}) terminated abnormally")
922 }
923 }
924
925 self.resolve_pending_waiters(None).await;
928 }
929
930 async fn get_upgrade_mode_token(&self) -> Result<Option<String>, BandwidthControllerError> {
935 let Some(emergency_credential) = self
936 .storage
937 .get_emergency_credential(UPGRADE_MODE_JWT_TYPE)
938 .await
939 .map_err(BandwidthControllerError::credential_storage_error)?
940 else {
941 return Ok(None);
942 };
943 let token = String::from_utf8(emergency_credential.data.content)
945 .map_err(|_| BandwidthControllerError::MalformedUpgradeModeToken)?;
946 Ok(Some(token))
947 }
948
949 async fn get_available_ticketbooks(
950 &self,
951 ) -> Result<AvailableTicketbooks, BandwidthControllerError> {
952 let ticketbooks_info = self
953 .storage
954 .get_ticketbooks_info()
955 .await
956 .map_err(BandwidthControllerError::credential_storage_error)?;
957 AvailableTicketbooks::try_from(ticketbooks_info)
958 }
959
960 async fn print_info(&self) -> Result<(), BandwidthControllerError> {
961 let ticketbooks_info = self.get_available_ticketbooks().await?;
962 let num_ticketbooks = ticketbooks_info.len_not_expired();
963 let num_total_ticketbooks = ticketbooks_info.len();
964 tracing::info!("Ticketbooks stored: {num_ticketbooks}");
965 tracing::debug!("Total ticketbooks stored: {num_total_ticketbooks}");
966 for ticketbook in ticketbooks_info {
967 if ticketbook.has_expired() {
968 tracing::debug!("Expired ticketbook: {ticketbook}");
969 } else if ticketbook.expired_soon(OffsetDateTime::now_utc(), &self.config) {
970 tracing::info!("Soon expired ticketbook: {ticketbook}");
971 } else {
972 tracing::info!("Ticketbook: {ticketbook}");
973 }
974 }
975
976 Ok(())
977 }
978}
979
980#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
982#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
983impl<St: Storage> BandwidthTicketProvider for BandwidthController<St> {
984 async fn get_ecash_ticket(
985 &self,
986 ticket_type: TicketType,
987 gateway_id: ed25519::PublicKey,
988 tickets_to_spend: u32,
989 spend_time: OffsetDateTime,
990 ) -> Result<Option<PreparedCredential>, BandwidthControllerError> {
991 self.prepare_ecash_ticket(
992 ticket_type,
993 gateway_id.to_bytes(),
994 tickets_to_spend,
995 spend_time,
996 )
997 .await
998 }
999
1000 async fn get_upgrade_mode_token(&self) -> Result<Option<String>, BandwidthControllerError> {
1001 self.get_upgrade_mode_token().await
1002 }
1003
1004 async fn attempt_revert_spending(
1005 &self,
1006 metadata: PreparedCredentialMetadata,
1007 ) -> Result<bool, BandwidthControllerError> {
1008 self.attempt_revert_ticket_usage(metadata).await
1009 }
1010
1011 async fn close(&self) {
1012 self.storage.close().await
1013 }
1014}