-
Notifications
You must be signed in to change notification settings - Fork 35
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
Add support for parsing Shelley genesis blocks #331
Changes from 1 commit
e26d80b
4321b38
a0953c7
352d0bb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
pub mod shelley; | ||
pub mod byron; | ||
pub mod network_info; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
use std::{ | ||
collections::BTreeMap, | ||
}; | ||
use cml_crypto::{Ed25519KeyHash, VRFKeyHash}; | ||
use fraction::Fraction; | ||
|
||
use crate::{ | ||
Coin, address::Address, block::ProtocolVersion, | ||
}; | ||
|
||
/// A subset of the Shelley genesis data. The genesis data is a JSON file | ||
/// is something completely different from a epoch genesis block and the Byron genesis block | ||
#[derive(Debug, Clone)] | ||
pub struct ShelleyGenesisData { | ||
pub active_slots_coeff: Fraction, | ||
pub epoch_length: u64, | ||
pub gen_delegs: BTreeMap<Ed25519KeyHash, ShelleyGenesisDelegations>, | ||
pub initial_funds: BTreeMap<Address, Coin>, | ||
pub max_kes_evolutions: u64, | ||
pub max_lovelace_supply: u64, | ||
pub network_id: u64, | ||
pub network_magic: u64, | ||
pub protocol_params: ShelleyGenesisProtocolParameters, | ||
pub security_param: u64, | ||
pub slot_length: u64, | ||
pub slots_per_kes_period: u64, | ||
pub staking: Option<ShelleyGenesisStaking>, | ||
pub system_start: chrono::DateTime<chrono::Utc>, | ||
pub update_quorum: u64, | ||
} | ||
|
||
#[derive(Debug, Clone)] | ||
pub struct ShelleyGenesisDelegations { | ||
pub delegate: Ed25519KeyHash, | ||
pub vrf: VRFKeyHash, | ||
} | ||
|
||
#[derive(Debug, Clone)] | ||
pub struct ShelleyGenesisStaking { | ||
pub pools: BTreeMap<Ed25519KeyHash, crate::certs::PoolParams>, | ||
// initial delegations of staking key -> pool id | ||
pub stake: BTreeMap<Ed25519KeyHash, Ed25519KeyHash>, | ||
} | ||
|
||
#[derive(Debug, Clone)] | ||
pub struct ShelleyGenesisProtocolParameters { | ||
pub a0: Fraction, | ||
pub decentralisation_param: Fraction, | ||
pub e_max: u64, | ||
pub extra_entropy: ShelleyGenesisExtraEntropy, | ||
pub key_deposit: u64, | ||
pub max_block_body_size: u64, | ||
pub max_block_header_size: u64, | ||
pub max_tx_size: u64, | ||
pub min_fee_a: u64, | ||
pub min_fee_b: u64, | ||
pub min_pool_cost: u64, | ||
pub min_utxo_value: u64, | ||
pub n_opt: u64, | ||
pub pool_deposit: u64, | ||
pub protocol_version: ProtocolVersion, | ||
pub rho: Fraction, | ||
pub tau: Fraction, | ||
} | ||
|
||
|
||
#[derive(Debug, Clone)] | ||
pub struct ShelleyGenesisExtraEntropy { | ||
pub tag: String, | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
pub mod config; | ||
pub mod parse; | ||
pub mod raw; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,227 @@ | ||
use cml_crypto::{CryptoError, Ed25519KeyHash, VRFKeyHash, PoolMetadataHash, chain_crypto::Blake2b256, TransactionHash}; | ||
use serde_json; | ||
use std::collections::BTreeMap; | ||
use std::io::Read; | ||
use std::str::FromStr; | ||
|
||
use crate::{address::{Address, RewardAccount}, certs::{PoolParams, StakeCredential, Relay, Ipv4, Ipv6, PoolMetadata, Url}, UnitInterval, block::ProtocolVersion}; | ||
|
||
use super::{config, raw::{self}}; | ||
|
||
#[derive(Debug, thiserror::Error)] | ||
pub enum GenesisJSONError { | ||
#[error("JSON: {0:?}")] | ||
Serde(#[from] serde_json::Error), | ||
#[error("Crypto: {0:?}")] | ||
CryptoError(#[from] CryptoError), | ||
#[error("ParseInt: {0:?}")] | ||
ParseInt(#[from] std::num::ParseIntError), | ||
#[error("ParseIP: {0:?}")] | ||
ParseIP(#[from] crate::certs::utils::IPStringParsingError), | ||
#[error("Unexpected network type: {0:?}")] | ||
ParseNetwork(String), | ||
} | ||
|
||
pub fn parse_genesis_data<R: Read>(json: R) -> Result<config::ShelleyGenesisData, GenesisJSONError> { | ||
let data_value: serde_json::Value = serde_json::from_reader(json)?; | ||
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. Why the clone? I don't see it used anywhere else. |
||
let data: raw::ShelleyGenesisData = serde_json::from_value(data_value.clone())?; | ||
|
||
let mut initial_funds = BTreeMap::new(); | ||
for (addr_hex, balance) in &data.initialFunds { | ||
initial_funds.insert( | ||
Address::from_hex(addr_hex) | ||
.map_err(CryptoError::from)?, | ||
*balance, | ||
); | ||
} | ||
|
||
let network_id = match data.networkId.as_str() { | ||
"Mainnet" => crate::NetworkId::mainnet().network, | ||
"Testnet" => crate::NetworkId::testnet().network, | ||
val => return Err(GenesisJSONError::ParseNetwork(val.to_string())), | ||
}; | ||
|
||
let staking = match data.staking.as_ref() { | ||
Some(raw) => { | ||
// 1) Get stake pools | ||
let mut pools: BTreeMap<Ed25519KeyHash, PoolParams> = BTreeMap::new(); | ||
for (pool_id, params) in &raw.pools { | ||
let ration = fraction::Fraction::from_str(¶ms.margin).unwrap(); | ||
let mut owners = Vec::<Ed25519KeyHash>::new(); | ||
for owner in & params.owners { | ||
owners.push(Ed25519KeyHash::from_hex(owner)?); | ||
} | ||
let mut relays = Vec::<Relay>::new(); | ||
for relay in & params.relays { | ||
if let Some((key, value)) = relay.iter().next() { | ||
match key.as_str() { | ||
"single host address" => { | ||
let ipv4 = match value.IPv4.as_ref() { | ||
Some(s) => Some(Ipv4::from_str(s)?), | ||
_ => None | ||
}; | ||
let ipv6 = match value.IPv6.as_ref() { | ||
Some(s) => Some(Ipv6::from_str(s)?), | ||
_ => None | ||
}; | ||
relays.push(Relay::new_single_host_addr( | ||
value.port, | ||
ipv4, | ||
ipv6 | ||
)); | ||
}, | ||
_ => panic!("Only single host address relays are supported in cardano-node Relay JSON parsing") | ||
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. If we're going to panic here it'd be good to say so in the function comment. Edit: I read the comment on |
||
} | ||
} | ||
|
||
} | ||
let pool_metadata = match params.metadata.as_ref() { | ||
Some(metadata) => Some(PoolMetadata::new( | ||
Url::new(metadata.url.clone()).unwrap(), | ||
PoolMetadataHash::from_hex(&metadata.hash)?, | ||
)), | ||
_ => None, | ||
}; | ||
let parsed_params = PoolParams::new( | ||
Ed25519KeyHash::from_hex(¶ms.publicKey)?, | ||
VRFKeyHash::from_hex(¶ms.vrf)?, | ||
params.pledge, | ||
params.cost, | ||
UnitInterval::new( | ||
*ration.numer().unwrap(), | ||
*ration.denom().unwrap() | ||
), | ||
RewardAccount::new( | ||
match data.networkId.as_str() { | ||
"Mainnet" => crate::NetworkId::mainnet().network as u8, | ||
"Testnet" => crate::NetworkId::testnet().network as u8, | ||
val => return Err(GenesisJSONError::ParseNetwork(val.to_string())), | ||
}, | ||
StakeCredential::new_pub_key(Ed25519KeyHash::from_hex(¶ms.rewardAccount.credential.keyHash)?) | ||
), | ||
owners.into(), | ||
relays, | ||
pool_metadata | ||
); | ||
pools.insert( | ||
Ed25519KeyHash::from_hex(pool_id)?, | ||
parsed_params | ||
); | ||
} | ||
// 2) Get initial delegations | ||
let mut stake: BTreeMap<Ed25519KeyHash, Ed25519KeyHash> = BTreeMap::new(); | ||
for (staking_key, pool_id) in &raw.stake { | ||
stake.insert( | ||
Ed25519KeyHash::from_hex(staking_key)?, | ||
Ed25519KeyHash::from_hex(pool_id)? | ||
); | ||
} | ||
Some(config::ShelleyGenesisStaking { | ||
stake, | ||
pools, | ||
}) | ||
}, | ||
_ => None, | ||
}; | ||
|
||
|
||
let mut gen_delegs = BTreeMap::new(); | ||
for (key, val) in data.genDelegs.iter() { | ||
gen_delegs.insert( | ||
Ed25519KeyHash::from_hex(key)?, | ||
config::ShelleyGenesisDelegations { | ||
delegate: Ed25519KeyHash::from_hex(&val.delegate)?, | ||
vrf: VRFKeyHash::from_hex(&val.vrf)?, | ||
} | ||
); | ||
} | ||
Ok(config::ShelleyGenesisData { | ||
active_slots_coeff: fraction::Fraction::from_str(&data.activeSlotsCoeff).unwrap(), | ||
epoch_length: data.epochLength, | ||
gen_delegs, | ||
initial_funds, | ||
max_kes_evolutions: data.maxKESEvolutions, | ||
max_lovelace_supply: data.maxLovelaceSupply, | ||
network_id, | ||
network_magic: data.networkMagic, | ||
protocol_params: config::ShelleyGenesisProtocolParameters { | ||
a0: fraction::Fraction::from_str(&data.protocolParams.a0).unwrap(), | ||
decentralisation_param: fraction::Fraction::from_str(&data.protocolParams.decentralisationParam).unwrap(), | ||
e_max: data.protocolParams.eMax, | ||
extra_entropy: config::ShelleyGenesisExtraEntropy { | ||
tag: data.protocolParams.extraEntropy.tag | ||
}, | ||
key_deposit: data.protocolParams.keyDeposit, | ||
max_block_body_size: data.protocolParams.maxBlockBodySize, | ||
max_block_header_size: data.protocolParams.maxBlockHeaderSize, | ||
max_tx_size: data.protocolParams.maxTxSize, | ||
min_fee_a: data.protocolParams.minFeeA, | ||
min_fee_b: data.protocolParams.minFeeB, | ||
min_pool_cost: data.protocolParams.minPoolCost, | ||
min_utxo_value: data.protocolParams.minUTxOValue, | ||
n_opt: data.protocolParams.nOpt, | ||
pool_deposit: data.protocolParams.poolDeposit, | ||
protocol_version: ProtocolVersion::new(data.protocolParams.protocolVersion.major, data.protocolParams.protocolVersion.minor), | ||
rho: fraction::Fraction::from_str(&data.protocolParams.rho).unwrap(), | ||
tau: fraction::Fraction::from_str(&data.protocolParams.tau).unwrap(), | ||
}, | ||
security_param: data.securityParam, | ||
slot_length: data.slotLength, | ||
slots_per_kes_period: data.slotsPerKESPeriod, | ||
staking, | ||
system_start: data.systemStart.parse().expect("Failed to parse date"), | ||
update_quorum: data.updateQuorum, | ||
}) | ||
} | ||
|
||
pub fn redeem_address_to_txid( | ||
pubkey: &Address, | ||
) -> TransactionHash { | ||
let txid = Blake2b256::new(&pubkey.to_raw_bytes()); | ||
TransactionHash::from(*txid.as_hash_bytes()) | ||
} | ||
|
||
#[cfg(test)] | ||
mod test { | ||
use super::*; | ||
|
||
fn get_test_genesis_data() -> &'static str { | ||
include_str!( | ||
"./test_data/test.json" | ||
) | ||
} | ||
|
||
#[test] | ||
fn calc_address_txid() { | ||
let hash = redeem_address_to_txid( | ||
&Address::from_bech32("addr_test1qpefp65049pncyz95nyyww2e44sgumqr5kx8mcemm0fuumeftwv8zdtpqct0836wz8y56aakem2uejf604cee7cn2p3qp9p8te").unwrap(), | ||
); | ||
assert_eq!( | ||
hash.to_hex(), | ||
"66dc6b2e628bf1fb6204797f1a07f8e949d9520a70e859ecbf3ea3076029871e" | ||
); | ||
} | ||
|
||
#[test] | ||
fn parse_test_genesis_files() { | ||
|
||
let genesis_data = | ||
super::parse_genesis_data(get_test_genesis_data().as_bytes()) | ||
.unwrap(); | ||
|
||
assert_eq!(genesis_data.epoch_length, 432000u64); | ||
assert_eq!(genesis_data.network_id, 0); | ||
assert_eq!(genesis_data.network_magic, 764824073u64); | ||
|
||
assert_eq!( | ||
*genesis_data | ||
.initial_funds | ||
.iter() | ||
.find(|(n, _)| n.to_hex() | ||
== "605276322ac7882434173dcc6441905f6737689bd309b68ad8b3614fd8") | ||
.unwrap() | ||
.1, | ||
3000000000000000u64 | ||
); | ||
} | ||
} |
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.
maybe some of these could be
Coin
when relevant? It's still just an alias but it would be consistent with the rest of the protocol param structs.