feat(traverse): Introduce time helpers (#234)

This commit is contained in:
Santiago Carmuega 2023-03-13 22:55:44 +01:00 committed by GitHub
parent 2612bb1038
commit 8cdde5e9a8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 250 additions and 50 deletions

View file

@ -18,5 +18,8 @@ pallas-codec = { version = "0.18.0", path = "../pallas-codec" }
hex = "0.4.3"
thiserror = "1.0.31"
# TODO: remove once GenesisValue moves into new genesis crate
serde = "1.0.155"
[features]
unstable = []

View file

@ -5,7 +5,7 @@ use pallas_codec::minicbor;
use pallas_crypto::hash::{Hash, Hasher};
use pallas_primitives::{alonzo, babbage, byron};
use crate::{time, Era, Error, MultiEraHeader, OriginalHash};
use crate::{wellknown::GenesisValues, Era, Error, MultiEraHeader, OriginalHash};
impl<'b> MultiEraHeader<'b> {
pub fn decode(tag: u8, subtag: Option<u8>, cbor: &'b [u8]) -> Result<Self, Error> {
@ -56,15 +56,16 @@ impl<'b> MultiEraHeader<'b> {
pub fn slot(&self) -> u64 {
match self {
MultiEraHeader::EpochBoundary(x) => {
time::byron_epoch_slot_to_absolute(x.consensus_data.epoch_id, 0)
}
MultiEraHeader::AlonzoCompatible(x) => x.header_body.slot,
MultiEraHeader::Babbage(x) => x.header_body.slot,
MultiEraHeader::Byron(x) => time::byron_epoch_slot_to_absolute(
x.consensus_data.0.epoch,
x.consensus_data.0.slot,
),
MultiEraHeader::EpochBoundary(x) => {
let genesis = GenesisValues::default();
genesis.relative_slot_to_absolute(x.consensus_data.epoch_id, 0)
}
MultiEraHeader::Byron(x) => {
let genesis = GenesisValues::default();
genesis.relative_slot_to_absolute(x.consensus_data.0.epoch, x.consensus_data.0.slot)
}
}
}

View file

@ -30,6 +30,9 @@ pub mod tx;
pub mod withdrawals;
pub mod witnesses;
// TODO: move to genesis crate
pub mod wellknown;
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
#[non_exhaustive]
pub enum Era {

View file

@ -1,32 +1,230 @@
// TODO: is it safe to hardcode these values?
const WELLKNOWN_SLOT_LENGTH: u64 = 20; // 20 secs
const WELLKNOWN_EPOCH_LENGTH: u64 = 5 * 24 * 60 * 60; // 5 days
use crate::{wellknown::GenesisValues, MultiEraBlock};
pub fn byron_epoch_slot_to_absolute(epoch: u64, sub_epoch_slot: u64) -> u64 {
((epoch * WELLKNOWN_EPOCH_LENGTH) / WELLKNOWN_SLOT_LENGTH) + sub_epoch_slot
pub type Epoch = u64;
pub type Slot = u64;
pub type SubSlot = u64;
#[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
}
#[inline]
fn compute_era_epoch(era_slot: Slot, era_slot_length: u64, era_epoch_length: u64) -> (Epoch, Slot) {
assert!(
era_epoch_length > 0,
"epoch length needs to be greater than zero"
);
let epoch = (era_slot * era_slot_length) / era_epoch_length;
let reminder = era_slot % era_epoch_length;
(epoch, reminder)
}
pub fn compute_absolute_slot_within_era(
sub_era_epoch: Epoch,
sub_epoch_slot: Slot,
era_epoch_length: u32,
era_slot_length: u32,
) -> u64 {
((sub_era_epoch * era_epoch_length as u64) / era_slot_length as u64) + sub_epoch_slot
}
impl GenesisValues {
pub fn shelley_start_epoch(&self) -> Epoch {
let (epoch, _) = compute_era_epoch(
self.shelley_known_slot,
self.byron_slot_length as u64,
self.byron_epoch_length as u64,
);
epoch
}
pub fn slot_to_wallclock(&self, slot: u64) -> u64 {
if slot < self.shelley_known_slot {
compute_linear_timestamp(
self.byron_known_slot,
self.byron_known_time,
self.byron_slot_length as u64,
slot,
)
} else {
compute_linear_timestamp(
self.shelley_known_slot,
self.shelley_known_time,
self.shelley_slot_length as u64,
slot,
)
}
}
pub fn absolute_slot_to_relative(&self, slot: u64) -> (u64, u64) {
if slot < self.shelley_known_slot {
compute_era_epoch(
slot,
self.byron_slot_length as u64,
self.byron_epoch_length as u64,
)
} else {
let era_slot = slot - self.shelley_known_slot;
let (era_epoch, reminder) = compute_era_epoch(
era_slot,
self.shelley_slot_length as u64,
self.shelley_epoch_length as u64,
);
(self.shelley_start_epoch() + era_epoch, reminder)
}
}
pub fn relative_slot_to_absolute(&self, epoch: Epoch, slot: Slot) -> Slot {
let shelley_start_epoch = self.shelley_start_epoch();
if epoch < shelley_start_epoch {
compute_absolute_slot_within_era(
epoch,
slot,
self.byron_epoch_length,
self.byron_slot_length,
)
} else {
let byron_slots = compute_absolute_slot_within_era(
shelley_start_epoch,
0,
self.byron_epoch_length,
self.byron_slot_length,
);
let shelley_slots = compute_absolute_slot_within_era(
epoch - shelley_start_epoch,
slot,
self.shelley_epoch_length,
self.shelley_slot_length,
);
byron_slots + shelley_slots
}
}
}
impl<'a> MultiEraBlock<'a> {
pub fn epoch(&self, genesis: &GenesisValues) -> (Epoch, SubSlot) {
match self {
MultiEraBlock::EpochBoundary(x) => (x.header.consensus_data.epoch_id, 0),
MultiEraBlock::Byron(x) => (
x.header.consensus_data.0.epoch,
x.header.consensus_data.0.slot,
),
MultiEraBlock::AlonzoCompatible(x, _) => {
genesis.absolute_slot_to_relative(x.header.header_body.slot)
}
MultiEraBlock::Babbage(x) => {
genesis.absolute_slot_to_relative(x.header.header_body.slot)
}
}
}
/// Computes the unix timestamp for the slot of the tx
pub fn wallclock(&self, genesis: &GenesisValues) -> u64 {
let slot = self.slot();
genesis.slot_to_wallclock(slot)
}
}
#[cfg(test)]
mod tests {
use pallas_codec::minicbor;
use super::*;
use crate::MultiEraBlock;
use crate::{byron::Block, time::byron_epoch_slot_to_absolute};
fn assert_slot_matches_timestamp(
genesis: &GenesisValues,
slot: u64,
expected_ts: u64,
expected_epoch: u64,
expected_epoch_slot: u64,
) {
let wallclock = genesis.slot_to_wallclock(slot);
assert_eq!(wallclock, expected_ts);
type BlockWrapper = (u16, Block);
let (epoch, epoch_slot) = genesis.absolute_slot_to_relative(slot);
assert_eq!(epoch, expected_epoch);
assert_eq!(epoch_slot, expected_epoch_slot);
}
#[test]
fn knwon_slot_matches() {
fn calc_matches_mainnet_values() {
let genesis = GenesisValues::mainnet();
// Byron start, value copied from:
// https://explorer.cardano.org/en/block?id=f0f7892b5c333cffc4b3c4344de48af4cc63f55e44936196f365a9ef2244134f
assert_slot_matches_timestamp(&genesis, 0, 1506203091, 0, 0);
// Byron middle, value copied from:
// https://explorer.cardano.org/en/block?id=c1b57d58761af4dc3c6bdcb3542170cec6db3c81e551cd68012774d1c38129a3
assert_slot_matches_timestamp(&genesis, 2160007, 1549403231, 100, 7);
// Shelley start, value copied from:
// https://explorer.cardano.org/en/block?id=aa83acbf5904c0edfe4d79b3689d3d00fcfc553cf360fd2229b98d464c28e9de
assert_slot_matches_timestamp(&genesis, 4492800, 1596059091, 208, 0);
// Shelly middle, value copied from:
// https://explorer.cardano.org/en/block?id=ca60833847d0e70a1adfa6b7f485766003cf7d96d28d481c20d4390f91b76d68
assert_slot_matches_timestamp(&genesis, 51580240, 1643146531, 316, 431440);
// Shelly middle, value copied from:
// https://explorer.cardano.org/en/block?id=ec07c6f74f344062db5340480e5b364aac8bb40768d184c1b1491e05c5bec4c4
assert_slot_matches_timestamp(&genesis, 54605026, 1646171317, 324, 226);
}
#[test]
fn calc_matches_testnet_values() {
let genesis = GenesisValues::testnet();
// Byron origin, value copied from:
// https://explorer.cardano-testnet.iohkdev.io/en/block?id=8f8602837f7c6f8b8867dd1cbc1842cf51a27eaed2c70ef48325d00f8efb320f
assert_slot_matches_timestamp(&genesis, 0, 1564010416, 0, 0);
// Byron start, value copied from:
// https://explorer.cardano-testnet.iohkdev.io/en/block?id=388a82f053603f3552717d61644a353188f2d5500f4c6354cc1ad27a36a7ea91
assert_slot_matches_timestamp(&genesis, 1031, 1564031036, 0, 1031);
// Byron middle, value copied from:
// https://explorer.cardano-testnet.iohkdev.io/en/block?id=66102c0b80e1eebc9cddf9cab43c1bf912e4f1963d6f3b8ff948952f8409e779
assert_slot_matches_timestamp(&genesis, 561595, 1575242316, 25, 129595);
// Shelley start, value copied from:
// https://explorer.cardano-testnet.iohkdev.io/en/block?id=02b1c561715da9e540411123a6135ee319b02f60b9a11a603d3305556c04329f
assert_slot_matches_timestamp(&genesis, 1598400, 1595967616, 74, 0);
// Shelley middle, value copied from:
// https://explorer.cardano-testnet.iohkdev.io/en/block?id=26a1b5a649309c0c8dd48f3069d9adea5a27edf5171dfb941b708acaf2d76dcd
assert_slot_matches_timestamp(&genesis, 48783593, 1643152809, 183, 97193);
}
#[test]
fn known_slot_matches() {
// TODO: expand this test to include more test blocks
let block_idx = 1;
let block_str = include_str!("../../test_data/byron1.block");
let block_cbor = hex::decode(block_str).expect("invalid hex");
let block = MultiEraBlock::decode(&block_cbor).expect("invalid cbor");
let block_bytes = hex::decode(block_str).expect(&format!("bad block file {block_idx}"));
let (_, block): BlockWrapper = minicbor::decode(&block_bytes[..])
.expect(&format!("error decoding cbor for file {block_idx}"));
let byron = block.as_byron().unwrap();
let computed_slot = byron_epoch_slot_to_absolute(
block.header.consensus_data.0.epoch,
block.header.consensus_data.0.slot,
let genesis = GenesisValues::default();
let computed_slot = genesis.relative_slot_to_absolute(
byron.header.consensus_data.0.epoch,
byron.header.consensus_data.0.slot,
);
assert_eq!(computed_slot, 4492794);

View file

@ -1,20 +1,20 @@
use serde::{Deserialize, Serialize};
use pallas_miniprotocols::{
MAINNET_MAGIC,
TESTNET_MAGIC,
// PREVIEW_MAGIC, PRE_PRODUCTION_MAGIC,
};
/// Well-known magic for testnet
pub const TESTNET_MAGIC: u64 = 1097911063;
use crate::framework::*;
/// Well-known magic for mainnet
pub const MAINNET_MAGIC: u64 = 764824073;
// TODO: use from pallas once available
pub const PRE_PRODUCTION_MAGIC: u64 = 1;
/// Well-known magic for preview
pub const PREVIEW_MAGIC: u64 = 2;
/// Well-known magic for pre-production
pub const PRE_PRODUCTION_MAGIC: u64 = 1;
/// Well-known information about specific networks
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct WellKnownChainInfo {
pub struct GenesisValues {
pub magic: u64,
pub byron_epoch_length: u32,
pub byron_slot_length: u32,
@ -28,10 +28,10 @@ pub struct WellKnownChainInfo {
pub shelley_known_time: u64,
}
impl WellKnownChainInfo {
impl GenesisValues {
/// Hardcoded values for mainnet
pub fn mainnet() -> Self {
WellKnownChainInfo {
GenesisValues {
magic: MAINNET_MAGIC,
byron_epoch_length: 432000,
byron_slot_length: 20,
@ -50,7 +50,7 @@ impl WellKnownChainInfo {
/// Hardcoded values for testnet
pub fn testnet() -> Self {
WellKnownChainInfo {
GenesisValues {
magic: TESTNET_MAGIC,
byron_epoch_length: 432000,
byron_slot_length: 20,
@ -68,7 +68,7 @@ impl WellKnownChainInfo {
}
pub fn preview() -> Self {
WellKnownChainInfo {
GenesisValues {
magic: PREVIEW_MAGIC,
byron_epoch_length: 432000,
byron_slot_length: 20,
@ -86,7 +86,7 @@ impl WellKnownChainInfo {
/// Hardcoded values for the "pre-prod" testnet
pub fn preprod() -> Self {
WellKnownChainInfo {
GenesisValues {
magic: PRE_PRODUCTION_MAGIC,
byron_epoch_length: 432000,
byron_slot_length: 20,
@ -105,20 +105,18 @@ impl WellKnownChainInfo {
/// Uses the value of the magic to return either mainnet or testnet
/// hardcoded values.
pub fn try_from_magic(magic: u64) -> Result<WellKnownChainInfo, Error> {
pub fn from_magic(magic: u64) -> Option<GenesisValues> {
match magic {
MAINNET_MAGIC => Ok(Self::mainnet()),
TESTNET_MAGIC => Ok(Self::testnet()),
PREVIEW_MAGIC => Ok(Self::preview()),
PRE_PRODUCTION_MAGIC => Ok(Self::preprod()),
_ => Err(Error::message(
"can't infer well-known chain from specified magic",
)),
MAINNET_MAGIC => Some(Self::mainnet()),
TESTNET_MAGIC => Some(Self::testnet()),
PREVIEW_MAGIC => Some(Self::preview()),
PRE_PRODUCTION_MAGIC => Some(Self::preprod()),
_ => None,
}
}
}
impl Default for WellKnownChainInfo {
impl Default for GenesisValues {
fn default() -> Self {
Self::mainnet()
}

View file

@ -1,5 +1,3 @@
pub use crate::wellknown as chains;
pub use crate::cursor;
pub mod n2n {

View file

@ -4,7 +4,6 @@ pub(crate) mod framework;
pub(crate) mod plexer;
pub mod cursor;
pub mod wellknown;
mod api;