use std::{collections::BTreeMap, str::FromStr};
use clap::Args;
use fp_evm::GenesisAccount;
use phronesis_runtime::{
AccountId, BalancesConfig, RuntimeGenesisConfig, Signature, EVMConfig, SudoConfig, SystemConfig, WASM_BINARY,
phronesis::PHRON, Balance, SessionConfig, SessionKeys, FinalityVersion, PhronConfig, StakingConfig,
ElectionsConfig, StakerStatus, CommitteeManagementConfig
};
use core_primitives::{AuthorityId as PhronId, LEGACY_FINALITY_VERSION, SessionValidators};
use sc_service::ChainType;
use sp_consensus_aura::sr25519::AuthorityId as AuraId;
use sp_core::{Pair, Public, H160, U256};
use sp_runtime::traits::{IdentifyAccount};
use hex_literal::hex;
use sc_cli::Error as CliError;
pub const DEFAULT_BACKUP_FOLDER: &str = "backup-phron";
pub const CHAINTYPE_DEV: &str = "dev";
pub const CHAINTYPE_LOCAL: &str = "local";
pub const CHAINTYPE_LIVE: &str = "live";
pub const DEFAULT_CHAIN_ID: &str = "phron01";
pub const DEFAULT_SUDO_ACCOUNT: &str = "0xf24FF3a9CF04c71Dbc94D0b566f7A27B94566cac";
fn parse_chaintype(s: &str) -> Result<ChainType, CliError> {
Ok(match s {
CHAINTYPE_DEV => ChainType::Development,
CHAINTYPE_LOCAL => ChainType::Local,
CHAINTYPE_LIVE => ChainType::Live,
s => panic!("Wrong chain type {s} Possible values: dev local live"),
})
}
fn parse_account_id(s: &str) -> Result<AccountId, CliError> {
let clean_hex = if let Some(stripped_account) = s.strip_prefix("0x") { stripped_account } else { s };
let bytes = match hex::decode(clean_hex) {
Ok(data) => data,
Err(err) => panic!("Failed to decode hex string: {err}"),
};
Ok(AccountId::from(bytes))
}
#[derive(Debug, Clone)]
pub struct SerializablePeerId {
peer_id: libp2p::PeerId,
}
impl SerializablePeerId {
pub fn new(peer_id: libp2p::PeerId) -> Self {
Self { peer_id }
}
}
impl serde::Serialize for SerializablePeerId {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
let s: String = format!("{}", self.peer_id);
serializer.serialize_str(&s)
}
}
impl<'de> serde::Deserialize<'de> for SerializablePeerId {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let s: String = serde::Deserialize::deserialize(deserializer)?;
let peer_id = libp2p::PeerId::from_str(&s).map_err(
|_| serde::de::Error::custom(format!("Could not deserialize as peer id: {s}")))?;
Ok(Self::new(peer_id))
}
}
#[derive(Debug, serde::Serialize, serde::Deserialize, Clone)]
pub struct AuthorityKeys {
pub account_id: AccountId,
pub aura_id: AuraId,
pub phron_id: PhronId,
pub peer_id: SerializablePeerId,
}
fn to_account_ids(keys: &[AuthorityKeys]) -> impl Iterator<Item=AccountId> + '_ {
keys.iter().map(|keys| keys.account_id)
}
fn unique_account_ids(keys: Vec<AccountId>) -> Vec<AccountId> {
let set: std::collections::HashSet<_> = keys.into_iter().collect();
set.into_iter().collect()
}
const ENDOWMENT: Balance = 1_000_000_000 * PHRON;
const STASH: Balance = ENDOWMENT / 1000;
fn calculate_endowment(keys: &[AccountId]) -> u128 {
ENDOWMENT / (keys.len() as u128)
}
const TELEMETRY_URL: &str = "wss://telemetry.polkadot.io/submit/";
pub type ChainSpec = sc_service::GenericChainSpec<RuntimeGenesisConfig>;
pub fn get_from_seed<TPublic: Public>(seed: &str) -> <TPublic::Pair as Pair>::Public {
TPublic::Pair::from_string(&format!("//{}", seed), None)
.expect("static values are valid; qed")
.public()
}
#[allow(dead_code)]
type AccountPublic = <Signature as sp_runtime::traits::Verify>::Signer;
pub fn chainspec_properties() -> sc_service::Properties {
let mut properties = sc_service::Properties::new();
properties.insert("tokenDecimals".into(), 18.into());
properties.insert("tokenSymbol".into(), "PHR".into());
properties
}
#[allow(dead_code)]
pub fn get_account_id_from_seed<TPublic: Public>(seed: &str) -> AccountId
where
AccountPublic: From<<TPublic::Pair as Pair>::Public>,
{
AccountPublic::from(get_from_seed::<TPublic>(seed)).into_account()
}
#[derive(Debug, Args, Clone)]
pub struct ChainParams {
#[arg(long, value_name = "ID", default_value = DEFAULT_CHAIN_ID)]
chain_id: String,
#[arg(long, value_name = "TYPE", value_parser = parse_chaintype, default_value = CHAINTYPE_DEV)]
chain_type: ChainType,
#[arg(long, default_value = "Phronesis")]
chain_name: String,
#[arg(long, value_delimiter = ',', value_parser = parse_account_id, num_args=1..)]
account_ids: Vec<AccountId>,
#[arg(long, default_value(DEFAULT_SUDO_ACCOUNT), value_parser = parse_account_id)]
sudo_account_id: AccountId,
#[arg(long, value_delimiter = ',', num_args=1.., value_parser = parse_account_id)]
rich_account_ids: Option<Vec<AccountId>>,
#[arg(long, default_value = LEGACY_FINALITY_VERSION.to_string())]
finality_version: FinalityVersion,
}
impl ChainParams {
pub fn chain_id(&self) -> &str {
&self.chain_id
}
pub fn chain_type(&self) -> ChainType {
self.chain_type.clone()
}
pub fn chain_name(&self) -> &str {
&self.chain_name
}
pub fn account_ids(&self) -> Vec<AccountId> {
self.account_ids.clone()
}
pub fn sudo_account_id(&self) -> AccountId {
self.sudo_account_id
}
pub fn rich_account_ids(&self) -> Option<Vec<AccountId>> {
self.rich_account_ids.clone()
}
pub fn finality_version(&self) -> FinalityVersion {
self.finality_version
}
}
pub fn build_chain_spec(
chain_params: ChainParams,
authority_keys: Vec<AuthorityKeys>
) -> Result<ChainSpec, String> {
let wasm_binary = WASM_BINARY.ok_or_else(|| "Development wasm not available".to_string())?;
let chain_name = chain_params.chain_name();
let chain_id = chain_params.chain_id();
let chain_type = chain_params.chain_type();
let sudo_account_id = chain_params.sudo_account_id();
let account_ids = chain_params.account_ids();
let finality_version = chain_params.finality_version();
Ok(ChainSpec::from_genesis(
chain_name,
chain_id,
chain_type,
move || {
generate_genesis_config(
wasm_binary,
authority_keys.clone(),
sudo_account_id,
vec![],
account_ids.clone(),
finality_version,
)
},
vec![],
Some(sc_telemetry::TelemetryEndpoints::new(vec![(TELEMETRY_URL.into(), 1u8)]).unwrap()),
None,
None,
Some(chainspec_properties()),
None,
))
}
fn generate_genesis_config(
wasm_binary: &[u8],
initial_authorities: Vec<AuthorityKeys>,
root_key: AccountId,
_initial_nominators: Vec<AccountId>,
endowed_accounts: Vec<AccountId>,
finality_version: FinalityVersion,
) -> RuntimeGenesisConfig {
let unique_accounts = unique_account_ids(
to_account_ids(&initial_authorities)
.chain(endowed_accounts.clone())
.collect::<Vec<_>>());
let initial_balances = unique_accounts
.into_iter()
.map(|account_id| (account_id, calculate_endowment(&endowed_accounts)))
.collect::<Vec<_>>();
let committee_members: Vec<AccountId> = to_account_ids(&initial_authorities).collect();
let stakers = initial_authorities
.iter()
.enumerate()
.map(|(validator_idx, validator)| {
(
validator.account_id,
validator.account_id,
(validator_idx + 1) as u128 * STASH,
StakerStatus::<AccountId>::Validator,
)
}).collect();
RuntimeGenesisConfig {
system: SystemConfig {
code: wasm_binary.to_vec(),
..Default::default()
},
balances: BalancesConfig {
balances: initial_balances
},
aura: Default::default(),
phron: PhronConfig {
finality_version,
..Default::default()
},
sudo: SudoConfig {
key: Some(root_key),
},
transaction_payment: Default::default(),
session: SessionConfig {
keys: initial_authorities
.iter()
.map(|keys| {
(
keys.account_id,
keys.account_id,
SessionKeys {
aura: keys.aura_id.clone(),
phron: keys.phron_id.clone(),
},
)
}).collect(),
},
evm: EVMConfig {
accounts: {
let mut accounts = BTreeMap::new();
accounts.insert(
H160::from_slice(&hex!("C8742A5fBb4e5db67c50d326Ee7F7b846A842642")),
GenesisAccount {
nonce: U256::zero(),
balance: U256::from_str("0xffffffffffffffffffffffffffffffff")
.expect("internal U256 is valid; qed"),
code: vec![],
storage: BTreeMap::new(),
},
);
accounts
},
..Default::default()
},
ethereum: Default::default(),
base_fee: Default::default(),
dynamic_fee: Default::default(),
staking: StakingConfig {
validator_count: initial_authorities.len() as u32,
minimum_validator_count: initial_authorities.len() as u32,
invulnerables: initial_authorities.iter().map(|keys| keys.account_id).collect(),
slash_reward_fraction: sp_runtime::Perbill::from_percent(10),
stakers,
..Default::default()
},
elections: ElectionsConfig {
non_reserved_validators: vec![],
reserved_validators: committee_members.clone(),
committee_seats: Default::default(),
},
treasury: Default::default(),
committee_management: CommitteeManagementConfig {
committee_ban_config: Default::default(),
session_validators: SessionValidators {
committee: committee_members,
non_committee: vec![],
},
}
}
}
pub fn devnet_config() -> Result<ChainSpec, String> {
ChainSpec::from_json_bytes(crate::chain_resources::devnet_chainspec())
}
pub fn local_config() -> Result<ChainSpec, String> {
ChainSpec::from_json_bytes(crate::chain_resources::local_chainspec())
}
pub fn mainnet_config() -> Result<ChainSpec, String> {
ChainSpec::from_json_bytes(crate::chain_resources::mainnet_chainspec())
}