Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Slot to timestamp mapping matches public explorers #101

Merged
merged 2 commits into from
Jan 25, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
}
}