nym_bandwidth_controller/
ticketbooks.rs1use std::fmt;
5
6use nym_credential_storage::models::BasicTicketbookInformation;
7use nym_credentials_interface::TicketType;
8use nym_ecash_time::{Date, EcashTime, OffsetDateTime};
9use strum::IntoEnumIterator;
10
11use crate::{config::BandwidthControllerConfig, error::BandwidthControllerError};
12
13#[derive(Debug, Clone, PartialEq)]
14pub struct AvailableTicketbook {
15 pub id: i64,
16 pub typ: TicketType,
17 pub expiration: Date,
18 pub issued_tickets: u32,
19 pub claimed_tickets: u32,
20 pub ticket_size: u64,
21}
22
23impl AvailableTicketbook {
24 pub fn issued_tickets_si(&self) -> String {
25 si_scale::helpers::bibytes2(self.issued_tickets as u64 * self.ticket_size)
26 }
27
28 pub fn remaining_tickets(&self) -> u32 {
29 self.issued_tickets.saturating_sub(self.claimed_tickets)
30 }
31
32 pub fn remaining_tickets_si(&self) -> String {
33 si_scale::helpers::bibytes2(self.remaining_tickets() as u64 * self.ticket_size)
34 }
35
36 pub fn ticket_size_si(&self) -> String {
37 si_scale::helpers::bibytes2(self.ticket_size)
38 }
39
40 pub fn has_expired(&self) -> bool {
41 self.expiration <= nym_ecash_time::ecash_today().date()
42 }
43
44 pub fn expired_soon(
46 &self,
47 datetime: OffsetDateTime,
48 bc_config: BandwidthControllerConfig,
49 ) -> bool {
50 self.expiration.ecash_datetime() < datetime + bc_config.soon_expiry_threshold
51 }
52}
53
54impl fmt::Display for AvailableTicketbook {
55 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
56 let ecash_today = nym_ecash_time::ecash_today().date();
57
58 let expiration = if self.expiration <= ecash_today {
59 format!("EXPIRED ON: {}", self.expiration)
60 } else {
61 format!("expires: {}", self.expiration)
62 };
63
64 write!(
65 f,
66 "{{ id: {}, type: {}, tickets: {}/{}, size: {}, remaining: {}/{}, {} }}",
67 self.id,
68 self.typ,
69 self.remaining_tickets(),
70 self.issued_tickets,
71 self.ticket_size_si(),
72 self.remaining_tickets_si(),
73 self.issued_tickets_si(),
74 expiration
75 )
76 }
77}
78
79impl TryFrom<BasicTicketbookInformation> for AvailableTicketbook {
80 type Error = BandwidthControllerError;
81
82 fn try_from(value: BasicTicketbookInformation) -> Result<Self, Self::Error> {
83 let typ = value
84 .ticketbook_type
85 .parse()
86 .map_err(|_| BandwidthControllerError::ParseTicketType(value.ticketbook_type))?;
87 Ok(AvailableTicketbook {
88 id: value.id,
89 typ,
90 expiration: value.expiration_date,
91 issued_tickets: value.total_tickets,
92 claimed_tickets: value.used_tickets,
93 ticket_size: typ.to_repr().bandwidth_value(),
94 })
95 }
96}
97
98#[derive(Debug, Clone, PartialEq)]
99pub struct AvailableTicketbooks {
100 pub ticketbooks: Vec<AvailableTicketbook>,
101}
102
103impl AvailableTicketbooks {
104 pub fn remaining_tickets(&self, typ: TicketType) -> u64 {
105 self.tickets_by_type(typ)
106 .filter(|ticketbook| !ticketbook.has_expired())
107 .map(|ticketbook| ticketbook.remaining_tickets())
108 .fold(0, |acc, remaining| acc.saturating_add(remaining.into()))
109 }
110
111 pub fn remaining_data(&self, typ: TicketType) -> u64 {
112 self.remaining_tickets(typ) * typ.to_repr().bandwidth_value()
113 }
114
115 pub fn remaining_data_si(&self, typ: TicketType) -> String {
116 si_scale::helpers::bibytes2(self.remaining_data(typ) as f64)
117 }
118
119 fn tickets_by_type(&self, typ: TicketType) -> impl Iterator<Item = &AvailableTicketbook> {
120 self.ticketbooks
121 .iter()
122 .filter(move |ticketbook| ticketbook.typ == typ)
123 }
124
125 pub fn remaining_tickets_long_lasting(
126 &self,
127 typ: TicketType,
128 bc_config: BandwidthControllerConfig,
129 ) -> u64 {
130 self.tickets_by_type(typ)
131 .filter(|ticketbook| !ticketbook.expired_soon(OffsetDateTime::now_utc(), bc_config))
132 .map(|ticketbook| ticketbook.remaining_tickets())
133 .fold(0, |acc, remaining| acc.saturating_add(remaining.into()))
134 }
135
136 pub fn remaining_unexpired_tickets(&self, typ: TicketType) -> u64 {
137 self.tickets_by_type(typ)
138 .filter(|ticketbook| !ticketbook.has_expired())
139 .map(|ticketbook| ticketbook.remaining_tickets())
140 .fold(0, |acc, remaining| acc.saturating_add(remaining.into()))
141 }
142
143 pub fn needs_restock(&self, typ: TicketType, bc_config: BandwidthControllerConfig) -> bool {
145 let remaining = self.remaining_tickets_long_lasting(typ, bc_config);
146 remaining <= bc_config.nb_ticket_restock
147 }
148
149 pub fn contains_minimal_tickets(
150 &self,
151 typ: TicketType,
152 bc_config: BandwidthControllerConfig,
153 ) -> bool {
154 let remaining = self.remaining_unexpired_tickets(typ);
155 remaining > bc_config.min_nb_ticket_needed
156 }
157
158 pub fn len(&self) -> usize {
159 self.ticketbooks.len()
160 }
161
162 pub fn is_empty(&self) -> bool {
163 self.ticketbooks.is_empty()
164 }
165
166 pub fn len_not_expired(&self) -> usize {
167 self.ticketbooks
168 .iter()
169 .filter(|ticketbook| !ticketbook.has_expired())
170 .count()
171 }
172
173 pub fn ticketbook_types() -> Vec<TicketType> {
174 TicketType::iter()
176 .filter(|&t| t != TicketType::V1MixnetExit)
177 .collect()
178 }
179}
180
181impl Iterator for AvailableTicketbooks {
182 type Item = AvailableTicketbook;
183
184 fn next(&mut self) -> Option<Self::Item> {
185 self.ticketbooks.pop()
186 }
187}
188
189impl From<Vec<AvailableTicketbook>> for AvailableTicketbooks {
190 fn from(ticketbooks: Vec<AvailableTicketbook>) -> Self {
191 Self { ticketbooks }
192 }
193}
194
195impl TryFrom<Vec<BasicTicketbookInformation>> for AvailableTicketbooks {
196 type Error = BandwidthControllerError;
197
198 fn try_from(value: Vec<BasicTicketbookInformation>) -> Result<Self, Self::Error> {
199 let ticketbooks: Vec<_> = value
200 .into_iter()
201 .filter_map(|ticketbook| {
202 AvailableTicketbook::try_from(ticketbook)
203 .inspect_err(|err| {
204 tracing::error!("Failed to parse ticketbook {err}");
205 })
206 .ok()
207 })
208 .collect();
209 Ok(AvailableTicketbooks::from(ticketbooks))
210 }
211}