massa_models/
timeslots.rs

1// Copyright (c) 2022 MASSA LABS <info@massa.net>
2
3//! warning: assumes `thread_count >= 1, t0_millis >= 1, t0_millis % thread_count == 0`
4
5use massa_time::MassaTime;
6use std::convert::TryInto;
7
8use crate::{error::ModelsError, slot::Slot};
9
10/// Counts the number of slots in a slot range [a, b)
11///
12/// # Arguments
13/// * `a`: starting slot (included)
14/// * `b`: ending slot (excluded)
15/// * `thread_count`: number of threads
16pub fn slot_count_in_range(a: Slot, b: Slot, thread_count: u8) -> Result<u64, ModelsError> {
17    b.period
18        .checked_sub(a.period)
19        .ok_or(ModelsError::TimeOverflowError)?
20        .checked_mul(thread_count as u64)
21        .ok_or(ModelsError::TimeOverflowError)?
22        .checked_add(b.thread as u64)
23        .ok_or(ModelsError::TimeOverflowError)?
24        .checked_sub(a.thread as u64)
25        .ok_or(ModelsError::TimeOverflowError)
26}
27
28/// Gets timestamp in milliseconds for given slot.
29///
30/// # Arguments
31/// * `thread_count`: number of threads.
32/// * `t0`: time in milliseconds between two periods in the same thread.
33/// * `slot`: the considered slot.
34pub fn get_block_slot_timestamp(
35    thread_count: u8,
36    t0: MassaTime,
37    genesis_timestamp: MassaTime,
38    slot: Slot,
39) -> Result<MassaTime, ModelsError> {
40    let base: MassaTime = t0
41        .checked_div_u64(thread_count as u64)
42        .map_err(|_| ModelsError::TimeOverflowError)?
43        .checked_mul(slot.thread as u64)
44        .map_err(|_| ModelsError::TimeOverflowError)?;
45    let shift: MassaTime = t0
46        .checked_mul(slot.period)
47        .map_err(|_| ModelsError::TimeOverflowError)?;
48    genesis_timestamp
49        .checked_add(base)
50        .map_err(|_| ModelsError::TimeOverflowError)?
51        .checked_add(shift)
52        .map_err(|_| ModelsError::TimeOverflowError)
53}
54
55/// Returns the thread and block period index of the latest block slot at a given timestamp (inclusive), if any happened
56///
57/// # Arguments
58/// * `thread_count`: number of threads.
59/// * `t0`: time in milliseconds between two periods in the same thread.
60/// * `genesis_timestamp`: when the blockclique first started, in milliseconds.
61/// * `timestamp`: target timestamp in milliseconds.
62pub fn get_latest_block_slot_at_timestamp(
63    thread_count: u8,
64    t0: MassaTime,
65    genesis_timestamp: MassaTime,
66    timestamp: MassaTime,
67) -> Result<Option<Slot>, ModelsError> {
68    if let Ok(time_since_genesis) = timestamp.checked_sub(genesis_timestamp) {
69        let thread: u8 = time_since_genesis
70            .checked_rem_time(t0)?
71            .checked_div_time(t0.checked_div_u64(thread_count as u64)?)?
72            as u8;
73        return Ok(Some(Slot::new(
74            time_since_genesis.checked_div_time(t0)?,
75            thread,
76        )));
77    }
78    Ok(None)
79}
80
81/// Returns the thread and block slot index of the current block slot (inclusive), if any happened yet
82///
83/// # Arguments
84/// * `thread_count`: number of threads.
85/// * `t0`: time in milliseconds between two periods in the same thread.
86/// * `genesis_timestamp`: when the blockclique first started, in milliseconds.
87pub fn get_current_latest_block_slot(
88    thread_count: u8,
89    t0: MassaTime,
90    genesis_timestamp: MassaTime,
91) -> Result<Option<Slot>, ModelsError> {
92    get_latest_block_slot_at_timestamp(thread_count, t0, genesis_timestamp, MassaTime::now())
93}
94
95/// Turns an `MassaTime` range [start, end) with optional start/end to a `Slot` range [start, end) with optional start/end
96///
97/// # Arguments
98/// * `thread_count`: number of threads.
99/// * `t0`: time in milliseconds between two periods in the same thread.
100/// * `genesis_timestamp`: when the blockclique first started, in milliseconds
101/// * `start_time`: optional start time
102/// * `end_time`: optional end time
103/// # Returns
104/// `(Option<Slot>, Option<Slot>)` pair of options representing the start (included) and end (excluded) slots
105/// or `ConsensusError` on error
106pub fn time_range_to_slot_range(
107    thread_count: u8,
108    t0: MassaTime,
109    genesis_timestamp: MassaTime,
110    start_time: Option<MassaTime>,
111    end_time: Option<MassaTime>,
112) -> Result<(Option<Slot>, Option<Slot>), ModelsError> {
113    let start_slot = match start_time {
114        None => None,
115        Some(t) => {
116            let inter_slot = t0.checked_div_u64(thread_count as u64)?;
117            let slot_number: u64 = t
118                .saturating_sub(genesis_timestamp)
119                .checked_add(inter_slot)?
120                .saturating_sub(MassaTime::EPSILON)
121                .checked_div_time(inter_slot)?;
122            Some(Slot::new(
123                slot_number
124                    .checked_div(thread_count as u64)
125                    .ok_or(ModelsError::TimeOverflowError)?,
126                slot_number
127                    .checked_rem(thread_count as u64)
128                    .ok_or(ModelsError::TimeOverflowError)?
129                    .try_into()
130                    .map_err(|_| ModelsError::ThreadOverflowError)?,
131            ))
132        }
133    };
134
135    let end_slot = match end_time {
136        None => None,
137        Some(t) => {
138            let inter_slot = t0.checked_div_u64(thread_count as u64)?;
139            let slot_number: u64 = t
140                .saturating_sub(genesis_timestamp)
141                .checked_add(inter_slot)?
142                .saturating_sub(MassaTime::EPSILON)
143                .checked_div_time(inter_slot)?;
144            Some(Slot::new(
145                slot_number
146                    .checked_div(thread_count as u64)
147                    .ok_or(ModelsError::TimeOverflowError)?,
148                slot_number
149                    .checked_rem(thread_count as u64)
150                    .ok_or(ModelsError::TimeOverflowError)?
151                    .try_into()
152                    .map_err(|_| ModelsError::ThreadOverflowError)?,
153            ))
154        }
155    };
156
157    Ok((start_slot, end_slot))
158}
159
160/// TODO DOC
161pub fn get_closest_slot_to_timestamp(
162    thread_count: u8,
163    t0: MassaTime,
164    genesis_timestamp: MassaTime,
165    timestamp: MassaTime,
166) -> Slot {
167    // get the latest past slot at this timestamp (if any)
168    let latest_past_slot =
169        match get_latest_block_slot_at_timestamp(thread_count, t0, genesis_timestamp, timestamp)
170            .unwrap()
171        {
172            None => return Slot::new(0, 0), // we are before genesis
173            Some(s) => s,
174        };
175
176    // compute the time of the latest past slot
177    let t_latest = get_block_slot_timestamp(
178        thread_count,
179        t0,
180        genesis_timestamp,
181        latest_past_slot
182    ).expect("t_latest computation failed but it should be computable because that slot was obtained with get_latest_block_slot_at_timestamp");
183
184    // compute how much time has passed since that latest slot
185    let delta_t = timestamp.saturating_sub(t_latest);
186
187    // check whether delta_t is lower than half the time difference between two consecutive slots
188    if delta_t
189        .checked_mul(2)
190        .expect("delta_t should be multiplicate by 2")
191        <= t0
192            .checked_div_u64(thread_count as u64)
193            .expect("thread_count should not be 0")
194    {
195        latest_past_slot
196    } else {
197        latest_past_slot
198            .get_next_slot(thread_count)
199            .unwrap_or(latest_past_slot)
200    }
201}
202
203#[cfg(test)]
204mod tests {
205    use super::*;
206    use serial_test::serial;
207
208    #[test]
209    #[serial]
210    fn test_slot_count_in_range() {
211        assert_eq!(
212            slot_count_in_range(Slot::new(100, 3), Slot::new(100, 3), 32).unwrap(),
213            0
214        );
215        assert_eq!(
216            slot_count_in_range(Slot::new(100, 3), Slot::new(100, 5), 32).unwrap(),
217            2
218        );
219        assert_eq!(
220            slot_count_in_range(Slot::new(100, 4), Slot::new(103, 13), 32).unwrap(),
221            105
222        );
223        assert_eq!(
224            slot_count_in_range(Slot::new(100, 13), Slot::new(103, 4), 32).unwrap(),
225            87
226        );
227    }
228
229    #[test]
230    #[serial]
231    fn test_time_range_to_slot_range() {
232        let thread_count = 3u8;
233        let t0: MassaTime = MassaTime::from_millis(30);
234        let genesis_timestamp: MassaTime = MassaTime::from_millis(100);
235        /* slots:   (0, 0)  (0, 1)  (0, 2)  (1, 0)  (1, 1)  (1, 2)  (2, 0)  (2, 1)  (2, 2)
236            time:    100      110     120    130      140    150     160     170     180
237        */
238
239        // time [111, 115) => empty slot range
240        let (out_start, out_end) = time_range_to_slot_range(
241            thread_count,
242            t0,
243            genesis_timestamp,
244            Some(MassaTime::from_millis(111)),
245            Some(MassaTime::from_millis(115)),
246        )
247        .unwrap();
248        assert_eq!(out_start, out_end);
249
250        // time [10, 100) => empty slot range
251        let (out_start, out_end) = time_range_to_slot_range(
252            thread_count,
253            t0,
254            genesis_timestamp,
255            Some(MassaTime::from_millis(10)),
256            Some(MassaTime::from_millis(100)),
257        )
258        .unwrap();
259        assert_eq!(out_start, out_end);
260
261        // time [115, 145) => slots [(0,2), (1,2))
262        let (out_start, out_end) = time_range_to_slot_range(
263            thread_count,
264            t0,
265            genesis_timestamp,
266            Some(MassaTime::from_millis(115)),
267            Some(MassaTime::from_millis(145)),
268        )
269        .unwrap();
270        assert_eq!(out_start, Some(Slot::new(0, 2)));
271        assert_eq!(out_end, Some(Slot::new(1, 2)));
272
273        // time [110, 160) => slots [(0,1), (2,0))
274        let (out_start, out_end) = time_range_to_slot_range(
275            thread_count,
276            t0,
277            genesis_timestamp,
278            Some(MassaTime::from_millis(110)),
279            Some(MassaTime::from_millis(160)),
280        )
281        .unwrap();
282        assert_eq!(out_start, Some(Slot::new(0, 1)));
283        assert_eq!(out_end, Some(Slot::new(2, 0)));
284    }
285
286    #[test]
287    #[serial]
288    fn test_get_closest_slot_to_timestamp() {
289        let thread_count = 3u8;
290        let t0: MassaTime = MassaTime::from_millis(30);
291        let genesis_timestamp: MassaTime = MassaTime::from_millis(100);
292        /* slots:   (0, 0)  (0, 1)  (0, 2)  (1, 0)  (1, 1)  (1, 2)  (2, 0)  (2, 1)  (2, 2)
293            time:    100      110     120    130      140    150     160     170     180
294        */
295        let out_slot = get_closest_slot_to_timestamp(
296            thread_count,
297            t0,
298            genesis_timestamp,
299            MassaTime::from_millis(150),
300        );
301        assert_eq!(out_slot, Slot::new(1, 2));
302    }
303}