use datasize::DataSize;
use casper_types::{BlockHeader, TimeDiff, Timestamp};
#[derive(DataSize, Debug)]
pub struct MaxTtl(TimeDiff);
impl MaxTtl {
pub fn new(max_ttl: TimeDiff) -> Self {
MaxTtl(max_ttl)
}
pub fn value(&self) -> TimeDiff {
self.0
}
pub fn ttl_elapsed(&self, vantage: Timestamp, rearview: Timestamp) -> bool {
rearview < vantage.saturating_sub(self.0)
}
pub fn synced_to_ttl(
&self,
latest_switch_block_timestamp: Timestamp,
highest_orphaned_block_header: &BlockHeader,
) -> bool {
if highest_orphaned_block_header.is_genesis() {
true
} else {
self.ttl_elapsed(
latest_switch_block_timestamp,
highest_orphaned_block_header.timestamp(),
)
}
}
}
impl From<TimeDiff> for MaxTtl {
fn from(value: TimeDiff) -> Self {
MaxTtl::new(value)
}
}
#[cfg(test)]
mod tests {
use casper_types::{testing::TestRng, TestBlockBuilder, TimeDiff, Timestamp};
use crate::types::MaxTtl;
const SUB_MAX_TTL: TimeDiff = TimeDiff::from_millis(1);
const MAX_TTL: TimeDiff = TimeDiff::from_millis(2);
fn assert_ttl(
higher: Timestamp,
lower: Timestamp,
max_ttl: TimeDiff,
elapsed_expected: bool,
msg: &str,
) {
let max_ttl: MaxTtl = max_ttl.into();
let elapsed = max_ttl.ttl_elapsed(higher, lower);
assert_eq!(elapsed, elapsed_expected, "{}", msg);
}
#[test]
fn should_elapse() {
let higher = Timestamp::now();
let lower = higher
.saturating_sub(MAX_TTL)
.saturating_sub(TimeDiff::from_millis(1));
assert_ttl(
higher,
lower,
MAX_TTL,
true,
"1 milli over ttl should have elapsed",
);
}
#[test]
fn should_not_elapse() {
let higher = Timestamp::now();
let lower = higher.saturating_sub(SUB_MAX_TTL);
assert_ttl(higher, lower, MAX_TTL, false, "should not have elapsed");
}
#[test]
fn should_not_elapse_with_equal_timestamps() {
let timestamp = Timestamp::now();
assert_ttl(
timestamp,
timestamp,
MAX_TTL,
false,
"equal timestamps should not be elapsed",
);
}
#[test]
fn should_not_elapse_on_cusp() {
let higher = Timestamp::now();
let lower = higher.saturating_sub(MAX_TTL);
assert_ttl(
higher,
lower,
MAX_TTL,
false,
"should not have elapsed exactly on cusp of ttl",
);
}
#[test]
fn should_not_err() {
let higher = Timestamp::now();
let lower = higher.saturating_sub(SUB_MAX_TTL);
let max_ttl: MaxTtl = MAX_TTL.into();
let elapsed = max_ttl.ttl_elapsed(lower, higher);
assert!(
!elapsed,
"can't have elapsed because timestamps are chronologically reversed (programmer error)"
);
}
fn assert_sync_to_ttl(is_genesis: bool, ttl_synced_expected: bool, msg: &str) {
let max_ttl: MaxTtl = MAX_TTL.into();
let rng = &mut TestRng::new();
let (latest_switch_block_timestamp, highest_orphaned_block_header) = if is_genesis {
let block = TestBlockBuilder::new()
.era(0)
.height(0)
.switch_block(true)
.build(rng);
let timestamp = Timestamp::random(rng);
(timestamp, block.header().clone())
} else {
let block = TestBlockBuilder::new()
.era(1)
.height(1)
.switch_block(false)
.build(rng);
let mut timestamp = block.timestamp().saturating_add(max_ttl.value());
if ttl_synced_expected {
timestamp = timestamp.saturating_add(TimeDiff::from_millis(1))
}
(timestamp, block.header().clone())
};
let synced = max_ttl.synced_to_ttl(
latest_switch_block_timestamp,
&highest_orphaned_block_header.into(),
);
assert_eq!(synced, ttl_synced_expected, "{}", msg);
}
#[test]
fn should_handle_genesis_special_case() {
assert_sync_to_ttl(
true,
true,
"genesis should always satisfy sync to ttl requirement",
);
}
#[test]
fn should_be_synced_to_ttl() {
assert_sync_to_ttl(false, true, "should be sync'd to ttl");
}
#[test]
fn should_not_be_synced_to_ttl() {
assert_sync_to_ttl(false, false, "should not be sync'd to ttl");
}
}