use std::collections::HashSet;
use std::hash::Hash;
use std::time::{SystemTime, UNIX_EPOCH};
use super::{Height, Time};
pub const MAX_TIME_ADJUSTMENT: TimeOffset = 70 * 60;
pub const MAX_FUTURE_BLOCK_TIME: Time = 60 * 60 * 2;
pub const MEDIAN_TIME_SPAN: Height = 11;
pub const MIN_TIME_SAMPLES: usize = 5;
pub const MAX_TIME_SAMPLES: usize = 200;
pub type TimeOffset = i64;
pub trait Clock {
fn time(&self) -> Time;
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, Ord, PartialOrd)]
pub struct LocalTime {
millis: u128,
}
impl std::fmt::Display for LocalTime {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.as_secs())
}
}
impl Default for LocalTime {
fn default() -> Self {
Self { millis: 0 }
}
}
impl LocalTime {
pub fn now() -> Self {
Self::from(SystemTime::now())
}
pub const fn from_secs(secs: u64) -> Self {
Self {
millis: secs as u128 * 1000,
}
}
pub fn as_secs(&self) -> Time {
use std::convert::TryInto;
(self.millis / 1000).try_into().unwrap()
}
}
impl From<SystemTime> for LocalTime {
fn from(system: SystemTime) -> Self {
let millis = system.duration_since(UNIX_EPOCH).unwrap().as_millis();
Self { millis }
}
}
impl std::ops::Sub<LocalTime> for LocalTime {
type Output = LocalDuration;
fn sub(self, other: LocalTime) -> LocalDuration {
LocalDuration(self.millis - other.millis)
}
}
impl std::ops::Sub<LocalDuration> for LocalTime {
type Output = LocalTime;
fn sub(self, other: LocalDuration) -> LocalTime {
LocalTime {
millis: self.millis - other.0,
}
}
}
impl std::ops::Add<LocalDuration> for LocalTime {
type Output = LocalTime;
fn add(self, other: LocalDuration) -> LocalTime {
LocalTime {
millis: self.millis + other.0,
}
}
}
#[derive(Debug, Copy, Clone, PartialOrd, Ord, PartialEq, Eq)]
pub struct LocalDuration(u128);
impl LocalDuration {
pub const fn from_secs(secs: u64) -> Self {
Self(secs as u128 * 1000)
}
pub const fn from_millis(millis: u128) -> Self {
Self(millis)
}
}
impl<'a> std::iter::Sum<&'a LocalDuration> for LocalDuration {
fn sum<I: Iterator<Item = &'a LocalDuration>>(iter: I) -> LocalDuration {
let mut total: u128 = 0;
for entry in iter {
total = total
.checked_add(entry.0)
.expect("iter::sum should not overflow");
}
Self(total)
}
}
impl std::ops::Add<LocalDuration> for LocalDuration {
type Output = LocalDuration;
fn add(self, other: LocalDuration) -> LocalDuration {
LocalDuration(self.0 + other.0)
}
}
impl std::ops::Div<u32> for LocalDuration {
type Output = LocalDuration;
fn div(self, other: u32) -> LocalDuration {
LocalDuration(self.0 / other as u128)
}
}
impl From<LocalDuration> for std::time::Duration {
fn from(other: LocalDuration) -> Self {
std::time::Duration::from_millis(other.0 as u64)
}
}
#[derive(Debug, Clone)]
pub struct AdjustedTime<K> {
sources: HashSet<K>,
samples: Vec<TimeOffset>,
offset: TimeOffset,
local_time: LocalTime,
}
impl<K: Eq + Hash> Clock for AdjustedTime<K> {
fn time(&self) -> Time {
self.get()
}
}
impl<K: Hash + Eq> Default for AdjustedTime<K> {
fn default() -> Self {
Self::new(LocalTime::default())
}
}
impl<K: Hash + Eq> AdjustedTime<K> {
pub fn new(local_time: LocalTime) -> Self {
let offset = 0;
let mut samples = Vec::with_capacity(MAX_TIME_SAMPLES);
samples.push(offset);
let sources = HashSet::with_capacity(MAX_TIME_SAMPLES);
Self {
sources,
samples,
offset,
local_time,
}
}
pub fn record_offset(&mut self, source: K, sample: TimeOffset) {
if self.sources.len() == MAX_TIME_SAMPLES {
return;
}
if !self.sources.insert(source) {
return;
}
self.samples.push(sample);
let mut offsets = self.samples.clone();
let count = offsets.len();
offsets.sort();
if count < MIN_TIME_SAMPLES {
return;
}
if count % 2 == 1 {
let median_offset: TimeOffset = offsets[count / 2];
if median_offset.abs() <= MAX_TIME_ADJUSTMENT {
self.offset = median_offset;
} else {
self.offset = 0;
}
#[cfg(feature = "log")]
log::debug!("Time offset adjusted to {} seconds", self.offset);
};
}
pub fn offset(&self) -> TimeOffset {
self.offset
}
pub fn from(&self, time: Time) -> Time {
let adjustment = self.offset;
if adjustment > 0 {
time + adjustment as Time
} else {
time - adjustment.abs() as Time
}
}
pub fn get(&self) -> Time {
self.from(self.local_time.as_secs())
}
pub fn set_local_time(&mut self, time: LocalTime) {
self.local_time = time;
}
pub fn local_time(&self) -> LocalTime {
self.local_time
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::net::SocketAddr;
#[test]
fn test_adjusted_time() {
let mut adjusted_time: AdjustedTime<SocketAddr> = AdjustedTime::default();
assert_eq!(adjusted_time.offset(), 0);
adjusted_time.record_offset(([127, 0, 0, 1], 8333).into(), 42);
assert_eq!(adjusted_time.offset(), 0);
adjusted_time.record_offset(([127, 0, 0, 2], 8333).into(), 47);
assert_eq!(adjusted_time.offset(), 0);
for i in 3.. {
adjusted_time.record_offset(([127, 0, 0, i], 8333).into(), MAX_TIME_ADJUSTMENT + 1);
if adjusted_time.samples.len() >= MIN_TIME_SAMPLES {
break;
}
}
assert_eq!(adjusted_time.offset(), 47);
adjusted_time.record_offset(([127, 0, 0, 5], 8333).into(), MAX_TIME_ADJUSTMENT + 1);
assert_eq!(
adjusted_time.offset(),
47,
"No change when sample count is even"
);
adjusted_time.record_offset(([127, 0, 0, 6], 8333).into(), MAX_TIME_ADJUSTMENT + 1);
assert_eq!(
adjusted_time.offset(),
0,
"A too large time adjustment reverts back to 0",
);
}
#[test]
fn test_adjusted_time_negative() {
use std::time::SystemTime;
let local_time = SystemTime::now().into();
let mut adjusted_time: AdjustedTime<SocketAddr> = AdjustedTime::new(local_time);
assert_eq!(adjusted_time.offset(), 0);
for i in 1..5 {
adjusted_time.record_offset(([127, 0, 0, i], 8333).into(), 96);
}
assert_eq!(adjusted_time.offset(), 96);
assert_eq!(
adjusted_time.from(local_time.as_secs()),
local_time.as_secs() + 96
);
for i in 5..11 {
adjusted_time.record_offset(([127, 0, 0, i], 8333).into(), -96);
}
assert_eq!(adjusted_time.offset(), -96);
assert_eq!(
adjusted_time.from(local_time.as_secs()),
local_time.as_secs() - 96
);
}
#[test]
fn test_adjusted_time_max_samples() {
let mut adjusted_time: AdjustedTime<SocketAddr> = AdjustedTime::default();
assert_eq!(adjusted_time.offset(), 0);
for i in 1..(MAX_TIME_SAMPLES / 2) {
adjusted_time.record_offset(([127, 0, 0, i as u8], 8333).into(), -1);
}
assert_eq!(adjusted_time.offset(), -1);
for i in (MAX_TIME_SAMPLES / 2).. {
adjusted_time.record_offset(([127, 0, 0, i as u8], 8333).into(), 1);
if adjusted_time.samples.len() == MAX_TIME_SAMPLES {
break;
}
}
assert_eq!(adjusted_time.offset(), 0);
adjusted_time.record_offset(([127, 0, 0, 253], 8333).into(), 1);
adjusted_time.record_offset(([127, 0, 0, 254], 8333).into(), 2);
adjusted_time.record_offset(([127, 0, 0, 255], 8333).into(), 3);
assert_eq!(
adjusted_time.sources.len(),
MAX_TIME_SAMPLES,
"Adding a sample after the maximum is reached, has no effect"
);
}
}