use massa_time::MassaTime;
use std::convert::TryInto;
use crate::{error::ModelsError, slot::Slot};
pub fn slot_count_in_range(a: Slot, b: Slot, thread_count: u8) -> Result<u64, ModelsError> {
b.period
.checked_sub(a.period)
.ok_or(ModelsError::TimeOverflowError)?
.checked_mul(thread_count as u64)
.ok_or(ModelsError::TimeOverflowError)?
.checked_add(b.thread as u64)
.ok_or(ModelsError::TimeOverflowError)?
.checked_sub(a.thread as u64)
.ok_or(ModelsError::TimeOverflowError)
}
pub fn get_block_slot_timestamp(
thread_count: u8,
t0: MassaTime,
genesis_timestamp: MassaTime,
slot: Slot,
) -> Result<MassaTime, ModelsError> {
let base: MassaTime = t0
.checked_div_u64(thread_count as u64)
.map_err(|_| ModelsError::TimeOverflowError)?
.checked_mul(slot.thread as u64)
.map_err(|_| ModelsError::TimeOverflowError)?;
let shift: MassaTime = t0
.checked_mul(slot.period)
.map_err(|_| ModelsError::TimeOverflowError)?;
genesis_timestamp
.checked_add(base)
.map_err(|_| ModelsError::TimeOverflowError)?
.checked_add(shift)
.map_err(|_| ModelsError::TimeOverflowError)
}
pub fn get_latest_block_slot_at_timestamp(
thread_count: u8,
t0: MassaTime,
genesis_timestamp: MassaTime,
timestamp: MassaTime,
) -> Result<Option<Slot>, ModelsError> {
if let Ok(time_since_genesis) = timestamp.checked_sub(genesis_timestamp) {
let thread: u8 = time_since_genesis
.checked_rem_time(t0)?
.checked_div_time(t0.checked_div_u64(thread_count as u64)?)?
as u8;
return Ok(Some(Slot::new(
time_since_genesis.checked_div_time(t0)?,
thread,
)));
}
Ok(None)
}
pub fn get_current_latest_block_slot(
thread_count: u8,
t0: MassaTime,
genesis_timestamp: MassaTime,
) -> Result<Option<Slot>, ModelsError> {
get_latest_block_slot_at_timestamp(thread_count, t0, genesis_timestamp, MassaTime::now())
}
pub fn time_range_to_slot_range(
thread_count: u8,
t0: MassaTime,
genesis_timestamp: MassaTime,
start_time: Option<MassaTime>,
end_time: Option<MassaTime>,
) -> Result<(Option<Slot>, Option<Slot>), ModelsError> {
let start_slot = match start_time {
None => None,
Some(t) => {
let inter_slot = t0.checked_div_u64(thread_count as u64)?;
let slot_number: u64 = t
.saturating_sub(genesis_timestamp)
.checked_add(inter_slot)?
.saturating_sub(MassaTime::EPSILON)
.checked_div_time(inter_slot)?;
Some(Slot::new(
slot_number
.checked_div(thread_count as u64)
.ok_or(ModelsError::TimeOverflowError)?,
slot_number
.checked_rem(thread_count as u64)
.ok_or(ModelsError::TimeOverflowError)?
.try_into()
.map_err(|_| ModelsError::ThreadOverflowError)?,
))
}
};
let end_slot = match end_time {
None => None,
Some(t) => {
let inter_slot = t0.checked_div_u64(thread_count as u64)?;
let slot_number: u64 = t
.saturating_sub(genesis_timestamp)
.checked_add(inter_slot)?
.saturating_sub(MassaTime::EPSILON)
.checked_div_time(inter_slot)?;
Some(Slot::new(
slot_number
.checked_div(thread_count as u64)
.ok_or(ModelsError::TimeOverflowError)?,
slot_number
.checked_rem(thread_count as u64)
.ok_or(ModelsError::TimeOverflowError)?
.try_into()
.map_err(|_| ModelsError::ThreadOverflowError)?,
))
}
};
Ok((start_slot, end_slot))
}
pub fn get_closest_slot_to_timestamp(
thread_count: u8,
t0: MassaTime,
genesis_timestamp: MassaTime,
timestamp: MassaTime,
) -> Slot {
let latest_past_slot =
match get_latest_block_slot_at_timestamp(thread_count, t0, genesis_timestamp, timestamp)
.unwrap()
{
None => return Slot::new(0, 0), Some(s) => s,
};
let t_latest = get_block_slot_timestamp(
thread_count,
t0,
genesis_timestamp,
latest_past_slot
).expect("t_latest computation failed but it should be computable because that slot was obtained with get_latest_block_slot_at_timestamp");
let delta_t = timestamp.saturating_sub(t_latest);
if delta_t
.checked_mul(2)
.expect("delta_t should be multiplicate by 2")
<= t0
.checked_div_u64(thread_count as u64)
.expect("thread_count should not be 0")
{
latest_past_slot
} else {
latest_past_slot
.get_next_slot(thread_count)
.unwrap_or(latest_past_slot)
}
}
#[cfg(test)]
mod tests {
use super::*;
use serial_test::serial;
#[test]
#[serial]
fn test_slot_count_in_range() {
assert_eq!(
slot_count_in_range(Slot::new(100, 3), Slot::new(100, 3), 32).unwrap(),
0
);
assert_eq!(
slot_count_in_range(Slot::new(100, 3), Slot::new(100, 5), 32).unwrap(),
2
);
assert_eq!(
slot_count_in_range(Slot::new(100, 4), Slot::new(103, 13), 32).unwrap(),
105
);
assert_eq!(
slot_count_in_range(Slot::new(100, 13), Slot::new(103, 4), 32).unwrap(),
87
);
}
#[test]
#[serial]
fn test_time_range_to_slot_range() {
let thread_count = 3u8;
let t0: MassaTime = MassaTime::from_millis(30);
let genesis_timestamp: MassaTime = MassaTime::from_millis(100);
let (out_start, out_end) = time_range_to_slot_range(
thread_count,
t0,
genesis_timestamp,
Some(MassaTime::from_millis(111)),
Some(MassaTime::from_millis(115)),
)
.unwrap();
assert_eq!(out_start, out_end);
let (out_start, out_end) = time_range_to_slot_range(
thread_count,
t0,
genesis_timestamp,
Some(MassaTime::from_millis(10)),
Some(MassaTime::from_millis(100)),
)
.unwrap();
assert_eq!(out_start, out_end);
let (out_start, out_end) = time_range_to_slot_range(
thread_count,
t0,
genesis_timestamp,
Some(MassaTime::from_millis(115)),
Some(MassaTime::from_millis(145)),
)
.unwrap();
assert_eq!(out_start, Some(Slot::new(0, 2)));
assert_eq!(out_end, Some(Slot::new(1, 2)));
let (out_start, out_end) = time_range_to_slot_range(
thread_count,
t0,
genesis_timestamp,
Some(MassaTime::from_millis(110)),
Some(MassaTime::from_millis(160)),
)
.unwrap();
assert_eq!(out_start, Some(Slot::new(0, 1)));
assert_eq!(out_end, Some(Slot::new(2, 0)));
}
#[test]
#[serial]
fn test_get_closest_slot_to_timestamp() {
let thread_count = 3u8;
let t0: MassaTime = MassaTime::from_millis(30);
let genesis_timestamp: MassaTime = MassaTime::from_millis(100);
let out_slot = get_closest_slot_to_timestamp(
thread_count,
t0,
genesis_timestamp,
MassaTime::from_millis(150),
);
assert_eq!(out_slot, Slot::new(1, 2));
}
}