/* This file is part of DarkFi (https://dark.fi) * * Copyright (C) 2020-2023 Dyne.org foundation * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ use crate::{util::time::Timestamp, Result}; use log::debug; use std::{thread, time::Duration}; use url::Url; pub enum Ticks { GENESIS { e: u64, sl: u64 }, //genesis epoch NEWSLOT { e: u64, sl: u64 }, // new slot NEWEPOCH { e: u64, sl: u64 }, // new epoch TOCKS, //tocks, or slot is ending IDLE, // idle clock state OUTOFSYNC, //clock, and blockchain are out of sync } const BB_SL: u64 = u64::MAX - 1; //big bang slot time (need to be negative value) const BB_E: u64 = 0; //big bang epoch time. #[derive(Debug)] pub struct Clock { pub sl: u64, // relative slot index (zero-based) [0-len[ pub e: u64, // epoch index (zero-based) [0-\inf[ pub tick_len: u64, // tick length in time (seconds) pub sl_len: u64, // slot length in ticks pub e_len: u64, // epoch length in slots pub peers: Vec, pub genesis_time: Timestamp, } impl Clock { pub fn new( e_len: Option, sl_len: Option, tick_len: Option, peers: Vec, ) -> Self { let gt: Timestamp = Timestamp::current_time(); Self { sl: BB_SL, //necessary for genesis slot e: BB_E, tick_len: tick_len.unwrap_or(22), // 22 seconds sl_len: sl_len.unwrap_or(22), // ~8 minutes e_len: e_len.unwrap_or(3), // 24.2 minutes peers, genesis_time: gt, } } pub fn get_sl_len(&self) -> u64 { self.sl_len } pub fn get_e_len(&self) -> u64 { self.e_len } async fn time(&self) -> Result { //TODO (fix) add more than ntp server to time, and take the avg Ok(Timestamp::current_time()) } /// returns time since genesis in seconds. async fn time_to_genesis(&self) -> Timestamp { //TODO this value need to be assigned to kickoff time. let genesis_time = self.genesis_time.0; let abs_time = self.time().await.unwrap(); Timestamp(abs_time.0 - genesis_time) } /// return absolute tick to genesis, and relative tick index in the slot. async fn tick_time(&self) -> (u64, u64, u64) { let time = self.time_to_genesis().await.0; let tick_abs: u64 = time / self.tick_len; let tick_rel: u64 = time % self.tick_len; (time, tick_rel, tick_abs) } /// return true if the clock is at the begining (before 2/3 of the slot). async fn ticking(&self) -> bool { let (abs, rel, _) = self.tick_time().await; debug!(target: "consensus::clock", "abs time to genesis ticks: {}, rel ticks: {}", abs, rel); rel < (self.tick_len) * 2 / 3 } pub async fn sync(&mut self) -> Result<()> { let e = self.epoch_abs().await; let sl = self.slot_relative().await; self.sl = sl; self.e = e; Ok(()) } /// returns absolute zero based slot index async fn slot_abs(&self) -> u64 { let sl_abs = self.tick_time().await.0 / self.sl_len; debug!(target: "consensus::clock", "[slot_abs] slot len: {} - slot abs: {}", self.sl_len, sl_abs); sl_abs } /// returns relative zero based slot index async fn slot_relative(&self) -> u64 { let e_abs = self.slot_abs().await % self.e_len; debug!(target: "consensus::clock", "[slot_relative] slot len: {} - slot relative: {}", self.sl_len, e_abs); e_abs } /// returns absolute zero based epoch index. async fn epoch_abs(&self) -> u64 { let res = self.slot_abs().await / self.e_len; debug!(target: "consensus::clock", "[epoch_abs] epoch len: {} - epoch abs: {}", self.e_len, res); res } /// return the ticks phase with corresponding phase parameters /// /// the Ticks enum can include epoch index, and relative slot index (zero-based) pub async fn ticks(&mut self) -> Ticks { // also debug the failing function. let e = self.epoch_abs().await; let sl = self.slot_relative().await; if self.ticking().await { debug!( target: "consensus::clock", "e/e`: {}/{} sl/sl`: {}/{}, BB_E/BB_SL: {}/{}", e, self.e, sl, self.sl, BB_E, BB_SL ); if e == self.e && e == BB_E && self.sl == BB_SL { self.sl = sl + 1; // 0 self.e = e; // 0 debug!(target: "consensus::clock", "new genesis"); Ticks::GENESIS { e, sl } } else if e == self.e && sl == self.sl + 1 { self.sl = sl; debug!(target: "consensus::clock", "new slot"); Ticks::NEWSLOT { e, sl } } else if e == self.e + 1 && sl == 0 { self.e = e; self.sl = sl; debug!(target: "consensus::clock", "new epoch"); Ticks::NEWEPOCH { e, sl } } else if e == self.e && sl == self.sl { debug!(target: "consensus::clock", "clock is idle"); thread::sleep(Duration::from_millis(100)); Ticks::IDLE } else { debug!(target: "consensus::clock", "clock is out of sync"); //clock is out of sync Ticks::OUTOFSYNC } } else { debug!(target: "consensus::clock", "tocks"); Ticks::TOCKS } } } #[cfg(test)] mod tests { use super::{Clock, Ticks}; use futures::executor::block_on; use std::{thread, time::Duration}; #[test] fn clock_works() { let clock = Clock::new(Some(9), Some(9), Some(9), vec![]); //block th for 3 secs thread::sleep(Duration::from_millis(1000)); let ttg = block_on(clock.time_to_genesis()).0; assert!((1..2).contains(&ttg)); } fn _clock_ticking() { let clock = Clock::new(Some(9), Some(9), Some(9), vec![]); //block th for 3 secs thread::sleep(Duration::from_millis(1000)); assert!(block_on(clock.ticking())); thread::sleep(Duration::from_millis(1000)); assert!(block_on(clock.ticking())); } fn _clock_ticks() { let mut clock = Clock::new(Some(9), Some(9), Some(9), vec![]); // let tick: Ticks = block_on(clock.ticks()); assert!(matches!(tick, Ticks::GENESIS { e: 0, sl: 0 })); thread::sleep(Duration::from_millis(3000)); let tock: Ticks = block_on(clock.ticks()); assert!(matches!(tock, Ticks::TOCKS)); } }