massa_models/
slot.rs

1// Copyright (c) 2022 MASSA LABS <info@massa.net>
2
3use crate::error::ModelsError;
4use massa_hash::Hash;
5use massa_serialization::{
6    Deserializer, SerializeError, Serializer, U64VarIntDeserializer, U64VarIntSerializer,
7};
8use nom::bytes::complete::take;
9use nom::error::{context, ContextError, ParseError};
10use serde::{Deserialize, Serialize};
11use std::ops::{Bound, RangeBounds};
12use std::str::FromStr;
13use std::{cmp::Ordering, convert::TryInto};
14
15/// a point in time where a block is expected
16#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Serialize, Deserialize)]
17pub struct Slot {
18    /// period
19    pub period: u64,
20    /// thread
21    pub thread: u8,
22}
23
24/// size of the slot key representation
25pub const SLOT_KEY_SIZE: usize = 9;
26
27/// Basic serializer for `Slot`
28#[derive(Clone)]
29pub struct SlotSerializer {
30    u64_serializer: U64VarIntSerializer,
31}
32
33impl SlotSerializer {
34    /// Creates a `SlotSerializer`
35    pub const fn new() -> Self {
36        Self {
37            u64_serializer: U64VarIntSerializer::new(),
38        }
39    }
40}
41
42impl Default for SlotSerializer {
43    fn default() -> Self {
44        Self::new()
45    }
46}
47
48impl Serializer<Slot> for SlotSerializer {
49    /// ```
50    /// use std::ops::Bound::Included;
51    /// use massa_serialization::Serializer;
52    /// use massa_models::slot::{Slot, SlotSerializer};
53    ///
54    /// let slot: Slot = Slot::new(1, 3);
55    /// let mut serialized = Vec::new();
56    /// let serializer = SlotSerializer::new();
57    /// serializer.serialize(&slot, &mut serialized).unwrap();
58    /// ```
59    fn serialize(&self, value: &Slot, buffer: &mut Vec<u8>) -> Result<(), SerializeError> {
60        self.u64_serializer.serialize(&value.period, buffer)?;
61        buffer.push(value.thread);
62        Ok(())
63    }
64}
65
66/// Basic `Slot` Deserializer
67#[derive(Clone)]
68pub struct SlotDeserializer {
69    period_deserializer: U64VarIntDeserializer,
70    range_thread: (Bound<u8>, Bound<u8>),
71}
72
73impl SlotDeserializer {
74    /// Creates a `SlotDeserializer`
75    pub const fn new(
76        range_period: (Bound<u64>, Bound<u64>),
77        range_thread: (Bound<u8>, Bound<u8>),
78    ) -> Self {
79        Self {
80            period_deserializer: U64VarIntDeserializer::new(range_period.0, range_period.1),
81            range_thread,
82        }
83    }
84}
85
86impl Deserializer<Slot> for SlotDeserializer {
87    /// ```
88    /// use std::ops::Bound::Included;
89    /// use massa_serialization::{Serializer, Deserializer, DeserializeError};
90    /// use massa_models::slot::{Slot, SlotSerializer, SlotDeserializer};
91    ///
92    /// let slot: Slot = Slot::new(1, 3);
93    /// let mut serialized = Vec::new();
94    /// let serializer = SlotSerializer::new();
95    /// let deserializer = SlotDeserializer::new((Included(u64::MIN), Included(u64::MAX)), (Included(u8::MIN), Included(u8::MAX.into())));
96    /// serializer.serialize(&slot, &mut serialized).unwrap();
97    /// let (rest, slot_deser) = deserializer.deserialize::<DeserializeError>(&serialized).unwrap();
98    /// assert!(rest.is_empty());
99    /// assert_eq!(slot, slot_deser);
100    /// ```
101    fn deserialize<'a, E: ParseError<&'a [u8]> + ContextError<&'a [u8]>>(
102        &self,
103        buffer: &'a [u8],
104    ) -> nom::IResult<&'a [u8], Slot, E> {
105        context("Failed Slot deserialization", |input: &'a [u8]| {
106            let (rest, period) = self.period_deserializer.deserialize(input)?;
107            let (rest2, thread_) = take(1usize)(rest)?;
108            let thread = thread_[0];
109            if !self.range_thread.contains(&thread) {
110                return Err(nom::Err::Error(ParseError::from_error_kind(
111                    &rest[0..1],
112                    nom::error::ErrorKind::Digit,
113                )));
114            }
115            // Safe because we throw just above if there is no character.
116            Ok((rest2, Slot { period, thread }))
117        })(buffer)
118    }
119}
120
121impl PartialOrd for Slot {
122    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
123        Some(self.cmp(other))
124    }
125}
126
127impl Ord for Slot {
128    fn cmp(&self, other: &Self) -> Ordering {
129        (self.period, self.thread).cmp(&(other.period, other.thread))
130    }
131}
132
133impl std::fmt::Display for Slot {
134    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
135        write!(f, "(period: {}, thread: {})", self.period, self.thread)?;
136        Ok(())
137    }
138}
139
140impl FromStr for Slot {
141    type Err = ModelsError;
142    fn from_str(s: &str) -> Result<Self, Self::Err> {
143        let v: Vec<_> = s.split(',').collect();
144        if v.len() != 2 {
145            Err(ModelsError::DeserializeError(
146                "invalid slot format".to_string(),
147            ))
148        } else {
149            Ok(Slot::new(
150                v[0].parse::<u64>()
151                    .map_err(|_| ModelsError::DeserializeError("invalid period".to_string()))?,
152                v[1].parse::<u8>()
153                    .map_err(|_| ModelsError::DeserializeError("invalid thread".to_string()))?,
154            ))
155        }
156    }
157}
158
159impl Slot {
160    /// new slot from period and thread
161    pub fn new(period: u64, thread: u8) -> Slot {
162        Slot { period, thread }
163    }
164
165    /// create the last slot of a given cycle
166    pub fn new_last_of_cycle(
167        cycle: u64,
168        periods_per_cycle: u64,
169        thread_count: u8,
170    ) -> Result<Slot, ModelsError> {
171        let period = cycle
172            .checked_mul(periods_per_cycle)
173            .ok_or(ModelsError::PeriodOverflowError)?
174            .checked_add(periods_per_cycle.saturating_sub(1))
175            .ok_or(ModelsError::PeriodOverflowError)?;
176        Ok(Slot {
177            period,
178            thread: thread_count.saturating_sub(1),
179        })
180    }
181
182    /// create the first slot of a given cycle
183    pub fn new_first_of_cycle(cycle: u64, periods_per_cycle: u64) -> Result<Slot, ModelsError> {
184        let period = cycle
185            .checked_mul(periods_per_cycle)
186            .ok_or(ModelsError::PeriodOverflowError)?;
187        Ok(Slot { period, thread: 0 })
188    }
189
190    /// returns the minimal slot
191    pub const fn min() -> Slot {
192        Slot {
193            period: 0,
194            thread: 0,
195        }
196    }
197
198    /// returns the maximal slot
199    pub const fn max(thread_count: u8) -> Slot {
200        Slot {
201            period: u64::MAX,
202            thread: thread_count.saturating_sub(1),
203        }
204    }
205
206    /// first bit of the slot, for seed purpose
207    pub fn get_first_bit(&self) -> bool {
208        Hash::compute_from(&self.to_bytes_key()).to_bytes()[0] >> 7 == 1
209    }
210
211    /// cycle associated to that slot
212    pub fn get_cycle(&self, periods_per_cycle: u64) -> u64 {
213        self.period / periods_per_cycle
214    }
215
216    /// check if the slot is last in the cycle
217    pub fn is_last_of_cycle(&self, periods_per_cycle: u64, thread_count: u8) -> bool {
218        self.period % periods_per_cycle == (periods_per_cycle.saturating_sub(1))
219            && self.thread == (thread_count.saturating_sub(1))
220    }
221
222    /// check if the slot is first in the cycle
223    pub fn is_first_of_cycle(&self, periods_per_cycle: u64) -> bool {
224        self.period.is_multiple_of(periods_per_cycle) && self.thread == 0
225    }
226
227    /// Returns a fixed-size sortable binary key
228    ///
229    /// ## Example
230    /// ```rust
231    /// # use massa_models::slot::Slot;
232    /// let slot = Slot::new(10,5);
233    /// let key = slot.to_bytes_key();
234    /// let res = Slot::from_bytes_key(&key);
235    /// assert_eq!(slot, res);
236    /// ```
237    pub fn to_bytes_key(&self) -> [u8; SLOT_KEY_SIZE] {
238        let mut res = [0u8; SLOT_KEY_SIZE];
239        res[..8].clone_from_slice(&self.period.to_be_bytes());
240        res[8] = self.thread;
241        res
242    }
243
244    /// Deserializes a slot from its fixed-size sortable binary key representation
245    ///
246    /// ## Example
247    /// ```rust
248    /// # use massa_models::slot::Slot;
249    /// let slot = Slot::new(10,5);
250    /// let key = slot.to_bytes_key();
251    /// let res = Slot::from_bytes_key(&key);
252    /// assert_eq!(slot, res);
253    /// ```
254    pub fn from_bytes_key(buffer: &[u8; SLOT_KEY_SIZE]) -> Self {
255        Slot {
256            period: u64::from_be_bytes(buffer[..8].try_into().unwrap()), // cannot fail
257            thread: buffer[8],
258        }
259    }
260
261    /// Returns the next Slot
262    ///
263    /// ## Example
264    /// ```rust
265    /// # use massa_models::slot::Slot;
266    /// let slot = Slot::new(10,3);
267    /// assert_eq!(slot.get_next_slot(5).unwrap(), Slot::new(10, 4));
268    /// let slot = Slot::new(10,4);
269    /// assert_eq!(slot.get_next_slot(5).unwrap(), Slot::new(11, 0));
270    /// ```
271    pub fn get_next_slot(&self, thread_count: u8) -> Result<Slot, ModelsError> {
272        if self.thread.saturating_add(1u8) >= thread_count {
273            Ok(Slot::new(
274                self.period
275                    .checked_add(1u64)
276                    .ok_or(ModelsError::PeriodOverflowError)?,
277                0u8,
278            ))
279        } else {
280            Ok(Slot::new(
281                self.period,
282                self.thread
283                    .checked_add(1u8)
284                    .ok_or(ModelsError::ThreadOverflowError)?,
285            ))
286        }
287    }
288
289    /// Returns the previous Slot
290    ///
291    /// ## Example
292    /// ```rust
293    /// # use massa_models::slot::Slot;
294    /// let slot = Slot::new(10,1);
295    /// assert_eq!(slot.get_prev_slot(5).unwrap(), Slot::new(10, 0));
296    /// let slot = Slot::new(10,0);
297    /// assert_eq!(slot.get_prev_slot(5).unwrap(), Slot::new(9, 4));
298    /// ```
299    pub fn get_prev_slot(&self, thread_count: u8) -> Result<Slot, ModelsError> {
300        match self.thread.checked_sub(1u8) {
301            Some(t) => Ok(Slot::new(self.period, t)),
302            None => Ok(Slot::new(
303                self.period
304                    .checked_sub(1)
305                    .ok_or(ModelsError::PeriodOverflowError)?,
306                thread_count.saturating_sub(1),
307            )),
308        }
309    }
310
311    /// Counts the number of slots since the one passed in parameter and until self
312    /// If the two slots are equal, the returned value is `0`.
313    /// If the passed slot is strictly higher than self, an error is returned
314    pub fn slots_since(&self, s: &Slot, thread_count: u8) -> Result<u64, ModelsError> {
315        // if s > self, return an error
316        if s > self {
317            return Err(ModelsError::PeriodOverflowError);
318        }
319
320        // compute the number of slots from s to self
321        Ok((self.period - s.period)
322            .checked_mul(thread_count as u64)
323            .ok_or(ModelsError::PeriodOverflowError)?
324            .checked_add(self.thread as u64)
325            .ok_or(ModelsError::PeriodOverflowError)?
326            .saturating_sub(s.thread as u64))
327    }
328
329    /// Returns the n-th slot after the current one
330    ///
331    /// ## Example
332    /// ```rust
333    /// # use massa_models::slot::Slot;
334    /// let slot = Slot::new(10,3);
335    /// assert_eq!(slot.skip(62, 32).unwrap(), Slot::new(12, 1));
336    /// ```
337    pub fn skip(&self, n: u64, thread_count: u8) -> Result<Slot, ModelsError> {
338        let mut res_period = self
339            .period
340            .checked_add(n / (thread_count as u64))
341            .ok_or(ModelsError::PeriodOverflowError)?;
342        let mut res_thread = (self.thread as u64)
343            .checked_add(n % (thread_count as u64))
344            .ok_or(ModelsError::ThreadOverflowError)?;
345
346        if res_thread >= thread_count as u64 {
347            res_period = res_period
348                .checked_add(1)
349                .ok_or(ModelsError::PeriodOverflowError)?;
350            res_thread -= thread_count as u64;
351        }
352
353        Ok(Slot::new(res_period, res_thread as u8))
354    }
355}
356
357/// When an address is drawn to create an endorsement it is selected for a specific index
358#[derive(Debug, Clone, Deserialize, Serialize, Hash, PartialEq, Eq)]
359pub struct IndexedSlot {
360    /// slot
361    pub slot: Slot,
362    /// endorsement index in the slot
363    pub index: usize,
364}
365
366impl std::fmt::Display for IndexedSlot {
367    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
368        writeln!(f, "Slot: {}, Index: {}", self.slot, self.index)
369    }
370}
371
372#[cfg(test)]
373mod test {
374    use super::*;
375
376    #[test]
377    fn test_new_last_of_cycle() {
378        // Test case 1: Valid inputs
379        let expected_slot = Slot {
380            period: 767,
381            thread: 31,
382        };
383        let actual_slot = Slot::new_last_of_cycle(5, 128, 32).unwrap();
384        assert_eq!(actual_slot, expected_slot);
385
386        // Test case 2: Overflow scenario for period multiplication
387        let expected_error_mul = "period overflow error".to_string();
388        let actual_error_overflow_mul = Slot::new_last_of_cycle(u64::MAX, 128, 32)
389            .unwrap_err()
390            .to_string();
391
392        assert_eq!(actual_error_overflow_mul, expected_error_mul);
393
394        // Test case 3: Overflow scenario for period addition
395        let actual_error_overflow_add = Slot::new_last_of_cycle(u64::MAX - 1, u64::MAX, 32)
396            .unwrap_err()
397            .to_string();
398
399        assert_eq!(actual_error_overflow_add, expected_error_mul);
400    }
401
402    #[test]
403    fn test_new_first_of_cycle() {
404        // Test case 1: Valid inputs for new_first_of_cycle
405        let expected_slot = Slot {
406            period: 640,
407            thread: 0,
408        };
409        let actual_slot = Slot::new_first_of_cycle(5, 128).unwrap();
410        assert_eq!(actual_slot, expected_slot);
411
412        // Test case 2: Overflow scenario for new_first_of_cycle
413        let expected_error = "period overflow error".to_string();
414        let actual_error = Slot::new_first_of_cycle(u64::MAX, 128)
415            .unwrap_err()
416            .to_string();
417
418        assert_eq!(actual_error, expected_error);
419    }
420
421    #[test]
422    fn test_slot_serde() {
423        let expected_slot = Slot::new(12, 32);
424
425        let serialized = serde_json::to_string(&expected_slot).unwrap();
426        let actual_slot: Slot = serde_json::from_str(&serialized).unwrap();
427
428        assert_eq!(actual_slot, expected_slot);
429    }
430}