Skip to content

Commit

Permalink
fix: Slot to timestamp mapping matches public explorers (#101)
Browse files Browse the repository at this point in the history
* fix: Slot to timestamp mapping matches public explorers
* docs: Tidy up details
  • Loading branch information
scarmuega authored Jan 25, 2022
1 parent 0fb83e5 commit 876ac56
Show file tree
Hide file tree
Showing 5 changed files with 180 additions and 18 deletions.
62 changes: 47 additions & 15 deletions src/mapper/prelude.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
use crate::{
framework::{Event, EventContext, EventData},
pipelining::StageSender,
utils::time::{NaiveConfig as TimeConfig, NaiveProvider as NaiveTime, TimeProvider},
};
use merge::Merge;
use serde::Deserialize;

use pallas::ouroboros::network::handshake::{MAINNET_MAGIC, TESTNET_MAGIC};
use pallas::ouroboros::network::{
handshake::{MAINNET_MAGIC, TESTNET_MAGIC},
machines::primitives::Point,
};
use serde::Serialize;

use crate::Error;

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ChainWellKnownInfo {
pub shelley_slot_length: u32,
pub shelley_known_slot: u64,
pub shelley_known_hash: String,
pub shelley_known_time: u64,
Expand All @@ -21,22 +26,48 @@ impl ChainWellKnownInfo {
pub fn try_from_magic(magic: u64) -> Result<ChainWellKnownInfo, Error> {
match magic {
MAINNET_MAGIC => Ok(ChainWellKnownInfo {
shelley_known_slot: 4492799,
shelley_slot_length: 1,
shelley_known_slot: 4492800,
shelley_known_hash:
"f8084c61b6a238acec985b59310b6ecec49c0ab8352249afd7268da5cff2a457".to_string(),
shelley_known_time: 1596059071,
"aa83acbf5904c0edfe4d79b3689d3d00fcfc553cf360fd2229b98d464c28e9de".to_string(),
shelley_known_time: 1596059091,
}),
TESTNET_MAGIC => Ok(ChainWellKnownInfo {
shelley_known_slot: 1598399,
shelley_slot_length: 1,
shelley_known_slot: 1598400,
shelley_known_hash:
"7e16781b40ebf8b6da18f7b5e8ade855d6738095ef2f1c58c77e88b6e45997a4".to_string(),
shelley_known_time: 1595967596,
"02b1c561715da9e540411123a6135ee319b02f60b9a11a603d3305556c04329f".to_string(),
shelley_known_time: 1595967616,
}),
_ => Err("can't infer well-known chain infro from specified magic".into()),
}
}
}

// HACK: to glue together legacy config with new time provider
impl From<ChainWellKnownInfo> for TimeConfig {
fn from(other: ChainWellKnownInfo) -> Self {
TimeConfig {
slot_length: other.shelley_slot_length,
start_slot: other.shelley_known_slot,
start_timestamp: other.shelley_known_time,
}
}
}

impl TryFrom<ChainWellKnownInfo> for Point {
type Error = crate::Error;

fn try_from(other: ChainWellKnownInfo) -> Result<Self, Self::Error> {
let out = Point(
other.shelley_known_slot,
hex::decode(other.shelley_known_hash)?,
);

Ok(out)
}
}

#[derive(Deserialize, Clone, Debug, Default)]
pub struct Config {
#[serde(default)]
Expand All @@ -49,24 +80,24 @@ pub struct Config {
pub include_transaction_end_events: bool,
}

#[derive(Clone, Debug)]
#[derive(Clone)]
pub(crate) struct EventWriter {
context: EventContext,
output: StageSender,
chain_info: Option<ChainWellKnownInfo>,
time_provider: Option<NaiveTime>,
pub(crate) config: Config,
}

impl EventWriter {
pub fn new(
output: StageSender,
chain_info: Option<ChainWellKnownInfo>,
well_known: Option<ChainWellKnownInfo>,
config: Config,
) -> Self {
EventWriter {
context: EventContext::default(),
output,
chain_info,
time_provider: well_known.map(|x| NaiveTime::new(x.into())),
config,
}
}
Expand Down Expand Up @@ -98,14 +129,15 @@ impl EventWriter {
EventWriter {
context: extra_context,
output: self.output.clone(),
chain_info: self.chain_info.clone(),
time_provider: self.time_provider.clone(),
config: self.config.clone(),
}
}

pub fn compute_timestamp(&self, slot: u64) -> Option<u64> {
self.chain_info
.as_ref()
.map(|info| info.shelley_known_time + (slot - info.shelley_known_slot))
match &self.time_provider {
Some(provider) => provider.slot_to_wallclock(slot).ok(),
_ => None,
}
}
}
10 changes: 9 additions & 1 deletion src/sources/n2c/run.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::fmt::Debug;

use log::{error, info};

use pallas::{
Expand Down Expand Up @@ -43,9 +45,15 @@ impl BlockLike for Content {
}
}

#[derive(Debug)]
struct ChainObserver(EventWriter);

// workaround to put a stop on excessive debug requirement coming from Pallas
impl Debug for ChainObserver {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("ChainObserver").finish()
}
}

impl ChainObserver {
fn new(writer: EventWriter) -> Self {
Self(writer)
Expand Down
16 changes: 14 additions & 2 deletions src/sources/n2n/run.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use minicbor::data::Tag;
use std::fmt::Debug;

use log::{info, warn};

Expand Down Expand Up @@ -55,9 +56,14 @@ impl BlockLike for Content {
}
}

#[derive(Debug)]
struct Block2EventMapper(EventWriter);

impl Debug for Block2EventMapper {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("Block2EventMapper").finish()
}
}

impl BlockObserver for Block2EventMapper {
fn on_block_received(&self, body: Vec<u8>) -> Result<(), Error> {
let maybe_block = alonzo::BlockWrapper::decode_fragment(&body[..]);
Expand All @@ -80,12 +86,18 @@ impl BlockObserver for Block2EventMapper {
}
}

#[derive(Debug)]
struct ChainObserver {
block_requests: SyncSender<Point>,
event_writer: EventWriter,
}

// workaround to put a stop on excessive debug requirement coming from Pallas
impl Debug for ChainObserver {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("ChainObserver").finish()
}
}

impl Observer<Content> for ChainObserver {
fn on_block(&self, cursor: &Option<Point>, _content: &Content) -> Result<(), Error> {
info!("requesting block fetch for point {:?}", cursor);
Expand Down
2 changes: 2 additions & 0 deletions src/utils/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ use crate::framework::Error;

pub mod throttle;

pub(crate) mod time;

pub(crate) trait SwallowResult {
fn ok_or_warn(self, context: &'static str);
}
Expand Down
108 changes: 108 additions & 0 deletions src/utils/time.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
//! Blockchain time utils
//!
//! Common operations to deal with blockchain time and wallclock conversions

use serde::Deserialize;

use crate::Error;

/// 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,
}

/// A naive, standalone implementation of a time provider
///
/// This time provider doesn't require any external resources other than an
/// initial config. It works by applying simple slot => wallclock conversion
/// 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);

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

impl TimeProvider for NaiveProvider {
fn slot_to_wallclock(&self, slot: u64) -> Result<u64, Error> {
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(),
);
}

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");

assert_eq!(wallclock, ts);
}

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

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

// 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());

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

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

0 comments on commit 876ac56

Please sign in to comment.