// Copyright 2021 Axiom-Team // // This file is part of Duniter-v2S. // // Duniter-v2S is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published by // the Free Software Foundation, version 3 of the License. // // Duniter-v2S is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with Duniter-v2S. If not, see <https://www.gnu.org/licenses/>. use crate::chain_spec::{get_account_id_from_seed, get_from_seed, AccountPublic}; use common_runtime::constants::{DAYS, MILLISECS_PER_BLOCK}; use common_runtime::*; use log::{error, warn}; use num_format::{Locale, ToFormattedString}; use pallet_im_online::sr25519::AuthorityId as ImOnlineId; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use sp_authority_discovery::AuthorityId as AuthorityDiscoveryId; use sp_consensus_babe::AuthorityId as BabeId; use sp_consensus_grandpa::AuthorityId as GrandpaId; use sp_core::crypto::AccountId32; use sp_core::{blake2_256, ed25519, sr25519, Decode, Encode, H256}; use sp_runtime::traits::{IdentifyAccount, Verify}; use sp_runtime::{MultiSignature, Perbill}; use std::collections::{BTreeMap, HashMap}; use std::fmt::{Display, Formatter}; use std::fs; use std::ops::{Add, Sub}; static G1_DUNITER_V1_EXISTENTIAL_DEPOSIT: u64 = 100; static G1_DUNITER_V1_DECIMALS: usize = 2; static G1_DUNITER_V1_DT: u64 = 86400; static G1_DUNITER_V1_SIGPERIOD: u32 = 432000; static G1_DUNITER_V1_SIGSTOCK: u32 = 100; static G1_DUNITER_V1_SIGVALIDITY: u32 = 63115200; static G1_DUNITER_V1_SIGQTY: u32 = 5; static G1_DUNITER_V1_MSVALIDITY: u32 = 31557600; static G1_DUNITER_V1_STEPMAX: u32 = 5; static G1_DUNITER_V1_DTREEVAL: u64 = 15778800; // Warning: Duniter V1 "days" are expressed in seconds, while V2 are expressed in blocks static DUNITER_V1_DAYS: u32 = 3600 * 24; // Not used in V2S // static G1_DUNITER_V1_SIGWINDOW: u32 = 5259600; // no more pool // static G1_DUNITER_V1_IDTYWINDOW: u32 = 5259600; // no more pool // static G1_DUNITER_V1_MSWINDOW: u32 = 5259600; // no more pool // static G1_DUNITER_V1_PERCENTROT: f32 = 0.67; // no more PoW // static G1_DUNITER_V1_MEDIANTIMEBLOCKS: u32 = 24; // no more PoW // static G1_DUNITER_V1_AVGGENTIME: u32 = 300; // no more PoW // static G1_DUNITER_V1_DTDIFFEVAL: u32 = 12; // no more PoW // static G1_DUNITER_V1_UD0: u32 = 1000; // new value // static G1_DUNITER_V1_MSPERIOD: u32 = 5259600; // no more used // static G1_DUNITER_V1_UDTIME0: u32 = 1488970800; // duniter v1 specific // static G1_DUNITER_V1_UDREEVALTIME0: u32 = 1490094000; // duniter v1 specific type MembershipData = sp_membership::MembershipData<u32>; #[derive(Clone)] pub struct GenesisData<Parameters: DeserializeOwned, SessionKeys: Decode> { pub accounts: BTreeMap<AccountId, GenesisAccountData<u64, u32>>, pub treasury_balance: u64, pub certs_by_receiver: BTreeMap<u32, BTreeMap<u32, Option<u32>>>, pub first_ud: Option<u64>, pub first_ud_reeval: Option<u64>, pub identities: Vec<GenesisIdentity>, pub initial_authorities: BTreeMap<u32, (AccountId, bool)>, pub initial_monetary_mass: u64, pub memberships: BTreeMap<u32, MembershipData>, pub parameters: Option<Parameters>, pub common_parameters: Option<CommonParameters>, pub session_keys_map: BTreeMap<AccountId, SessionKeys>, pub smith_certs_by_receiver: BTreeMap<u32, BTreeMap<u32, Option<u32>>>, pub smith_memberships: BTreeMap<u32, MembershipData>, pub sudo_key: Option<AccountId>, pub technical_committee_members: Vec<AccountId>, pub ud: u64, } #[derive(Deserialize, Serialize)] struct BlockV1 { number: u32, #[serde(rename = "medianTime")] median_time: u64, } #[derive(Clone)] pub struct GenesisIdentity { pub idty_index: u32, pub name: String, pub owner_key: AccountId, pub old_owner_key: Option<AccountId>, pub active: bool, } #[derive(Deserialize, Serialize)] struct GenesisInput<Parameters> { first_ud: Option<u64>, first_ud_reeval: Option<u64>, #[serde(default)] parameters: Option<Parameters>, #[serde(rename = "smiths")] smith_identities: Option<BTreeMap<String, RawSmith>>, clique_smiths: Option<Vec<CliqueSmith>>, sudo_key: Option<AccountId>, treasury_funder_pubkey: Option<PubkeyV1>, treasury_funder_address: Option<AccountId>, technical_committee: Vec<String>, ud: u64, } #[derive(Deserialize, Serialize)] pub struct GenesisIndexerExport { first_ud: Option<u64>, first_ud_reeval: Option<u64>, genesis_parameters: CommonParameters, identities: HashMap<String, IdentityV2>, smiths: BTreeMap<String, SmithData>, sudo_key: Option<AccountId>, technical_committee: Vec<String>, ud: u64, wallets: BTreeMap<AccountId, u64>, transactions_history: Option<BTreeMap<AccountId, Vec<TransactionV2>>>, } #[derive(Deserialize, Serialize)] struct TransactionV1 { issuer: PubkeyV1, amount: String, written_time: Option<u32>, comment: String, } #[derive(Deserialize, Serialize)] struct TransactionV2 { issuer: AccountId, amount: String, written_time: Option<u32>, comment: String, } #[derive(Deserialize, Serialize)] struct GenesisMigrationData { initial_monetary_mass: u64, current_block: BlockV1, identities: BTreeMap<String, IdentityV1>, #[serde(default)] wallets: BTreeMap<PubkeyV1, u64>, transactions_history: Option<BTreeMap<PubkeyV1, Vec<TransactionV1>>>, } // Base58 encoded Ed25519 public key #[derive(Clone, Deserialize, Serialize, Ord, PartialOrd, Eq, PartialEq)] struct PubkeyV1(String); // Timestamp #[derive(Clone, Deserialize, Serialize)] struct TimestampV1(u32); impl Display for PubkeyV1 { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.0) } } /// identities #[derive(Clone, Deserialize, Serialize)] struct IdentityV1 { /// indentity index matching the order of appearance in the Ǧ1v1 blockchain index: u32, /// Base58 public key in Ğ1v1 owner_pubkey: Option<PubkeyV1>, /// Ğ1v2 address owner_address: Option<AccountId>, /// Optional Base58 public key in Ğ1v1 old_owner_key: Option<PubkeyV1>, /// timestamp at which the membership is set to expire (0 for expired members) membership_expire_on: TimestampV1, /// timestamp at which the next cert can be emitted next_cert_issuable_on: TimestampV1, // TODO: unused? /// balance of the account of this identity balance: u64, /// certs received with their expiration timestamp certs_received: HashMap<String, TimestampV1>, } /// identities #[derive(Clone, Deserialize, Serialize)] struct IdentityV2 { /// indentity index matching the order of appearance in the Ǧ1v1 blockchain index: u32, /// ss58 address in gx network owner_key: AccountId, /// optional ss58 address in the Ğ1v1 old_owner_key: Option<AccountId>, /// block at which the membership is set to expire (0 for expired members) membership_expire_on: u32, /// block at which the next cert can be emitted next_cert_issuable_on: u32, /// balance of the account of this identity balance: u64, /// certs received with their expiration block certs_received: HashMap<String, u32>, } #[derive(Clone, Deserialize, Serialize)] struct RawSmith { name: String, /// optional pre-set session keys (at least for the smith bootstraping the blockchain) session_keys: Option<String>, /// optional pre-set account migration migration_address: Option<AccountId>, #[serde(default)] certs_received: Vec<String>, } #[derive(Clone, Deserialize, Serialize)] struct SmithData { idty_index: u32, name: String, account: AccountId, /// optional pre-set session keys (at least for the smith bootstraping the blockchain) session_keys: Option<String>, #[serde(default)] certs_received: Vec<String>, } #[derive(Clone, Deserialize, Serialize)] struct CliqueSmith { name: String, migration_address: Option<AccountId>, session_keys: Option<String>, } struct SmithWoT<SK: Decode> { smith_certs_by_receiver: BTreeMap<u32, BTreeMap<u32, Option<u32>>>, smith_memberships: BTreeMap<u32, sp_membership::MembershipData<u32>>, session_keys_map: BTreeMap<<<MultiSignature as Verify>::Signer as IdentifyAccount>::AccountId, SK>, } struct GenesisInfo<'a> { genesis_timestamp: u64, accounts: &'a BTreeMap<AccountId32, GenesisAccountData<u64, u32>>, genesis_data_wallets_count: &'a usize, inactive_identities: &'a HashMap<u32, String>, identities: &'a Vec<GenesisIdentity>, identity_index: &'a HashMap<u32, String>, smith_memberships: &'a BTreeMap<u32, MembershipData>, counter_online_authorities: &'a u32, counter_cert: &'a u32, counter_smith_cert: &'a u32, technical_committee_members: &'a Vec<AccountId32>, common_parameters: &'a CommonParameters, } /// generate genesis data from a json file /// takes DUNITER_GENESIS_CONFIG env var if present or duniter-gen-conf.json by default // this function is targeting dev chainspecs, do not use in production network pub fn generate_genesis_data<P, SK, SessionKeys: Encode, SKP>( config_file_path: String, get_common_parameters: fn(&Option<P>) -> CommonParameters, maybe_force_authority: Option<String>, ) -> Result<GenesisData<P, SK>, String> where P: Default + DeserializeOwned, SK: Decode, SKP: SessionKeysProvider<SessionKeys>, { let genesis_timestamp: u64 = get_genesis_timestamp()?; // Per network input let GenesisInput { sudo_key, treasury_funder_pubkey, treasury_funder_address, first_ud, first_ud_reeval, parameters, smith_identities, clique_smiths, technical_committee, ud, } = get_genesis_input::<P>( std::env::var("DUNITER_GENESIS_CONFIG").unwrap_or_else(|_| config_file_path.to_owned()), )?; // Per network parameters let common_parameters = get_common_parameters(¶meters); // Per network smiths (without link to an account yet — identified by their pseudonym) let mut smiths = build_smiths_wot(&clique_smiths, smith_identities)?; // G1 data migration (common to all networks) let mut genesis_data = get_genesis_migration_data()?; check_parameters_consistency(&genesis_data.wallets, &first_ud, &first_ud_reeval, &ud)?; check_genesis_data_and_filter_expired_certs_since_export( &mut genesis_data, genesis_timestamp, &common_parameters, ); let mut identities_v2: HashMap<String, IdentityV2> = genesis_data_to_identities_v2(genesis_data.identities, genesis_timestamp, &smiths); check_identities_v2(&identities_v2, &common_parameters); // MONEY AND WOT // // declare variables for building genesis // ------------------------------------- // track if fatal error occured, but let processing continue let mut fatal = false; // initial Treasury balance let mut treasury_balance = 0; // track identity index let mut identity_index = HashMap::new(); // track inactive identities let mut inactive_identities = HashMap::<u32, String>::new(); // declare variables to fill in genesis // ------------------------------------- // members of technical committee let mut technical_committee_members: Vec<AccountId> = Vec::new(); // memberships let mut memberships = BTreeMap::new(); // certifications let mut certs_by_receiver = BTreeMap::new(); // initial authorities let mut initial_authorities = BTreeMap::new(); //let mut total_dust = 0; // FORCED AUTHORITY // // If this authority is defined (most likely Alice), then it must exist in both _identities_ // and _smiths_. We create all this, for *development purposes* (used with `gdev_dev` or `gtest_dev` chains). if let Some(authority_name) = &maybe_force_authority { make_authority_exist::<SessionKeys, SKP>( &mut identities_v2, &mut smiths, &common_parameters, authority_name, ); } // SIMPLE WALLETS // let genesis_data_wallets_count = genesis_data.wallets.len(); let (was_fatal, mut monetary_mass, mut accounts, invalid_wallets) = v1_wallets_to_v2_accounts(genesis_data.wallets, &common_parameters); if was_fatal { fatal = true; } // Technical Comittee // // NOTE : when changing owner key, the technical committee is not changed for name in &technical_committee { if let Some(identity) = &identities_v2.get(name) { technical_committee_members.push(identity.owner_key.clone()); } else { log::error!("Identity '{}' does not exist", name); fatal = true; } } // IDENTITIES // let (was_fatal, identities) = feed_identities( &mut accounts, &mut identity_index, &mut monetary_mass, &mut inactive_identities, &mut memberships, &identities_v2, )?; if was_fatal { fatal = true; } // CERTIFICATIONS // // counter for certifications let (was_fatal, counter_cert) = feed_certs_by_receiver(&mut certs_by_receiver, &identities_v2); if was_fatal { fatal = true; } // SMITHS SUB-WOT // // Authorities if let Some(name) = &maybe_force_authority { check_authority_exists_in_both_wots(name, &identities_v2, &smiths); } let smiths = decorate_smiths_with_identity(smiths, &identity_index, &identities_v2); // counter for online authorities at genesis let ( was_fatal, counter_online_authorities, counter_smith_cert, SmithWoT { smith_certs_by_receiver, smith_memberships, session_keys_map, }, ) = create_smith_wot( &mut initial_authorities, &identities_v2, &smiths, &common_parameters, &clique_smiths, )?; if was_fatal { fatal = true; } // Verify certifications coherence (can be ignored for old users) for (idty_index, receiver_certs) in &certs_by_receiver { if receiver_certs.len() < common_parameters.min_cert as usize { let name = identity_index.get(idty_index).unwrap(); let identity = identities_v2.get(&(*name).clone()).unwrap(); if identity.membership_expire_on != 0 { log::error!( "[{}] has received only {}/{} certifications", name, receiver_certs.len(), common_parameters.min_cert ); fatal = true; } } } // Verify smith certifications coherence for (idty_index, certs) in &smith_certs_by_receiver { if certs.len() < common_parameters.smith_min_cert as usize { log::error!( "[{}] has received only {}/{} smith certifications", identity_index.get(idty_index).unwrap(), certs.len(), common_parameters.smith_min_cert ); fatal = true; } } // check number of online authorities if maybe_force_authority.is_none() && counter_online_authorities != 1 { log::error!("one and only one smith must be online, not {counter_online_authorities}"); } // check monetary mass if monetary_mass != genesis_data.initial_monetary_mass { log::warn!( "actual monetary_mass ({}) and initial_monetary_mass ({}) do not match", monetary_mass.to_formatted_string(&Locale::en), genesis_data .initial_monetary_mass .to_formatted_string(&Locale::en) ); if monetary_mass > genesis_data.initial_monetary_mass { log::error!("money has been created"); fatal = true; } } // treasury balance must come from existing money let treasury_funder: AccountId = match (treasury_funder_address, treasury_funder_pubkey) { (Some(address), None) => address, (None, Some(pubkey)) => { v1_pubkey_to_account_id(pubkey).expect("treasury founder must have a valid public key") } _ => panic!("One of treasury_funder_address or treasury_funder_pubkey must be set"), }; if let Some(existing_account) = accounts.get_mut(&treasury_funder) { existing_account.balance = existing_account .balance .checked_sub(common_parameters.existential_deposit) .expect("should have enough money to fund Treasury"); treasury_balance = common_parameters.existential_deposit; } if treasury_balance < common_parameters.existential_deposit { log::error!( "Treasury balance {} is inferior to existential deposit {}", treasury_balance, common_parameters.existential_deposit ); fatal = true; } smiths.iter().for_each(|smith| { log::info!( "[Smith] {} ({} - {})", smith.idty_index, smith.account, smith.name.clone() ); }); initial_authorities .iter() .for_each(|(index, (authority_account, online))| { log::info!( "[Authority] {} : {} ({} - {})", index, if *online { "online" } else { "offline" }, authority_account, identity_index .get(index) .expect("authority should have an identity") ); }); let genesis_info = GenesisInfo { genesis_timestamp, accounts: &accounts, genesis_data_wallets_count: &genesis_data_wallets_count, identities: &identities, inactive_identities: &inactive_identities, identity_index: &identity_index, smith_memberships: &smith_memberships, counter_online_authorities: &counter_online_authorities, counter_cert: &counter_cert, counter_smith_cert: &counter_smith_cert, technical_committee_members: &technical_committee_members, common_parameters: &common_parameters, }; dump_genesis_info(genesis_info); if parameters.is_some() { let g1_duniter_v1_c = 0.0488; let g1_duniter_v1_xpercent: Perbill = Perbill::from_float(0.8); let c = f32::sqrt(common_parameters.c2.deconstruct() as f32 / 1_000_000_000f32); // static parameters (GTest or G1) if common_parameters.decimals != G1_DUNITER_V1_DECIMALS { warn!( "parameter `decimals` value ({}) is different from Ğ1 value ({})", common_parameters.decimals, G1_DUNITER_V1_DECIMALS ) } if common_parameters.existential_deposit != G1_DUNITER_V1_EXISTENTIAL_DEPOSIT { warn!( "parameter `existential_deposit` value ({}) is different from Ğ1 value ({})", common_parameters.existential_deposit, G1_DUNITER_V1_EXISTENTIAL_DEPOSIT ) } if common_parameters.membership_period / DAYS != G1_DUNITER_V1_MSVALIDITY / DUNITER_V1_DAYS { warn!( "parameter `membership_period` ({} days) is different from Ğ1's ({} days)", common_parameters.membership_period as f32 / DAYS as f32, G1_DUNITER_V1_MSVALIDITY as f32 / DUNITER_V1_DAYS as f32 ) } if common_parameters.cert_period / DAYS != G1_DUNITER_V1_SIGPERIOD / DUNITER_V1_DAYS { warn!( "parameter `cert_period` ({} days) is different from Ğ1's ({} days)", common_parameters.cert_period as f32 / DAYS as f32, G1_DUNITER_V1_SIGPERIOD as f32 / DUNITER_V1_DAYS as f32 ) } if common_parameters.cert_validity_period / DAYS != G1_DUNITER_V1_SIGVALIDITY / DUNITER_V1_DAYS { warn!( "parameter `cert_validity_period` ({} days) is different from Ğ1's ({} days)", common_parameters.cert_validity_period as f32 / DAYS as f32, G1_DUNITER_V1_SIGVALIDITY as f32 / DUNITER_V1_DAYS as f32 ) } if common_parameters.min_cert != G1_DUNITER_V1_SIGQTY { warn!( "parameter `min_cert` value ({}) is different from Ğ1 value ({})", common_parameters.min_cert, G1_DUNITER_V1_SIGQTY ) } if common_parameters.cert_max_by_issuer != G1_DUNITER_V1_SIGSTOCK { warn!( "parameter `cert_max_by_issuer` value ({}) is different from Ğ1 value ({})", common_parameters.cert_max_by_issuer, G1_DUNITER_V1_SIGSTOCK ) } if c != g1_duniter_v1_c { warn!( "parameter `c` value ({}) is different from Ğ1 value ({})", c, g1_duniter_v1_c ) } if common_parameters.ud_creation_period as f32 / DAYS as f32 != G1_DUNITER_V1_DT as f32 / DUNITER_V1_DAYS as f32 { warn!( "parameter `ud_creation_period` value ({} days) is different from Ğ1 value ({} days)", common_parameters.ud_creation_period as f32 / DAYS as f32, G1_DUNITER_V1_DT as f32 / DUNITER_V1_DAYS as f32 ) } if common_parameters.ud_reeval_period as f32 / DAYS as f32 != G1_DUNITER_V1_DTREEVAL as f32 / DUNITER_V1_DAYS as f32 { warn!( "parameter `ud_reeval_period` value ({} days) is different from Ğ1 value ({} days)", common_parameters.ud_reeval_period as f32 / DAYS as f32, G1_DUNITER_V1_DTREEVAL as f32 / DUNITER_V1_DAYS as f32 ) } if common_parameters.distance_min_accessible_referees != g1_duniter_v1_xpercent { warn!( "parameter `distance_min_accessible_referees` value ({}) is different from Ğ1 value ({})", format!("{:?}", common_parameters.distance_min_accessible_referees), format!("{:?}", g1_duniter_v1_xpercent) ) } if common_parameters.max_depth != G1_DUNITER_V1_STEPMAX { warn!( "parameter `max_depth` value ({}) is different from Ğ1 value ({})", common_parameters.max_depth, G1_DUNITER_V1_STEPMAX ) } let count_uds = common_parameters.ud_reeval_period / common_parameters.ud_creation_period; if count_uds == 0 { error!( "the `ud_reeval_period / ud_creation_period` is zero ({} days/{} days)", common_parameters.ud_reeval_period / DAYS as u64, common_parameters.ud_creation_period / DAYS as u64 ); fatal = true; } } // some more checks assert_eq!( identities.len() - inactive_identities.len(), memberships.len() ); assert_eq!(smith_memberships.len(), initial_authorities.len()); assert_eq!(smith_memberships.len(), session_keys_map.len()); assert_eq!(identity_index.len(), identities.len()); assert_eq!( accounts.len(), identity_index.len() + genesis_data_wallets_count.sub(invalid_wallets) ); smiths_and_technical_committee_checks(&inactive_identities, &technical_committee, &smiths); // check the logs to see all the fatal error preventing from starting gtest currency if fatal { log::error!("some previously logged error prevent from building a sane genesis"); panic!(); } // Indexer output if let Ok(path) = std::env::var("DUNITER_GENESIS_EXPORT") { // genesis_certs_min_received => min_cert // genesis_memberships_expire_on => membership_period // genesis_smith_certs_min_received => smith_min_cert // genesis_smith_memberships_expire_on => smith_membership_period let export = GenesisIndexerExport { first_ud, first_ud_reeval, genesis_parameters: common_parameters.clone(), identities: identities_v2, sudo_key: sudo_key.clone(), technical_committee, ud, wallets: accounts .iter() .map(|(account_id, data)| (account_id.clone(), data.balance)) .collect(), smiths: (smiths) .iter() .map(|smith| { ( smith.name.clone(), SmithData { idty_index: smith.idty_index, name: smith.name.clone(), account: smith.account.clone(), session_keys: smith.session_keys.clone(), certs_received: smith.certs_received.clone(), }, ) }) .collect::<BTreeMap<String, SmithData>>(), transactions_history: genesis_data.transactions_history.map(|history| { history .iter() // Avoid wrong pubkeys in tx history .filter(|(pubkey, _)| v1_pubkey_to_account_id((*pubkey).clone()).is_ok()) .map(|(pubkey, txs)| { ( v1_pubkey_to_account_id(pubkey.clone()) .expect("already checked account"), txs.iter() // Avoid wrong pubkeys in tx history .filter(|tx| v1_pubkey_to_account_id(tx.issuer.clone()).is_ok()) .map(|tx| TransactionV2 { issuer: v1_pubkey_to_account_id(tx.issuer.clone()) .expect("already checked tx.issuer"), amount: tx.amount.clone(), written_time: tx.written_time, comment: tx.comment.clone(), }) .collect::<Vec<TransactionV2>>(), ) }) .collect::<BTreeMap<AccountId, Vec<TransactionV2>>>() }), }; fs::write( &path, serde_json::to_string_pretty(&export).expect("should be serializable"), ) .unwrap_or_else(|_| panic!("Could not export genesis data to {}", &path)); } let genesis_data = GenesisData { accounts, treasury_balance, certs_by_receiver, first_ud, first_ud_reeval, identities, initial_authorities, initial_monetary_mass: genesis_data.initial_monetary_mass, memberships, parameters, common_parameters: Some(common_parameters), session_keys_map, smith_certs_by_receiver, smith_memberships, sudo_key, technical_committee_members, ud, }; Ok(genesis_data) } fn dump_genesis_info(info: GenesisInfo) { // give genesis info log::info!( "prepared genesis with: - {} as genesis timestamp - {} accounts ({} identities, {} simple wallets) - {} total identities ({} active, {} inactive) - {} smiths - {} initial online authorities - {} certifications - {} smith certifications - {} members in technical committee", info.genesis_timestamp, info.accounts.len(), info.identities.len() - info.inactive_identities.len(), info.genesis_data_wallets_count, info.identity_index.len(), info.identities.len() - info.inactive_identities.len(), info.inactive_identities.len(), info.smith_memberships.len(), info.counter_online_authorities, info.counter_cert, info.counter_smith_cert, info.technical_committee_members.len(), ); let (membership_period, membership_period_unit) = get_best_unit_and_diviser_for_blocks(info.common_parameters.membership_period); let (cert_period, cert_period_unit) = get_best_unit_and_diviser_for_blocks(info.common_parameters.cert_period); let (cert_validity_period, cert_validity_period_unit) = get_best_unit_and_diviser_for_blocks(info.common_parameters.cert_validity_period); let (smith_membership_period, smith_membership_period_unit) = get_best_unit_and_diviser_for_blocks(info.common_parameters.smith_membership_period); let (smith_certs_validity_period, smith_certs_validity_period_unit) = get_best_unit_and_diviser_for_blocks(info.common_parameters.smith_certs_validity_period); let (ud_reeval_period, ud_reeval_period_unit) = get_best_unit_and_diviser_for_ms(info.common_parameters.ud_reeval_period as f32); let (ud_creation_period, ud_creation_period_unit) = get_best_unit_and_diviser_for_ms(info.common_parameters.ud_creation_period as f32); // give genesis info log::info!( "currency parameters: - existential deposit: {} {} - currency decimals: {} - membership validity: {} {} - certification period: {} {} - certification validity duration: {} {} - smith membership validity: {} {} - smith certification validity: {} {} - required certifications: {} - smith required certifications: {} - max certifications by issuer: {} - money growth rate: {}% every {} {} - UD creation period: {} {} - distance percent of required referees: {}% - distance max depth: {}", info.common_parameters.existential_deposit as f64 / 100.0, info.common_parameters.currency_name, info.common_parameters.decimals, membership_period, membership_period_unit, cert_period, cert_period_unit, cert_validity_period, cert_validity_period_unit, smith_membership_period, smith_membership_period_unit, smith_certs_validity_period, smith_certs_validity_period_unit, info.common_parameters.min_cert, info.common_parameters.smith_min_cert, info.common_parameters.cert_max_by_issuer, f32::sqrt(info.common_parameters.c2.deconstruct() as f32 / 1_000_000_000f32) * 100f32, ud_reeval_period, ud_reeval_period_unit, ud_creation_period, ud_creation_period_unit, info.common_parameters .distance_min_accessible_referees .deconstruct() as f32 / 1_000_000_000f32 * 100f32, info.common_parameters.max_depth, ); } fn get_best_unit_and_diviser_for_ms(duration_in_ms: f32) -> (f32, String) { let diviser = get_best_diviser(duration_in_ms); let qty = duration_in_ms / diviser; let unit = diviser_to_unit(diviser, qty); (qty, unit) } fn get_best_unit_and_diviser_for_blocks(duration_in_blocks: u32) -> (f32, String) { let duration_in_ms = duration_in_blocks as f32 * (MILLISECS_PER_BLOCK as u32) as f32; get_best_unit_and_diviser_for_ms(duration_in_ms) } fn diviser_to_unit(value_in_ms: f32, qty: f32) -> String { let unit = if value_in_ms >= 24.0 * 3600.0 * 1000.0 { "day".to_string() } else if value_in_ms >= 3600.0 * 1000.0 { "hour".to_string() } else if value_in_ms >= 60.0 * 1000.0 { "minute".to_string() } else { "second".to_string() }; let plural = if qty > 1f32 { "s" } else { "" }; format!("{}{}", unit, plural) } fn get_best_diviser(ms_value: f32) -> f32 { let one_minute: f32 = 1000.0 * 60.0; let one_hour: f32 = one_minute * 60.0; let one_day: f32 = one_hour * 24.0; if ms_value > one_day { one_day } else if ms_value > one_hour { one_hour } else { one_minute } } fn smiths_and_technical_committee_checks( inactive_identities: &HashMap<u32, String>, technical_committee: &Vec<String>, smiths: &Vec<SmithData>, ) { // no inactive tech comm for tech_com_member in technical_committee { let inactive_commitee_member = inactive_identities.values().any(|v| v == tech_com_member); if inactive_commitee_member { log::error!( "{} is an inactive technical commitee member", tech_com_member ); assert!(!inactive_commitee_member); } } // no inactive smith for SmithData { name: smith, .. } in smiths { let inactive_smiths: Vec<_> = inactive_identities .values() .filter(|v| *v == smith) .collect(); inactive_smiths .iter() .for_each(|s| log::warn!("Smith {} is inactive", s)); assert_eq!(inactive_smiths.len(), 0); } } fn create_smith_wot<SK: Decode>( initial_authorities: &mut BTreeMap<u32, (AccountId32, bool)>, identities_v2: &HashMap<String, IdentityV2>, smiths: &Vec<SmithData>, common_parameters: &CommonParameters, clique_smiths: &Option<Vec<CliqueSmith>>, ) -> Result<(bool, u32, u32, SmithWoT<SK>), String> { let mut fatal = false; let mut counter_online_authorities = 0; // counter for smith certifications let mut counter_smith_cert = 0; let mut smith_certs_by_receiver = BTreeMap::new(); // smith memberships let mut smith_memberships = BTreeMap::new(); let mut session_keys_map = BTreeMap::new(); // Then create the smith WoT for smith in smiths { // check that smith exists let identities_v2_clone = identities_v2.clone(); if let Some(identity) = identities_v2.get(&smith.name.clone()) { counter_online_authorities = set_smith_session_keys_and_authority_status( initial_authorities, &mut session_keys_map, &smith, identity, )?; // smith certifications counter_smith_cert += feed_smith_certs_by_receiver( &mut smith_certs_by_receiver, clique_smiths, &smith, identity, &identities_v2_clone, common_parameters, )?; // smith memberships smith_memberships.insert( identity.index, MembershipData { expire_on: common_parameters.smith_membership_period, }, ); } else { log::error!( "Smith '{}' does not correspond to exising identity", &smith.name ); fatal = true; } } Ok(( fatal, counter_online_authorities, counter_smith_cert, SmithWoT { smith_certs_by_receiver, smith_memberships, session_keys_map, }, )) } fn v1_wallets_to_v2_accounts( wallets: BTreeMap<PubkeyV1, u64>, common_parameters: &CommonParameters, ) -> ( bool, u64, BTreeMap<AccountId32, GenesisAccountData<u64, u32>>, usize, ) { // monetary mass for double check let mut monetary_mass = 0u64; // account inserted in genesis let mut accounts: BTreeMap<AccountId, GenesisAccountData<u64, u32>> = BTreeMap::new(); let mut invalid_wallets = 0; let mut fatal = false; for (pubkey, balance) in wallets { // check existential deposit if balance < common_parameters.existential_deposit { log::error!( "wallet {pubkey} has {balance} cǦT which is below {}", common_parameters.existential_deposit ); fatal = true; } // double check the monetary mass monetary_mass += balance; // json prevents duplicate wallets if let Ok(owner_key) = v1_pubkey_to_account_id(pubkey.clone()) { accounts.insert( owner_key.clone(), GenesisAccountData { random_id: H256(blake2_256(&(balance, &owner_key).encode())), balance, idty_id: None, }, ); } else { log::warn!("wallet {pubkey} has wrong format"); invalid_wallets = invalid_wallets.add(1); } } (fatal, monetary_mass, accounts, invalid_wallets) } fn check_identities_v2( identities_v2: &HashMap<String, IdentityV2>, common_parameters: &CommonParameters, ) { // // Identities whose membership was lost since export // identities_v2.iter_mut() // .filter(|(name, i)| (i.membership_expire_on as u64) < genesis_timestamp) // .for_each(|(name, i)| { // log::warn!("{} membership expired since export", name); // i.membership_expire_on = 0; // }); // // Identities that are no more members because of a lack of certs // identities_v2.iter_mut() // .filter(|(name, i)| i.membership_expire_on != 0 && (i.certs_received.len() as u32) < common_parameters.min_cert) // .for_each(|(name, i)| { // log::warn!("{} lost membership because of lost certifications since export", name); // i.membership_expire_on = 0; // }); // Check that members have enough certs identities_v2 .iter() .filter(|(_, i)| i.membership_expire_on != 0) .for_each(|(name, i)| { let nb_certs = i.certs_received.len() as u32; if nb_certs < common_parameters.min_cert { log::warn!("{} has only {} valid certifications", name, nb_certs); } }); } fn check_genesis_data_and_filter_expired_certs_since_export( genesis_data: &mut GenesisMigrationData, genesis_timestamp: u64, common_parameters: &CommonParameters, ) { // Remove expired certs since export genesis_data .identities .iter_mut() .for_each(|(receiver, i)| { i.certs_received.retain(|issuer, v| { let retain = (v.0 as u64) >= genesis_timestamp; if !retain { log::warn!("{} -> {} cert expired since export", issuer, receiver); } retain }); }); genesis_data.identities.iter_mut().for_each(|(name, i)| { if (i.membership_expire_on.0 as u64) < genesis_timestamp { if (i.membership_expire_on.0 as u64) >= genesis_data.current_block.median_time { log::warn!("{} membership expired since export", name); } i.membership_expire_on = TimestampV1(0); } }); genesis_data.identities.iter_mut().for_each(|(name, i)| { if i.membership_expire_on.0 != 0 && i.certs_received.len() < common_parameters.min_cert as usize { i.membership_expire_on = TimestampV1(0); log::warn!( "{} lost membership because of lost certifications since export", name ); } }); genesis_data.identities.iter().for_each(|(name, i)| { if i.owner_pubkey.is_some() && i.owner_address.is_some() { log::warn!( "{} both has a pubkey and an address defined - address will be used", name ); } if i.owner_pubkey.is_none() && i.owner_address.is_none() { log::error!("{} neither has a pubkey and an address defined", name); } }); } fn genesis_data_to_identities_v2( genesis_identities: BTreeMap<String, IdentityV1>, genesis_timestamp: u64, smiths: &[RawSmith], ) -> HashMap<String, IdentityV2> { let key_migrations: HashMap<String, AccountId> = smiths .iter() .filter(|s| s.migration_address.is_some()) .map(|s| { ( s.name.clone(), s.migration_address.clone().expect("already filtered"), ) }) .collect(); genesis_identities .into_iter() .map(|(name, i)| { let legacy_account = i .owner_pubkey .map(|pubkey| { v1_pubkey_to_account_id(pubkey) .expect("a G1 identity necessarily has a valid pubkey") }) .unwrap_or_else(|| { i.owner_address.unwrap_or_else(|| { panic!("neither pubkey nor address is defined for {}", name) }) }); let migration = key_migrations.get(name.as_str()); let owner_key = if let Some(migrated_account) = migration { migrated_account.clone() } else { legacy_account.clone() }; let old_owner_key = if migration.is_none() { None } else { Some(legacy_account) }; ( name, IdentityV2 { index: i.index, owner_key, old_owner_key, membership_expire_on: timestamp_to_relative_blocs( i.membership_expire_on, genesis_timestamp, ), next_cert_issuable_on: timestamp_to_relative_blocs( i.next_cert_issuable_on, genesis_timestamp, ), balance: i.balance, certs_received: i .certs_received .into_iter() .map(|(issuer, timestamp)| { ( issuer, timestamp_to_relative_blocs(timestamp, genesis_timestamp), ) }) .collect(), }, ) }) .collect() } fn make_authority_exist<SessionKeys: Encode, SKP: SessionKeysProvider<SessionKeys>>( identities_v2: &mut HashMap<String, IdentityV2>, smiths: &mut Vec<RawSmith>, common_parameters: &CommonParameters, authority_name: &String, ) { // The identity might already exist, notably: G1 "Alice" already exists if let Some(authority) = identities_v2.get_mut(authority_name) { // Force authority to be active authority.membership_expire_on = common_parameters.membership_period; } else { // Not found: we must create it identities_v2.insert( authority_name.clone(), IdentityV2 { index: (identities_v2.len() as u32 + 1), owner_key: get_account_id_from_seed::<sr25519::Public>(authority_name), balance: common_parameters.existential_deposit, certs_received: HashMap::new(), membership_expire_on: common_parameters.membership_period, old_owner_key: None, next_cert_issuable_on: 0, }, ); }; // Forced authority gets its required certs from first "minCert" WoT identities (fake certs) let mut new_certs: HashMap<String, u32> = HashMap::new(); let certs_of_authority = &identities_v2.get(authority_name).unwrap().certs_received; identities_v2 .keys() // Identities which are not the authority and have not already certified her .filter(|issuer| { issuer != &authority_name && !certs_of_authority .iter() .any(|(authority_issuer, _)| issuer == &authority_issuer) }) .take(common_parameters.min_cert as usize) .map(String::clone) .for_each(|issuer| { new_certs.insert(issuer, common_parameters.cert_period); }); let authority = identities_v2 .get_mut(authority_name) .expect("authority must exist or be created"); new_certs.into_iter().for_each(|(issuer, c)| { authority.certs_received.insert(issuer, c); }); let sk: SessionKeys = SKP::session_keys(&get_authority_keys_from_seed(authority_name.as_str())); let forced_authority_session_keys = format!("0x{}", hex::encode(sk.encode())); // Add forced authority to smiths (whether explicit smith WoT or clique) if let Some(smith) = smiths.iter_mut().find(|s| &s.name == authority_name) { smith.session_keys = Some(forced_authority_session_keys); smith.migration_address = None; } else { smiths.push(RawSmith { name: authority_name.clone(), session_keys: Some(forced_authority_session_keys), migration_address: None, certs_received: vec![], }) } } fn feed_identities( accounts: &mut BTreeMap<AccountId32, GenesisAccountData<u64, u32>>, identity_index: &mut HashMap<u32, String>, monetary_mass: &mut u64, inactive_identities: &mut HashMap<u32, String>, memberships: &mut BTreeMap<u32, MembershipData>, identities_v2: &HashMap<String, IdentityV2>, ) -> Result<(bool, Vec<GenesisIdentity>), String> { let mut fatal = false; let mut identities: Vec<GenesisIdentity> = Vec::new(); for (name, identity) in identities_v2 { // identity name if !validate_idty_name(name) { return Err(format!("Identity name '{}' is invalid", &name)); } // TODO: re-check this code origin and wether it should be included or not // do not check existential deposit of identities // // check existential deposit // if identity.balance < common_parameters.existencial_deposit { // if identity.membership_expire_on == 0 { // log::warn!( // "expired identity {name} has {} cǦT which is below {}", // identity.balance, common_parameters.existencial_deposit // ); // fatal = true; // } else { // member identities can still be below existential deposit thanks to sufficient // log::info!( // "identity {name} has {} cǦT which is below {}", // identity.balance, common_parameters.existencial_deposit // ); // } // } // Money // check that wallet with same owner_key does not exist if accounts.get(&identity.owner_key).is_some() { log::error!( "{name} owner_key {} already exists as a simple wallet", identity.owner_key ); fatal = true; } // insert as an account accounts.insert( identity.owner_key.clone(), GenesisAccountData { random_id: H256(blake2_256(&(identity.index, &identity.owner_key).encode())), balance: identity.balance, idty_id: Some(identity.index), }, ); // double check the monetary mass *monetary_mass += identity.balance; // insert identity // check that index does not already exist if let Some(other_name) = identity_index.get(&identity.index) { log::error!( "{other_name} already has identity index {} of {name}", identity.index ); fatal = true; } identity_index.insert(identity.index, name.to_owned()); let expired = identity.membership_expire_on == 0; // only add the identity if not expired if expired { inactive_identities.insert(identity.index, name.clone()); }; identities.push(GenesisIdentity { // N.B.: every **non-expired** identity on Genesis is considered to have: // - removable_on: 0, // - next_creatable_identity_on: 0, // - status: IdtyStatus::Validated, idty_index: identity.index, name: name.to_owned().clone(), owner_key: identity.owner_key.clone(), old_owner_key: identity.old_owner_key.clone(), // but expired identities will just have their pseudonym reserved in the storage active: !expired, }); // insert the membershup data (only if not expired) if !expired { memberships.insert( identity.index, MembershipData { expire_on: identity.membership_expire_on, }, ); } } // sort the identities by index for reproducibility (should have been a vec in json) identities.sort_unstable_by(|a, b| a.idty_index.cmp(&b.idty_index)); Ok((fatal, identities)) } fn set_smith_session_keys_and_authority_status<SK>( initial_authorities: &mut BTreeMap< u32, ( <<MultiSignature as Verify>::Signer as IdentifyAccount>::AccountId, bool, ), >, session_keys_map: &mut BTreeMap< <<MultiSignature as Verify>::Signer as IdentifyAccount>::AccountId, SK, >, smith: &&SmithData, identity: &IdentityV2, ) -> Result<u32, String> where SK: Decode, { let mut counter_online_authorities = 0; // Initial authorities and session keys let session_keys_bytes = if let Some(declared_session_keys) = &smith.session_keys { counter_online_authorities += 1; // insert authority as online initial_authorities.insert(identity.index, (identity.owner_key.clone(), true)); // decode session keys or force to given value hex::decode(&declared_session_keys[2..]) .map_err(|_| format!("invalid session keys for idty {}", smith.name))? } else { // still authority but offline initial_authorities.insert(identity.index, (identity.owner_key.clone(), false)); // fake session keys let mut fake_bytes = Vec::with_capacity(128); for _ in 0..4 { fake_bytes.extend_from_slice(identity.owner_key.as_ref()) } fake_bytes }; // insert session keys to map session_keys_map.insert( identity.owner_key.clone(), SK::decode(&mut &session_keys_bytes[..]).unwrap(), ); Ok(counter_online_authorities) } fn feed_smith_certs_by_receiver( smith_certs_by_receiver: &mut BTreeMap<u32, BTreeMap<u32, Option<u32>>>, clique_smiths: &Option<Vec<CliqueSmith>>, smith: &&SmithData, identity: &IdentityV2, identities_v2: &HashMap<String, IdentityV2>, common_parameters: &CommonParameters, ) -> Result<u32, String> { let mut counter_smith_cert = 0; let mut certs = BTreeMap::new(); if clique_smiths.is_some() { // All initial smiths are considered to be certifying all each other clique_smiths .as_ref() .unwrap() .iter() .filter(|other_smith| *other_smith.name.as_str() != *smith.name) .for_each(|other_smith| { let issuer_index = &identities_v2 .get(other_smith.name.as_str()) .unwrap_or_else(|| { panic!("Identity '{}' does not exist", other_smith.name.as_str()) }) .index; certs.insert(*issuer_index, None); counter_smith_cert += 1; }); } else { for issuer in &smith.certs_received { let issuer_index = &identities_v2 .get(issuer) .ok_or(format!("Identity '{}' does not exist", issuer))? .index; certs.insert( *issuer_index, Some(common_parameters.smith_certs_validity_period), ); counter_smith_cert += 1; } } smith_certs_by_receiver.insert(identity.index, certs); Ok(counter_smith_cert) } fn feed_certs_by_receiver( certs_by_receiver: &mut BTreeMap<u32, BTreeMap<u32, Option<u32>>>, identities_v2: &HashMap<String, IdentityV2>, ) -> (bool, u32) { let mut fatal = false; let mut counter_cert = 0; for identity in identities_v2.values() { let mut certs = BTreeMap::new(); for (issuer, expire_on) in &identity.certs_received { if let Some(issuer) = &identities_v2.get(issuer) { certs.insert(issuer.index, Some(*expire_on)); counter_cert += 1; } else { log::error!("Identity '{}' does not exist", issuer); fatal = true; }; } certs_by_receiver.insert(identity.index, certs); } (fatal, counter_cert) } fn check_authority_exists_in_both_wots( name: &String, identities_v2: &HashMap<String, IdentityV2>, smiths: &[RawSmith], ) { identities_v2 .get(name) .ok_or(format!("Identity '{}' not exist", name)) .expect("Initial authority must have an identity"); smiths .iter() .find(|smith| &smith.name == name) .expect("Forced authority must be present in smiths"); } fn build_smiths_wot( clique_smiths: &Option<Vec<CliqueSmith>>, smith_identities: Option<BTreeMap<String, RawSmith>>, ) -> Result<Vec<RawSmith>, String> { if smith_identities.is_some() && clique_smiths.is_some() { return Err( "'smiths' and 'clique_smiths' cannot be both defined at the same time".to_string(), ); } // Create a single source of smiths let smiths = if let Some(clique) = &clique_smiths { // From a clique clique .iter() .map(|smith| RawSmith { name: smith.name.clone(), session_keys: smith.session_keys.clone(), certs_received: vec![], migration_address: smith.migration_address.clone(), }) .collect::<Vec<RawSmith>>() } else { // From explicit smith WoT smith_identities .expect("existence has been tested earlier") .into_values() .collect::<Vec<RawSmith>>() }; Ok(smiths) } fn decorate_smiths_with_identity( smiths: Vec<RawSmith>, identity_index: &HashMap<u32, String>, identities_v2: &HashMap<String, IdentityV2>, ) -> Vec<SmithData> { smiths .into_iter() .map(|smith| SmithData { idty_index: identity_index .iter() .find(|(_, v)| ***v == smith.name) .map(|(k, _)| *k) .expect("smith must have an identity"), account: identities_v2 .get(smith.name.as_str()) .map(|i| i.owner_key.clone()) .expect("identity must exist"), name: smith.name, session_keys: smith.session_keys, certs_received: smith.certs_received, }) .collect() } #[cfg(feature = "gdev")] pub fn generate_genesis_data_for_local_chain<P, SK, SessionKeys: Encode, SKP>( initial_authorities_len: usize, initial_smiths_len: usize, initial_identities_len: usize, treasury_balance: u64, parameters: Option<P>, root_key: AccountId, get_common_parameters: fn(&Option<P>) -> CommonParameters, ) -> Result<GenesisData<P, SK>, String> where P: Default + DeserializeOwned, SK: Decode, SKP: SessionKeysProvider<SessionKeys>, { assert!(initial_identities_len <= 6); assert!(initial_smiths_len <= initial_identities_len); assert!(initial_authorities_len <= initial_smiths_len); let ud = 1_000; let idty_index_start: u32 = 1; let common_parameters = get_common_parameters(¶meters); let names: [&str; 6] = ["Alice", "Bob", "Charlie", "Dave", "Eve", "Ferdie"]; let initial_smiths = (0..initial_smiths_len) .map(|i| get_authority_keys_from_seed(names[i])) .collect::<Vec<AuthorityKeys>>(); let initial_identities = (0..initial_identities_len) .map(|i| { ( IdtyName::from(names[i]), get_account_id_from_seed::<sr25519::Public>(names[i]), ) }) .collect::<BTreeMap<IdtyName, AccountId>>(); let mut session_keys_map = BTreeMap::new(); initial_smiths.iter().for_each(|x| { let session_keys_bytes = SKP::session_keys(x).encode(); let sk = SK::decode(&mut &session_keys_bytes[..]) .map_err(|_| format!("invalid session keys for idty {}", x.0.clone())) .unwrap(); session_keys_map.insert(x.0.clone(), sk); }); let identities: Vec<GenesisIdentity> = initial_identities .iter() .enumerate() .map(|(i, (name, owner_key))| GenesisIdentity { idty_index: i as u32 + idty_index_start, name: String::from_utf8(name.0.clone()).unwrap(), owner_key: owner_key.clone(), old_owner_key: None, active: true, }) .collect(); let (certs_by_receiver, counter_cert) = clique_wot(initial_identities.len()); let accounts = initial_identities .iter() .enumerate() .map(|(i, (_, owner_key))| { ( owner_key.clone(), GenesisAccountData { random_id: H256(blake2_256( &(i as u32 + idty_index_start, owner_key).encode(), )), balance: ud, idty_id: Some(i as u32 + idty_index_start), }, ) }) .collect(); let identity_index = identities .iter() .map(|i| (i.idty_index, i.name.clone())) .collect(); let genesis_data_wallets_count = 0; let inactive_identities = HashMap::new(); let smith_memberships = (1..=initial_smiths_len) .map(|i| (i as u32, MembershipData { expire_on: 0 })) .collect(); let (smith_certs_by_receiver, counter_smith_cert) = clique_wot(initial_smiths_len); let initial_authorities: BTreeMap< u32, ( <<MultiSignature as Verify>::Signer as IdentifyAccount>::AccountId, bool, ), > = initial_smiths .iter() .enumerate() .map(|(i, keys)| { ( i as u32 + idty_index_start, (keys.0.clone(), i < initial_authorities_len), ) }) .collect(); let counter_online_authorities = initial_authorities .iter() .filter(|(_, authority)| authority.1) .count() as u32; let technical_committee_members = initial_smiths .iter() .map(|x| x.0.clone()) .collect::<Vec<_>>(); let genesis_timestamp: u64 = get_genesis_timestamp()?; let genesis_info = GenesisInfo { genesis_timestamp, accounts: &accounts, genesis_data_wallets_count: &genesis_data_wallets_count, identities: &identities, inactive_identities: &inactive_identities, identity_index: &identity_index, smith_memberships: &smith_memberships, counter_online_authorities: &counter_online_authorities, counter_cert: &counter_cert, counter_smith_cert: &counter_smith_cert, technical_committee_members: &technical_committee_members, common_parameters: &common_parameters, }; dump_genesis_info(genesis_info); let genesis_data = GenesisData { accounts, // Treasury balance is created out of nothing for local blockchain treasury_balance, certs_by_receiver, first_ud: None, first_ud_reeval: None, identities, initial_authorities, initial_monetary_mass: initial_identities_len as u64 * ud, memberships: (1..=initial_identities.len()) .map(|i| (i as u32, MembershipData { expire_on: 0 })) .collect(), parameters, common_parameters: None, session_keys_map, smith_certs_by_receiver, smith_memberships, sudo_key: Some(root_key), technical_committee_members, ud, }; Ok(genesis_data) } #[cfg(feature = "gdev")] fn clique_wot( initial_identities_len: usize, ) -> ( BTreeMap<IdtyIndex, BTreeMap<IdtyIndex, Option<common_runtime::BlockNumber>>>, u32, ) { let mut certs_by_issuer = BTreeMap::new(); let mut count: u32 = 0; for i in 1..=initial_identities_len { count += initial_identities_len as u32; certs_by_issuer.insert( i as IdtyIndex, (1..=initial_identities_len) .filter_map(|j| { if i != j { Some((j as IdtyIndex, None)) } else { None } }) .collect(), ); } (certs_by_issuer, count) } fn check_parameters_consistency( wallets: &BTreeMap<PubkeyV1, u64>, first_ud: &Option<u64>, first_reeval: &Option<u64>, ud: &u64, ) -> Result<(), String> { // No empty wallet if let Some((account, _)) = wallets.iter().find(|(_, amount)| **amount == 0) { return Err(format!("Wallet {} is empty", account)); } if let (Some(first_ud), Some(first_reeval)) = (first_ud, first_reeval) { if first_ud > first_reeval { return Err(format!( "`first_ud` ({}) should be lower than `first_ud_reeval` ({})", first_ud, first_reeval )); } } if *ud == 0 { return Err("`ud` is expected to be > 0".to_owned()); } Ok(()) } fn get_genesis_input<P: Default + DeserializeOwned>( config_file_path: String, ) -> Result<GenesisInput<P>, String> { // We mmap the file into memory first, as this is *a lot* faster than using // `serde_json::from_reader`. See https://github.com/serde-rs/json/issues/160 let file = std::fs::File::open(&config_file_path) .map_err(|e| format!("Error opening gen conf file `{}`: {}", config_file_path, e))?; // SAFETY: `mmap` is fundamentally unsafe since technically the file can change // underneath us while it is mapped; in practice it's unlikely to be a problem let bytes = unsafe { memmap2::Mmap::map(&file) .map_err(|e| format!("Error mmaping gen conf file `{}`: {}", config_file_path, e))? }; if config_file_path.ends_with(".json") { serde_json::from_slice::<GenesisInput<P>>(&bytes) .map_err(|e| format!("Error parsing JSON gen conf file: {}", e)) } else { serde_yaml::from_slice::<GenesisInput<P>>(&bytes) .map_err(|e| format!("Error parsing YAML gen conf file: {}", e)) } } fn get_genesis_migration_data() -> Result<GenesisMigrationData, String> { let json_file_path = std::env::var("DUNITER_GENESIS_DATA") .unwrap_or_else(|_| "./resources/g1-data.json".to_owned()); let file = std::fs::File::open(&json_file_path).map_err(|e| { format!( "Error opening gen migration file `{}`: {}", json_file_path, e ) })?; let bytes = unsafe { memmap2::Mmap::map(&file).map_err(|e| { format!( "Error mmaping gen migration file `{}`: {}", json_file_path, e ) })? }; serde_json::from_slice::<GenesisMigrationData>(&bytes) .map_err(|e| format!("Error parsing gen migration file: {}", e)) } fn get_genesis_timestamp() -> Result<u64, String> { if let Ok(genesis_timestamp) = std::env::var("DUNITER_GENESIS_TIMESTAMP") { genesis_timestamp .parse() .map_err(|_| "DUNITER_GENESIS_TIMESTAMP must be a number".to_owned()) } else { use std::time::SystemTime; Ok(SystemTime::now() .duration_since(SystemTime::UNIX_EPOCH) .expect("SystemTime before UNIX EPOCH!") .as_secs()) } } fn validate_idty_name(name: &str) -> bool { name.len() <= 64 } pub type AuthorityKeys = ( AccountId, GrandpaId, BabeId, ImOnlineId, AuthorityDiscoveryId, ); /// Because SessionKeys struct is defined by each Runtime, it cannot be constructed here. /// Its construction must be provided. pub trait SessionKeysProvider<SessionKeys: Encode> { fn session_keys(keys: &AuthorityKeys) -> SessionKeys; } #[derive(Default, Deserialize, Serialize, Clone)] pub struct CommonParameters { // TODO: replace u32 by BlockNumber when appropriate pub currency_name: String, pub decimals: usize, pub existential_deposit: u64, pub membership_period: u32, pub cert_period: u32, pub smith_membership_period: u32, pub smith_certs_validity_period: u32, pub min_cert: u32, pub smith_min_cert: u32, pub cert_max_by_issuer: u32, pub cert_validity_period: u32, pub c2: Perbill, pub ud_creation_period: u64, pub distance_min_accessible_referees: Perbill, pub max_depth: u32, pub ud_reeval_period: u64, } /// Generate an authority keys. fn get_authority_keys_from_seed(s: &str) -> AuthorityKeys { ( get_account_id_from_seed::<sr25519::Public>(s), get_from_seed::<GrandpaId>(s), get_from_seed::<BabeId>(s), get_from_seed::<ImOnlineId>(s), get_from_seed::<AuthorityDiscoveryId>(s), ) } /// Converts a Duniter v1 public key (Ed25519) to an Account Id. /// No need to convert to address. fn v1_pubkey_to_account_id(pubkey: PubkeyV1) -> Result<AccountId, String> { let bytes = bs58::decode(pubkey.0) .into_vec() .expect("Duniter v1 pubkey should be decodable"); if bytes.len() > 32 { return Err("Pubkey is too long".to_string()); } let prepend = vec![0u8; 32 - &bytes.len()]; let bytes: [u8; 32] = [prepend.as_slice(), bytes.as_slice()] .concat() .as_slice() .try_into() .expect("incorrect pubkey length"); Ok(AccountPublic::from(ed25519::Public::from_raw(bytes)).into_account()) } fn timestamp_to_relative_blocs(timestamp: TimestampV1, start: u64) -> u32 { let diff = (timestamp.0 as u64).saturating_sub(start); seconds_to_blocs(diff as u32) } /// Converts a number of seconds to a number of 6-seconds blocs /// use lower approximation /// example : 2 seconds will be block 0 /// example : 7 seconds will be block 1 fn seconds_to_blocs(seconds: u32) -> u32 { seconds / 6 } #[cfg(test)] mod tests { use super::*; use sp_core::crypto::{Ss58AddressFormat, Ss58Codec}; use sp_core::ByteArray; use std::str::FromStr; #[test] fn test_timestamp_to_relative_blocs() { assert_eq!(seconds_to_blocs(2), 0); assert_eq!(seconds_to_blocs(6), 1); assert_eq!(seconds_to_blocs(7), 1); } #[test] fn test_v1_pubkey_to_v2_address_translation() { assert_eq!( v1_pubkey_to_account_id(PubkeyV1( "2ny7YAdmzReQxAayyJZsyVYwYhVyax2thKcGknmQy5nQ".to_string() )) .unwrap() .to_ss58check_with_version(Ss58AddressFormat::custom(42)), "5CfdJjEgh3jDkg3bzmZ1ED1xVhXAARtNmZJWbcXh53rU8z5a".to_owned() ); } #[test] fn test_pubkey_with_33_bytes() { assert_eq!( v1_pubkey_to_account_id(PubkeyV1( "d2meevcahfts2gqmvmrw5hzi25jddikk4nc4u1fkwrau".to_string() )), Err("Pubkey is too long".to_owned()) ); } #[test] fn test_address_to_pubkey_v1() { let account = AccountId::from_str("5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY") .expect("valid address"); let pubkey = bs58::encode(account.as_slice()).into_vec(); let pubkey = String::from_utf8(pubkey).expect("valid conversion"); assert_eq!(pubkey, "FHNpKmJrUtusuvKPGomAygQqeiks98bdV6yD61Stb6vg"); } }