Files
darkfi/src/consensus/clock.rs
2023-05-11 00:26:31 +03:00

207 lines
7.2 KiB
Rust

/* 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 <https://www.gnu.org/licenses/>.
*/
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<Url>,
pub genesis_time: Timestamp,
}
impl Clock {
pub fn new(
e_len: Option<u64>,
sl_len: Option<u64>,
tick_len: Option<u64>,
peers: Vec<Url>,
) -> 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<Timestamp> {
//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));
}
}