Skip to content
Snippets Groups Projects
gen_genesis_data.rs 66.86 KiB
// 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(&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();
    //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(&parameters);
    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");
    }
}