feat(traverse): Introduce time helpers (#234)
This commit is contained in:
parent
2612bb1038
commit
8cdde5e9a8
7 changed files with 250 additions and 50 deletions
|
|
@ -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 = []
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
@ -1,5 +1,3 @@
|
|||
pub use crate::wellknown as chains;
|
||||
|
||||
pub use crate::cursor;
|
||||
|
||||
pub mod n2n {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ pub(crate) mod framework;
|
|||
pub(crate) mod plexer;
|
||||
|
||||
pub mod cursor;
|
||||
pub mod wellknown;
|
||||
|
||||
mod api;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue