Skip to content
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 4 commits into from
Jul 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions chain/rust/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ num-integer = "0.1.45"
thiserror = "1.0.37"
num = "0.4"
unicode-segmentation = "1.10.1"
serde-aux = "4.5.0"
chrono = "0.4.38"

# non-wasm
noop_proc_macro = { version = "0.3.0", optional = false }
Expand Down
1 change: 1 addition & 0 deletions chain/rust/src/genesis/mod.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
pub mod byron;
pub mod network_info;
pub mod shelley;
65 changes: 65 additions & 0 deletions chain/rust/src/genesis/shelley/config.rs
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,
}
3 changes: 3 additions & 0 deletions chain/rust/src/genesis/shelley/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pub mod config;
pub mod parse;
pub mod raw;
226 changes: 226 additions & 0 deletions chain/rust/src/genesis/shelley/parse.rs
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)?;
Copy link
Contributor

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.

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(&params.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")
Copy link
Contributor

Choose a reason for hiding this comment

The 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 RelayTypeMap saying you couldn't even find JSON for other types so maybe this is okay.

}
}
}
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(&params.publicKey)?,
VRFKeyHash::from_hex(&params.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(
&params.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
);
}
}
Loading
Loading