Refactor slot clock to remove underflow

Previously I had used `Instant` to refer to the genesis time.
This commit is contained in:
Paul Hauner 2019-09-21 11:21:47 +10:00
parent 1b497e2e24
commit 8ceb2e3d95
No known key found for this signature in database
GPG Key ID: 303E4494BB28068C
5 changed files with 81 additions and 84 deletions

View File

@ -158,12 +158,11 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
));
// Slot clock
let slot_clock = T::SlotClock::from_eth2_genesis(
let slot_clock = T::SlotClock::new(
spec.genesis_slot,
genesis_state.genesis_time,
Duration::from_secs(genesis_state.genesis_time),
Duration::from_millis(spec.milliseconds_per_slot),
)
.map_err(|_| Error::SlotClockDidNotStart)?;
);
info!(log, "Beacon chain initialized from genesis";
"validator_count" => genesis_state.validators.len(),
@ -202,12 +201,11 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
let state = &p.canonical_head.beacon_state;
let slot_clock = T::SlotClock::from_eth2_genesis(
let slot_clock = T::SlotClock::new(
spec.genesis_slot,
state.genesis_time,
Duration::from_secs(state.genesis_time),
Duration::from_millis(spec.milliseconds_per_slot),
)
.map_err(|_| Error::SlotClockDidNotStart)?;
);
let last_finalized_root = p.canonical_head.beacon_state.finalized_checkpoint.root;
let last_finalized_block = &p.canonical_head.beacon_block;

View File

@ -5,40 +5,27 @@ mod metrics;
mod system_time_slot_clock;
mod testing_slot_clock;
use std::time::{Duration, Instant, SystemTime, SystemTimeError, UNIX_EPOCH};
use std::time::Duration;
pub use crate::system_time_slot_clock::SystemTimeSlotClock;
pub use crate::testing_slot_clock::TestingSlotClock;
pub use metrics::scrape_for_metrics;
pub use types::Slot;
/// A clock that reports the current slot.
///
/// The clock is not required to be monotonically increasing and may go backwards.
pub trait SlotClock: Send + Sync + Sized {
fn from_eth2_genesis(
genesis_slot: Slot,
genesis_seconds: u64,
slot_duration: Duration,
) -> Result<Self, SystemTimeError> {
let duration_between_now_and_unix_epoch = SystemTime::now().duration_since(UNIX_EPOCH)?;
let duration_between_unix_epoch_and_genesis = Duration::from_secs(genesis_seconds);
let genesis_instant = if duration_between_now_and_unix_epoch
< duration_between_unix_epoch_and_genesis
{
Instant::now()
+ (duration_between_unix_epoch_and_genesis - duration_between_now_and_unix_epoch)
} else {
Instant::now()
- (duration_between_now_and_unix_epoch - duration_between_unix_epoch_and_genesis)
};
Ok(Self::new(genesis_slot, genesis_instant, slot_duration))
}
fn new(genesis_slot: Slot, genesis: Instant, slot_duration: Duration) -> Self;
/// Creates a new slot clock where the first slot is `genesis_slot`, genesis occured
/// `genesis_duration` after the `UNIX_EPOCH` and each slot is `slot_duration` apart.
fn new(genesis_slot: Slot, genesis_duration: Duration, slot_duration: Duration) -> Self;
/// Returns the slot at this present time.
fn now(&self) -> Option<Slot>;
fn duration_to_next_slot(&self) -> Option<Duration>;
/// Returns the duration between slots
fn slot_duration(&self) -> Duration;
/// Returns the duration until the next slot.
fn duration_to_next_slot(&self) -> Option<Duration>;
}

View File

@ -1,5 +1,5 @@
use super::SlotClock;
use std::time::{Duration, Instant};
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use types::Slot;
pub use std::time::SystemTimeError;
@ -8,53 +8,60 @@ pub use std::time::SystemTimeError;
#[derive(Clone)]
pub struct SystemTimeSlotClock {
genesis_slot: Slot,
genesis: Instant,
genesis_duration: Duration,
slot_duration: Duration,
}
impl SlotClock for SystemTimeSlotClock {
fn new(genesis_slot: Slot, genesis: Instant, slot_duration: Duration) -> Self {
fn new(genesis_slot: Slot, genesis_duration: Duration, slot_duration: Duration) -> Self {
if slot_duration.as_millis() == 0 {
panic!("SystemTimeSlotClock cannot have a < 1ms slot duration.");
}
Self {
genesis_slot,
genesis,
genesis_duration,
slot_duration,
}
}
fn now(&self) -> Option<Slot> {
let now = Instant::now();
let now = SystemTime::now().duration_since(UNIX_EPOCH).ok()?;
let genesis = self.genesis_duration;
if now < self.genesis {
None
} else {
let slot = Slot::from(
(now.duration_since(self.genesis).as_millis() / self.slot_duration.as_millis())
as u64,
);
if now > genesis {
let since_genesis = now
.checked_sub(genesis)
.expect("Control flow ensures now is greater than genesis");
let slot =
Slot::from((since_genesis.as_millis() / self.slot_duration.as_millis()) as u64);
Some(slot + self.genesis_slot)
} else {
None
}
}
fn duration_to_next_slot(&self) -> Option<Duration> {
let now = Instant::now();
if now < self.genesis {
Some(self.genesis - now)
let now = SystemTime::now().duration_since(UNIX_EPOCH).ok()?;
let genesis = self.genesis_duration;
let slot_start = |slot: Slot| -> Duration {
let slot = slot.as_u64() as u32;
genesis + slot * self.slot_duration
};
if now > genesis {
Some(
slot_start(self.now()? + 1)
.checked_sub(now)
.expect("The next slot cannot start before now"),
)
} else {
let duration_since_genesis = now - self.genesis;
let millis_since_genesis = duration_since_genesis.as_millis();
let millis_per_slot = self.slot_duration.as_millis();
let current_slot = millis_since_genesis / millis_per_slot;
let next_slot = current_slot + 1;
let next_slot =
self.genesis + Duration::from_millis((next_slot * millis_per_slot) as u64);
Some(next_slot.duration_since(now))
Some(
genesis
.checked_sub(now)
.expect("Control flow ensures genesis is greater than or equal to now"),
)
}
}
@ -75,30 +82,28 @@ mod tests {
fn test_slot_now() {
let genesis_slot = Slot::new(0);
let prior_genesis =
|seconds_prior: u64| Instant::now() - Duration::from_secs(seconds_prior);
let prior_genesis = |milliseconds_prior: u64| {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("should get system time")
- Duration::from_millis(milliseconds_prior)
};
let clock =
SystemTimeSlotClock::new(genesis_slot, prior_genesis(0), Duration::from_secs(1));
assert_eq!(clock.now(), Some(Slot::new(0)));
let clock =
SystemTimeSlotClock::new(genesis_slot, prior_genesis(5), Duration::from_secs(1));
SystemTimeSlotClock::new(genesis_slot, prior_genesis(5_000), Duration::from_secs(1));
assert_eq!(clock.now(), Some(Slot::new(5)));
let clock = SystemTimeSlotClock::new(
genesis_slot,
Instant::now() - Duration::from_millis(500),
Duration::from_secs(1),
);
let clock =
SystemTimeSlotClock::new(genesis_slot, prior_genesis(500), Duration::from_secs(1));
assert_eq!(clock.now(), Some(Slot::new(0)));
assert!(clock.duration_to_next_slot().unwrap() < Duration::from_millis(500));
let clock = SystemTimeSlotClock::new(
genesis_slot,
Instant::now() - Duration::from_millis(1_500),
Duration::from_secs(1),
);
let clock =
SystemTimeSlotClock::new(genesis_slot, prior_genesis(1_500), Duration::from_secs(1));
assert_eq!(clock.now(), Some(Slot::new(1)));
assert!(clock.duration_to_next_slot().unwrap() < Duration::from_millis(500));
}
@ -106,18 +111,26 @@ mod tests {
#[test]
#[should_panic]
fn zero_seconds() {
SystemTimeSlotClock::new(Slot::new(0), Instant::now(), Duration::from_secs(0));
SystemTimeSlotClock::new(Slot::new(0), Duration::from_secs(0), Duration::from_secs(0));
}
#[test]
#[should_panic]
fn zero_millis() {
SystemTimeSlotClock::new(Slot::new(0), Instant::now(), Duration::from_millis(0));
SystemTimeSlotClock::new(
Slot::new(0),
Duration::from_secs(0),
Duration::from_millis(0),
);
}
#[test]
#[should_panic]
fn less_than_one_millis() {
SystemTimeSlotClock::new(Slot::new(0), Instant::now(), Duration::from_nanos(999));
SystemTimeSlotClock::new(
Slot::new(0),
Duration::from_secs(0),
Duration::from_nanos(999),
);
}
}

View File

@ -1,6 +1,6 @@
use super::SlotClock;
use std::sync::RwLock;
use std::time::{Duration, Instant};
use std::time::Duration;
use types::Slot;
/// A slot clock where the slot is manually set instead of being determined by the system time.
@ -21,7 +21,7 @@ impl TestingSlotClock {
}
impl SlotClock for TestingSlotClock {
fn new(genesis_slot: Slot, _genesis: Instant, _slot_duration: Duration) -> Self {
fn new(genesis_slot: Slot, _genesis_duration: Duration, _slot_duration: Duration) -> Self {
TestingSlotClock {
slot: RwLock::new(genesis_slot),
}
@ -49,7 +49,9 @@ mod tests {
#[test]
fn test_slot_now() {
let clock = TestingSlotClock::new(Slot::new(10), Instant::now(), Duration::from_secs(0));
let null = Duration::from_secs(0);
let clock = TestingSlotClock::new(Slot::new(10), null, null);
assert_eq!(clock.now(), Some(Slot::new(10)));
clock.set_slot(123);
assert_eq!(clock.now(), Some(Slot::new(123)));

View File

@ -159,14 +159,11 @@ impl<B: BeaconNodeDuties + 'static, S: Signer + 'static, E: EthSpec> Service<B,
};
// build the validator slot clock
let slot_clock = SystemTimeSlotClock::from_eth2_genesis(
let slot_clock = SystemTimeSlotClock::new(
genesis_slot,
genesis_time,
Duration::from_secs(genesis_time),
Duration::from_millis(eth2_config.spec.milliseconds_per_slot),
)
.map_err::<error_chain::Error, _>(|e| {
format!("Unable to start slot clock: {}.", e).into()
})?;
);
/* Generate the duties manager */