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 self.prune_expired().await;
159 let _ = self.print_info().await;
160 self.check_and_restock(self.config.managed_ticket_types.clone()).await;
161 }
162 (key, res) = self.in_flight.next_result(), if !self.in_flight.is_empty() => {
163 self.on_fetch_complete(key, res).await;
164 }
165 request = self.request_channel.1.recv() => match request {
166 Some(request) => self.handle_request(request).await,
167 None => {
168 log::warn!("bandwidth controller request channel closed; this should never happen as we own a sender; shutting down");
169 break;
170 }
171 }
172 }
173 }
174
175 self.in_flight.cancel_and_join().await;
177 if let Some(fetcher) = self.credential_fetcher {
178 fetcher.cleanup().await;
179 }
180 self.storage.close().await;
181 }
182
183 async fn handle_request(&mut self, request: BandwidthControllerRequest) {
188 match request {
189 BandwidthControllerRequest::EcashTicket(return_sender, request) => {
190 let ticket_type = request.ticket_type;
191 let credential_result = self
192 .prepare_ecash_ticket(
193 ticket_type,
194 request.gateway_id.to_bytes(),
195 request.tickets_to_spend,
196 request.spend_time,
197 )
198 .await;
199 return_sender.send(credential_result);
200 if self.config.managed_ticket_types.contains(&ticket_type) {
204 self.check_and_restock(vec![ticket_type]).await;
205 }
206 }
207 BandwidthControllerRequest::UpgradeModeToken(return_sender) => {
208 return_sender.send(self.get_upgrade_mode_token().await)
209 }
210 BandwidthControllerRequest::AttemptRevertSpending(return_sender, metadata) => {
211 return_sender.send(self.attempt_revert_ticket_usage(metadata).await)
212 }
213 BandwidthControllerRequest::SetCredentialFetcher(return_sender, fetcher) => {
214 self.handle_set_credential_fetcher(fetcher).await;
215 return_sender.send(Ok(()))
216 }
217
218 BandwidthControllerRequest::SetPublicDataFetcher(return_sender, fetcher) => {
219 self.handle_set_public_data_fetcher(fetcher).await;
220 return_sender.send(Ok(()))
221 }
222 BandwidthControllerRequest::Reset(return_sender) => {
223 return_sender.send(self.handle_reset().await)
224 }
225 BandwidthControllerRequest::ClearEmergencyCredentials(return_sender) => return_sender
226 .send(
227 self.storage
228 .clear_emergency_credentials()
229 .await
230 .map_err(BandwidthControllerError::credential_storage_error),
231 ),
232 BandwidthControllerRequest::GetAvailableTicketbooks(return_sender) => {
233 return_sender.send(self.handle_get_available_ticketbooks().await)
234 }
235 BandwidthControllerRequest::RestockTicketbooks(return_sender, ticket_types) => {
236 self.check_and_restock(ticket_types).await;
237 return_sender.send(Ok(()))
238 }
239 BandwidthControllerRequest::WaitForTicketbooks(return_sender, ticket_types) => {
240 self.handle_wait_for_ticketbooks(return_sender, ticket_types)
241 .await
242 }
243 }
244 }
245
246 async fn handle_set_credential_fetcher(&mut self, fetcher: Option<Arc<dyn CredentialFetcher>>) {
247 self.in_flight.cancel_and_join().await;
248 if let Some(old_fetcher) = self.credential_fetcher.take() {
249 old_fetcher.cleanup().await;
250 }
251 let public_data_fetcher = fetcher
253 .clone()
254 .map(|f| f as Arc<dyn CredentialPublicDataFetcher>);
255 self.public_data_fetcher = public_data_fetcher;
256 self.credential_fetcher = fetcher;
259 self.check_and_restock(self.config.managed_ticket_types.clone())
260 .await;
261 }
262
263 async fn handle_set_public_data_fetcher(
264 &mut self,
265 fetcher: Option<Arc<dyn CredentialPublicDataFetcher>>,
266 ) {
267 self.public_data_fetcher = fetcher;
268 self.prefetch_global_data().await;
271 }
272
273 async fn handle_reset(&mut self) -> Result<(), BandwidthControllerError> {
275 self.in_flight.cancel_and_join().await;
280
281 if let Some(fetcher) = &self.credential_fetcher {
282 fetcher.cleanup().await;
283 }
284 self.credential_fetcher = None;
285
286 let requests = std::mem::take(&mut self.pending_readiness);
287 requests.into_iter().for_each(|r| r.cancel());
288
289 self.storage
291 .clear_ticketbooks()
292 .await
293 .map_err(BandwidthControllerError::credential_storage_error)?;
294 self.storage
295 .clear_emergency_credentials()
296 .await
297 .map_err(BandwidthControllerError::credential_storage_error)
298 }
299
300 async fn handle_get_available_ticketbooks(
301 &self,
302 ) -> Result<AvailableTicketbooks, BandwidthControllerError> {
303 self.print_info().await?;
304 self.get_available_ticketbooks().await
305 }
306
307 async fn handle_wait_for_ticketbooks(
308 &mut self,
309 return_sender: ReturnSender<()>,
310 ticket_types: Vec<TicketType>,
311 ) {
312 let global_data = self
316 .required_global_data(&ticket_types)
317 .await
318 .unwrap_or_default();
319 let request = ReadinessRequest {
320 return_sender,
321 ticket_types,
322 global_data,
323 };
324
325 let snapshot = match self.build_readiness_snapshot(None).await {
326 Ok(snapshot) => snapshot,
327 Err(err) => {
328 tracing::warn!("could not assess ticketbook readiness: {err}");
330 self.pending_readiness.push(request);
331 return;
332 }
333 };
334 tracing::debug!("Readiness snapshot : {:#?}", snapshot);
335 if let Some(request) = request.try_resolve(&snapshot) {
336 self.pending_readiness.push(request);
337 }
338 }
339
340 pub async fn prepare_ecash_ticket(
345 &self,
346 ticketbook_type: TicketType,
347 provider_pk: [u8; 32],
348 tickets_to_spend: u32,
349 spend_time: OffsetDateTime,
350 ) -> Result<Option<PreparedCredential>, BandwidthControllerError> {
351 let Some(retrieved_ticketbook) = self
352 .get_next_usable_ticketbook(ticketbook_type, tickets_to_spend)
353 .await?
354 else {
355 return Ok(None);
356 };
357
358 let ticketbook_id = retrieved_ticketbook.ticketbook_id;
359 let epoch_id = retrieved_ticketbook.ticketbook.epoch_id();
360
361 let used_tickets =
362 retrieved_ticketbook.ticketbook.spent_tickets() as u32 + tickets_to_spend;
363 let metadata = PreparedCredentialMetadata {
364 ticketbook_id,
365 tickets_withdrawn: tickets_to_spend,
366 used_tickets,
367 };
368
369 match self
370 .prepare_ecash_ticket_inner(
371 provider_pk,
372 spend_time,
373 tickets_to_spend,
374 retrieved_ticketbook,
375 )
376 .await
377 {
378 Ok(data) => Ok(Some(PreparedCredential {
379 data,
380 epoch_id,
381 metadata,
382 })),
383 Err(err) => {
384 error!("failed to prepare credential spending request. attempting to revert withdrawal...");
385 self.attempt_revert_ticket_usage(metadata).await?;
386 Err(err)
387 }
388 }
389 }
390
391 async fn prepare_ecash_ticket_inner(
392 &self,
393 provider_pk: [u8; 32],
394 spend_time: OffsetDateTime,
395 tickets_to_spend: u32,
396 mut retrieved_ticketbook: RetrievedTicketbook,
397 ) -> Result<CredentialSpendingData, BandwidthControllerError> {
398 let epoch_id = retrieved_ticketbook.ticketbook.epoch_id();
399 let expiration_date = retrieved_ticketbook.ticketbook.expiration_date();
400
401 let verification_key = self.ensure_master_verification_key(epoch_id).await?;
402 let expiration_signatures = self
403 .ensure_expiration_date_signatures(epoch_id, expiration_date)
404 .await?;
405 let coin_indices_signatures = self.ensure_coin_index_signatures(epoch_id).await?;
406
407 let pay_info = retrieved_ticketbook
408 .ticketbook
409 .generate_pay_info(provider_pk, spend_time);
410
411 let spend_request = retrieved_ticketbook.ticketbook.prepare_for_spending(
412 &verification_key,
413 pay_info.into(),
414 &coin_indices_signatures,
415 &expiration_signatures,
416 tickets_to_spend as u64,
417 )?;
418 Ok(spend_request)
419 }
420
421 pub async fn get_next_usable_ticketbook(
424 &self,
425 ticketbook_type: TicketType,
426 tickets: u32,
427 ) -> Result<Option<RetrievedTicketbook>, BandwidthControllerError> {
428 self.storage
429 .get_next_unspent_usable_ticketbook(ticketbook_type.to_string(), tickets)
430 .await
431 .map_err(BandwidthControllerError::credential_storage_error)
432 }
433
434 async fn attempt_revert_ticket_usage(
435 &self,
436 info: PreparedCredentialMetadata,
437 ) -> Result<bool, BandwidthControllerError> {
438 self.storage
439 .attempt_revert_ticketbook_withdrawal(
440 info.ticketbook_id,
441 info.tickets_withdrawn,
442 info.used_tickets,
443 )
444 .await
445 .map_err(BandwidthControllerError::credential_storage_error)
446 }
447
448 pub async fn fetch_ticketbook(
456 &self,
457 ticketbook_type: TicketType,
458 ) -> Result<(), BandwidthControllerError> {
459 let Some(fetcher) = &self.credential_fetcher else {
460 return Err(BandwidthControllerError::MissingCredentialFetcher);
461 };
462 let credentials = fetcher
463 .fetch_ticketbooks(ticketbook_type)
464 .await
465 .map_err(BandwidthControllerError::fetcher_error)?;
466 self.store_fetched(credentials).await;
467
468 self.ensure_global_data().await;
471
472 Ok(())
473 }
474
475 async fn check_and_restock(&mut self, ticketbook_types: Vec<TicketType>) {
478 let available = match self.get_available_ticketbooks().await {
479 Ok(available) => available,
480 Err(err) => {
481 tracing::warn!("could not assess ticket stock for restocking: {err}");
482 return;
483 }
484 };
485
486 for typ in ticketbook_types {
487 if self.config.managed_ticket_types.contains(&typ) {
488 tracing::debug!("Checking credential stock for {typ} ticket");
489 if available.needs_restock(typ, &self.config) {
490 tracing::debug!("{typ} tickets need a restock");
491 self.ensure_stocked(typ);
492 }
493 } else {
494 tracing::debug!("Credential {typ} is not managed, check and restock skipped");
495 }
496 }
497 self.prefetch_global_data().await;
498 }
499
500 pub async fn prune_expired(&self) {
502 if let Err(err) = self.storage.cleanup_expired().await {
503 tracing::warn!("Could not cleanup expired ticketbooks: {err}");
504 } else {
505 tracing::debug!("Finished pruning of expired ticketbooks");
506 }
507 }
508
509 fn ensure_stocked(&mut self, ticket_type: TicketType) {
512 let Some(fetcher) = &self.credential_fetcher else {
513 tracing::debug!("No credential fetcher set. No restock possible");
514 return;
515 };
516 if self.in_flight.is_in_flight(ticket_type) {
517 tracing::debug!("{ticket_type} ticket restock already in flight");
519 return;
520 }
521 tracing::debug!("requesting more {ticket_type} ticketbooks");
522 self.in_flight.spawn(FetchJob::Ticketbook {
523 ticket_type,
524 fetcher: Arc::clone(fetcher),
525 });
526 }
527
528 async fn on_fetch_complete(&mut self, key: FetchKey, received: FetchResult) {
530 match key {
531 FetchKey::Ticketbook(ticket_type) => {
532 self.on_ticketbook_fetch_complete(ticket_type, received)
533 .await
534 }
535 FetchKey::GlobalData(request) => {
536 self.on_global_data_fetch_complete(request, received).await
537 }
538 }
539 }
540
541 async fn on_ticketbook_fetch_complete(
544 &mut self,
545 ticket_type: TicketType,
546 received: FetchResult,
547 ) {
548 let failure = match received {
550 Ok(Some(Ok(FetchedData::Ticketbooks(credentials)))) => {
551 self.store_fetched(credentials).await;
552 tracing::info!("fetched and stored a {ticket_type} ticketbook");
553 self.prefetch_global_data().await;
555 None
556 }
557 Ok(Some(Err(err))) => {
558 tracing::warn!("failed to fetch {ticket_type} ticketbooks: {err}");
559 Some(FetchFailure {
560 ticket_type,
561 error: err,
562 })
563 }
564 Ok(None) => {
565 tracing::debug!("fetch for {ticket_type} ticketbooks was cancelled");
567 None
568 }
569 Ok(Some(Ok(_))) | Err(_) => {
572 tracing::error!("a credential fetch task for {ticket_type} terminated abnormally");
573 None
574 }
575 };
576 self.resolve_pending_waiters(failure).await;
577 }
578
579 async fn store_fetched(&self, credentials: Vec<NymCredential>) {
581 for credential in credentials {
582 match credential {
583 NymCredential::Ticketbook(ticketbook) => self.store_ticketbook(*ticketbook).await,
584 NymCredential::UpgradeModeToken { jwt, expiration } => {
585 self.store_upgrade_token(jwt, expiration).await
586 }
587 }
588 }
589 }
590
591 async fn store_ticketbook(&self, ticketbook: IssuedTicketBook) {
593 if let Err(err) = self.storage.insert_issued_ticketbook(&ticketbook).await {
594 tracing::warn!("failed to store ticketbook: {err}");
595 }
596 }
597
598 async fn store_upgrade_token(&self, jwt: String, expiration: OffsetDateTime) {
599 let credential_content = EmergencyCredentialContent {
600 typ: UPGRADE_MODE_JWT_TYPE.into(),
601 content: jwt.into_bytes(),
602 expiration: Some(expiration),
603 };
604 if let Err(e) = self
605 .storage
606 .insert_emergency_credential(&credential_content)
607 .await
608 {
609 tracing::warn!("failed to store emergency credential: {e}");
610 }
611 }
612
613 async fn build_readiness_snapshot(
621 &self,
622 failure: Option<FetchFailure>,
623 ) -> Result<ReadinessSnapshot, BandwidthControllerError> {
624 let upgrade_mode = self.get_upgrade_mode_token().await?.is_some();
625
626 let available = self.get_available_ticketbooks().await?;
627 let stocked_types = AvailableTicketbooks::ticketbook_types()
628 .into_iter()
629 .filter(|typ| available.contains_minimal_tickets(*typ, &self.config))
630 .collect();
631
632 let available_global_data = self
633 .storage
634 .get_available_global_data()
635 .await
636 .map_err(BandwidthControllerError::credential_storage_error)?;
637
638 Ok(ReadinessSnapshot::build(
639 upgrade_mode,
640 stocked_types,
641 self.in_flight.in_flight_ticketbooks(),
642 failure,
643 available_global_data,
644 self.in_flight.in_flight_global_data(),
645 ))
646 }
647
648 async fn required_global_data(
655 &self,
656 ticket_types: &[TicketType],
657 ) -> Result<Vec<(EpochId, Date)>, BandwidthControllerError> {
658 let available = self.get_available_ticketbooks().await?;
659 Ok(ticket_types
660 .iter()
661 .filter_map(|&typ| {
662 available
663 .next_usable(typ)
664 .map(|ticketbook| (ticketbook.epoch_id, ticketbook.expiration))
665 })
666 .collect())
667 }
668
669 async fn resolve_pending_waiters(&mut self, failure: Option<FetchFailure>) {
672 if self.pending_readiness.is_empty() {
673 return;
674 }
675 let snapshot = match self.build_readiness_snapshot(failure).await {
676 Ok(snapshot) => snapshot,
677 Err(err) => {
678 tracing::warn!("could not assess ticketbook readiness: {err}");
680 return;
681 }
682 };
683
684 tracing::debug!("Readiness snapshot : {:#?}", snapshot);
685
686 let mut still_waiting = Vec::new();
687 for mut request in std::mem::take(&mut self.pending_readiness) {
688 request.global_data = self
691 .required_global_data(&request.ticket_types)
692 .await
693 .unwrap_or_default();
694 if let Some(request) = request.try_resolve(&snapshot) {
695 still_waiting.push(request);
696 }
697 }
698
699 self.pending_readiness = still_waiting;
700 }
701
702 async fn ensure_master_verification_key(
709 &self,
710 epoch_id: EpochId,
711 ) -> Result<VerificationKeyAuth, BandwidthControllerError> {
712 if let Some(key) = self
713 .storage
714 .get_master_verification_key(epoch_id)
715 .await
716 .map_err(BandwidthControllerError::credential_storage_error)?
717 {
718 return Ok(key);
719 }
720 self.fetch_and_store_global_data(GlobalDataRequest::MasterVerificationKey(epoch_id))
721 .await?;
722 self.storage
723 .get_master_verification_key(epoch_id)
724 .await
725 .map_err(BandwidthControllerError::credential_storage_error)?
726 .ok_or(BandwidthControllerError::MissingVerificationKey { epoch_id })
727 }
728
729 async fn ensure_coin_index_signatures(
732 &self,
733 epoch_id: EpochId,
734 ) -> Result<Vec<AnnotatedCoinIndexSignature>, BandwidthControllerError> {
735 if let Some(signatures) = self
736 .storage
737 .get_coin_index_signatures(epoch_id)
738 .await
739 .map_err(BandwidthControllerError::credential_storage_error)?
740 {
741 return Ok(signatures);
742 }
743 self.fetch_and_store_global_data(GlobalDataRequest::CoinIndexSignatures(epoch_id))
744 .await?;
745 self.storage
746 .get_coin_index_signatures(epoch_id)
747 .await
748 .map_err(BandwidthControllerError::credential_storage_error)?
749 .ok_or(BandwidthControllerError::MissingCoinIndexSignatures { epoch_id })
750 }
751
752 async fn ensure_expiration_date_signatures(
755 &self,
756 epoch_id: EpochId,
757 expiration_date: Date,
758 ) -> Result<Vec<AnnotatedExpirationDateSignature>, BandwidthControllerError> {
759 if let Some(signatures) = self
760 .storage
761 .get_expiration_date_signatures(expiration_date, epoch_id)
762 .await
763 .map_err(BandwidthControllerError::credential_storage_error)?
764 {
765 return Ok(signatures);
766 }
767 self.fetch_and_store_global_data(GlobalDataRequest::ExpirationDateSignatures {
768 epoch_id,
769 expiration_date,
770 })
771 .await?;
772 self.storage
773 .get_expiration_date_signatures(expiration_date, epoch_id)
774 .await
775 .map_err(BandwidthControllerError::credential_storage_error)?
776 .ok_or(BandwidthControllerError::MissingExpirationDateSignatures { epoch_id })
777 }
778
779 async fn missing_global_data(
782 &self,
783 ) -> Result<Vec<GlobalDataRequest>, BandwidthControllerError> {
784 let available_global_data = self
785 .storage
786 .get_available_global_data()
787 .await
788 .map_err(BandwidthControllerError::credential_storage_error)?;
789
790 let ticketbooks = self
791 .storage
792 .get_ticketbooks_info()
793 .await
794 .map_err(BandwidthControllerError::credential_storage_error)?;
795
796 let required = AvailableTicketbooks::try_from(ticketbooks)?
797 .filter(|ticketbook| !ticketbook.has_expired())
798 .flat_map(|ticketbook| {
799 GlobalDataRequest::for_ticketbook(ticketbook.epoch_id, ticketbook.expiration)
800 })
801 .collect::<HashSet<_>>();
802
803 let missing = required
804 .into_iter()
805 .filter(|request| {
806 let present = match *request {
807 GlobalDataRequest::MasterVerificationKey(epoch_id) => available_global_data
808 .master_verification_key_epochs
809 .contains(&epoch_id),
810 GlobalDataRequest::CoinIndexSignatures(epoch_id) => available_global_data
811 .coin_index_signature_epochs
812 .contains(&epoch_id),
813 GlobalDataRequest::ExpirationDateSignatures {
814 epoch_id,
815 expiration_date,
816 } => available_global_data
817 .expiration_date_signatures
818 .contains(&(epoch_id, expiration_date)),
819 };
820 !present
821 })
822 .collect();
823
824 Ok(missing)
825 }
826
827 async fn prefetch_global_data(&mut self) {
831 let missing = match self.missing_global_data().await {
832 Ok(missing) => missing,
833 Err(err) => {
834 tracing::warn!("could not assess global data for prefetch: {err}");
835 return;
836 }
837 };
838 for request in missing {
839 self.spawn_global_data_fetch(request);
840 }
841 }
842
843 async fn ensure_global_data(&self) {
846 let missing = match self.missing_global_data().await {
847 Ok(missing) => missing,
848 Err(err) => {
849 tracing::warn!("could not assess global data: {err}");
850 return;
851 }
852 };
853 for request in missing {
854 if let Err(err) = self.fetch_and_store_global_data(request).await {
855 tracing::warn!("failed to ensure global data ({request:?}): {err}");
856 }
857 }
858 }
859
860 fn spawn_global_data_fetch(&mut self, request: GlobalDataRequest) {
863 let Some(fetcher) = &self.public_data_fetcher else {
864 tracing::debug!("no public data fetcher set; cannot prefetch {request:?}");
865 return;
866 };
867 if self.in_flight.is_in_flight(request) {
868 return;
869 }
870 tracing::debug!("prefetching global data: {request:?}");
871 self.in_flight.spawn(FetchJob::GlobalData {
872 request,
873 fetcher: Arc::clone(fetcher),
874 });
875 }
876
877 async fn fetch_and_store_global_data(
881 &self,
882 request: GlobalDataRequest,
883 ) -> Result<(), BandwidthControllerError> {
884 let Some(fetcher) = &self.public_data_fetcher else {
885 return Ok(());
886 };
887 let data = request
888 .fetch(&**fetcher)
889 .await
890 .map_err(BandwidthControllerError::fetcher_error)?;
891 self.persist_global_data(data).await
892 }
893
894 async fn persist_global_data(&self, data: GlobalData) -> Result<(), BandwidthControllerError> {
896 match data {
897 GlobalData::MasterVerificationKey(key) => {
898 self.storage.insert_master_verification_key(&key).await
899 }
900 GlobalData::CoinIndexSignatures(signatures) => {
901 self.storage.insert_coin_index_signatures(&signatures).await
902 }
903 GlobalData::ExpirationDateSignatures(signatures) => {
904 self.storage
905 .insert_expiration_date_signatures(&signatures)
906 .await
907 }
908 }
909 .map_err(BandwidthControllerError::credential_storage_error)
910 }
911
912 async fn on_global_data_fetch_complete(
915 &mut self,
916 request: GlobalDataRequest,
917 received: FetchResult,
918 ) {
919 match received {
920 Ok(Some(Ok(FetchedData::GlobalData(data)))) => {
921 if let Err(err) = self.persist_global_data(data).await {
922 tracing::warn!("failed to persist prefetched global data ({request:?}): {err}");
923 }
924 }
925 Ok(Some(Err(err))) => {
926 tracing::warn!("failed to prefetch global data ({request:?}): {err}")
927 }
928 Ok(None) => tracing::debug!("global data prefetch cancelled: {request:?}"),
929 Ok(Some(Ok(_))) | Err(_) => {
931 tracing::error!("a global data prefetch task ({request:?}) terminated abnormally")
932 }
933 }
934
935 self.resolve_pending_waiters(None).await;
938 }
939
940 async fn get_upgrade_mode_token(&self) -> Result<Option<String>, BandwidthControllerError> {
945 let Some(emergency_credential) = self
946 .storage
947 .get_emergency_credential(UPGRADE_MODE_JWT_TYPE)
948 .await
949 .map_err(BandwidthControllerError::credential_storage_error)?
950 else {
951 return Ok(None);
952 };
953 let token = String::from_utf8(emergency_credential.data.content)
955 .map_err(|_| BandwidthControllerError::MalformedUpgradeModeToken)?;
956 Ok(Some(token))
957 }
958
959 async fn get_available_ticketbooks(
960 &self,
961 ) -> Result<AvailableTicketbooks, BandwidthControllerError> {
962 let ticketbooks_info = self
963 .storage
964 .get_ticketbooks_info()
965 .await
966 .map_err(BandwidthControllerError::credential_storage_error)?;
967 AvailableTicketbooks::try_from(ticketbooks_info)
968 }
969
970 async fn print_info(&self) -> Result<(), BandwidthControllerError> {
971 let ticketbooks_info = self.get_available_ticketbooks().await?;
972 let num_ticketbooks = ticketbooks_info.len_not_expired();
973 let num_total_ticketbooks = ticketbooks_info.len();
974 tracing::info!("Ticketbooks stored: {num_ticketbooks}");
975 tracing::debug!("Total ticketbooks stored: {num_total_ticketbooks}");
976 for ticketbook in ticketbooks_info {
977 if ticketbook.has_expired() {
978 tracing::debug!("Expired ticketbook: {ticketbook}");
979 } else if ticketbook.expired_soon(OffsetDateTime::now_utc(), &self.config) {
980 tracing::info!("Soon expired ticketbook: {ticketbook}");
981 } else {
982 tracing::info!("Ticketbook: {ticketbook}");
983 }
984 }
985
986 Ok(())
987 }
988}
989
990#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
992#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
993impl<St: Storage> BandwidthTicketProvider for BandwidthController<St> {
994 async fn get_ecash_ticket(
995 &self,
996 ticket_type: TicketType,
997 gateway_id: ed25519::PublicKey,
998 tickets_to_spend: u32,
999 spend_time: OffsetDateTime,
1000 ) -> Result<Option<PreparedCredential>, BandwidthControllerError> {
1001 self.prepare_ecash_ticket(
1002 ticket_type,
1003 gateway_id.to_bytes(),
1004 tickets_to_spend,
1005 spend_time,
1006 )
1007 .await
1008 }
1009
1010 async fn get_upgrade_mode_token(&self) -> Result<Option<String>, BandwidthControllerError> {
1011 self.get_upgrade_mode_token().await
1012 }
1013
1014 async fn attempt_revert_spending(
1015 &self,
1016 metadata: PreparedCredentialMetadata,
1017 ) -> Result<bool, BandwidthControllerError> {
1018 self.attempt_revert_ticket_usage(metadata).await
1019 }
1020
1021 async fn close(&self) {
1022 self.storage.close().await
1023 }
1024}