ソースを参照

consensus: created helper struct for all time related calculations, for easier access by the runtime

aggstam 3 年 前
コミット
01dad53892

+ 2 - 2
src/consensus/proto/protocol_sync.rs

@@ -165,7 +165,7 @@ impl ProtocolSync {
             // in case they go out of sync and become a none-consensus node.
             if self.consensus_mode {
                 let lock = self.state.read().await;
-                let current = lock.consensus.current_slot();
+                let current = lock.consensus.time_keeper.current_slot();
                 let participating = lock.consensus.participating;
                 if participating.is_some() {
                     let slot = participating.unwrap();
@@ -313,7 +313,7 @@ impl ProtocolSync {
             // in case they go out of sync and become a none-consensus node.
             if self.consensus_mode {
                 let lock = self.state.read().await;
-                let current = lock.consensus.current_slot();
+                let current = lock.consensus.time_keeper.current_slot();
                 let participating = lock.consensus.participating;
                 if participating.is_some() {
                     let slot = participating.unwrap();

+ 1 - 1
src/consensus/proto/protocol_sync_consensus.rs

@@ -94,7 +94,7 @@ impl ProtocolSyncConsensus {
             // Extra validations can be added here.
             let lock = self.state.read().await;
             let bootstrap_slot = lock.consensus.bootstrap_slot;
-            let current_slot = lock.consensus.current_slot();
+            let current_slot = lock.consensus.time_keeper.current_slot();
             let mut forks = vec![];
             for fork in &lock.consensus.forks {
                 forks.push(fork.clone().into());

+ 21 - 69
src/consensus/state.rs

@@ -16,9 +16,6 @@
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
 
-use std::time::Duration;
-
-use chrono::{NaiveDateTime, Utc};
 use darkfi_sdk::{
     crypto::{constants::MERKLE_DEPTH, MerkleNode},
     incrementalmerkletree::bridgetree::BridgeTree,
@@ -36,8 +33,12 @@ use super::{
     Block, BlockProposal, Float10,
 };
 use crate::{
-    blockchain::Blockchain, net, tx::Transaction, util::time::Timestamp, wallet::WalletPtr, Error,
-    Result,
+    blockchain::Blockchain,
+    net,
+    tx::Transaction,
+    util::time::{TimeKeeper, Timestamp},
+    wallet::WalletPtr,
+    Error, Result,
 };
 
 use std::{
@@ -53,8 +54,8 @@ pub struct ConsensusState {
     pub blockchain: Blockchain,
     /// Network bootstrap timestamp
     pub bootstrap_ts: Timestamp,
-    /// Genesis block creation timestamp
-    pub genesis_ts: Timestamp,
+    /// Helper structure to calculate time related operations
+    pub time_keeper: TimeKeeper,
     /// Genesis block hash
     pub genesis_block: blake3::Hash,
     /// Total sum of initial staking coins
@@ -99,13 +100,15 @@ impl ConsensusState {
         genesis_data: blake3::Hash,
         initial_distribution: u64,
         single_node: bool,
-    ) -> Result<Self> {
+    ) -> Self {
         let genesis_block = Block::genesis_block(genesis_ts, genesis_data).blockhash();
-        Ok(Self {
+        let time_keeper =
+            TimeKeeper::new(genesis_ts, constants::EPOCH_LENGTH as u64, constants::SLOT_TIME);
+        Self {
             wallet,
             blockchain,
             bootstrap_ts,
-            genesis_ts,
+            time_keeper,
             genesis_block,
             initial_distribution,
             single_node,
@@ -122,29 +125,7 @@ impl ConsensusState {
             coins: vec![],
             coins_tree: BridgeTree::<MerkleNode, MERKLE_DEPTH>::new(constants::EPOCH_LENGTH * 100),
             nullifiers: vec![],
-        })
-    }
-
-    /// Calculates current epoch.
-    pub fn current_epoch(&self) -> u64 {
-        self.slot_epoch(self.current_slot())
-    }
-
-    /// Calculates the epoch of the provided slot.
-    /// Epoch duration is configured using the `EPOCH_LENGTH` value.
-    pub fn slot_epoch(&self, slot: u64) -> u64 {
-        slot / constants::EPOCH_LENGTH as u64
-    }
-
-    /// Calculates current slot, based on elapsed time from the genesis block.
-    /// Slot duration is configured using the `SLOT_TIME` constant.
-    pub fn current_slot(&self) -> u64 {
-        self.genesis_ts.elapsed() / constants::SLOT_TIME
-    }
-
-    /// Calculates the relative number of the provided slot.
-    pub fn relative_slot(&self, slot: u64) -> u64 {
-        slot % constants::EPOCH_LENGTH as u64
+        }
     }
 
     /// Finds the last slot a proposal or block was generated.
@@ -168,44 +149,15 @@ impl ConsensusState {
         Ok(last_slot)
     }
 
-    /// Calculates seconds until next Nth slot starting time.
-    /// Slots duration is configured using the SLOT_TIME constant.
-    pub fn next_n_slot_start(&self, n: u64) -> Duration {
-        assert!(n > 0);
-        let start_time = NaiveDateTime::from_timestamp_opt(self.genesis_ts.0, 0).unwrap();
-        let current_slot = self.current_slot() + n;
-        let next_slot_start =
-            (current_slot * constants::SLOT_TIME) + (start_time.timestamp() as u64);
-        let next_slot_start = NaiveDateTime::from_timestamp_opt(next_slot_start as i64, 0).unwrap();
-        let current_time = NaiveDateTime::from_timestamp_opt(Utc::now().timestamp(), 0).unwrap();
-        let diff = next_slot_start - current_time;
-
-        Duration::new(diff.num_seconds().try_into().unwrap(), 0)
-    }
-
-    /// Calculate slots until next Nth epoch.
-    /// Epoch duration is configured using the EPOCH_LENGTH value.
-    pub fn slots_to_next_n_epoch(&self, n: u64) -> u64 {
-        assert!(n > 0);
-        let slots_till_next_epoch =
-            constants::EPOCH_LENGTH as u64 - self.relative_slot(self.current_slot());
-        ((n - 1) * constants::EPOCH_LENGTH as u64) + slots_till_next_epoch
-    }
-
-    /// Calculates seconds until next Nth epoch starting time.
-    pub fn next_n_epoch_start(&self, n: u64) -> Duration {
-        self.next_n_slot_start(self.slots_to_next_n_epoch(n))
-    }
-
     /// Set participating slot to next.
     pub fn set_participating(&mut self) -> Result<()> {
-        self.participating = Some(self.current_slot() + 1);
+        self.participating = Some(self.time_keeper.current_slot() + 1);
         Ok(())
     }
 
     /// Generate current slot checkpoint
     fn generate_slot_checkpoint(&mut self, sigma1: pallas::Base, sigma2: pallas::Base) {
-        let slot = self.current_slot();
+        let slot = self.time_keeper.current_slot();
         let eta = self.get_eta();
         info!(target: "consensus::state", "generate_slot_checkpoint: slot: {:?}, eta: {:?}", slot, eta);
         let checkpoint = SlotCheckpoint { slot, eta, sigma1, sigma2 };
@@ -214,7 +166,7 @@ impl ConsensusState {
 
     // Initialize node lead coins and set current epoch and eta.
     pub async fn init_coins(&mut self) -> Result<()> {
-        self.epoch = self.current_epoch();
+        self.epoch = self.time_keeper.current_epoch();
         self.coins = self.create_coins().await?;
         self.update_forks_checkpoints();
         Ok(())
@@ -228,7 +180,7 @@ impl ConsensusState {
         sigma2: pallas::Base,
     ) -> Result<bool> {
         self.generate_slot_checkpoint(sigma1, sigma2);
-        let epoch = self.current_epoch();
+        let epoch = self.time_keeper.current_epoch();
         if epoch <= self.epoch {
             return Ok(false)
         }
@@ -313,7 +265,7 @@ impl ConsensusState {
                 //let stake = self.initial_distribution;
                 let c = LeadCoin::new(
                     0,
-                    self.current_slot(),
+                    self.time_keeper.current_slot(),
                     epoch_secrets.secret_keys[0].inner(),
                     epoch_secrets.merkle_roots[0],
                     0,
@@ -472,7 +424,7 @@ impl ConsensusState {
                 sigma1,
                 sigma2,
                 self.get_eta(),
-                pallas::Base::from(self.current_slot()),
+                pallas::Base::from(self.time_keeper.current_slot()),
             );
 
             if first_winning && !won {
@@ -613,7 +565,7 @@ impl ConsensusState {
     /// Auxillary function to check if node has seen current or previous slot checkpoints.
     /// This check ensures that either the slots exist in memory or node has seen the finalization of these slots.
     pub fn slot_checkpoints_is_empty(&self) -> bool {
-        let current_slot = self.current_slot();
+        let current_slot = self.time_keeper.current_slot();
         if self.get_slot_checkpoint(current_slot).is_ok() {
             return false
         }

+ 1 - 1
src/consensus/task/consensus_sync.rs

@@ -36,7 +36,7 @@ use crate::{
 /// so it can immediately start proposing proposals.
 pub async fn consensus_sync_task(p2p: P2pPtr, state: ValidatorStatePtr) -> Result<bool> {
     info!(target: "consensus::consensus_sync", "Starting consensus state sync...");
-    let current_slot = state.read().await.consensus.current_slot();
+    let current_slot = state.read().await.consensus.time_keeper.current_slot();
     // Loop through connected channels
     let channels_map = p2p.channels().lock().await;
     let values = channels_map.values();

+ 15 - 15
src/consensus/task/proposal.rs

@@ -46,7 +46,7 @@ pub async fn proposal_task(
         info!(target: "consensus::proposal", "consensus: Waiting for network bootstrap: {} seconds", diff);
         sleep(diff as u64).await;
     } else {
-        let mut sleep_time = state.read().await.consensus.next_n_slot_start(1);
+        let mut sleep_time = state.read().await.consensus.time_keeper.next_n_slot_start(1);
         let sync_offset = Duration::new(constants::FINAL_SYNC_DUR, 0);
         loop {
             if sleep_time > sync_offset {
@@ -55,7 +55,7 @@ pub async fn proposal_task(
             }
             info!(target: "consensus::proposal", "consensus: Waiting for next slot ({:?})", sleep_time);
             sleep(sleep_time.as_secs()).await;
-            sleep_time = state.read().await.consensus.next_n_slot_start(1);
+            sleep_time = state.read().await.consensus.time_keeper.next_n_slot_start(1);
         }
         info!(target: "consensus::proposal", "consensus: Waiting for finalization sync period ({:?})", sleep_time);
         sleep(sleep_time.as_secs()).await;
@@ -104,13 +104,13 @@ pub async fn proposal_task(
         }
 
         // Record epoch we start the consensus loop
-        let start_epoch = state.read().await.consensus.current_epoch();
+        let start_epoch = state.read().await.consensus.time_keeper.current_epoch();
 
         // Start executing consensus
         consensus_loop(consensus_p2p.clone(), sync_p2p.clone(), state.clone(), ex.clone()).await;
 
         // Reset retries counter if more epochs have passed than sync retries duration
-        let break_epoch = state.read().await.consensus.current_epoch();
+        let break_epoch = state.read().await.consensus.time_keeper.current_epoch();
         if (break_epoch - start_epoch) > constants::SYNC_RETRIES_DURATION {
             retries = 0;
         }
@@ -156,7 +156,7 @@ async fn consensus_loop(
             warn!(
                 target: "consensus::proposal",
                 "consensus: Node missed slot {} due to proposal processing, resyncing...",
-                state.read().await.consensus.current_slot()
+                state.read().await.consensus.time_keeper.current_slot()
             );
             break
         }
@@ -167,7 +167,7 @@ async fn consensus_loop(
             warn!(
                 target: "consensus::proposal",
                 "consensus: Node missed slot {} due to finalizated blocks processing, resyncing...",
-                state.read().await.consensus.current_slot()
+                state.read().await.consensus.time_keeper.current_slot()
             );
             break
         }
@@ -181,12 +181,12 @@ async fn consensus_loop(
 /// Returns flag in case node needs to resync.
 async fn propose_period(consensus_p2p: P2pPtr, state: ValidatorStatePtr) -> bool {
     // Node sleeps until next slot
-    let seconds_next_slot = state.read().await.consensus.next_n_slot_start(1).as_secs();
+    let seconds_next_slot = state.read().await.consensus.time_keeper.next_n_slot_start(1).as_secs();
     info!(target: "consensus::proposal", "consensus: Waiting for next slot ({} sec)", seconds_next_slot);
     sleep(seconds_next_slot).await;
 
     // Keep a record of slot to verify if next slot got skipped during processing
-    let processing_slot = state.read().await.consensus.current_slot();
+    let processing_slot = state.read().await.consensus.time_keeper.current_slot();
 
     // Retrieve slot sigmas
     let (sigma1, sigma2) = state.write().await.consensus.sigmas();
@@ -228,12 +228,12 @@ async fn propose_period(consensus_p2p: P2pPtr, state: ValidatorStatePtr) -> bool
     };
 
     // Node checks if it missed finalization period due to proposal creation
-    let next_slot_start = state.read().await.consensus.next_n_slot_start(1);
+    let next_slot_start = state.read().await.consensus.time_keeper.next_n_slot_start(1);
     if next_slot_start.as_secs() <= constants::FINAL_SYNC_DUR {
         warn!(
             target: "consensus::proposal",
             "consensus: Node missed slot {} finalization period due to proposal creation, resyncing...",
-            state.read().await.consensus.current_slot()
+            state.read().await.consensus.time_keeper.current_slot()
         );
         return true
     }
@@ -268,7 +268,7 @@ async fn propose_period(consensus_p2p: P2pPtr, state: ValidatorStatePtr) -> bool
     }
 
     // Verify node didn't skip next slot
-    processing_slot != state.read().await.consensus.current_slot()
+    processing_slot != state.read().await.consensus.time_keeper.current_slot()
 }
 
 /// async function to wait and execute consensus protocol finalization period.
@@ -279,7 +279,7 @@ async fn finalization_period(
     ex: Arc<smol::Executor<'_>>,
 ) -> bool {
     // Node sleeps until finalization sync period starts
-    let next_slot_start = state.read().await.consensus.next_n_slot_start(1);
+    let next_slot_start = state.read().await.consensus.time_keeper.next_n_slot_start(1);
     if next_slot_start.as_secs() > constants::FINAL_SYNC_DUR {
         let seconds_sync_period =
             (next_slot_start - Duration::new(constants::FINAL_SYNC_DUR, 0)).as_secs();
@@ -289,13 +289,13 @@ async fn finalization_period(
         warn!(
             target: "consensus::proposal",
             "consensus: Node missed slot {} finalization period due to proposals processing, resyncing...",
-            state.read().await.consensus.current_slot()
+            state.read().await.consensus.time_keeper.current_slot()
         );
         return true
     }
 
     // Keep a record of slot to verify if next slot got skipped during processing
-    let completed_slot = state.read().await.consensus.current_slot();
+    let completed_slot = state.read().await.consensus.time_keeper.current_slot();
 
     // Check if any forks can be finalized
     match state.write().await.chain_finalization().await {
@@ -334,5 +334,5 @@ async fn finalization_period(
     }
 
     // Verify node didn't skip next slot
-    completed_slot != state.read().await.consensus.current_slot()
+    completed_slot != state.read().await.consensus.time_keeper.current_slot()
 }

+ 21 - 11
src/consensus/validator.rs

@@ -131,7 +131,7 @@ impl ValidatorState {
             genesis_data,
             initial_distribution,
             single_node,
-        )?;
+        );
 
         // -----NATIVE WASM CONTRACTS-----
         // This is the current place where native contracts are being deployed.
@@ -170,7 +170,12 @@ impl ValidatorState {
         let blockchain_overlay = BlockchainOverlay::new(&blockchain)?;
         for nc in native_contracts {
             info!(target: "consensus::validator", "Deploying {} with ContractID {}", nc.0, nc.1);
-            let mut runtime = Runtime::new(&nc.2[..], blockchain_overlay.clone(), nc.1)?;
+            let mut runtime = Runtime::new(
+                &nc.2[..],
+                blockchain_overlay.clone(),
+                nc.1,
+                consensus.time_keeper.clone(),
+            )?;
             runtime.deploy(&nc.3)?;
             info!(target: "consensus::validator", "Successfully deployed {}", nc.0);
         }
@@ -376,7 +381,7 @@ impl ValidatorState {
             sigma1,
             sigma2,
             eta,
-            pallas::Base::from(self.consensus.current_slot()),
+            pallas::Base::from(self.consensus.time_keeper.current_slot()),
             self.lead_proving_key.as_ref().unwrap(),
             derived_blind,
         );
@@ -385,7 +390,7 @@ impl ValidatorState {
         let secret_key = coin.coin1_sk;
         let header = Header::new(
             prev_hash,
-            self.consensus.slot_epoch(slot),
+            self.consensus.time_keeper.slot_epoch(slot),
             slot,
             Timestamp::current_time(),
             root,
@@ -447,7 +452,7 @@ impl ValidatorState {
         proposal: &BlockProposal,
         coin: Option<(usize, LeadCoin, pallas::Scalar)>,
     ) -> Result<bool> {
-        let current = self.consensus.current_slot();
+        let current = self.consensus.time_keeper.current_slot();
         // Node hasn't started participating
         match self.consensus.participating {
             Some(start) => {
@@ -547,7 +552,7 @@ impl ValidatorState {
             // Validate proposal public value against coin creation slot checkpoint
             let (mu_y, mu_rho) = LeadCoin::election_seeds_u64(
                 self.consensus.get_eta(),
-                self.consensus.current_slot(),
+                self.consensus.time_keeper.current_slot(),
             );
             // y
             let prop_mu_y = lf.public_inputs[constants::PI_MU_Y_INDEX];
@@ -692,7 +697,7 @@ impl ValidatorState {
     /// When fork chain proposals are finalized, the rest of fork chains are removed and all
     /// slot checkpoints are apppended to canonical state.
     pub async fn chain_finalization(&mut self) -> Result<(Vec<BlockInfo>, Vec<SlotCheckpoint>)> {
-        let slot = self.consensus.current_slot();
+        let slot = self.consensus.time_keeper.current_slot();
         info!(target: "consensus::validator", "chain_finalization(): Started finalization check for slot: {}", slot);
         // Set last slot finalization check occured to current slot
         self.consensus.checked_finalization = slot;
@@ -872,7 +877,7 @@ impl ValidatorState {
     /// Validate and append to canonical state received finalized block.
     /// Returns boolean flag indicating already existing block.
     pub async fn receive_finalized_block(&mut self, block: BlockInfo) -> Result<bool> {
-        if block.header.slot > self.consensus.current_slot() {
+        if block.header.slot > self.consensus.time_keeper.current_slot() {
             warn!(target: "consensus::validator", "receive_finalized_block(): Ignoring future block: {}", block.header.slot);
             return Ok(false)
         }
@@ -915,7 +920,7 @@ impl ValidatorState {
     pub async fn receive_sync_blocks(&mut self, blocks: &[BlockInfo]) -> Result<()> {
         let mut new_blocks = vec![];
         for block in blocks {
-            if block.header.slot > self.consensus.current_slot() {
+            if block.header.slot > self.consensus.time_keeper.current_slot() {
                 warn!(target: "consensus::validator", "receive_sync_blocks(): Ignoring future block: {}", block.header.slot);
                 continue
             }
@@ -993,7 +998,12 @@ impl ValidatorState {
             let runtime_key = call.contract_id.to_string();
             if !runtimes.contains_key(&runtime_key) {
                 let wasm = self.blockchain.wasm_bincode.get(call.contract_id)?;
-                let r = Runtime::new(&wasm, blockchain_overlay.clone(), call.contract_id)?;
+                let r = Runtime::new(
+                    &wasm,
+                    blockchain_overlay.clone(),
+                    call.contract_id,
+                    self.consensus.time_keeper.clone(),
+                )?;
                 runtimes.insert(runtime_key.clone(), r);
             }
             let runtime = runtimes.get_mut(&runtime_key).unwrap();
@@ -1140,7 +1150,7 @@ impl ValidatorState {
         info!(target: "consensus::validator", "receive_slot_checkpoints(): Appending slot checkpoints to ledger");
         let mut filtered = vec![];
         for slot_checkpoint in slot_checkpoints {
-            if slot_checkpoint.slot > self.consensus.current_slot() {
+            if slot_checkpoint.slot > self.consensus.time_keeper.current_slot() {
                 warn!(target: "consensus::validator", "receive_slot_checkpoints(): Ignoring future slot checkpoint: {}", slot_checkpoint.slot);
                 continue
             }

+ 5 - 5
src/runtime/import/util.rs

@@ -16,8 +16,6 @@
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
 
-use std::time::SystemTime;
-
 use log::error;
 use wasmer::{FunctionEnvMut, WasmPtr};
 
@@ -151,9 +149,11 @@ pub(crate) fn get_object_size(ctx: FunctionEnvMut<Env>, idx: u32) -> i64 {
     obj.len() as i64
 }
 
-pub(crate) fn get_system_time() -> u64 {
-    match SystemTime::now().duration_since(SystemTime::UNIX_EPOCH) {
-        Ok(t) => t.as_secs(),
+pub(crate) fn get_system_time(ctx: FunctionEnvMut<Env>) -> u64 {
+    let env = ctx.data();
+
+    match env.time_keeper.unix_timestamp() {
+        Ok(t) => t,
         Err(_) => 0,
     }
 }

+ 7 - 2
src/runtime/vm_runtime.rs

@@ -35,7 +35,7 @@ use wasmer_middlewares::{
 };
 
 use super::{import, import::db::DbHandle, memory::MemoryManipulation};
-use crate::{blockchain::BlockchainOverlayPtr, Error, Result};
+use crate::{blockchain::BlockchainOverlayPtr, util::time::TimeKeeper, Error, Result};
 
 /// Name of the wasm linear memory in our guest module
 const MEMORY: &str = "memory";
@@ -92,6 +92,8 @@ pub struct Env {
     pub memory: Option<Memory>,
     /// Object store for transferring memory from the host to VM
     pub objects: RefCell<Vec<Vec<u8>>>,
+    /// Helper structure to calculate time related operations
+    pub time_keeper: TimeKeeper,
 }
 
 impl Env {
@@ -124,6 +126,7 @@ impl Runtime {
         wasm_bytes: &[u8],
         blockchain: BlockchainOverlayPtr,
         contract_id: ContractId,
+        time_keeper: TimeKeeper,
     ) -> Result<Self> {
         info!(target: "runtime::vm_runtime", "Instantiating a new runtime");
         // TODO: Add necessary operators
@@ -171,6 +174,7 @@ impl Runtime {
                 logs,
                 memory: None,
                 objects: RefCell::new(vec![]),
+                time_keeper,
             },
         );
 
@@ -254,8 +258,9 @@ impl Runtime {
                     import::merkle::merkle_add,
                 ),
 
-                "get_system_time_" => Function::new_typed(
+                "get_system_time_" => Function::new_typed_with_env(
                     &mut store,
+                    &ctx,
                     import::util::get_system_time,
                 ),
             }

+ 68 - 5
src/util/time.rs

@@ -16,7 +16,7 @@
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
 
-use std::time::UNIX_EPOCH;
+use std::time::{Duration, UNIX_EPOCH};
 
 use chrono::{NaiveDateTime, Utc};
 use darkfi_serial::{SerialDecodable, SerialEncodable};
@@ -24,6 +24,73 @@ use serde::{Deserialize, Serialize};
 
 use crate::Result;
 
+/// Helper structure providing time related calculations.
+#[derive(Clone)]
+pub struct TimeKeeper {
+    /// Genesis block creation timestamp
+    pub genesis_ts: Timestamp,
+    /// Currently configured epoch duration.
+    pub epoch_length: u64,
+    /// Currently configured slot duration.
+    pub slot_time: u64,
+}
+
+impl TimeKeeper {
+    pub fn new(genesis_ts: Timestamp, epoch_length: u64, slot_time: u64) -> Self {
+        Self { genesis_ts, epoch_length, slot_time }
+    }
+
+    /// Calculates current epoch.
+    pub fn current_epoch(&self) -> u64 {
+        self.slot_epoch(self.current_slot())
+    }
+
+    /// Calculates the epoch of the provided slot.    
+    pub fn slot_epoch(&self, slot: u64) -> u64 {
+        slot / self.epoch_length
+    }
+
+    /// Calculates current slot, based on elapsed time from the genesis block.
+    pub fn current_slot(&self) -> u64 {
+        self.genesis_ts.elapsed() / self.slot_time
+    }
+
+    /// Calculates the relative number of the provided slot.
+    pub fn relative_slot(&self, slot: u64) -> u64 {
+        slot % self.epoch_length
+    }
+
+    /// Calculates seconds until next Nth slot starting time.
+    pub fn next_n_slot_start(&self, n: u64) -> Duration {
+        assert!(n > 0);
+        let start_time = NaiveDateTime::from_timestamp_opt(self.genesis_ts.0, 0).unwrap();
+        let current_slot = self.current_slot() + n;
+        let next_slot_start = (current_slot * self.slot_time) + (start_time.timestamp() as u64);
+        let next_slot_start = NaiveDateTime::from_timestamp_opt(next_slot_start as i64, 0).unwrap();
+        let current_time = NaiveDateTime::from_timestamp_opt(Utc::now().timestamp(), 0).unwrap();
+        let diff = next_slot_start - current_time;
+
+        Duration::new(diff.num_seconds().try_into().unwrap(), 0)
+    }
+
+    /// Calculate slots until next Nth epoch.
+    /// Epoch duration is configured using the EPOCH_LENGTH value.
+    pub fn slots_to_next_n_epoch(&self, n: u64) -> u64 {
+        assert!(n > 0);
+        let slots_till_next_epoch = self.epoch_length - self.relative_slot(self.current_slot());
+        ((n - 1) * self.epoch_length) + slots_till_next_epoch
+    }
+
+    /// Calculates seconds until next Nth epoch starting time.
+    pub fn next_n_epoch_start(&self, n: u64) -> Duration {
+        self.next_n_slot_start(self.slots_to_next_n_epoch(n))
+    }
+
+    pub fn unix_timestamp(&self) -> Result<u64> {
+        Ok(UNIX_EPOCH.elapsed()?.as_secs())
+    }
+}
+
 /// Wrapper struct to represent [`chrono`] UTC timestamps.
 #[derive(
     Clone,
@@ -124,7 +191,3 @@ pub fn timestamp_to_date(timestamp: i64, format: DateFormat) -> String {
         DateFormat::Default => "".to_string(),
     }
 }
-
-pub fn unix_timestamp() -> Result<u64> {
-    Ok(UNIX_EPOCH.elapsed()?.as_secs())
-}