Skip to content

Commit

Permalink
feat: Implement multi-era timestamp calculation (#155)
Browse files Browse the repository at this point in the history
  • Loading branch information
scarmuega authored Feb 19, 2022
1 parent e62a88a commit 1b8ab2d
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 62 deletions.
2 changes: 1 addition & 1 deletion src/mapper/prelude.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ impl EventWriter {

pub fn compute_timestamp(&self, slot: u64) -> Option<u64> {
match &self.utils.time {
Some(provider) => provider.slot_to_wallclock(slot).ok(),
Some(provider) => provider.slot_to_wallclock(slot).into(),
_ => None,
}
}
Expand Down
18 changes: 16 additions & 2 deletions src/utils/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use crate::{
model::Event,
utils::{
bech32::{Bech32Config, Bech32Provider},
time::{NaiveConfig as TimeConfig, NaiveProvider as NaiveTime},
time::NaiveProvider as NaiveTime,
},
};

Expand Down Expand Up @@ -50,6 +50,10 @@ impl SwallowResult for Result<(), Error> {
/// values.
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ChainWellKnownInfo {
pub byron_slot_length: u32,
pub byron_known_slot: u64,
pub byron_known_hash: String,
pub byron_known_time: u64,
pub shelley_slot_length: u32,
pub shelley_known_slot: u64,
pub shelley_known_hash: String,
Expand All @@ -61,6 +65,11 @@ impl ChainWellKnownInfo {
/// Hardcoded values for mainnet
pub fn mainnet() -> Self {
ChainWellKnownInfo {
byron_slot_length: 20,
byron_known_slot: 0,
byron_known_time: 1506203091,
byron_known_hash: "f0f7892b5c333cffc4b3c4344de48af4cc63f55e44936196f365a9ef2244134f"
.to_string(),
shelley_slot_length: 1,
shelley_known_slot: 4492800,
shelley_known_hash: "aa83acbf5904c0edfe4d79b3689d3d00fcfc553cf360fd2229b98d464c28e9de"
Expand All @@ -73,6 +82,11 @@ impl ChainWellKnownInfo {
/// Hardcoded values for testnet
pub fn testnet() -> Self {
ChainWellKnownInfo {
byron_slot_length: 20,
byron_known_slot: 1031,
byron_known_time: 1564020236,
byron_known_hash: "388a82f053603f3552717d61644a353188f2d5500f4c6354cc1ad27a36a7ea91"
.to_string(),
shelley_slot_length: 1,
shelley_known_slot: 1598400,
shelley_known_hash: "02b1c561715da9e540411123a6135ee319b02f60b9a11a603d3305556c04329f"
Expand Down Expand Up @@ -111,7 +125,7 @@ impl Utils {
// TODO: refactor this using the builder pattern
pub fn new(well_known: ChainWellKnownInfo, cursor: Option<cursor::Provider>) -> Self {
Self {
time: NaiveTime::new(TimeConfig::from_well_known(&well_known)).into(),
time: NaiveTime::new(well_known.clone()).into(),
bech32: Bech32Provider::new(Bech32Config::from_well_known(&well_known)),
cursor,
well_known,
Expand Down
111 changes: 52 additions & 59 deletions src/utils/time.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,12 @@
//!
//! Common operations to deal with blockchain time and wallclock conversions

use serde::Deserialize;

use crate::{utils::ChainWellKnownInfo, Error};
use crate::utils::ChainWellKnownInfo;

/// Abstraction available to stages to deal with blockchain time conversions
pub(crate) trait TimeProvider {
/// Maps between slots and wallclock
fn slot_to_wallclock(&self, slot: u64) -> Result<u64, Error>;
}

#[derive(Deserialize, Clone)]
pub struct NaiveConfig {
pub slot_length: u32,
pub start_slot: u64,
pub start_timestamp: u64,
}

impl NaiveConfig {
pub(crate) fn from_well_known(info: &ChainWellKnownInfo) -> Self {
Self {
slot_length: info.shelley_slot_length,
start_slot: info.shelley_known_slot,
start_timestamp: info.shelley_known_time,
}
}
fn slot_to_wallclock(&self, slot: u64) -> u64;
}

/// A naive, standalone implementation of a time provider
Expand All @@ -36,82 +17,94 @@ impl NaiveConfig {
/// logic from a well-known configured point in the chain, assuming homogeneous
/// slot length from that point forward.
#[derive(Clone)]
pub(crate) struct NaiveProvider(NaiveConfig);
pub(crate) struct NaiveProvider(ChainWellKnownInfo);

impl NaiveProvider {
pub fn new(config: NaiveConfig) -> Self {
pub fn new(config: ChainWellKnownInfo) -> Self {
NaiveProvider(config)
}
}

#[inline]
fn compute_linear_timestamp(
known_slot: u64,
known_time: u64,
slot_length: u64,
query_slot: u64,
) -> u64 {
known_time + (query_slot - known_slot) * slot_length
}

impl TimeProvider for NaiveProvider {
fn slot_to_wallclock(&self, slot: u64) -> Result<u64, Error> {
fn slot_to_wallclock(&self, slot: u64) -> u64 {
let NaiveProvider(config) = self;

if slot < config.start_slot {
return Err(
"naive time provider can't compute wallclock for slots prior to start_slot".into(),
);
if slot < config.shelley_known_slot {
compute_linear_timestamp(
config.byron_known_slot,
config.byron_known_time,
config.byron_slot_length as u64,
slot,
)
} else {
compute_linear_timestamp(
config.shelley_known_slot,
config.shelley_known_time,
config.shelley_slot_length as u64,
slot,
)
}

let total_delta_secs = (slot - config.start_slot) * config.slot_length as u64;

let out = config.start_timestamp + total_delta_secs;

Ok(out)
}
}

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

fn shelley_mainnet() -> NaiveConfig {
NaiveConfig {
slot_length: 1,
start_slot: 4492800,
start_timestamp: 1596059091,
}
}

fn shelley_testnet() -> NaiveConfig {
NaiveConfig {
slot_length: 1,
start_slot: 1598400,
start_timestamp: 1595967616,
}
}

fn assert_slot_matches_timestamp(provider: &NaiveProvider, slot: u64, ts: u64) {
let wallclock = provider
.slot_to_wallclock(slot)
.expect("unable to compute wallclock");
let wallclock = provider.slot_to_wallclock(slot);

assert_eq!(wallclock, ts);
}

#[test]
fn naive_provider_matches_mainnet_values() {
let provider = NaiveProvider::new(shelley_mainnet());
let provider = NaiveProvider::new(ChainWellKnownInfo::mainnet());

// Byron start, value copied from:
// https://explorer.cardano.org/en/block?id=f0f7892b5c333cffc4b3c4344de48af4cc63f55e44936196f365a9ef2244134f
assert_slot_matches_timestamp(&provider, 0, 1506203091);

// Byron middle, value copied from:
// https://explorer.cardano.org/en/block?id=c1b57d58761af4dc3c6bdcb3542170cec6db3c81e551cd68012774d1c38129a3
assert_slot_matches_timestamp(&provider, 2160007, 1549403231);

// value copied from:
// Shelley start, value copied from:
// https://explorer.cardano.org/en/block?id=aa83acbf5904c0edfe4d79b3689d3d00fcfc553cf360fd2229b98d464c28e9de
assert_slot_matches_timestamp(&provider, 4492800, 1596059091);

// value copied from:
// Shelly middle, value copied from:
// https://explorer.cardano.org/en/block?id=ca60833847d0e70a1adfa6b7f485766003cf7d96d28d481c20d4390f91b76d68
assert_slot_matches_timestamp(&provider, 51580240, 1643146531);
}

#[test]
fn naive_provider_matches_testnet_values() {
let provider = NaiveProvider::new(shelley_testnet());
let provider = NaiveProvider::new(ChainWellKnownInfo::testnet());

// Byron start, value copied from:
// https://explorer.cardano-testnet.iohkdev.io/en/block?id=388a82f053603f3552717d61644a353188f2d5500f4c6354cc1ad27a36a7ea91
assert_slot_matches_timestamp(&provider, 1031, 1564020236);

// Byron middle, value copied from:
// https://explorer.cardano-testnet.iohkdev.io/en/block?id=66102c0b80e1eebc9cddf9cab43c1bf912e4f1963d6f3b8ff948952f8409e779
assert_slot_matches_timestamp(&provider, 561595, 1575231516);

// value copied from:
// Shelley start, value copied from:
// https://explorer.cardano-testnet.iohkdev.io/en/block?id=02b1c561715da9e540411123a6135ee319b02f60b9a11a603d3305556c04329f
assert_slot_matches_timestamp(&provider, 1598400, 1595967616);

// value copied from:
// Shelley middle, value copied from:
// https://explorer.cardano-testnet.iohkdev.io/en/block?id=26a1b5a649309c0c8dd48f3069d9adea5a27edf5171dfb941b708acaf2d76dcd
assert_slot_matches_timestamp(&provider, 48783593, 1643152809);
}
Expand Down

0 comments on commit 1b8ab2d

Please sign in to comment.