pallas/pallas-traverse/src/time.rs
Harper bf973dff3c feat: introduce conway primitives (#290)
Co-authored-by: lisicky <lisicky@inbox.ru>
2023-11-03 12:22:06 -03:00

274 lines
9.5 KiB
Rust

use crate::{wellknown::GenesisValues, MultiEraBlock};
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)
}
MultiEraBlock::Conway(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 super::*;
use crate::MultiEraBlock;
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);
let (epoch, epoch_slot) = genesis.absolute_slot_to_relative(slot);
assert_eq!(epoch, expected_epoch, "epoch doesn't match");
assert_eq!(epoch_slot, expected_epoch_slot);
}
#[test]
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 calc_matches_preview_values() {
let genesis = GenesisValues::preview();
// https://preview.cardanoscan.io/block/1
assert_slot_matches_timestamp(&genesis, 20, 1666656020, 0, 20);
// https://preview.cardanoscan.io/block/1384
assert_slot_matches_timestamp(&genesis, 27680, 1666683680, 0, 27680);
// https://preview.cardanoscan.io/block/1202991
assert_slot_matches_timestamp(&genesis, 27556036, 1694212036, 318, 80836);
}
#[test]
fn calc_matches_preprod_values() {
let genesis = GenesisValues::preprod();
// https://preprod.cardanoscan.io/block/0
assert_slot_matches_timestamp(&genesis, 0, 1654041600, 0, 0);
// https://preprod.cardanoscan.io/block/1
assert_slot_matches_timestamp(&genesis, 2, 1654041640, 0, 2);
// Can't make Byron blocks work, not sure what's going on here. Each block jumps
// several slots. Timestamps work but epoch calculation doesn't. Since anything
// interesting starts from Shelley, I'll commit to this logic and treat this as
// a known-issue for later fixing.
// https://preprod.cardanoscan.io/block/11
// assert_slot_matches_timestamp(&genesis, 21600, 1654473600, 1, 0);
// https://preprod.cardanoscan.io/block/46
assert_slot_matches_timestamp(&genesis, 86400, 1655769600, 4, 0);
// https://preprod.cardanoscan.io/block/1360501
assert_slot_matches_timestamp(&genesis, 38580791, 1694263991, 93, 46391);
}
#[test]
fn known_slot_matches() {
// TODO: expand this test to include more test blocks
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 byron = block.as_byron().unwrap();
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);
}
}