Skip to content

Commit

Permalink
Add support for parsing Shelley genesis blocks (#331)
Browse files Browse the repository at this point in the history
* Add support for parsing Shelley genesis blocks

* Genesis keyHash alias + minor fixes

* Add alias for `ShelleyGenesisCredential::keyHash` to account for
cardano-node json format

* Remove unnecessary clone()

* Change `u64` to `Coin` when appropriate in Genesis types

* ran cargo fmt

* Update Genesis parsing for changes in #340

---------

Co-authored-by: rooooooooob <[email protected]>
Co-authored-by: rooooooooob <[email protected]>
  • Loading branch information
3 people authored Jul 17, 2024
1 parent 6a28e88 commit bf34fe4
Show file tree
Hide file tree
Showing 7 changed files with 571 additions and 0 deletions.
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)?;
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")
}
}
}
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

0 comments on commit bf34fe4

Please sign in to comment.