Skip to content
Snippets Groups Projects
gen_genesis_data.rs 66.9 KiB
Newer Older
// 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 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,
    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
    /// 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)]
}

#[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)
    #[serde(default)]
}

#[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 {
        treasury_funder_pubkey,
        treasury_funder_address,
        parameters,
        smith_identities,
        std::env::var("DUNITER_GENESIS_CONFIG").unwrap_or_else(|_| config_file_path.to_owned()),
    )?;

    // Per network parameters
    let common_parameters = get_common_parameters(&parameters);
    // 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();
    // 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());
            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,
                },
            );
            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
        // 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,
            log::warn!("wallet {pubkey} has wrong format");
            invalid_wallets = invalid_wallets.add(1);
        }
    }
    (fatal, monetary_mass, accounts, invalid_wallets)
}