referrerpolicy=no-referrer-when-downgrade

staging_node_cli/
chain_spec.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
5
6// This program is free software: you can redistribute it and/or modify
7// it under the terms of the GNU General Public License as published by
8// the Free Software Foundation, either version 3 of the License, or
9// (at your option) any later version.
10
11// This program is distributed in the hope that it will be useful,
12// but WITHOUT ANY WARRANTY; without even the implied warranty of
13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14// GNU General Public License for more details.
15
16// You should have received a copy of the GNU General Public License
17// along with this program. If not, see <https://www.gnu.org/licenses/>.
18
19//! Substrate chain configurations.
20
21use polkadot_sdk::*;
22
23use crate::chain_spec::sc_service::Properties;
24use kitchensink_runtime::{
25	genesis_config_presets::{Staker, ENDOWMENT, STASH},
26	wasm_binary_unwrap, Block, MaxNominations, StakerStatus,
27};
28use pallet_im_online::sr25519::AuthorityId as ImOnlineId;
29use pallet_revive::is_eth_derived;
30use sc_chain_spec::ChainSpecExtension;
31use sc_service::ChainType;
32use sc_telemetry::TelemetryEndpoints;
33use serde::{Deserialize, Serialize};
34use sp_authority_discovery::AuthorityId as AuthorityDiscoveryId;
35use sp_consensus_babe::AuthorityId as BabeId;
36use sp_consensus_beefy::ecdsa_crypto::AuthorityId as BeefyId;
37use sp_consensus_grandpa::AuthorityId as GrandpaId;
38use sp_core::crypto::UncheckedInto;
39use sp_mixnet::types::AuthorityId as MixnetId;
40
41pub use kitchensink_runtime::RuntimeGenesisConfig;
42pub use node_primitives::{AccountId, Balance, Signature};
43
44const STAGING_TELEMETRY_URL: &str = "wss://telemetry.polkadot.io/submit/";
45
46/// Node `ChainSpec` extensions.
47///
48/// Additional parameters for some Substrate core modules,
49/// customizable from the chain spec.
50#[derive(Default, Clone, Serialize, Deserialize, ChainSpecExtension)]
51#[serde(rename_all = "camelCase")]
52pub struct Extensions {
53	/// Block numbers with known hashes.
54	pub fork_blocks: sc_client_api::ForkBlocks<Block>,
55	/// Known bad block hashes.
56	pub bad_blocks: sc_client_api::BadBlocks<Block>,
57	/// The light sync state extension used by the sync-state rpc.
58	pub light_sync_state: sc_sync_state_rpc::LightSyncStateExtension,
59}
60
61/// Specialized `ChainSpec`.
62pub type ChainSpec = sc_service::GenericChainSpec<Extensions>;
63/// Flaming Fir testnet generator
64pub fn flaming_fir_config() -> Result<ChainSpec, String> {
65	ChainSpec::from_json_bytes(&include_bytes!("../res/flaming-fir.json")[..])
66}
67
68fn configure_accounts_for_staging_testnet() -> (
69	Vec<(
70		AccountId,
71		AccountId,
72		GrandpaId,
73		BabeId,
74		ImOnlineId,
75		AuthorityDiscoveryId,
76		MixnetId,
77		BeefyId,
78	)>,
79	AccountId,
80	Vec<AccountId>,
81) {
82	#[rustfmt::skip]
83	// stash, controller, session-key, beefy id
84	// generated with secret:
85	// for i in 1 2 3 4 ; do for j in stash controller; do subkey inspect "$secret"/fir/$j/$i; done; done
86	//
87	// and
88	//
89	// for i in 1 2 3 4 ; do for j in session; do subkey inspect --scheme ed25519 "$secret"//fir//$j//$i; done; done
90	//
91	// and
92	//
93	// for i in 1 2 3 4 ; do for j in session; do subkey inspect --scheme ecdsa "$secret"//fir//$j//$i; done; done
94
95	let initial_authorities: Vec<(
96		AccountId,
97		AccountId,
98		GrandpaId,
99		BabeId,
100		ImOnlineId,
101		AuthorityDiscoveryId,
102		MixnetId,
103		BeefyId,
104	)> = vec![
105		(
106			// 5Fbsd6WXDGiLTxunqeK5BATNiocfCqu9bS1yArVjCgeBLkVy
107			array_bytes::hex_n_into_unchecked("9c7a2ee14e565db0c69f78c7b4cd839fbf52b607d867e9e9c5a79042898a0d12"),
108			// 5EnCiV7wSHeNhjW3FSUwiJNkcc2SBkPLn5Nj93FmbLtBjQUq
109			array_bytes::hex_n_into_unchecked("781ead1e2fa9ccb74b44c19d29cb2a7a4b5be3972927ae98cd3877523976a276"),
110			// 5Fb9ayurnxnaXj56CjmyQLBiadfRCqUbL2VWNbbe1nZU6wiC
111			array_bytes::hex2array_unchecked("9becad03e6dcac03cee07edebca5475314861492cdfc96a2144a67bbe9699332")
112				.unchecked_into(),
113			// 5EZaeQ8djPcq9pheJUhgerXQZt9YaHnMJpiHMRhwQeinqUW8
114			array_bytes::hex2array_unchecked("6e7e4eb42cbd2e0ab4cae8708ce5509580b8c04d11f6758dbf686d50fe9f9106")
115				.unchecked_into(),
116			// 5EZaeQ8djPcq9pheJUhgerXQZt9YaHnMJpiHMRhwQeinqUW8
117			array_bytes::hex2array_unchecked("6e7e4eb42cbd2e0ab4cae8708ce5509580b8c04d11f6758dbf686d50fe9f9106")
118				.unchecked_into(),
119			// 5EZaeQ8djPcq9pheJUhgerXQZt9YaHnMJpiHMRhwQeinqUW8
120			array_bytes::hex2array_unchecked("6e7e4eb42cbd2e0ab4cae8708ce5509580b8c04d11f6758dbf686d50fe9f9106")
121				.unchecked_into(),
122			// 5EZaeQ8djPcq9pheJUhgerXQZt9YaHnMJpiHMRhwQeinqUW8
123			array_bytes::hex2array_unchecked("6e7e4eb42cbd2e0ab4cae8708ce5509580b8c04d11f6758dbf686d50fe9f9106")
124				.unchecked_into(),
125			// 5DMLFcDdLLQbw696YfHaWBpQR99HwR456ycSCfr6L7KXGYK8
126			array_bytes::hex2array_unchecked("035560fafa241739869360aa4b32bc98953172ceb41a19c6cc1a27962fb3d1ecec")
127				.unchecked_into(),
128		),
129		(
130			// 5ERawXCzCWkjVq3xz1W5KGNtVx2VdefvZ62Bw1FEuZW4Vny2
131			array_bytes::hex_n_into_unchecked("68655684472b743e456907b398d3a44c113f189e56d1bbfd55e889e295dfde78"),
132			// 5Gc4vr42hH1uDZc93Nayk5G7i687bAQdHHc9unLuyeawHipF
133			array_bytes::hex_n_into_unchecked("c8dc79e36b29395413399edaec3e20fcca7205fb19776ed8ddb25d6f427ec40e"),
134			// 5EockCXN6YkiNCDjpqqnbcqd4ad35nU4RmA1ikM4YeRN4WcE
135			array_bytes::hex2array_unchecked("7932cff431e748892fa48e10c63c17d30f80ca42e4de3921e641249cd7fa3c2f")
136				.unchecked_into(),
137			// 5DhLtiaQd1L1LU9jaNeeu9HJkP6eyg3BwXA7iNMzKm7qqruQ
138			array_bytes::hex2array_unchecked("482dbd7297a39fa145c570552249c2ca9dd47e281f0c500c971b59c9dcdcd82e")
139				.unchecked_into(),
140			// 5DhLtiaQd1L1LU9jaNeeu9HJkP6eyg3BwXA7iNMzKm7qqruQ
141			array_bytes::hex2array_unchecked("482dbd7297a39fa145c570552249c2ca9dd47e281f0c500c971b59c9dcdcd82e")
142				.unchecked_into(),
143			// 5DhLtiaQd1L1LU9jaNeeu9HJkP6eyg3BwXA7iNMzKm7qqruQ
144			array_bytes::hex2array_unchecked("482dbd7297a39fa145c570552249c2ca9dd47e281f0c500c971b59c9dcdcd82e")
145				.unchecked_into(),
146			// 5DhLtiaQd1L1LU9jaNeeu9HJkP6eyg3BwXA7iNMzKm7qqruQ
147			array_bytes::hex2array_unchecked("482dbd7297a39fa145c570552249c2ca9dd47e281f0c500c971b59c9dcdcd82e")
148				.unchecked_into(),
149			// 5FYk11kNtB4178wLKJ2RNoUzzcjgRUciFe3SJDVZXhqX4dzG
150			array_bytes::hex2array_unchecked("02da1ab255ed888ee3e19b73d335fc13160b3eb10456c2d17c6a8ea7de403d2445")
151				.unchecked_into(),
152		),
153		(
154			// 5DyVtKWPidondEu8iHZgi6Ffv9yrJJ1NDNLom3X9cTDi98qp
155			array_bytes::hex_n_into_unchecked("547ff0ab649283a7ae01dbc2eb73932eba2fb09075e9485ff369082a2ff38d65"),
156			// 5FeD54vGVNpFX3PndHPXJ2MDakc462vBCD5mgtWRnWYCpZU9
157			array_bytes::hex_n_into_unchecked("9e42241d7cd91d001773b0b616d523dd80e13c6c2cab860b1234ef1b9ffc1526"),
158			// 5E1jLYfLdUQKrFrtqoKgFrRvxM3oQPMbf6DfcsrugZZ5Bn8d
159			array_bytes::hex2array_unchecked("5633b70b80a6c8bb16270f82cca6d56b27ed7b76c8fd5af2986a25a4788ce440")
160				.unchecked_into(),
161			// 5DhKqkHRkndJu8vq7pi2Q5S3DfftWJHGxbEUNH43b46qNspH
162			array_bytes::hex2array_unchecked("482a3389a6cf42d8ed83888cfd920fec738ea30f97e44699ada7323f08c3380a")
163				.unchecked_into(),
164			// 5DhKqkHRkndJu8vq7pi2Q5S3DfftWJHGxbEUNH43b46qNspH
165			array_bytes::hex2array_unchecked("482a3389a6cf42d8ed83888cfd920fec738ea30f97e44699ada7323f08c3380a")
166				.unchecked_into(),
167			// 5DhKqkHRkndJu8vq7pi2Q5S3DfftWJHGxbEUNH43b46qNspH
168			array_bytes::hex2array_unchecked("482a3389a6cf42d8ed83888cfd920fec738ea30f97e44699ada7323f08c3380a")
169				.unchecked_into(),
170			// 5DhKqkHRkndJu8vq7pi2Q5S3DfftWJHGxbEUNH43b46qNspH
171			array_bytes::hex2array_unchecked("482a3389a6cf42d8ed83888cfd920fec738ea30f97e44699ada7323f08c3380a")
172				.unchecked_into(),
173			// 5GQx4FToRBPqfani6o7owFJE1UstiviqbPP7HPWyvtXWWukn
174			array_bytes::hex2array_unchecked("036a818b3f59579c5fbbe4fede64f49dbf090ba883eb2a175d5ca90e5adb5f0b3e")
175				.unchecked_into(),
176		),
177		(
178			// 5HYZnKWe5FVZQ33ZRJK1rG3WaLMztxWrrNDb1JRwaHHVWyP9
179			array_bytes::hex_n_into_unchecked("f26cdb14b5aec7b2789fd5ca80f979cef3761897ae1f37ffb3e154cbcc1c2663"),
180			// 5EPQdAQ39WQNLCRjWsCk5jErsCitHiY5ZmjfWzzbXDoAoYbn
181			array_bytes::hex_n_into_unchecked("66bc1e5d275da50b72b15de072a2468a5ad414919ca9054d2695767cf650012f"),
182			// 5DMa31Hd5u1dwoRKgC4uvqyrdK45RHv3CpwvpUC1EzuwDit4
183			array_bytes::hex2array_unchecked("3919132b851ef0fd2dae42a7e734fe547af5a6b809006100f48944d7fae8e8ef")
184				.unchecked_into(),
185			// 5C4vDQxA8LTck2xJEy4Yg1hM9qjDt4LvTQaMo4Y8ne43aU6x
186			array_bytes::hex2array_unchecked("00299981a2b92f878baaf5dbeba5c18d4e70f2a1fcd9c61b32ea18daf38f4378")
187				.unchecked_into(),
188			// 5C4vDQxA8LTck2xJEy4Yg1hM9qjDt4LvTQaMo4Y8ne43aU6x
189			array_bytes::hex2array_unchecked("00299981a2b92f878baaf5dbeba5c18d4e70f2a1fcd9c61b32ea18daf38f4378")
190				.unchecked_into(),
191			// 5C4vDQxA8LTck2xJEy4Yg1hM9qjDt4LvTQaMo4Y8ne43aU6x
192			array_bytes::hex2array_unchecked("00299981a2b92f878baaf5dbeba5c18d4e70f2a1fcd9c61b32ea18daf38f4378")
193				.unchecked_into(),
194			// 5C4vDQxA8LTck2xJEy4Yg1hM9qjDt4LvTQaMo4Y8ne43aU6x
195			array_bytes::hex2array_unchecked("00299981a2b92f878baaf5dbeba5c18d4e70f2a1fcd9c61b32ea18daf38f4378")
196				.unchecked_into(),
197			// 5FCu2pY928VVHPgnNVJssvxFJZECyNe1CyH3WTG79Wisx58B
198			array_bytes::hex2array_unchecked("020ce02b963548f9f8ade8765f7a4a06638c17819c78422a1cc35b647873583eef")
199				.unchecked_into(),
200		),
201	];
202
203	// generated with secret: subkey inspect "$secret"/fir
204	let root_key: AccountId = array_bytes::hex_n_into_unchecked(
205		// 5Ff3iXP75ruzroPWRP2FYBHWnmGGBSb63857BgnzCoXNxfPo
206		"9ee5e5bdc0ec239eb164f865ecc345ce4c88e76ee002e0f7e318097347471809",
207	);
208
209	let endowed_accounts: Vec<AccountId> = vec![root_key.clone()];
210	(initial_authorities, root_key, endowed_accounts)
211}
212
213fn staging_testnet_genesis_patch() -> serde_json::Value {
214	let (initial_authorities, root_key, endowed_accounts) =
215		configure_accounts_for_staging_testnet();
216	testnet_genesis_patch(initial_authorities, vec![], root_key, endowed_accounts)
217}
218
219/// Staging testnet config.
220pub fn staging_testnet_config() -> ChainSpec {
221	ChainSpec::builder(wasm_binary_unwrap(), Default::default())
222		.with_name("Staging Testnet")
223		.with_id("staging_testnet")
224		.with_chain_type(ChainType::Live)
225		.with_genesis_config_preset_name(sp_genesis_builder::LOCAL_TESTNET_RUNTIME_PRESET)
226		.with_genesis_config_patch(staging_testnet_genesis_patch())
227		.with_telemetry_endpoints(
228			TelemetryEndpoints::new(vec![(STAGING_TELEMETRY_URL.to_string(), 0)])
229				.expect("Staging telemetry url is valid; qed"),
230		)
231		.build()
232}
233
234/// Configure the accounts for the testnet.
235///
236/// * Adds `initial_authorities` and `initial_nominators` to endowed accounts if missing.
237/// * Sets up the stakers consisting of the `initial_authorities` and `initial_nominators`.
238fn configure_accounts(
239	initial_authorities: Vec<(
240		AccountId,
241		AccountId,
242		GrandpaId,
243		BabeId,
244		ImOnlineId,
245		AuthorityDiscoveryId,
246		MixnetId,
247		BeefyId,
248	)>,
249	initial_nominators: Vec<AccountId>,
250	endowed_accounts: Vec<AccountId>,
251	stash: Balance,
252) -> (
253	Vec<(
254		AccountId,
255		AccountId,
256		GrandpaId,
257		BabeId,
258		ImOnlineId,
259		AuthorityDiscoveryId,
260		MixnetId,
261		BeefyId,
262	)>,
263	Vec<AccountId>,
264	Vec<Staker>,
265) {
266	let mut endowed_accounts = endowed_accounts;
267	// endow all authorities and nominators.
268	initial_authorities
269		.iter()
270		.map(|x| &x.0)
271		.chain(initial_nominators.iter())
272		.for_each(|x| {
273			if !endowed_accounts.contains(x) {
274				endowed_accounts.push(x.clone())
275			}
276		});
277
278	// stakers: all validators and nominators.
279	let mut rng = rand::thread_rng();
280	let stakers = initial_authorities
281		.iter()
282		.map(|x| (x.0.clone(), x.0.clone(), stash, StakerStatus::Validator))
283		.chain(initial_nominators.iter().map(|x| {
284			use rand::{seq::SliceRandom, Rng};
285			let limit = (MaxNominations::get() as usize).min(initial_authorities.len());
286			let count = rng.gen::<usize>() % limit;
287			let nominations = initial_authorities
288				.as_slice()
289				.choose_multiple(&mut rng, count)
290				.into_iter()
291				.map(|choice| choice.0.clone())
292				.collect::<Vec<_>>();
293			(x.clone(), x.clone(), stash, StakerStatus::Nominator(nominations))
294		}))
295		.collect::<Vec<_>>();
296
297	(initial_authorities, endowed_accounts, stakers)
298}
299
300/// Helper function to create RuntimeGenesisConfig json patch for testing.
301pub fn testnet_genesis_patch(
302	initial_authorities: Vec<(
303		AccountId,
304		AccountId,
305		GrandpaId,
306		BabeId,
307		ImOnlineId,
308		AuthorityDiscoveryId,
309		MixnetId,
310		BeefyId,
311	)>,
312	initial_nominators: Vec<AccountId>,
313	root_key: AccountId,
314	endowed_accounts: Vec<AccountId>,
315) -> serde_json::Value {
316	let (initial_authorities, endowed_accounts, stakers) =
317		configure_accounts(initial_authorities, initial_nominators, endowed_accounts, STASH);
318
319	let validator_count = initial_authorities.len();
320	let minimum_validator_count = validator_count;
321
322	let collective = collective(&endowed_accounts);
323
324	serde_json::json!({
325		"balances": {
326			"balances": endowed_accounts.iter().cloned().map(|x| (x, ENDOWMENT)).collect::<Vec<_>>()
327		},
328		"session": {
329			"keys": initial_authorities
330			.iter()
331			.map(|x| {
332				(
333					x.0.clone(),
334					// stash account is controller
335					x.0.clone(),
336					session_keys_json(
337						x.2.clone(),
338						x.3.clone(),
339						x.4.clone(),
340						x.5.clone(),
341						x.6.clone(),
342						x.7.clone(),
343					)
344				)
345			})
346			.collect::<Vec<_>>()
347		},
348		"elections": {
349			"members": collective.iter().cloned().map(|member| (member, STASH)).collect::<Vec<_>>(),
350		},
351		"technicalCommittee": {
352			"members": collective,
353		},
354		"staking": {
355			"validatorCount": validator_count,
356			"minimumValidatorCount": minimum_validator_count,
357			"invulnerables": initial_authorities
358				.iter()
359				.map(|x| x.0.clone())
360				.collect::<Vec<_>>(),
361			"stakers": stakers,
362		},
363		"sudo": {
364			"key": root_key,
365		},
366		"revive": {
367			"mappedAccounts": endowed_accounts.iter().filter(|x| ! is_eth_derived(x)).cloned().collect::<Vec<_>>()
368		}
369	})
370}
371
372/// Creates the session keys as defined by the runtime.
373fn session_keys_json(
374	grandpa: GrandpaId,
375	babe: BabeId,
376	im_online: ImOnlineId,
377	authority_discovery: AuthorityDiscoveryId,
378	mixnet: MixnetId,
379	beefy: BeefyId,
380) -> serde_json::Value {
381	serde_json::json!({
382		"authority_discovery": authority_discovery,
383		"babe": babe,
384		"beefy": beefy,
385		"grandpa": grandpa,
386		"im_online": im_online,
387		"mixnet": mixnet
388	})
389}
390
391/// Extract some accounts from endowed to be put into the collective.
392fn collective(endowed: &[AccountId]) -> Vec<AccountId> {
393	const MAX_COLLECTIVE_SIZE: usize = 50;
394	let endowed_accounts_count = endowed.len();
395	endowed
396		.iter()
397		.take((endowed_accounts_count.div_ceil(2)).min(MAX_COLLECTIVE_SIZE))
398		.cloned()
399		.collect()
400}
401
402fn props() -> Properties {
403	let mut properties = Properties::new();
404	properties.insert("tokenDecimals".to_string(), 12.into());
405	properties
406}
407
408/// Development config (single validator Alice).
409pub fn development_config() -> ChainSpec {
410	ChainSpec::builder(wasm_binary_unwrap(), Default::default())
411		.with_name("Development")
412		.with_id("dev")
413		.with_chain_type(ChainType::Development)
414		.with_properties(props())
415		.with_genesis_config_preset_name(sp_genesis_builder::DEV_RUNTIME_PRESET)
416		.build()
417}
418
419/// Local testnet config (multivalidator Alice + Bob).
420pub fn local_testnet_config() -> ChainSpec {
421	ChainSpec::builder(wasm_binary_unwrap(), Default::default())
422		.with_name("Local Testnet")
423		.with_id("local_testnet")
424		.with_chain_type(ChainType::Local)
425		.with_genesis_config_preset_name(sp_genesis_builder::LOCAL_TESTNET_RUNTIME_PRESET)
426		.build()
427}
428
429#[cfg(test)]
430pub(crate) mod tests {
431	use super::*;
432	use crate::service::{new_full_base, NewFullBase};
433	use kitchensink_runtime::genesis_config_presets::well_known_including_eth_accounts;
434	use sc_service_test;
435	use sp_runtime::{AccountId32, BuildStorage};
436
437	/// Local testnet config (single validator - Alice).
438	pub fn integration_test_config_with_single_authority() -> ChainSpec {
439		ChainSpec::builder(wasm_binary_unwrap(), Default::default())
440			.with_name("Integration Test")
441			.with_id("test")
442			.with_chain_type(ChainType::Development)
443			.with_genesis_config_preset_name(sp_genesis_builder::DEV_RUNTIME_PRESET)
444			.build()
445	}
446
447	/// Local testnet config (multivalidator Alice + Bob).
448	pub fn integration_test_config_with_two_authorities() -> ChainSpec {
449		ChainSpec::builder(wasm_binary_unwrap(), Default::default())
450			.with_name("Integration Test")
451			.with_id("test")
452			.with_chain_type(ChainType::Local)
453			.with_genesis_config_preset_name(sp_genesis_builder::LOCAL_TESTNET_RUNTIME_PRESET)
454			.build()
455	}
456
457	fn eth_account(from: subxt_signer::eth::Keypair) -> AccountId32 {
458		let mut account_id = AccountId32::new([0xEE; 32]);
459		<AccountId32 as AsMut<[u8; 32]>>::as_mut(&mut account_id)[..20]
460			.copy_from_slice(&from.public_key().to_account_id().as_ref());
461		account_id
462	}
463
464	#[test]
465	#[ignore]
466	fn test_connectivity() {
467		sp_tracing::try_init_simple();
468
469		sc_service_test::connectivity(integration_test_config_with_two_authorities(), |config| {
470			let NewFullBase { task_manager, client, network, sync, transaction_pool, .. } =
471				new_full_base::<sc_network::NetworkWorker<_, _>>(config, None, false, |_, _| ())?;
472			Ok(sc_service_test::TestNetComponents::new(
473				task_manager,
474				client,
475				network,
476				sync,
477				transaction_pool,
478			))
479		});
480	}
481
482	#[test]
483	fn test_create_development_chain_spec() {
484		development_config().build_storage().unwrap();
485	}
486
487	#[test]
488	fn test_create_local_testnet_chain_spec() {
489		local_testnet_config().build_storage().unwrap();
490	}
491
492	#[test]
493	fn test_staging_test_net_chain_spec() {
494		staging_testnet_config().build_storage().unwrap();
495	}
496
497	#[test]
498	fn ensure_eth_accounts_are_in_endowed() {
499		let alith = eth_account(subxt_signer::eth::dev::alith());
500		let baltathar = eth_account(subxt_signer::eth::dev::baltathar());
501
502		let endowed = well_known_including_eth_accounts();
503
504		assert!(endowed.contains(&alith), "Alith must be in endowed for integration tests");
505		assert!(endowed.contains(&baltathar), "Baltathar must be in endowed for integration tests");
506	}
507}