// This file is part of Substrate. // Copyright (C) 2019-2020 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program 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 General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see . use std::{fs, path::{Path, PathBuf}}; use ansi_term::Style; use rand::{Rng, distributions::Alphanumeric, rngs::OsRng}; use structopt::StructOpt; use sc_keystore::{Store as Keystore}; use node_cli::chain_spec::{self, AccountId}; use sp_core::{sr25519, crypto::{Public, Ss58Codec}, traits::BareCryptoStore}; /// A utility to easily create a testnet chain spec definition with a given set /// of authorities and endowed accounts and/or generate random accounts. #[derive(StructOpt)] #[structopt(rename_all = "kebab-case")] enum ChainSpecBuilder { /// Create a new chain spec with the given authorities, endowed and sudo /// accounts. New { /// Authority key seed. #[structopt(long, short, required = true)] authority_seeds: Vec, /// Endowed account address (SS58 format). #[structopt(long, short)] endowed_accounts: Vec, /// Sudo account address (SS58 format). #[structopt(long, short)] sudo_account: String, /// The path where the chain spec should be saved. #[structopt(long, short, default_value = "./chain_spec.json")] chain_spec_path: PathBuf, }, /// Create a new chain spec with the given number of authorities and endowed /// accounts. Random keys will be generated as required. Generate { /// The number of authorities. #[structopt(long, short)] authorities: usize, /// The number of endowed accounts. #[structopt(long, short, default_value = "0")] endowed: usize, /// The path where the chain spec should be saved. #[structopt(long, short, default_value = "./chain_spec.json")] chain_spec_path: PathBuf, /// Path to use when saving generated keystores for each authority. /// /// At this path, a new folder will be created for each authority's /// keystore named `auth-$i` where `i` is the authority index, i.e. /// `auth-0`, `auth-1`, etc. #[structopt(long, short)] keystore_path: Option, }, } impl ChainSpecBuilder { /// Returns the path where the chain spec should be saved. fn chain_spec_path(&self) -> &Path { match self { ChainSpecBuilder::New { chain_spec_path, .. } => chain_spec_path.as_path(), ChainSpecBuilder::Generate { chain_spec_path, .. } => chain_spec_path.as_path(), } } } fn genesis_constructor( authority_seeds: &[String], endowed_accounts: &[AccountId], sudo_account: &AccountId, ) -> chain_spec::GenesisConfig { let authorities = authority_seeds .iter() .map(AsRef::as_ref) .map(chain_spec::authority_keys_from_seed) .collect::>(); let enable_println = true; chain_spec::testnet_genesis( authorities, sudo_account.clone(), Some(endowed_accounts.to_vec()), enable_println, ) } fn generate_chain_spec( authority_seeds: Vec, endowed_accounts: Vec, sudo_account: String, ) -> Result { let parse_account = |address: &String| { AccountId::from_string(address) .map_err(|err| format!("Failed to parse account address: {:?}", err)) }; let endowed_accounts = endowed_accounts .iter() .map(parse_account) .collect::, String>>()?; let sudo_account = parse_account(&sudo_account)?; let chain_spec = chain_spec::ChainSpec::from_genesis( "Custom", "custom", sc_chain_spec::ChainType::Live, move || genesis_constructor(&authority_seeds, &endowed_accounts, &sudo_account), vec![], None, None, None, Default::default(), ); chain_spec.as_json(false).map_err(|err| err.to_string()) } fn generate_authority_keys_and_store( seeds: &[String], keystore_path: &Path, ) -> Result<(), String> { for (n, seed) in seeds.into_iter().enumerate() { let keystore = Keystore::open( keystore_path.join(format!("auth-{}", n)), None, ).map_err(|err| err.to_string())?; let (_, _, grandpa, babe, im_online, authority_discovery) = chain_spec::authority_keys_from_seed(seed); let insert_key = |key_type, public| { keystore.write().insert_unknown( key_type, &format!("//{}", seed), public, ).map_err(|_| format!("Failed to insert key: {}", grandpa)) }; insert_key( sp_core::crypto::key_types::BABE, babe.as_slice(), )?; insert_key( sp_core::crypto::key_types::GRANDPA, grandpa.as_slice(), )?; insert_key( sp_core::crypto::key_types::IM_ONLINE, im_online.as_slice(), )?; insert_key( sp_core::crypto::key_types::AUTHORITY_DISCOVERY, authority_discovery.as_slice(), )?; } Ok(()) } fn print_seeds( authority_seeds: &[String], endowed_seeds: &[String], sudo_seed: &str, ) { let header = Style::new().bold().underline(); let entry = Style::new().bold(); println!("{}", header.paint("Authority seeds")); for (n, seed) in authority_seeds.iter().enumerate() { println!("{} //{}", entry.paint(format!("auth-{}:", n)), seed, ); } println!(); if !endowed_seeds.is_empty() { println!("{}", header.paint("Endowed seeds")); for (n, seed) in endowed_seeds.iter().enumerate() { println!("{} //{}", entry.paint(format!("endowed-{}:", n)), seed, ); } println!(); } println!("{}", header.paint("Sudo seed")); println!("//{}", sudo_seed); } fn main() -> Result<(), String> { #[cfg(build_type="debug")] println!( "The chain spec builder builds a chain specification that includes a Substrate runtime compiled as WASM. To \ ensure proper functioning of the included runtime compile (or run) the chain spec builder binary in \ `--release` mode.\n", ); let builder = ChainSpecBuilder::from_args(); let chain_spec_path = builder.chain_spec_path().to_path_buf(); let (authority_seeds, endowed_accounts, sudo_account) = match builder { ChainSpecBuilder::Generate { authorities, endowed, keystore_path, .. } => { let authorities = authorities.max(1); let rand_str = || -> String { OsRng.sample_iter(&Alphanumeric) .take(32) .collect() }; let authority_seeds = (0..authorities).map(|_| rand_str()).collect::>(); let endowed_seeds = (0..endowed).map(|_| rand_str()).collect::>(); let sudo_seed = rand_str(); print_seeds( &authority_seeds, &endowed_seeds, &sudo_seed, ); if let Some(keystore_path) = keystore_path { generate_authority_keys_and_store( &authority_seeds, &keystore_path, )?; } let endowed_accounts = endowed_seeds.iter().map(|seed| { chain_spec::get_account_id_from_seed::(seed) .to_ss58check() }).collect(); let sudo_account = chain_spec::get_account_id_from_seed::(&sudo_seed) .to_ss58check(); (authority_seeds, endowed_accounts, sudo_account) }, ChainSpecBuilder::New { authority_seeds, endowed_accounts, sudo_account, .. } => { (authority_seeds, endowed_accounts, sudo_account) }, }; let json = generate_chain_spec( authority_seeds, endowed_accounts, sudo_account, )?; fs::write(chain_spec_path, json).map_err(|err| err.to_string()) }