diff --git a/Cargo.lock b/Cargo.lock index 7c5fc58d699276aae555459c39ab04a3a3d0e754..f6684b603f95d700986d4df8084f8677180589ce 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1598,6 +1598,7 @@ dependencies = [ "sc-finality-grandpa", "sc-keystore", "sc-network", + "sc-network-common", "sc-rpc-api", "sc-service", "sc-telemetry", diff --git a/Cargo.toml b/Cargo.toml index 42515b104c23d9b439972ac2d0d268895ffb91fa..0896f98b8037d2e72ff49d5552a27b58b2df43f0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -99,6 +99,7 @@ sc-executor = { git = "https://github.com/duniter/substrate", branch = "duniter- sc-finality-grandpa = { git = "https://github.com/duniter/substrate", branch = "duniter-substrate-v0.9.32" } sc-keystore = { git = "https://github.com/duniter/substrate", branch = "duniter-substrate-v0.9.32" } sc-network = { git = "https://github.com/duniter/substrate", branch = "duniter-substrate-v0.9.32" } +sc-network-common = { git = "https://github.com/duniter/substrate", branch = "duniter-substrate-v0.9.32" } sc-rpc-api = { git = "https://github.com/duniter/substrate", branch = "duniter-substrate-v0.9.32" } sc-service = { git = "https://github.com/duniter/substrate", branch = "duniter-substrate-v0.9.32", default-features = false } sc-telemetry = { git = "https://github.com/duniter/substrate", branch = "duniter-substrate-v0.9.32" } diff --git a/node/specs/gtest_client-specs.json b/node/specs/gtest_client-specs.json new file mode 100644 index 0000000000000000000000000000000000000000..a554af2e64ad2ecf26fd2444325e7d2d881c1ed1 --- /dev/null +++ b/node/specs/gtest_client-specs.json @@ -0,0 +1,20 @@ +{ + "name": "Ğdev", + "id": "gdev", + "chainType": "Live", + "bootNodes": [ + "/dns/gdev.p2p.legal/tcp/30334/p2p/12D3KooW9v5WsP38qU1kmafvA4CDw2vzYnFoWtdUqwonZtJK597r", + "/dns/gdev.elo.tf/tcp/30334/p2p/12D3KooWH4gAzvcRRCH8bFGZYomCGn3tosXDGtnL5yVVMkpGRL7B", + "/dns/gdev.librelois.fr/tcp/30334/p2p/12D3KooWMb8hmxdtBQhGLB8caokbAZQ3YokN9erMe6oAEsPDrwYf" + ], + "telemetryEndpoints": [ + [ + "/dns/telemetry.polkadot.io/tcp/443/x-parity-wss/%2Fsubmit%2F", + 0 + ] + ], + "properties": { + "tokenDecimals": 2, + "tokenSymbol": "ĞD" + } +} \ No newline at end of file diff --git a/resources/gtest.json b/node/specs/gtest_genesis.json similarity index 100% rename from resources/gtest.json rename to node/specs/gtest_genesis.json diff --git a/node/src/chain_spec/gtest.rs b/node/src/chain_spec/gtest.rs index a00b51ea1a097240da30c98cf6ec5f28383b2c4b..023aef9f39e0ec298e9953a216cbb5bf9e7c11c8 100644 --- a/node/src/chain_spec/gtest.rs +++ b/node/src/chain_spec/gtest.rs @@ -18,13 +18,18 @@ use super::*; use common_runtime::constants::*; use common_runtime::entities::IdtyData; use common_runtime::*; +use gtest_genesis::{build_genesis, GenesisJson}; use gtest_runtime::{ opaque::SessionKeys, AccountConfig, AccountId, AuthorityMembersConfig, BabeConfig, CertConfig, GenesisConfig, IdentityConfig, ImOnlineId, MembershipConfig, SessionConfig, SmithCertConfig, SmithMembershipConfig, SudoConfig, SystemConfig, TechnicalCommitteeConfig, UniversalDividendConfig, WASM_BINARY, }; +use jsonrpsee::core::JsonValue; +use sc_network_common::config::MultiaddrWithPeerId; // in the future available in sc_network::config use sc_service::ChainType; +use sc_telemetry::TelemetryEndpoints; +use serde::Deserialize; use sp_authority_discovery::AuthorityId as AuthorityDiscoveryId; use sp_consensus_babe::AuthorityId as BabeId; use sp_core::{blake2_256, sr25519, Encode, H256}; @@ -32,6 +37,7 @@ use sp_finality_grandpa::AuthorityId as GrandpaId; use sp_membership::MembershipData; use std::collections::BTreeMap; +pub type ChainSpec = sc_service::GenericChainSpec<GenesisConfig>; pub type AuthorityKeys = ( AccountId, GrandpaId, @@ -39,14 +45,6 @@ pub type AuthorityKeys = ( ImOnlineId, AuthorityDiscoveryId, ); - -pub type ChainSpec = sc_service::GenericChainSpec<GenesisConfig>; - -const TOKEN_DECIMALS: usize = 2; -const TOKEN_SYMBOL: &str = "ĞT"; -// The URL for the telemetry server. -// const STAGING_TELEMETRY_URL: &str = "wss://telemetry.polkadot.io/submit/"; - /// Generate an authority keys. pub fn get_authority_keys_from_seed(s: &str) -> AuthorityKeys { ( @@ -57,7 +55,6 @@ pub fn get_authority_keys_from_seed(s: &str) -> AuthorityKeys { get_from_seed::<AuthorityDiscoveryId>(s), ) } - /// Generate session keys fn get_session_keys_from_seed(s: &str) -> SessionKeys { let authority_keys = get_authority_keys_from_seed(s); @@ -68,7 +65,6 @@ fn get_session_keys_from_seed(s: &str) -> SessionKeys { authority_keys.4, ) } - /// make session keys struct fn session_keys( grandpa: GrandpaId, @@ -84,29 +80,27 @@ fn session_keys( } } -// can not use ClientSpec which is private :'( -// // client specs used in chainspecs -// static client_spec_dev = ClientSpec { -// name: "ĞTest Development", -// id: "gtest_dev", -// chain_type: ChainType::Development, -// boot_nodes: vec![], -// telemetry_endpoints: None, -// protocol_id: None, -// fork_id: None, -// properties: Some( -// serde_json::json!({ -// "tokenDecimals": TOKEN_DECIMALS, -// "tokenSymbol": TOKEN_SYMBOL, -// }) -// .as_object() -// .expect("must be a map") -// .clone()), -// extensions: None, -// consensus_engine: (), -// genesis: Default::default(), -// code_substitutes: BTreeMap::new(), -// }; +// === client specifications === + +/// emulate client specifications to get them from json +#[derive(Deserialize, Clone)] +#[serde(rename_all = "camelCase")] +#[serde(deny_unknown_fields)] +pub struct ClientSpec { + name: String, + id: String, + chain_type: ChainType, + boot_nodes: Vec<MultiaddrWithPeerId>, + telemetry_endpoints: Option<TelemetryEndpoints>, + // protocol_id: Option<String>, + // #[serde(default = "Default::default", skip_serializing_if = "Option::is_none")] + // fork_id: Option<String>, + properties: Option<serde_json::Map<std::string::String, JsonValue>>, + // #[serde(default)] + // code_substitutes: BTreeMap<String, Bytes>, +} + +// === development chainspecs === /// generate development chainspec with Alice validator // there is some code duplication because we can not use ClientSpec @@ -117,6 +111,19 @@ pub fn development_chainspecs() -> Result<ChainSpec, String> { if let Ok(genesis_json_path) = std::env::var("DUNITER_GTEST_GENESIS") { // log log::info!("loading genesis from {genesis_json_path}"); + // open json genesis file + let file = std::fs::File::open(&genesis_json_path) + .map_err(|e| format!("Error opening gen conf file `{}`: {}", genesis_json_path, e))?; + // memory map the file to avoid loading it in memory + let bytes = unsafe { + memmap2::Mmap::map(&file).map_err(|e| { + format!("Error mmaping gen conf file `{}`: {}", genesis_json_path, e) + })? + }; + // parse the json file + let genesis_data: GenesisJson = serde_json::from_slice(&bytes) + .map_err(|e| format!("Error parsing gen conf file: {}", e))?; + // return chainspecs Ok(ChainSpec::from_genesis( // Name @@ -127,9 +134,9 @@ pub fn development_chainspecs() -> Result<ChainSpec, String> { sc_service::ChainType::Development, // genesis config constructor move || { - super::gtest_genesis::build_genesis( - // path of json genesis - &genesis_json_path, + build_genesis( + // genesis data built from json + genesis_data.clone(), // wasm binary wasm_binary, // replace authority by Alice @@ -148,8 +155,8 @@ pub fn development_chainspecs() -> Result<ChainSpec, String> { // Properties Some( serde_json::json!({ - "tokenDecimals": TOKEN_DECIMALS, - "tokenSymbol": TOKEN_SYMBOL, + "tokenDecimals": 2, + "tokenSymbol": "ĞT", }) .as_object() .expect("must be a map") @@ -194,8 +201,8 @@ pub fn development_chainspecs() -> Result<ChainSpec, String> { // Properties Some( serde_json::json!({ - "tokenDecimals": TOKEN_DECIMALS, - "tokenSymbol": TOKEN_SYMBOL, + "tokenDecimals": 2, + "tokenSymbol": "ĞT", }) .as_object() .expect("must be a map") @@ -207,29 +214,29 @@ pub fn development_chainspecs() -> Result<ChainSpec, String> { } } -/// generate live chainspecs from DUNITER_GTEST_GENESIS -/// one smith must have session keys -pub fn live_chainspecs() -> Result<ChainSpec, String> { - let wasm_binary = WASM_BINARY.ok_or_else(|| "wasm not available".to_string())?; +// === live chainspecs === - let genesis_json_path = - std::env::var("DUNITER_GTEST_GENESIS").expect("DUNITER_GTEST_GENESIS needed"); +/// live chainspecs +// one smith must have session keys +pub fn live_chainspecs( + client_spec: ClientSpec, + genesis_data: GenesisJson, +) -> Result<ChainSpec, String> { + let wasm_binary = WASM_BINARY.ok_or_else(|| "wasm not available".to_string())?; - // log - log::info!("loading genesis from {genesis_json_path}"); // return chainspecs Ok(ChainSpec::from_genesis( // Name - "ĞTest", + client_spec.name.as_str(), // ID - "gtest", + client_spec.id.as_str(), // chain type - sc_service::ChainType::Live, + client_spec.chain_type, // genesis config constructor move || { - super::gtest_genesis::build_genesis( - // path of json genesis - &genesis_json_path, + build_genesis( + // genesis data + genesis_data.clone(), // wasm binary wasm_binary, // do not replace session keys @@ -237,29 +244,16 @@ pub fn live_chainspecs() -> Result<ChainSpec, String> { ) .expect("genesis building failed") }, - // Bootnodes (TODO add bootnodes) - vec![], + // Bootnodes + client_spec.boot_nodes, // Telemetry (by default, enable telemetry, can be disabled with argument) - Some( - sc_service::config::TelemetryEndpoints::new(vec![( - "wss://telemetry.polkadot.io/submit/".to_owned(), - 0, - )]) - .expect("invalid telemetry endpoints"), - ), // Protocol ID - Some("gtest1"), - //Fork ID + client_spec.telemetry_endpoints, + // Protocol ID + None, + // Fork ID None, // Properties - Some( - serde_json::json!({ - "tokenDecimals": TOKEN_DECIMALS, - "tokenSymbol": TOKEN_SYMBOL, - }) - .as_object() - .expect("must be a map") - .clone(), - ), + client_spec.properties, // Extensions None, )) diff --git a/node/src/chain_spec/gtest_genesis.rs b/node/src/chain_spec/gtest_genesis.rs index 9aa6f737fd9c253d9e735772df834b111c39baed..004a1cc6b1c3157a55a6e28ce8ea3d76ad9d75c4 100644 --- a/node/src/chain_spec/gtest_genesis.rs +++ b/node/src/chain_spec/gtest_genesis.rs @@ -38,7 +38,8 @@ static SMITH_MIN_CERT: u32 = parameters::SmithWotMinCertForMembership::get(); // define structure of json #[derive(Clone, Deserialize)] -struct GenesisJson { +#[serde(deny_unknown_fields)] +pub struct GenesisJson { identities: HashMap<String, Identity>, smiths: HashMap<String, Smith>, first_ud: u64, @@ -96,8 +97,8 @@ fn validate_idty_name(idty_name: &str) -> bool { /// ============================================================================================ /// /// build genesis from json file pub fn build_genesis( - // path of genesis config - genesis_config_path: &str, + // genesis data build from json + genesis_data: GenesisJson, // wasm binary wasm_binary: &[u8], // useful to enforce Alice authority when developing @@ -120,28 +121,6 @@ pub fn build_genesis( }; log::info!("genesis timestamp: {}", genesis_timestamp); - // open json genesis file - let file = std::fs::File::open(&genesis_config_path).map_err(|e| { - format!( - "Error opening gen conf file `{}`: {}", - genesis_config_path, e - ) - })?; - - // memory map the file to avoid loading it in memory - let bytes = unsafe { - memmap2::Mmap::map(&file).map_err(|e| { - format!( - "Error mmaping gen conf file `{}`: {}", - genesis_config_path, e - ) - })? - }; - - // parse the json file - let genesis_data: GenesisJson = serde_json::from_slice(&bytes) - .map_err(|e| format!("Error parsing gen conf file: {}", e))?; - // declare variables for building genesis // ------------------------------------- // track if fatal error occured, but let processing continue diff --git a/node/src/command.rs b/node/src/command.rs index 107ccd59c2a5f76080d93a5e5acbaf0be5cc5e2a..bd16a8f0fe2759936a3afb38872fe0f2f18efa2d 100644 --- a/node/src/command.rs +++ b/node/src/command.rs @@ -18,6 +18,8 @@ pub mod key; pub mod utils; +#[cfg(feature = "gtest")] +use crate::chain_spec::{gtest, gtest_genesis}; use crate::cli::{Cli, Subcommand}; #[cfg(feature = "g1")] use crate::service::G1Executor; @@ -130,9 +132,29 @@ impl SubstrateCli for Cli { // otherwise you get a local testnet with generated genesis #[cfg(feature = "gtest")] "gtest_dev" => Box::new(chain_spec::gtest::development_chainspecs()?), - // chainspecs for live network (must set DUNITER_GTEST_GENESIS) + // chainspecs for live network + // must have following files in node/specs folder: + // - gtest.json + // - gtest_client-specs.json #[cfg(feature = "gtest")] - "gtest_live" => Box::new(chain_spec::gtest::live_chainspecs()?), + "gtest_live" => { + const JSON_CLIENT_SPEC: &str = "./node/specs/gtest_client-specs.json"; + const JSON_GENESIS: &str = "./node/specs/gtest_genesis.json"; + let client_spec: gtest::ClientSpec = serde_json::from_slice( + &std::fs::read(JSON_CLIENT_SPEC) + .map_err(|e| format!("failed to read {JSON_CLIENT_SPEC} {e}"))?[..], + ) + .map_err(|e| format!("failed to parse {e}"))?; + let genesis_data: gtest_genesis::GenesisJson = serde_json::from_slice( + &std::fs::read(JSON_GENESIS) + .map_err(|e| format!("failed to read {JSON_GENESIS} {e}"))?[..], + ) + .map_err(|e| format!("failed to parse {e}"))?; + Box::new(chain_spec::gtest::live_chainspecs( + client_spec, + genesis_data, + )?) + } // hardcoded previously generated raw chainspecs #[cfg(feature = "gtest")] "gtest" => {