Skip to content

Commit

Permalink
Slot clock based on genesis timestamp and system clock
Browse files Browse the repository at this point in the history
  • Loading branch information
mskrzypkows committed Jul 19, 2024
1 parent 1143431 commit bfdff57
Show file tree
Hide file tree
Showing 8 changed files with 320 additions and 116 deletions.
138 changes: 73 additions & 65 deletions Node/Cargo.lock

Large diffs are not rendered by default.

11 changes: 3 additions & 8 deletions Node/src/ethereum_l1/consensus_layer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,13 @@ impl ConsensusLayer {
Ok(Self { client })
}

pub async fn get_current_epoch(&self) -> Result<u64, Error> {
let header = self.client.get_beacon_header_at_head().await?;
let slot = header.header.message.slot;
Ok(slot / 32)
}

pub async fn get_lookahead(&self, epoch: u64) -> Result<Vec<ProposerDuty>, Error> {
let (_, duties) = self.client.get_proposer_duties(epoch).await?;
tracing::debug!("got duties len: {}", duties.len());
Ok(duties)
}

pub async fn get_genesis_data(&self) -> Result<GenesisDetails, Error> {
pub async fn get_genesis_details(&self) -> Result<GenesisDetails, Error> {
self.client.get_genesis_details().await.map_err(Error::new)
}
}
Expand All @@ -48,7 +43,7 @@ pub mod tests {
async fn test_get_genesis_data() {
let server = setup_server().await;
let cl = ConsensusLayer::new(server.url().as_str()).unwrap();
let genesis_data = cl.get_genesis_data().await.unwrap();
let genesis_data = cl.get_genesis_details().await.unwrap();

assert_eq!(genesis_data.genesis_time, 1590832934);
assert_eq!(
Expand Down
31 changes: 14 additions & 17 deletions Node/src/ethereum_l1/execution_layer.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use super::slot_clock::SlotClock;
use alloy::{
network::{Ethereum, EthereumWallet, NetworkWallet},
primitives::{Address, Bytes, FixedBytes, U256},
Expand All @@ -8,14 +9,14 @@ use alloy::{
};
use anyhow::Error;
use beacon_api_client::ProposerDuty;
use std::rc::Rc;
use std::str::FromStr;

pub struct ExecutionLayer {
rpc_url: reqwest::Url,
wallet: EthereumWallet,
taiko_preconfirming_address: Address,
genesis_timestamp_sec: u64,
slot_duration_sec: u64,
slot_clock: Rc<SlotClock>,
}

sol!(
Expand Down Expand Up @@ -50,8 +51,7 @@ impl ExecutionLayer {
rpc_url: &str,
private_key: &str,
taiko_preconfirming_address: &str,
genesis_timestamp_sec: u64,
slot_duration_sec: u64,
slot_clock: Rc<SlotClock>,
) -> Result<Self, Error> {
let signer = PrivateKeySigner::from_str(private_key)?;
let wallet = EthereumWallet::from(signer);
Expand All @@ -60,8 +60,7 @@ impl ExecutionLayer {
rpc_url: rpc_url.parse()?,
wallet,
taiko_preconfirming_address: taiko_preconfirming_address.parse()?,
genesis_timestamp_sec,
slot_duration_sec,
slot_clock,
})
}

Expand Down Expand Up @@ -94,13 +93,15 @@ impl ExecutionLayer {
let encoded_block_params = Bytes::from(BlockParams::abi_encode_sequence(&block_params));

let tx_list = Bytes::from(tx_list);
let lookahead_set_param: Vec<PreconfTaskManager::LookaheadSetParam> = lookahead_set
let lookahead_set_param = lookahead_set
.iter()
.map(|duty| PreconfTaskManager::LookaheadSetParam {
timestamp: U256::from(self.calculate_slot_timestamp(duty.slot)),
preconfer: Address::ZERO, //TODO: Replace it with a BLS key when the contract is ready.
.map(|duty| {
Ok(PreconfTaskManager::LookaheadSetParam {
timestamp: U256::from(self.slot_clock.start_of(duty.slot)?.as_millis()),
preconfer: Address::ZERO, //TODO: Replace it with a BLS key when the contract is ready.
})
})
.collect();
.collect::<Result<Vec<_>, Error>>()?;

let builder = contract.newBlockProposal(
encoded_block_params,
Expand All @@ -115,25 +116,21 @@ impl ExecutionLayer {
Ok(())
}

fn calculate_slot_timestamp(&self, slot: u64) -> u64 {
self.genesis_timestamp_sec + slot * self.slot_duration_sec
}

#[cfg(test)]
pub fn new_from_pk(
rpc_url: reqwest::Url,
private_key: elliptic_curve::SecretKey<k256::Secp256k1>,
) -> Result<Self, Error> {
let signer = PrivateKeySigner::from_signing_key(private_key.into());
let wallet = EthereumWallet::from(signer);
let clock = SlotClock::new(0u64, 0u64, 12u64, 32u64);

Ok(Self {
rpc_url,
wallet,
taiko_preconfirming_address: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2" // some random address for test
.parse()?,
genesis_timestamp_sec: 0,
slot_duration_sec: 12,
slot_clock: Rc::new(clock),
})
}

Expand Down
19 changes: 16 additions & 3 deletions Node/src/ethereum_l1/mod.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
pub mod consensus_layer;
pub mod execution_layer;
pub mod slot_clock;

use consensus_layer::ConsensusLayer;
use execution_layer::ExecutionLayer;
use slot_clock::SlotClock;
use std::rc::Rc;

pub struct EthereumL1 {
pub slot_clock: Rc<SlotClock>,
pub consensus_layer: ConsensusLayer,
pub execution_layer: ExecutionLayer,
}
Expand All @@ -16,17 +20,26 @@ impl EthereumL1 {
taiko_preconfirming_address: &str,
consensus_rpc_url: &str,
slot_duration_sec: u64,
slots_per_epoch: u64,
) -> Result<Self, anyhow::Error> {
let consensus_layer = ConsensusLayer::new(consensus_rpc_url)?;
let genesis_data = consensus_layer.get_genesis_data().await?;
let genesis_details = consensus_layer.get_genesis_details().await?;
let slot_clock = Rc::new(SlotClock::new(
0u64,
genesis_details.genesis_time,
slot_duration_sec,
slots_per_epoch,
));

let execution_layer = ExecutionLayer::new(
execution_rpc_url,
private_key,
taiko_preconfirming_address,
genesis_data.genesis_time,
slot_duration_sec,
slot_clock.clone(),
)?;

Ok(Self {
slot_clock,
consensus_layer,
execution_layer,
})
Expand Down
170 changes: 170 additions & 0 deletions Node/src/ethereum_l1/slot_clock.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
#![allow(dead_code)] // TODO remove for production
use anyhow::Error;
use std::time::{Duration, SystemTime, UNIX_EPOCH};

pub type Slot = u64;
pub type Epoch = u64;

/// Determines the present slot based upon a manually-incremented UNIX timestamp.
/// based on: https://github.com/sigp/lighthouse/blob/stable/common/slot_clock/src/manual_slot_clock.rs
pub struct SlotClock {
genesis_slot: Slot,
/// Duration from UNIX epoch to genesis.
genesis_duration: Duration,
/// Duration from UNIX epoch to right now.
// current_time: Arc<RwLock<Duration>>,
/// The length of each slot.
slot_duration: Duration,
slots_per_epoch: u64,
}

impl SlotClock {
pub fn new(
genesis_slot: Slot,
genesis_timestamp_sec: u64,
slot_duration_sec: u64,
slots_per_epoch: u64,
) -> Self {
Self {
genesis_slot,
genesis_duration: Duration::from_secs(genesis_timestamp_sec),
slot_duration: Duration::from_secs(slot_duration_sec),
slots_per_epoch,
}
}

fn duration_to_next_slot(&self) -> Result<Duration, Error> {
let now = SystemTime::now().duration_since(UNIX_EPOCH)?;
self.duration_to_next_slot_from(now)
}

pub fn get_current_slot(&self) -> Result<Slot, Error> {
let now = SystemTime::now().duration_since(UNIX_EPOCH)?;
self.slot_of(now)
}

/// Returns the duration between `now` and the start of the next slot.
pub fn duration_to_next_slot_from(&self, now: Duration) -> Result<Duration, Error> {
if now < self.genesis_duration {
Ok(self
.genesis_duration
.checked_sub(now)
.ok_or(anyhow::anyhow!(
"duration_to_next_slot_from: Subtraction overflow"
))?)
} else {
self.duration_to_slot(self.slot_of(now)? + 1, now)
}
}

fn slot_of(&self, now: Duration) -> Result<Slot, Error> {
let genesis = self.genesis_duration;

if now >= genesis {
let since_genesis = now
.checked_sub(genesis)
.ok_or(anyhow::anyhow!("slot_of: Subtraction overflow"))?;
let slot =
Slot::from((since_genesis.as_millis() / self.slot_duration.as_millis()) as u64);
Ok(slot + self.genesis_slot)
} else {
Err(anyhow::anyhow!("slot_of: now is less than genesis"))
}
}

/// Returns the duration from `now` until the start of `slot`.
///
/// Will return `None` if `now` is later than the start of `slot`.
pub fn duration_to_slot(&self, slot: Slot, now: Duration) -> Result<Duration, Error> {
self.start_of(slot)?
.checked_sub(now)
.ok_or(anyhow::anyhow!("duration_to_slot: Subtraction overflow"))
}

/// Returns the duration between UNIX epoch and the start of `slot`.
pub fn start_of(&self, slot: Slot) -> Result<Duration, Error> {
let slot = slot
.checked_sub(self.genesis_slot)
.ok_or(anyhow::anyhow!("start_of: Slot is less than genesis slot"))?
.try_into()?;
let unadjusted_slot_duration = self
.slot_duration
.checked_mul(slot)
.ok_or(anyhow::anyhow!("start_of: Multiplication overflow"))?;

self.genesis_duration
.checked_add(unadjusted_slot_duration)
.ok_or(anyhow::anyhow!("start_of: Addition overflow"))
}

/// Calculates the current epoch from the genesis time and current time.
pub fn get_current_epoch(&self) -> Result<Epoch, Error> {
let now = SystemTime::now().duration_since(UNIX_EPOCH)?;
let slot = self.slot_of(now)?;
Ok(slot / self.slots_per_epoch)
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_duration_to_next_slot() {
let genesis_slot = Slot::from(0u64);
let slot_clock = SlotClock::new(genesis_slot, 0, 12, 32);

let now = Duration::from_secs(10);
let duration_to_next_slot = slot_clock.duration_to_next_slot_from(now).unwrap();
assert_eq!(duration_to_next_slot, Duration::from_secs(2));
}

#[test]
fn test_slot_of() {
let genesis_slot = Slot::from(0u64);
let slot_clock = SlotClock::new(genesis_slot, 0, 12, 32);

let now = Duration::from_secs(25);
let slot = slot_clock.slot_of(now).unwrap();
assert_eq!(slot, Slot::from(2u64));
}

#[test]
fn test_duration_to_slot() {
let genesis_slot = Slot::from(0u64);
let slot_clock = SlotClock::new(genesis_slot, 0, 12, 32);

let now = Duration::from_secs(10);
let slot = Slot::from(2u64);
let duration_to_slot = slot_clock.duration_to_slot(slot, now).unwrap();
assert_eq!(duration_to_slot, Duration::from_secs(14));
}

#[test]
fn test_start_of() {
let genesis_slot = Slot::from(0u64);
let slot_clock = SlotClock::new(genesis_slot, 0, 12, 32);

let start_of_slot = slot_clock.start_of(Slot::from(3u64)).unwrap();
assert_eq!(start_of_slot, Duration::from_secs(36));
}

#[test]
fn test_get_current_slot() {
let genesis_slot = Slot::from(0u64);
let slot_clock = SlotClock::new(genesis_slot, 1721387493, 12, 32);

let current_slot = slot_clock.get_current_slot().unwrap();
println!("current_slot: {}", current_slot);
assert!(current_slot > genesis_slot);
}

#[test]
fn test_get_current_epoch() {
let genesis_slot = Slot::from(0u64);
let slot_clock = SlotClock::new(genesis_slot, 1721387493, 12, 32);

let current_epoch = slot_clock.get_current_epoch().unwrap();
assert!(current_epoch > 0);
}
}
1 change: 1 addition & 0 deletions Node/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ async fn main() -> Result<(), Error> {
&config.taiko_preconfirming_address,
&config.l1_beacon_url,
config.l1_slot_duration_sec,
config.l1_slots_per_epoch,
)
.await?;
let mev_boost = mev_boost::MevBoost::new(&config.mev_boost_url);
Expand Down
Loading

0 comments on commit bfdff57

Please sign in to comment.