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