feat(traverse): Introduce time helpers (#234)
This commit is contained in:
parent
c1446ebea5
commit
7b3708ac46
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"
|
hex = "0.4.3"
|
||||||
thiserror = "1.0.31"
|
thiserror = "1.0.31"
|
||||||
|
|
||||||
|
# TODO: remove once GenesisValue moves into new genesis crate
|
||||||
|
serde = "1.0.155"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
unstable = []
|
unstable = []
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ use pallas_codec::minicbor;
|
||||||
use pallas_crypto::hash::{Hash, Hasher};
|
use pallas_crypto::hash::{Hash, Hasher};
|
||||||
use pallas_primitives::{alonzo, babbage, byron};
|
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> {
|
impl<'b> MultiEraHeader<'b> {
|
||||||
pub fn decode(tag: u8, subtag: Option<u8>, cbor: &'b [u8]) -> Result<Self, Error> {
|
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 {
|
pub fn slot(&self) -> u64 {
|
||||||
match self {
|
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::AlonzoCompatible(x) => x.header_body.slot,
|
||||||
MultiEraHeader::Babbage(x) => x.header_body.slot,
|
MultiEraHeader::Babbage(x) => x.header_body.slot,
|
||||||
MultiEraHeader::Byron(x) => time::byron_epoch_slot_to_absolute(
|
MultiEraHeader::EpochBoundary(x) => {
|
||||||
x.consensus_data.0.epoch,
|
let genesis = GenesisValues::default();
|
||||||
x.consensus_data.0.slot,
|
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 withdrawals;
|
||||||
pub mod witnesses;
|
pub mod witnesses;
|
||||||
|
|
||||||
|
// TODO: move to genesis crate
|
||||||
|
pub mod wellknown;
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
pub enum Era {
|
pub enum Era {
|
||||||
|
|
|
||||||
|
|
@ -1,32 +1,230 @@
|
||||||
// TODO: is it safe to hardcode these values?
|
use crate::{wellknown::GenesisValues, MultiEraBlock};
|
||||||
const WELLKNOWN_SLOT_LENGTH: u64 = 20; // 20 secs
|
|
||||||
const WELLKNOWN_EPOCH_LENGTH: u64 = 5 * 24 * 60 * 60; // 5 days
|
|
||||||
|
|
||||||
pub fn byron_epoch_slot_to_absolute(epoch: u64, sub_epoch_slot: u64) -> u64 {
|
pub type Epoch = u64;
|
||||||
((epoch * WELLKNOWN_EPOCH_LENGTH) / WELLKNOWN_SLOT_LENGTH) + sub_epoch_slot
|
|
||||||
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
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]
|
#[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
|
// TODO: expand this test to include more test blocks
|
||||||
let block_idx = 1;
|
|
||||||
let block_str = include_str!("../../test_data/byron1.block");
|
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 byron = block.as_byron().unwrap();
|
||||||
let (_, block): BlockWrapper = minicbor::decode(&block_bytes[..])
|
|
||||||
.expect(&format!("error decoding cbor for file {block_idx}"));
|
|
||||||
|
|
||||||
let computed_slot = byron_epoch_slot_to_absolute(
|
let genesis = GenesisValues::default();
|
||||||
block.header.consensus_data.0.epoch,
|
|
||||||
block.header.consensus_data.0.slot,
|
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);
|
assert_eq!(computed_slot, 4492794);
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,20 @@
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use pallas_miniprotocols::{
|
/// Well-known magic for testnet
|
||||||
MAINNET_MAGIC,
|
pub const TESTNET_MAGIC: u64 = 1097911063;
|
||||||
TESTNET_MAGIC,
|
|
||||||
// PREVIEW_MAGIC, PRE_PRODUCTION_MAGIC,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::framework::*;
|
/// Well-known magic for mainnet
|
||||||
|
pub const MAINNET_MAGIC: u64 = 764824073;
|
||||||
|
|
||||||
// TODO: use from pallas once available
|
/// Well-known magic for preview
|
||||||
pub const PRE_PRODUCTION_MAGIC: u64 = 1;
|
|
||||||
pub const PREVIEW_MAGIC: u64 = 2;
|
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
|
/// Well-known information about specific networks
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
pub struct WellKnownChainInfo {
|
pub struct GenesisValues {
|
||||||
pub magic: u64,
|
pub magic: u64,
|
||||||
pub byron_epoch_length: u32,
|
pub byron_epoch_length: u32,
|
||||||
pub byron_slot_length: u32,
|
pub byron_slot_length: u32,
|
||||||
|
|
@ -28,10 +28,10 @@ pub struct WellKnownChainInfo {
|
||||||
pub shelley_known_time: u64,
|
pub shelley_known_time: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WellKnownChainInfo {
|
impl GenesisValues {
|
||||||
/// Hardcoded values for mainnet
|
/// Hardcoded values for mainnet
|
||||||
pub fn mainnet() -> Self {
|
pub fn mainnet() -> Self {
|
||||||
WellKnownChainInfo {
|
GenesisValues {
|
||||||
magic: MAINNET_MAGIC,
|
magic: MAINNET_MAGIC,
|
||||||
byron_epoch_length: 432000,
|
byron_epoch_length: 432000,
|
||||||
byron_slot_length: 20,
|
byron_slot_length: 20,
|
||||||
|
|
@ -50,7 +50,7 @@ impl WellKnownChainInfo {
|
||||||
|
|
||||||
/// Hardcoded values for testnet
|
/// Hardcoded values for testnet
|
||||||
pub fn testnet() -> Self {
|
pub fn testnet() -> Self {
|
||||||
WellKnownChainInfo {
|
GenesisValues {
|
||||||
magic: TESTNET_MAGIC,
|
magic: TESTNET_MAGIC,
|
||||||
byron_epoch_length: 432000,
|
byron_epoch_length: 432000,
|
||||||
byron_slot_length: 20,
|
byron_slot_length: 20,
|
||||||
|
|
@ -68,7 +68,7 @@ impl WellKnownChainInfo {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn preview() -> Self {
|
pub fn preview() -> Self {
|
||||||
WellKnownChainInfo {
|
GenesisValues {
|
||||||
magic: PREVIEW_MAGIC,
|
magic: PREVIEW_MAGIC,
|
||||||
byron_epoch_length: 432000,
|
byron_epoch_length: 432000,
|
||||||
byron_slot_length: 20,
|
byron_slot_length: 20,
|
||||||
|
|
@ -86,7 +86,7 @@ impl WellKnownChainInfo {
|
||||||
|
|
||||||
/// Hardcoded values for the "pre-prod" testnet
|
/// Hardcoded values for the "pre-prod" testnet
|
||||||
pub fn preprod() -> Self {
|
pub fn preprod() -> Self {
|
||||||
WellKnownChainInfo {
|
GenesisValues {
|
||||||
magic: PRE_PRODUCTION_MAGIC,
|
magic: PRE_PRODUCTION_MAGIC,
|
||||||
byron_epoch_length: 432000,
|
byron_epoch_length: 432000,
|
||||||
byron_slot_length: 20,
|
byron_slot_length: 20,
|
||||||
|
|
@ -105,20 +105,18 @@ impl WellKnownChainInfo {
|
||||||
|
|
||||||
/// Uses the value of the magic to return either mainnet or testnet
|
/// Uses the value of the magic to return either mainnet or testnet
|
||||||
/// hardcoded values.
|
/// hardcoded values.
|
||||||
pub fn try_from_magic(magic: u64) -> Result<WellKnownChainInfo, Error> {
|
pub fn from_magic(magic: u64) -> Option<GenesisValues> {
|
||||||
match magic {
|
match magic {
|
||||||
MAINNET_MAGIC => Ok(Self::mainnet()),
|
MAINNET_MAGIC => Some(Self::mainnet()),
|
||||||
TESTNET_MAGIC => Ok(Self::testnet()),
|
TESTNET_MAGIC => Some(Self::testnet()),
|
||||||
PREVIEW_MAGIC => Ok(Self::preview()),
|
PREVIEW_MAGIC => Some(Self::preview()),
|
||||||
PRE_PRODUCTION_MAGIC => Ok(Self::preprod()),
|
PRE_PRODUCTION_MAGIC => Some(Self::preprod()),
|
||||||
_ => Err(Error::message(
|
_ => None,
|
||||||
"can't infer well-known chain from specified magic",
|
|
||||||
)),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for WellKnownChainInfo {
|
impl Default for GenesisValues {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::mainnet()
|
Self::mainnet()
|
||||||
}
|
}
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
pub use crate::wellknown as chains;
|
|
||||||
|
|
||||||
pub use crate::cursor;
|
pub use crate::cursor;
|
||||||
|
|
||||||
pub mod n2n {
|
pub mod n2n {
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ pub(crate) mod framework;
|
||||||
pub(crate) mod plexer;
|
pub(crate) mod plexer;
|
||||||
|
|
||||||
pub mod cursor;
|
pub mod cursor;
|
||||||
pub mod wellknown;
|
|
||||||
|
|
||||||
mod api;
|
mod api;
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue