-
Notifications
You must be signed in to change notification settings - Fork 3
Add timestamp
and epoch_slot
to BlockInfo
#150
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
Changes from 5 commits
18979be
bc70099
2f0dae4
8eee1ab
4dded3a
f95759b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,74 +1,86 @@ | ||
//! Common calculations for Cardano | ||
|
||
const BYRON_SLOTS_PER_EPOCH: u64 = 21_600; | ||
pub const SHELLEY_SLOTS_PER_EPOCH: u64 = 432_000; | ||
const SHELLEY_START_SLOT: u64 = 4_492_800; | ||
const SHELLEY_START_EPOCH: u64 = 208; | ||
|
||
/// Derive an epoch number from a slot, handling Byron/Shelley era changes | ||
pub fn slot_to_epoch(slot: u64) -> u64 { | ||
slot_to_epoch_with_shelley_params(slot, SHELLEY_START_EPOCH, SHELLEY_SLOTS_PER_EPOCH) | ||
} | ||
|
||
pub fn slot_to_epoch_with_shelley_params( | ||
slot: u64, | ||
shelley_epoch: u64, | ||
shelley_epoch_len: u64, | ||
) -> u64 { | ||
) -> (u64, u64) { | ||
let shelley_start_slot = shelley_epoch * BYRON_SLOTS_PER_EPOCH; | ||
if slot < shelley_start_slot { | ||
slot / BYRON_SLOTS_PER_EPOCH | ||
(slot / BYRON_SLOTS_PER_EPOCH, slot % BYRON_SLOTS_PER_EPOCH) | ||
} else { | ||
shelley_epoch + (slot - shelley_start_slot) / shelley_epoch_len | ||
let slots_since_shelley_start = slot - shelley_start_slot; | ||
( | ||
shelley_epoch + slots_since_shelley_start / shelley_epoch_len, | ||
slots_since_shelley_start % shelley_epoch_len, | ||
) | ||
} | ||
} | ||
|
||
pub fn slot_to_timestamp_with_params(slot: u64, byron_timestamp: u64, shelley_epoch: u64) -> u64 { | ||
let shelley_start_slot = shelley_epoch * BYRON_SLOTS_PER_EPOCH; | ||
if slot < shelley_start_slot { | ||
byron_timestamp + slot * 20 | ||
} else { | ||
let shelley_timestamp = byron_timestamp + shelley_start_slot * 20; | ||
shelley_timestamp + (slot - shelley_start_slot) | ||
} | ||
} | ||
|
||
// -- Tests -- | ||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
const SHELLEY_START_EPOCH: u64 = 208; | ||
const SHELLEY_SLOTS_PER_EPOCH: u64 = 432_000; | ||
const BYRON_START_TIMESTAMP: u64 = 1506203091; | ||
|
||
fn slot_to_epoch(slot: u64) -> (u64, u64) { | ||
slot_to_epoch_with_shelley_params(slot, SHELLEY_START_EPOCH, SHELLEY_SLOTS_PER_EPOCH) | ||
} | ||
|
||
fn slot_to_timestamp(slot: u64) -> u64 { | ||
slot_to_timestamp_with_params(slot, BYRON_START_TIMESTAMP, SHELLEY_START_EPOCH) | ||
} | ||
|
||
#[test] | ||
fn byron_epoch_0() { | ||
assert_eq!(0, slot_to_epoch(0)); | ||
assert_eq!(slot_to_epoch(0), (0, 0)); | ||
assert_eq!(slot_to_timestamp(0), 1506203091); | ||
} | ||
|
||
#[test] | ||
fn byron_epoch_1() { | ||
assert_eq!(1, slot_to_epoch(21_600)); | ||
assert_eq!(slot_to_epoch(21_600), (1, 0)); | ||
assert_eq!(slot_to_timestamp(21_600), 1506635091); | ||
} | ||
|
||
#[test] | ||
fn byron_last_slot() { | ||
assert_eq!(slot_to_epoch(4_492_799), 207); | ||
assert_eq!(slot_to_epoch(4_492_799), (207, 21_599)); | ||
assert_eq!(slot_to_timestamp(4_492_799), 1596059071); | ||
} | ||
|
||
#[test] | ||
fn shelley_first_slot() { | ||
assert_eq!(slot_to_epoch(4_492_800), 208); | ||
assert_eq!(slot_to_epoch(4_492_800), (208, 0)); | ||
assert_eq!(slot_to_timestamp(4_492_800), 1596059091); | ||
} | ||
|
||
#[test] | ||
fn shelley_epoch_209_start() { | ||
// 432_000 slots later | ||
assert_eq!(slot_to_epoch(4_492_800 + 432_000), 209); | ||
} | ||
|
||
#[test] | ||
fn before_transition_boundary() { | ||
// One slot before Shelley starts | ||
assert_eq!(slot_to_epoch(4_492_799), 207); | ||
} | ||
|
||
#[test] | ||
fn after_transition_boundary() { | ||
// First Shelley slot | ||
assert_eq!(slot_to_epoch(4_492_800), 208); | ||
assert_eq!(slot_to_epoch(4_492_800 + 432_000), (209, 0)); | ||
assert_eq!(slot_to_timestamp(4_492_800 + 432_000), 1596491091); | ||
} | ||
|
||
#[test] | ||
fn mainnet_example_from_cexplorer() { | ||
// Slot 98_272_003 maps to epoch 425 | ||
assert_eq!(slot_to_epoch(98_272_003), 425); | ||
assert_eq!(slot_to_epoch(98_272_003), (425, 35_203)); | ||
assert_eq!(slot_to_timestamp(98_272_003), 1689838294); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
use crate::calculations::{slot_to_epoch_with_shelley_params, slot_to_timestamp_with_params}; | ||
|
||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] | ||
pub struct GenesisValues { | ||
pub byron_timestamp: u64, | ||
pub shelley_epoch: u64, | ||
pub shelley_epoch_len: u64, | ||
} | ||
|
||
impl GenesisValues { | ||
pub fn mainnet() -> Self { | ||
Self { | ||
byron_timestamp: 1506203091, | ||
shelley_epoch: 208, | ||
shelley_epoch_len: 432000, | ||
} | ||
} | ||
|
||
pub fn slot_to_epoch(&self, slot: u64) -> (u64, u64) { | ||
slot_to_epoch_with_shelley_params(slot, self.shelley_epoch, self.shelley_epoch_len) | ||
} | ||
pub fn slot_to_timestamp(&self, slot: u64) -> u64 { | ||
slot_to_timestamp_with_params(slot, self.byron_timestamp, self.shelley_epoch) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,36 +1,60 @@ | ||
// Build-time script to download generics | ||
use reqwest::blocking::get; | ||
use std::fs; | ||
use std::io::Write; | ||
use std::path::Path; | ||
|
||
use anyhow::{Context, Result}; | ||
|
||
const OUTPUT_DIR: &str = "downloads"; | ||
|
||
/// Download a URL to a file in OUTPUT_DIR | ||
fn download(url: &str, filename: &str) { | ||
let response = get(url).expect("Failed to fetch {url}"); | ||
let data = response.text().expect("Failed to read response"); | ||
async fn download(client: &reqwest::Client, url: &str, filename: &str) -> Result<()> { | ||
let request = client.get(url).build().with_context(|| format!("Failed to request {url}"))?; | ||
let response = | ||
client.execute(request).await.with_context(|| format!("Failed to fetch {url}"))?; | ||
let data = response.text().await.context("Failed to read response")?; | ||
|
||
let output_path = Path::new(OUTPUT_DIR); | ||
if !output_path.exists() { | ||
fs::create_dir_all(output_path).expect("Failed to create {OUTPUT_DIR} directory"); | ||
fs::create_dir_all(output_path) | ||
.with_context(|| format!("Failed to create {OUTPUT_DIR} directory"))?; | ||
} | ||
|
||
let file_path = output_path.join(filename); | ||
let mut file = fs::File::create(&file_path).expect("Failed to create file {file_path}"); | ||
file.write_all(data.as_bytes()).expect("Failed to write file {file_path}"); | ||
let mut file = fs::File::create(&file_path) | ||
.with_context(|| format!("Failed to create file {}", file_path.display()))?; | ||
file.write_all(data.as_bytes()) | ||
.with_context(|| format!("Failed to write file {}", file_path.display()))?; | ||
Ok(()) | ||
} | ||
|
||
fn main() { | ||
#[tokio::main] | ||
async fn main() -> Result<()> { | ||
println!("cargo:rerun-if-changed=build.rs"); // Ensure the script runs if modified | ||
let client = reqwest::Client::new(); | ||
|
||
download( | ||
"https://book.world.dev.cardano.org/environments/mainnet/byron-genesis.json", | ||
"mainnet-byron-genesis.json", | ||
); | ||
tokio::try_join!( | ||
download( | ||
&client, | ||
"https://book.world.dev.cardano.org/environments/mainnet/byron-genesis.json", | ||
"mainnet-byron-genesis.json", | ||
), | ||
download( | ||
&client, | ||
"https://book.world.dev.cardano.org/environments/mainnet/shelley-genesis.json", | ||
"mainnet-shelley-genesis.json", | ||
), | ||
download( | ||
&client, | ||
"https://raw.githubusercontent.com/Hornan7/SanchoNet-Tutorials/refs/heads/main/genesis/byron-genesis.json", | ||
"sanchonet-byron-genesis.json", | ||
), | ||
download( | ||
&client, | ||
"https://raw.githubusercontent.com/Hornan7/SanchoNet-Tutorials/refs/heads/main/genesis/shelley-genesis.json", | ||
"sanchonet-shelley-genesis.json", | ||
) | ||
)?; | ||
|
||
download( | ||
"https://raw.githubusercontent.com/Hornan7/SanchoNet-Tutorials/refs/heads/main/genesis/byron-genesis.json", | ||
"sanchonet-byron-genesis.json", | ||
); | ||
Ok(()) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,6 +2,7 @@ | |
//! Reads genesis files and outputs initial UTXO events | ||
|
||
use acropolis_common::{ | ||
genesis_values::GenesisValues, | ||
messages::{ | ||
CardanoMessage, GenesisCompleteMessage, Message, PotDeltasMessage, UTXODeltasMessage, | ||
}, | ||
|
@@ -23,7 +24,12 @@ const DEFAULT_NETWORK_NAME: &str = "mainnet"; | |
|
||
// Include genesis data (downloaded by build.rs) | ||
const MAINNET_BYRON_GENESIS: &[u8] = include_bytes!("../downloads/mainnet-byron-genesis.json"); | ||
const MAINNET_SHELLEY_GENESIS: &[u8] = include_bytes!("../downloads/mainnet-shelley-genesis.json"); | ||
const MAINNET_SHELLEY_START_EPOCH: u64 = 208; | ||
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This value is still hardcoded. Why not compute it? |
||
const SANCHONET_BYRON_GENESIS: &[u8] = include_bytes!("../downloads/sanchonet-byron-genesis.json"); | ||
const SANCHONET_SHELLEY_GENESIS: &[u8] = | ||
include_bytes!("../downloads/sanchonet-shelley-genesis.json"); | ||
const SANCHONET_SHELLEY_START_EPOCH: u64 = 0; | ||
|
||
// Initial reserves (=maximum ever Lovelace supply) | ||
const INITIAL_RESERVES: Lovelace = 45_000_000_000_000_000; | ||
|
@@ -70,19 +76,30 @@ impl GenesisBootstrapper { | |
let network_name = | ||
config.get_string("network-name").unwrap_or(DEFAULT_NETWORK_NAME.to_string()); | ||
|
||
let genesis = match network_name.as_ref() { | ||
"mainnet" => MAINNET_BYRON_GENESIS, | ||
"sanchonet" => SANCHONET_BYRON_GENESIS, | ||
_ => { | ||
error!("Cannot find genesis for {network_name}"); | ||
return; | ||
} | ||
}; | ||
let (byron_genesis, shelley_genesis, shelley_start_epoch) = | ||
match network_name.as_ref() { | ||
"mainnet" => ( | ||
MAINNET_BYRON_GENESIS, | ||
MAINNET_SHELLEY_GENESIS, | ||
MAINNET_SHELLEY_START_EPOCH, | ||
), | ||
"sanchonet" => ( | ||
SANCHONET_BYRON_GENESIS, | ||
SANCHONET_SHELLEY_GENESIS, | ||
SANCHONET_SHELLEY_START_EPOCH, | ||
), | ||
_ => { | ||
error!("Cannot find genesis for {network_name}"); | ||
return; | ||
} | ||
}; | ||
info!("Reading genesis for '{network_name}'"); | ||
|
||
// Read genesis data | ||
let genesis: byron::GenesisFile = | ||
serde_json::from_slice(genesis).expect("Invalid JSON in BYRON_GENESIS file"); | ||
let byron_genesis: byron::GenesisFile = serde_json::from_slice(byron_genesis) | ||
.expect("Invalid JSON in BYRON_GENESIS file"); | ||
let shelley_genesis: shelley::GenesisFile = serde_json::from_slice(shelley_genesis) | ||
.expect("Invalid JSON in SHELLEY_GENESIS file"); | ||
|
||
// Construct messages | ||
let block_info = BlockInfo { | ||
|
@@ -91,14 +108,16 @@ impl GenesisBootstrapper { | |
number: 0, | ||
hash: Vec::new(), | ||
epoch: 0, | ||
epoch_slot: 0, | ||
new_epoch: false, | ||
timestamp: byron_genesis.start_time, | ||
era: Era::Byron, | ||
}; | ||
|
||
let mut utxo_deltas_message = UTXODeltasMessage { deltas: Vec::new() }; | ||
|
||
// Convert the AVVM distributions into pseudo-UTXOs | ||
let gen_utxos = genesis_utxos(&genesis); | ||
let gen_utxos = genesis_utxos(&byron_genesis); | ||
let mut total_allocated: u64 = 0; | ||
for (hash, address, amount) in gen_utxos.iter() { | ||
let tx_output = TxOutput { | ||
|
@@ -145,10 +164,16 @@ impl GenesisBootstrapper { | |
.await | ||
.unwrap_or_else(|e| error!("Failed to publish: {e}")); | ||
|
||
let values = GenesisValues { | ||
byron_timestamp: byron_genesis.start_time, | ||
shelley_epoch: shelley_start_epoch, | ||
shelley_epoch_len: shelley_genesis.epoch_length.unwrap() as u64, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd propose to use parameter-state module output here: it should provide all these data. If it does not, then it's much easier to add additional fields there. |
||
}; | ||
|
||
// Send completion message | ||
let message_enum = Message::Cardano(( | ||
block_info, | ||
CardanoMessage::GenesisComplete(GenesisCompleteMessage {}), | ||
CardanoMessage::GenesisComplete(GenesisCompleteMessage { values }), | ||
)); | ||
context | ||
.message_bus | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shelley genesis has bugs, and cannot be properly parsed by Pallas, see parameters-state module.