use solana_clock::Clock;
use solana_epoch_info::EpochInfo;
use crate::types::{TimeTravelConfig, TimeTravelError};
pub fn calculate_absolute_timestamp_clock(
timestamp_target: u64,
current_updated_at: u64,
slot_time: u64,
epoch_info: &EpochInfo,
) -> Result<Clock, TimeTravelError> {
if timestamp_target < current_updated_at {
return Err(TimeTravelError::PastTimestamp {
target: timestamp_target,
current: current_updated_at,
});
}
if slot_time == 0 {
return Err(TimeTravelError::ZeroSlotTime);
}
let time_jump_in_ms = timestamp_target - current_updated_at;
let time_jump_in_absolute_slots = time_jump_in_ms / slot_time;
let remaining_slots_for_current_epoch = epoch_info.slots_in_epoch - epoch_info.slot_index;
let time_jump_in_epochs = if time_jump_in_absolute_slots >= remaining_slots_for_current_epoch {
(time_jump_in_absolute_slots - remaining_slots_for_current_epoch)
/ epoch_info.slots_in_epoch
} else {
0
};
let time_jump_in_relative_slots = if time_jump_in_epochs == 0 {
epoch_info.slot_index + time_jump_in_absolute_slots
} else {
time_jump_in_absolute_slots - (time_jump_in_epochs * epoch_info.slots_in_epoch)
};
let timestamp_target_seconds = timestamp_target / 1000;
Ok(Clock {
slot: time_jump_in_relative_slots,
epoch_start_timestamp: timestamp_target_seconds as i64,
epoch: epoch_info.epoch + time_jump_in_epochs,
leader_schedule_epoch: 0,
unix_timestamp: timestamp_target_seconds as i64,
})
}
pub fn calculate_absolute_slot_clock(
new_absolute_slot: u64,
current_absolute_slot: u64,
current_updated_at: u64,
slot_time: u64,
epoch_info: &EpochInfo,
) -> Result<Clock, TimeTravelError> {
if new_absolute_slot < current_absolute_slot {
return Err(TimeTravelError::PastSlot {
target: new_absolute_slot,
current: current_absolute_slot,
});
}
let time_jump_in_absolute_slots = new_absolute_slot - current_absolute_slot;
let time_jump_in_ms = time_jump_in_absolute_slots * slot_time;
let timestamp_target = current_updated_at + time_jump_in_ms;
let epoch = new_absolute_slot / epoch_info.slots_in_epoch;
let slot = new_absolute_slot - epoch * epoch_info.slots_in_epoch;
let timestamp_target_seconds = timestamp_target / 1000;
Ok(Clock {
slot,
epoch_start_timestamp: timestamp_target_seconds as i64,
epoch,
leader_schedule_epoch: 0,
unix_timestamp: timestamp_target_seconds as i64,
})
}
pub fn calculate_absolute_epoch_clock(
new_epoch: u64,
current_epoch: u64,
current_absolute_slot: u64,
current_updated_at: u64,
slot_time: u64,
epoch_info: &EpochInfo,
) -> Result<Clock, TimeTravelError> {
if new_epoch < current_epoch {
return Err(TimeTravelError::PastEpoch {
target: new_epoch,
current: current_epoch,
});
}
let new_absolute_slot = new_epoch * epoch_info.slots_in_epoch;
let time_jump_in_absolute_slots = new_absolute_slot.saturating_sub(current_absolute_slot);
let time_jump_in_ms = time_jump_in_absolute_slots * slot_time;
let timestamp_target = current_updated_at + time_jump_in_ms;
let timestamp_target_seconds = timestamp_target / 1000;
Ok(Clock {
slot: 0,
epoch_start_timestamp: timestamp_target_seconds as i64,
epoch: new_epoch,
leader_schedule_epoch: 0,
unix_timestamp: timestamp_target_seconds as i64,
})
}
pub fn calculate_time_travel_clock(
config: &TimeTravelConfig,
current_updated_at: u64,
slot_time: u64,
epoch_info: &EpochInfo,
) -> Result<Clock, TimeTravelError> {
match config {
TimeTravelConfig::AbsoluteTimestamp(timestamp_target) => {
calculate_absolute_timestamp_clock(
*timestamp_target,
current_updated_at,
slot_time,
epoch_info,
)
}
TimeTravelConfig::AbsoluteSlot(new_absolute_slot) => calculate_absolute_slot_clock(
*new_absolute_slot,
epoch_info.absolute_slot,
current_updated_at,
slot_time,
epoch_info,
),
TimeTravelConfig::AbsoluteEpoch(new_epoch) => calculate_absolute_epoch_clock(
*new_epoch,
epoch_info.epoch,
epoch_info.absolute_slot,
current_updated_at,
slot_time,
epoch_info,
),
}
}
#[cfg(test)]
mod tests {
use solana_epoch_info::EpochInfo;
use super::*;
fn create_test_epoch_info(epoch: u64, slot_index: u64, absolute_slot: u64) -> EpochInfo {
EpochInfo {
epoch,
slot_index,
slots_in_epoch: 432_000,
absolute_slot,
block_height: 0,
transaction_count: Some(0),
}
}
#[test]
fn test_calculate_absolute_timestamp_clock_basic() {
let epoch_info = create_test_epoch_info(1, 1000, 433_000);
let current_time = 1_000_000_000; let slot_time = 400; let target_time = current_time + 1_000_000;
let clock =
calculate_absolute_timestamp_clock(target_time, current_time, slot_time, &epoch_info)
.unwrap();
assert_eq!(clock.unix_timestamp, target_time as i64 / 1_000);
assert_eq!(clock.epoch_start_timestamp, target_time as i64 / 1_000);
assert_eq!(clock.epoch, 1); assert_eq!(clock.slot, 1000 + (1_000_000 / 400)); }
#[test]
fn test_calculate_absolute_timestamp_clock_epoch_transition() {
let epoch_info = create_test_epoch_info(1, 431_000, 863_000); let current_time = 1_000_000_000;
let slot_time = 400;
let target_time = current_time + 10_000_000;
let clock =
calculate_absolute_timestamp_clock(target_time, current_time, slot_time, &epoch_info)
.unwrap();
assert_eq!(clock.unix_timestamp, target_time as i64 / 1000);
assert_eq!(clock.epoch_start_timestamp, target_time as i64 / 1000);
assert_eq!(clock.epoch, 1); }
#[test]
fn test_calculate_absolute_timestamp_clock_past() {
let epoch_info = create_test_epoch_info(1, 1000, 433_000);
let current_time = 1_000_000_000;
let slot_time = 400;
let target_time = current_time - 1_000_000;
let result =
calculate_absolute_timestamp_clock(target_time, current_time, slot_time, &epoch_info);
assert!(result.is_err());
assert!(
matches!(result.unwrap_err(), TimeTravelError::PastTimestamp { target, current } if target == target_time && current == current_time)
);
}
#[test]
fn test_calculate_absolute_slot_clock_basic() {
let epoch_info = create_test_epoch_info(1, 1000, 433_000);
let current_time = 1_000_000_000;
let slot_time = 400;
let target_slot = 500_000;
let clock = calculate_absolute_slot_clock(
target_slot,
epoch_info.absolute_slot,
current_time,
slot_time,
&epoch_info,
)
.unwrap();
assert_eq!(clock.slot, target_slot % epoch_info.slots_in_epoch);
assert_eq!(clock.epoch, target_slot / epoch_info.slots_in_epoch);
assert_eq!(
clock.unix_timestamp,
(current_time + (target_slot - epoch_info.absolute_slot) * slot_time) as i64 / 1_000
);
}
#[test]
fn test_calculate_absolute_slot_clock_epoch_boundary() {
let epoch_info = create_test_epoch_info(1, 431_999, 863_999); let current_time = 1_000_000_000;
let slot_time = 400;
let target_slot = 864_000;
let clock = calculate_absolute_slot_clock(
target_slot,
epoch_info.absolute_slot,
current_time,
slot_time,
&epoch_info,
)
.unwrap();
assert_eq!(clock.slot, 0); assert_eq!(clock.epoch, 2); assert_eq!(
clock.unix_timestamp,
(current_time + slot_time) as i64 / 1_000
);
}
#[test]
fn test_calculate_absolute_slot_clock_past() {
let epoch_info = create_test_epoch_info(1, 1000, 433_000);
let current_time = 1_000_000_000;
let slot_time = 400;
let target_slot = 432_000;
let result = calculate_absolute_slot_clock(
target_slot,
epoch_info.absolute_slot,
current_time,
slot_time,
&epoch_info,
);
assert!(result.is_err());
assert!(
matches!(result.unwrap_err(), TimeTravelError::PastSlot { target, current } if target == target_slot && current == epoch_info.absolute_slot)
);
}
#[test]
fn test_calculate_absolute_epoch_clock_basic() {
let epoch_info = create_test_epoch_info(1, 1000, 433_000);
let current_time = 1_000_000_000;
let slot_time = 400;
let target_epoch = 5;
let clock = calculate_absolute_epoch_clock(
target_epoch,
epoch_info.epoch,
epoch_info.absolute_slot,
current_time,
slot_time,
&epoch_info,
)
.unwrap();
assert_eq!(clock.slot, 0); assert_eq!(clock.epoch, target_epoch);
assert_eq!(
clock.unix_timestamp,
(current_time
+ (target_epoch * epoch_info.slots_in_epoch - epoch_info.absolute_slot) * slot_time)
as i64
/ 1_000
);
}
#[test]
fn test_calculate_absolute_epoch_clock_same_epoch() {
let epoch_info = create_test_epoch_info(1, 1000, 433_000);
let current_time = 1_000_000_000;
let slot_time = 400;
let target_epoch = 1;
let clock = calculate_absolute_epoch_clock(
target_epoch,
epoch_info.epoch,
epoch_info.absolute_slot,
current_time,
slot_time,
&epoch_info,
)
.unwrap();
assert_eq!(clock.slot, 0);
assert_eq!(clock.epoch, 1);
assert_eq!(clock.unix_timestamp, current_time as i64 / 1_000);
}
#[test]
fn test_calculate_absolute_epoch_clock_past() {
let epoch_info = create_test_epoch_info(5, 1000, 2_161_000);
let current_time = 1_000_000_000;
let slot_time = 400;
let target_epoch = 1;
let result = calculate_absolute_epoch_clock(
target_epoch,
epoch_info.epoch,
epoch_info.absolute_slot,
current_time,
slot_time,
&epoch_info,
);
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
TimeTravelError::PastEpoch {
target: 1,
current: 5
}
));
}
#[test]
fn test_calculate_time_travel_clock_absolute_timestamp() {
let epoch_info = create_test_epoch_info(1, 1000, 433_000);
let current_time = 1_000_000_000;
let slot_time = 400;
let config = TimeTravelConfig::AbsoluteTimestamp(current_time + 1_000_000);
let clock =
calculate_time_travel_clock(&config, current_time, slot_time, &epoch_info).unwrap();
assert_eq!(
clock.unix_timestamp,
(current_time + 1_000_000) as i64 / 1_000
);
assert_eq!(clock.epoch, 1);
}
#[test]
fn test_calculate_time_travel_clock_absolute_slot() {
let epoch_info = create_test_epoch_info(1, 1000, 433_000);
let current_time = 1_000_000_000;
let slot_time = 400;
let config = TimeTravelConfig::AbsoluteSlot(500_000);
let clock =
calculate_time_travel_clock(&config, current_time, slot_time, &epoch_info).unwrap();
assert_eq!(clock.slot, 500_000 % epoch_info.slots_in_epoch);
assert_eq!(clock.epoch, 500_000 / epoch_info.slots_in_epoch);
}
#[test]
fn test_calculate_time_travel_clock_absolute_epoch() {
let epoch_info = create_test_epoch_info(1, 1000, 433_000);
let current_time = 1_000_000_000;
let slot_time = 400;
let config = TimeTravelConfig::AbsoluteEpoch(5);
let clock =
calculate_time_travel_clock(&config, current_time, slot_time, &epoch_info).unwrap();
assert_eq!(clock.slot, 0);
assert_eq!(clock.epoch, 5);
}
#[test]
fn test_edge_case_zero_slot_time() {
let epoch_info = create_test_epoch_info(1, 1000, 433_000);
let current_time = 1_000_000_000;
let slot_time = 0; let target_time = current_time + 1_000_000;
let result =
calculate_absolute_timestamp_clock(target_time, current_time, slot_time, &epoch_info);
assert!(result.is_err());
assert!(matches!(result.unwrap_err(), TimeTravelError::ZeroSlotTime));
}
#[test]
fn test_edge_case_large_time_jump() {
let epoch_info = create_test_epoch_info(1, 1000, 433_000);
let current_time = 1_000_000_000;
let slot_time = 400;
let target_time = current_time + 1_000_000_000_000;
let clock =
calculate_absolute_timestamp_clock(target_time, current_time, slot_time, &epoch_info)
.unwrap();
assert_eq!(clock.unix_timestamp, target_time as i64 / 1_000);
assert!(clock.epoch > 1); }
#[test]
fn test_edge_case_exact_epoch_boundary() {
let epoch_info = create_test_epoch_info(1, 431_999, 863_999); let current_time = 1_000_000_000;
let slot_time = 400;
let target_time = current_time + slot_time;
let clock =
calculate_absolute_timestamp_clock(target_time, current_time, slot_time, &epoch_info)
.unwrap();
assert_eq!(clock.slot, 432_000); assert_eq!(clock.epoch, 1); }
}