From eee8c80dbfe23c8e0e439810f5a64c753aebd5c9 Mon Sep 17 00:00:00 2001
From: librelois <c@elo.tf>
Date: Tue, 25 Jan 2022 00:19:47 +0100
Subject: [PATCH] feat: define a format for duniter genesis config

---
 Cargo.lock                              |   3 +
 Cargo.toml                              |   3 +
 node/src/chain_spec.rs                  |   2 +
 node/src/chain_spec/gdev.rs             | 155 ++++++++++++--
 node/src/chain_spec/gen_genesis_data.rs | 270 ++++++++++++++++++++++++
 node/src/command.rs                     |   2 +
 6 files changed, 419 insertions(+), 16 deletions(-)
 create mode 100644 node/src/chain_spec/gen_genesis_data.rs

diff --git a/Cargo.lock b/Cargo.lock
index 3a730637b..9dab97d18 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1239,6 +1239,7 @@ dependencies = [
  "jsonrpc-core",
  "log",
  "maplit",
+ "memmap2 0.5.0",
  "pallet-certification",
  "pallet-grandpa",
  "pallet-transaction-payment-rpc",
@@ -1254,11 +1255,13 @@ dependencies = [
  "sc-executor",
  "sc-finality-grandpa",
  "sc-keystore",
+ "sc-network",
  "sc-rpc-api",
  "sc-service",
  "sc-telemetry",
  "sc-transaction-pool",
  "sc-transaction-pool-api",
+ "serde",
  "serde_json",
  "sp-api",
  "sp-authority-discovery",
diff --git a/Cargo.toml b/Cargo.toml
index 262ac7325..21c3818fa 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -51,6 +51,8 @@ hex = "0.4.3"
 jsonrpc-core = '18.0.0'
 log = "0.4"
 maplit = '1.0.2'
+memmap2 = "0.5.0"
+serde = "1.0"
 serde_json = "1.0.64"
 structopt = '0.3.8'
 
@@ -71,6 +73,7 @@ sc-consensus-uncles = { git = "https://github.com/librelois/substrate.git", bran
 sc-executor = { git = "https://github.com/librelois/substrate.git", branch = "duniter-monthly-2022-01" }
 sc-finality-grandpa = { git = "https://github.com/librelois/substrate.git", branch = "duniter-monthly-2022-01" }
 sc-keystore = { git = "https://github.com/librelois/substrate.git", branch = "duniter-monthly-2022-01" }
+sc-network = { git = "https://github.com/librelois/substrate.git", branch = "duniter-monthly-2022-01" }
 sc-rpc-api = { git = "https://github.com/librelois/substrate.git", branch = "duniter-monthly-2022-01" }
 sc-service = { git = "https://github.com/librelois/substrate.git", branch = "duniter-monthly-2022-01" }
 sc-telemetry = { git = "https://github.com/librelois/substrate.git", branch = "duniter-monthly-2022-01" }
diff --git a/node/src/chain_spec.rs b/node/src/chain_spec.rs
index bb7421458..57d8aa0c0 100644
--- a/node/src/chain_spec.rs
+++ b/node/src/chain_spec.rs
@@ -14,6 +14,8 @@
 // You should have received a copy of the GNU Affero General Public License
 // along with Substrate-Libre-Currency. If not, see <https://www.gnu.org/licenses/>.
 
+pub mod gen_genesis_data;
+
 #[cfg(feature = "g1")]
 pub mod g1;
 #[cfg(feature = "gdev")]
diff --git a/node/src/chain_spec/gdev.rs b/node/src/chain_spec/gdev.rs
index a94868dc4..f256efabe 100644
--- a/node/src/chain_spec/gdev.rs
+++ b/node/src/chain_spec/gdev.rs
@@ -16,12 +16,12 @@
 
 use super::*;
 use common_runtime::constants::*;
-use common_runtime::entities::IdtyName;
+use common_runtime::*;
 use gdev_runtime::{
     opaque::SessionKeys, AccountId, AuthorityMembersConfig, BabeConfig, BalancesConfig, CertConfig,
-    GenesisConfig, GenesisParameters, IdentityConfig, IdtyValue, ImOnlineId, MembershipConfig,
-    ParametersConfig, SessionConfig, SmithsCertConfig, SmithsMembershipConfig, SudoConfig,
-    SystemConfig, UdAccountsStorageConfig, UniversalDividendConfig, WASM_BINARY,
+    GenesisConfig, IdentityConfig, ImOnlineId, MembershipConfig, ParametersConfig, SessionConfig,
+    SmithsCertConfig, SmithsMembershipConfig, SudoConfig, SystemConfig, UdAccountsStorageConfig,
+    UniversalDividendConfig, WASM_BINARY,
 };
 use sc_service::ChainType;
 use sp_authority_discovery::AuthorityId as AuthorityDiscoveryId;
@@ -41,6 +41,8 @@ pub type AuthorityKeys = (
 
 pub type ChainSpec = sc_service::GenericChainSpec<GenesisConfig>;
 
+type GenesisParameters = gdev_runtime::GenesisParameters<u32, u32, u64>;
+
 const TOKEN_DECIMALS: usize = 2;
 const TOKEN_SYMBOL: &str = "ÄžD";
 // The URL for the telemetry server.
@@ -101,6 +103,49 @@ pub fn development_chain_spec() -> Result<ChainSpec, String> {
     ))
 }
 
+pub fn gen_live_conf() -> Result<ChainSpec, String> {
+    let wasm_binary = WASM_BINARY.ok_or_else(|| "wasm not available".to_string())?;
+
+    super::gen_genesis_data::generate_genesis_data(
+        |genesis_data| {
+            ChainSpec::from_genesis(
+                // Name
+                "Äždev",
+                // ID
+                "gdev",
+                sc_service::ChainType::Live,
+                move || genesis_data_to_gdev_genesis_conf(genesis_data.clone(), wasm_binary),
+                // Bootnodes
+                vec![],
+                // Telemetry
+                None,
+                // Protocol ID
+                None,
+                // Properties
+                Some(
+                    serde_json::json!({
+                        "tokenDecimals": TOKEN_DECIMALS,
+                        "tokenSymbol": TOKEN_SYMBOL,
+                    })
+                    .as_object()
+                    .expect("must be a map")
+                    .clone(),
+                ),
+                // Extensions
+                None,
+            )
+        },
+        Some(super::gen_genesis_data::ParamsAppliedAtGenesis {
+            genesis_certs_expire_on: 100_000,
+            genesis_smith_certs_expire_on: 100_000,
+            genesis_memberships_expire_on: 100_000,
+            genesis_memberships_renewable_on: 50,
+            genesis_smith_memberships_expire_on: 100_000,
+            genesis_smith_memberships_renewable_on: 50,
+        }),
+    )
+}
+
 pub fn local_testnet_config(
     initial_authorities_len: usize,
     initial_smiths_len: usize,
@@ -256,18 +301,16 @@ fn gen_genesis_conf(
         identity: IdentityConfig {
             identities: initial_identities
                 .iter()
-                .map(|(name, owner_key)| {
-                    (
-                        owner_key.clone(),
-                        (
-                            name.clone(),
-                            IdtyValue {
-                                next_creatable_identity_on: Default::default(),
-                                removable_on: 0,
-                                status: gdev_runtime::IdtyStatus::Validated,
-                            },
-                        ),
-                    )
+                .enumerate()
+                .map(|(i, (name, owner_key))| GenesisIdty {
+                    index: i as u32 + 1,
+                    owner_key: owner_key.clone(),
+                    name: name.clone(),
+                    value: IdtyValue {
+                        next_creatable_identity_on: Default::default(),
+                        removable_on: 0,
+                        status: IdtyStatus::Validated,
+                    },
                 })
                 .collect(),
         },
@@ -339,3 +382,83 @@ fn session_keys(
         authority_discovery,
     }
 }
+
+fn genesis_data_to_gdev_genesis_conf(
+    genesis_data: super::gen_genesis_data::GenesisData<GenesisParameters, SessionKeys>,
+    wasm_binary: &[u8],
+) -> gdev_runtime::GenesisConfig {
+    let super::gen_genesis_data::GenesisData {
+        balances,
+        certs_by_issuer,
+        first_ud,
+        identities,
+        initial_authorities,
+        initial_monetary_mass,
+        memberships,
+        parameters,
+        session_keys_map,
+        smiths_certs_by_issuer,
+        smiths_memberships,
+        sudo_key,
+        ud_accounts,
+    } = genesis_data;
+
+    gdev_runtime::GenesisConfig {
+        system: SystemConfig {
+            // Add Wasm runtime to storage.
+            code: wasm_binary.to_vec(),
+        },
+        parameters: ParametersConfig { parameters },
+        authority_discovery: Default::default(),
+        authority_members: AuthorityMembersConfig {
+            initial_authorities,
+        },
+        balances: BalancesConfig { balances },
+        babe: BabeConfig {
+            authorities: Vec::with_capacity(0),
+            epoch_config: Some(common_runtime::constants::BABE_GENESIS_EPOCH_CONFIG),
+        },
+        grandpa: Default::default(),
+        im_online: Default::default(),
+        session: SessionConfig {
+            keys: session_keys_map
+                .into_iter()
+                .map(|(account_id, session_keys)| (account_id.clone(), account_id, session_keys))
+                .collect::<Vec<_>>(),
+        },
+        sudo: SudoConfig { key: sudo_key },
+        identity: IdentityConfig {
+            identities: identities
+                .into_iter()
+                .enumerate()
+                .map(|(i, (name, pubkey))| common_runtime::GenesisIdty {
+                    index: i as u32 + 1,
+                    owner_key: pubkey,
+                    name: common_runtime::IdtyName::from(name.as_str()),
+                    value: common_runtime::IdtyValue {
+                        next_creatable_identity_on: 0,
+                        removable_on: 0,
+                        status: IdtyStatus::Validated,
+                    },
+                })
+                .collect(),
+        },
+        cert: CertConfig {
+            apply_cert_period_at_genesis: true,
+            certs_by_issuer,
+        },
+        membership: MembershipConfig { memberships },
+        smiths_cert: SmithsCertConfig {
+            apply_cert_period_at_genesis: true,
+            certs_by_issuer: smiths_certs_by_issuer,
+        },
+        smiths_membership: SmithsMembershipConfig {
+            memberships: smiths_memberships,
+        },
+        ud_accounts_storage: UdAccountsStorageConfig { ud_accounts },
+        universal_dividend: UniversalDividendConfig {
+            first_ud,
+            initial_monetary_mass,
+        },
+    }
+}
diff --git a/node/src/chain_spec/gen_genesis_data.rs b/node/src/chain_spec/gen_genesis_data.rs
new file mode 100644
index 000000000..62d63d952
--- /dev/null
+++ b/node/src/chain_spec/gen_genesis_data.rs
@@ -0,0 +1,270 @@
+// Copyright 2021 Axiom-Team
+//
+// This file is part of Substrate-Libre-Currency.
+//
+// Substrate-Libre-Currency 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.
+//
+// Substrate-Libre-Currency 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 Substrate-Libre-Currency. If not, see <https://www.gnu.org/licenses/>.
+
+use common_runtime::*;
+use serde::{de::DeserializeOwned, Deserialize, Serialize};
+use sp_core::Decode;
+use std::collections::BTreeMap;
+
+type MembershipData = sp_membership::MembershipData<u32>;
+
+#[derive(Clone)]
+pub struct GenesisData<Parameters: DeserializeOwned, SessionKeys: Decode> {
+    pub balances: Vec<(AccountId, u64)>,
+    pub certs_by_issuer: BTreeMap<u32, BTreeMap<u32, u32>>,
+    pub first_ud: u64,
+    pub identities: Vec<(String, AccountId)>,
+    pub initial_authorities: BTreeMap<u32, (AccountId, bool)>,
+    pub initial_monetary_mass: u64,
+    pub memberships: BTreeMap<u32, MembershipData>,
+    pub parameters: Parameters,
+    pub session_keys_map: BTreeMap<AccountId, SessionKeys>,
+    pub smiths_certs_by_issuer: BTreeMap<u32, BTreeMap<u32, u32>>,
+    pub smiths_memberships: BTreeMap<u32, MembershipData>,
+    pub sudo_key: Option<AccountId>,
+    pub ud_accounts: BTreeMap<AccountId, u32>,
+}
+
+#[derive(Default)]
+pub struct ParamsAppliedAtGenesis {
+    pub genesis_certs_expire_on: u32,
+    pub genesis_smith_certs_expire_on: u32,
+    pub genesis_memberships_expire_on: u32,
+    pub genesis_memberships_renewable_on: u32,
+    pub genesis_smith_memberships_expire_on: u32,
+    pub genesis_smith_memberships_renewable_on: u32,
+}
+
+#[derive(Deserialize, Serialize)]
+struct GenesisConfig<Parameters> {
+    first_ud: u64,
+    identities: BTreeMap<String, Idty>,
+    #[serde(default)]
+    parameters: Parameters,
+    #[serde(rename = "smiths")]
+    smith_identities: BTreeMap<String, SmithData>,
+    sudo_key: Option<AccountId>,
+}
+
+#[derive(Clone, Deserialize, Serialize)]
+struct Idty {
+    #[serde(default)]
+    balance: u64,
+    #[serde(default)]
+    certs: Vec<String>,
+    #[serde(rename = "expire_on")]
+    membership_expire_on: Option<u64>,
+    pubkey: AccountId,
+}
+
+#[derive(Clone, Deserialize, Serialize)]
+struct SmithData {
+    #[serde(default)]
+    authority: bool,
+    session_keys: String,
+    #[serde(default)]
+    certs: Vec<String>,
+}
+
+pub fn generate_genesis_data<CS, P, SK, F>(
+    f: F,
+    params_applied_at_genesis: Option<ParamsAppliedAtGenesis>,
+) -> Result<CS, String>
+where
+    P: Default + DeserializeOwned,
+    SK: Decode,
+    F: Fn(GenesisData<P, SK>) -> CS,
+{
+    let ParamsAppliedAtGenesis {
+        genesis_certs_expire_on,
+        genesis_smith_certs_expire_on,
+        genesis_memberships_expire_on,
+        genesis_memberships_renewable_on,
+        genesis_smith_memberships_expire_on,
+        genesis_smith_memberships_renewable_on,
+    } = params_applied_at_genesis.unwrap_or_default();
+
+    let genesis_timestamp: u64 =
+        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;
+            SystemTime::now()
+                .duration_since(SystemTime::UNIX_EPOCH)
+                .expect("SystemTime before UNIX EPOCH!")
+                .as_secs()
+        };
+
+    let json_file_path = std::env::var("DUNITER_GENESIS_CONFIG")
+        .unwrap_or_else(|_| "duniter-gen-conf.json".to_owned());
+
+    // 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(&json_file_path)
+        .map_err(|e| format!("Error opening gen conf file `{}`: {}", json_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 `{}`: {}", json_file_path, e))?
+    };
+
+    let genesis_config = serde_json::from_slice(&bytes)
+        .map_err(|e| format!("Error parsing gen conf file: {}", e))?;
+    let GenesisConfig {
+        sudo_key,
+        first_ud,
+        parameters,
+        identities,
+        smith_identities,
+    } = genesis_config;
+
+    // MONEY AND WOT //
+
+    let mut balances = Vec::new();
+    let mut identities_ = Vec::with_capacity(identities.len());
+    let mut idty_index: u32 = 1;
+    let mut idty_index_of = BTreeMap::new();
+    let mut initial_monetary_mass = 0;
+    let mut memberships = BTreeMap::new();
+    let mut ud_accounts = BTreeMap::new();
+    for (idty_name, identity) in &identities {
+        if !validate_idty_name(idty_name) {
+            return Err(format!("Identity name '{}' is invalid", &idty_name));
+        }
+
+        // Money
+        if identity.balance >= 100 {
+            balances.push((identity.pubkey.clone(), identity.balance));
+        }
+        // We must count the money under the existential deposit because what we count is
+        // the monetary mass created (for the revaluation of the DU)
+        initial_monetary_mass += identity.balance;
+        ud_accounts.insert(identity.pubkey.clone(), idty_index);
+
+        // Wot
+        identities_.push((idty_name.clone(), identity.pubkey.clone()));
+        memberships.insert(
+            idty_index,
+            MembershipData {
+                expire_on: identity
+                    .membership_expire_on
+                    .map_or(genesis_memberships_expire_on, |expire_on| {
+                        to_bn(genesis_timestamp, expire_on)
+                    }),
+                renewable_on: genesis_memberships_renewable_on,
+            },
+        );
+
+        // Identity index
+        idty_index_of.insert(idty_name, idty_index);
+        idty_index += 1;
+    }
+
+    // CERTIFICATIONS //
+
+    let mut certs_by_issuer = BTreeMap::new();
+    for (idty_name, identity) in &identities {
+        let issuer_index = idty_index_of
+            .get(&idty_name)
+            .ok_or(format!("Identity '{}' not exist", &idty_name))?;
+        let mut issuer_certs = BTreeMap::new();
+        for receiver in &identity.certs {
+            let receiver_index = idty_index_of
+                .get(receiver)
+                .ok_or(format!("Identity '{}' not exist", receiver))?;
+            issuer_certs.insert(*receiver_index, genesis_certs_expire_on);
+        }
+        certs_by_issuer.insert(*issuer_index, issuer_certs);
+    }
+
+    // SMITHS SUB-WOT //
+
+    let mut initial_authorities = BTreeMap::new();
+    let mut session_keys_map = BTreeMap::new();
+    let mut smiths_memberships = BTreeMap::new();
+    let mut smiths_certs_by_issuer = BTreeMap::new();
+    for (idty_name, smith_data) in smith_identities {
+        let idty_index = idty_index_of
+            .get(&idty_name)
+            .ok_or(format!("Identity '{}' not exist", &idty_name))?;
+        let identity = identities
+            .get(&idty_name)
+            .ok_or(format!("Identity '{}' not exist", &idty_name))?;
+
+        // Initial authorities
+        initial_authorities.insert(*idty_index, (identity.pubkey.clone(), smith_data.authority));
+
+        // Session keys
+        let session_keys_bytes = hex::decode(&smith_data.session_keys[2..])
+            .map_err(|_| format!("invalid session keys for idty {}", &idty_name))?;
+        session_keys_map.insert(
+            identity.pubkey.clone(),
+            SK::decode(&mut &session_keys_bytes[..])
+                .map_err(|_| format!("invalid session keys for idty {}", &idty_name))?,
+        );
+
+        // Certifications
+        let mut issuer_certs = BTreeMap::new();
+        for receiver in &smith_data.certs {
+            let receiver_index = idty_index_of
+                .get(receiver)
+                .ok_or(format!("Identity '{}' not exist", receiver))?;
+            issuer_certs.insert(*receiver_index, genesis_smith_certs_expire_on);
+        }
+        smiths_certs_by_issuer.insert(*idty_index, issuer_certs);
+
+        // Memberships
+        smiths_memberships.insert(
+            *idty_index,
+            MembershipData {
+                expire_on: genesis_smith_memberships_expire_on,
+                renewable_on: genesis_smith_memberships_renewable_on,
+            },
+        );
+    }
+
+    let genesis_data = GenesisData {
+        balances,
+        certs_by_issuer,
+        first_ud,
+        identities: identities_,
+        initial_authorities,
+        initial_monetary_mass,
+        memberships,
+        parameters,
+        session_keys_map,
+        smiths_certs_by_issuer,
+        smiths_memberships,
+        sudo_key,
+        ud_accounts,
+    };
+
+    Ok(f(genesis_data))
+}
+
+// Timestamp to block number
+fn to_bn(genesis_timestamp: u64, timestamp: u64) -> u32 {
+    let duration_in_secs = timestamp.saturating_sub(genesis_timestamp);
+    (duration_in_secs / 6) as u32
+}
+
+fn validate_idty_name(name: &str) -> bool {
+    name.len() <= 64
+}
diff --git a/node/src/command.rs b/node/src/command.rs
index 0c1339030..a34400f22 100644
--- a/node/src/command.rs
+++ b/node/src/command.rs
@@ -64,6 +64,8 @@ impl SubstrateCli for Cli {
             #[cfg(feature = "gdev")]
             "local4" => Box::new(chain_spec::gdev::local_testnet_config(4, 4, 5)?),
             #[cfg(feature = "gdev")]
+            "gdev-gl" | "gdev_gl" => Box::new(chain_spec::gdev::gen_live_conf()?),
+            #[cfg(feature = "gdev")]
             "gdev" => {
                 unimplemented!()
                 //Box::new(chain_spec::gdev::ChainSpec::from_json_file(file_path)?)
-- 
GitLab