-
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
Merged
Merged
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
e26d80b
Add support for parsing Shelley genesis blocks
SebastienGllmt 4321b38
Merge branch 'develop' into shelley-genesis-parsing
rooooooooob a0953c7
Genesis keyHash alias + minor fixes
rooooooooob 352d0bb
Update Genesis parsing for changes in #340
rooooooooob File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
pub mod byron; | ||
pub mod network_info; | ||
pub mod shelley; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
use cml_crypto::{Ed25519KeyHash, VRFKeyHash}; | ||
use fraction::Fraction; | ||
use std::collections::BTreeMap; | ||
|
||
use crate::{address::Address, block::ProtocolVersion, Coin}; | ||
|
||
/// 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: Coin, | ||
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: Coin, | ||
pub max_block_body_size: u64, | ||
pub max_block_header_size: u64, | ||
pub max_tx_size: u64, | ||
pub min_fee_a: Coin, | ||
pub min_fee_b: Coin, | ||
pub min_pool_cost: Coin, | ||
pub min_utxo_value: Coin, | ||
pub n_opt: u64, | ||
pub pool_deposit: Coin, | ||
pub protocol_version: ProtocolVersion, | ||
pub rho: Fraction, | ||
pub tau: Fraction, | ||
} | ||
|
||
#[derive(Debug, Clone)] | ||
pub struct ShelleyGenesisExtraEntropy { | ||
pub tag: String, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
pub mod config; | ||
pub mod parse; | ||
pub mod raw; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,226 @@ | ||
use cml_core::DeserializeError; | ||
use cml_crypto::{ | ||
chain_crypto::Blake2b256, Ed25519KeyHash, PoolMetadataHash, TransactionHash, VRFKeyHash, | ||
}; | ||
use serde_json; | ||
use std::collections::BTreeMap; | ||
use std::io::Read; | ||
use std::str::FromStr; | ||
|
||
use crate::{ | ||
address::{Address, RewardAccount}, | ||
block::ProtocolVersion, | ||
certs::{Ipv4, Ipv6, PoolMetadata, PoolParams, Relay, StakeCredential, Url}, | ||
UnitInterval, | ||
}; | ||
|
||
use super::{ | ||
config, | ||
raw::{self}, | ||
}; | ||
|
||
#[derive(Debug, thiserror::Error)] | ||
pub enum GenesisJSONError { | ||
#[error("JSON: {0:?}")] | ||
Serde(#[from] serde_json::Error), | ||
#[error("Deserialize: {0:?}")] | ||
Deserialize(#[from] DeserializeError), | ||
#[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)?; | ||
let data: raw::ShelleyGenesisData = serde_json::from_value(data_value)?; | ||
|
||
let mut initial_funds = BTreeMap::new(); | ||
for (addr_hex, balance) in &data.initialFunds { | ||
initial_funds.insert(Address::from_hex(addr_hex)?, *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 ¶ms.owners { | ||
owners.push(Ed25519KeyHash::from_hex(owner)?); | ||
} | ||
let mut relays = Vec::<Relay>::new(); | ||
for relay in ¶ms.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 | ||
); | ||
} | ||
} |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
Why the clone? I don't see it used anywhere else.