Skip to main content

sc_chain_spec/
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#![warn(missing_docs)]
21use crate::{
22	extension::GetExtension, genesis_config_builder::HostFunctions, json_merge, ChainType,
23	GenesisConfigBuilderRuntimeCaller as RuntimeCaller, Properties,
24};
25use sc_network::config::MultiaddrWithPeerId;
26use sc_telemetry::TelemetryEndpoints;
27use serde::{Deserialize, Serialize};
28use serde_json as json;
29use sp_core::{
30	storage::{ChildInfo, Storage, StorageChild, StorageData, StorageKey},
31	Bytes,
32};
33use sp_runtime::BuildStorage;
34use std::{
35	borrow::Cow,
36	collections::{BTreeMap, VecDeque},
37	fs::File,
38	marker::PhantomData,
39	path::PathBuf,
40};
41
42#[derive(Serialize, Deserialize)]
43#[serde(rename_all = "camelCase")]
44enum GenesisBuildAction<EHF> {
45	/// Gets the default config (aka PresetId=None) and patches it.
46	Patch(json::Value),
47	/// Assumes that the full `RuntimeGenesisConfig` is supplied.
48	Full(json::Value),
49	/// Gets the named preset and applies an optional patch.
50	NamedPreset(String, json::Value, PhantomData<EHF>),
51}
52
53impl<EHF> GenesisBuildAction<EHF> {
54	pub fn merge_patch(&mut self, patch: json::Value) {
55		match self {
56			GenesisBuildAction::Patch(value) |
57			GenesisBuildAction::Full(value) |
58			GenesisBuildAction::NamedPreset(_, value, _) => json_merge(value, patch),
59		}
60	}
61}
62
63impl<EHF> Clone for GenesisBuildAction<EHF> {
64	fn clone(&self) -> Self {
65		match self {
66			Self::Patch(ref p) => Self::Patch(p.clone()),
67			Self::Full(ref f) => Self::Full(f.clone()),
68			Self::NamedPreset(ref p, patch, _) => {
69				Self::NamedPreset(p.clone(), patch.clone(), Default::default())
70			},
71		}
72	}
73}
74
75enum GenesisSource<EHF> {
76	File(PathBuf),
77	Binary(Cow<'static, [u8]>),
78	/// factory function + code
79	Storage(Storage),
80	/// build action + code
81	GenesisBuilderApi(GenesisBuildAction<EHF>, Vec<u8>),
82}
83
84impl<EHF> Clone for GenesisSource<EHF> {
85	fn clone(&self) -> Self {
86		match *self {
87			Self::File(ref path) => Self::File(path.clone()),
88			Self::Binary(ref d) => Self::Binary(d.clone()),
89			Self::Storage(ref s) => Self::Storage(s.clone()),
90			Self::GenesisBuilderApi(ref s, ref c) => Self::GenesisBuilderApi(s.clone(), c.clone()),
91		}
92	}
93}
94
95impl<EHF: HostFunctions> GenesisSource<EHF> {
96	fn resolve(&self) -> Result<Genesis, String> {
97		/// helper container for deserializing genesis from the JSON file (ChainSpec JSON file is
98		/// also supported here)
99		#[derive(Serialize, Deserialize)]
100		struct GenesisContainer {
101			genesis: Genesis,
102		}
103
104		match self {
105			Self::File(path) => {
106				let file = File::open(path).map_err(|e| {
107					format!("Error opening spec file at `{}`: {}", path.display(), e)
108				})?;
109				// SAFETY: `mmap` is fundamentally unsafe since technically the file can change
110				//         underneath us while it is mapped; in practice it's unlikely to be a
111				//         problem
112				let bytes = unsafe {
113					memmap2::Mmap::map(&file).map_err(|e| {
114						format!("Error mmaping spec file `{}`: {}", path.display(), e)
115					})?
116				};
117
118				let genesis: GenesisContainer = json::from_slice(&bytes)
119					.map_err(|e| format!("Error parsing spec file: {}", e))?;
120				Ok(genesis.genesis)
121			},
122			Self::Binary(buf) => {
123				let genesis: GenesisContainer = json::from_reader(buf.as_ref())
124					.map_err(|e| format!("Error parsing embedded file: {}", e))?;
125				Ok(genesis.genesis)
126			},
127			Self::Storage(storage) => Ok(Genesis::Raw(RawGenesis::from(storage.clone()))),
128			Self::GenesisBuilderApi(GenesisBuildAction::Full(config), code) => {
129				Ok(Genesis::RuntimeGenesis(RuntimeGenesisInner {
130					json_blob: RuntimeGenesisConfigJson::Config(config.clone()),
131					code: code.clone(),
132				}))
133			},
134			Self::GenesisBuilderApi(GenesisBuildAction::Patch(patch), code) => {
135				Ok(Genesis::RuntimeGenesis(RuntimeGenesisInner {
136					json_blob: RuntimeGenesisConfigJson::Patch(patch.clone()),
137					code: code.clone(),
138				}))
139			},
140			Self::GenesisBuilderApi(GenesisBuildAction::NamedPreset(name, patch, _), code) => {
141				let mut preset =
142					RuntimeCaller::<EHF>::new(&code[..]).get_named_preset(Some(name))?;
143				json_merge(&mut preset, patch.clone());
144				Ok(Genesis::RuntimeGenesis(RuntimeGenesisInner {
145					json_blob: RuntimeGenesisConfigJson::Patch(preset),
146					code: code.clone(),
147				}))
148			},
149		}
150	}
151}
152
153impl<E, EHF> BuildStorage for ChainSpec<E, EHF>
154where
155	EHF: HostFunctions,
156{
157	fn assimilate_storage(&self, storage: &mut Storage) -> Result<(), String> {
158		match self.genesis.resolve()? {
159			Genesis::Raw(RawGenesis { top: map, children_default: children_map }) => {
160				storage.top.extend(map.into_iter().map(|(k, v)| (k.0, v.0)));
161				children_map.into_iter().for_each(|(k, v)| {
162					let child_info = ChildInfo::new_default(k.0.as_slice());
163					storage
164						.children_default
165						.entry(k.0)
166						.or_insert_with(|| StorageChild { data: Default::default(), child_info })
167						.data
168						.extend(v.into_iter().map(|(k, v)| (k.0, v.0)));
169				});
170			},
171			// The `StateRootHash` variant exists as a way to keep note that other clients support
172			// it, but Substrate itself isn't capable of loading chain specs with just a hash at the
173			// moment.
174			Genesis::StateRootHash(_) => {
175				return Err("Genesis storage in hash format not supported".into())
176			},
177			Genesis::RuntimeGenesis(RuntimeGenesisInner {
178				json_blob: RuntimeGenesisConfigJson::Config(config),
179				code,
180			}) => {
181				RuntimeCaller::<EHF>::new(&code[..])
182					.get_storage_for_config(config)?
183					.assimilate_storage(storage)?;
184				storage
185					.top
186					.insert(sp_core::storage::well_known_keys::CODE.to_vec(), code.clone());
187			},
188			Genesis::RuntimeGenesis(RuntimeGenesisInner {
189				json_blob: RuntimeGenesisConfigJson::Patch(patch),
190				code,
191			}) => {
192				RuntimeCaller::<EHF>::new(&code[..])
193					.get_storage_for_patch(patch)?
194					.assimilate_storage(storage)?;
195				storage
196					.top
197					.insert(sp_core::storage::well_known_keys::CODE.to_vec(), code.clone());
198			},
199		};
200
201		Ok(())
202	}
203}
204
205pub type GenesisStorage = BTreeMap<StorageKey, StorageData>;
206
207/// Raw storage content for genesis block.
208#[derive(Serialize, Deserialize)]
209#[serde(rename_all = "camelCase")]
210#[serde(deny_unknown_fields)]
211pub struct RawGenesis {
212	pub top: GenesisStorage,
213	pub children_default: BTreeMap<StorageKey, GenesisStorage>,
214}
215
216impl From<sp_core::storage::Storage> for RawGenesis {
217	fn from(value: sp_core::storage::Storage) -> Self {
218		Self {
219			top: value.top.into_iter().map(|(k, v)| (StorageKey(k), StorageData(v))).collect(),
220			children_default: value
221				.children_default
222				.into_iter()
223				.map(|(sk, child)| {
224					(
225						StorageKey(sk),
226						child
227							.data
228							.into_iter()
229							.map(|(k, v)| (StorageKey(k), StorageData(v)))
230							.collect(),
231					)
232				})
233				.collect(),
234		}
235	}
236}
237
238/// Inner representation of [`Genesis::RuntimeGenesis`] format
239#[derive(Serialize, Deserialize, Debug)]
240struct RuntimeGenesisInner {
241	/// Runtime wasm code, expected to be hex-encoded in JSON.
242	/// The code shall be capable of parsing `json_blob`.
243	#[serde(default, with = "sp_core::bytes")]
244	code: Vec<u8>,
245	/// The patch or full representation of runtime's `RuntimeGenesisConfig` struct.
246	#[serde(flatten)]
247	json_blob: RuntimeGenesisConfigJson,
248}
249
250/// Represents two possible variants of the contained JSON blob for the
251/// [`Genesis::RuntimeGenesis`] format.
252#[derive(Serialize, Deserialize, Debug)]
253#[serde(rename_all = "camelCase")]
254enum RuntimeGenesisConfigJson {
255	/// Represents the explicit and comprehensive runtime genesis config in JSON format.
256	/// The contained object is a JSON blob that can be parsed by a compatible runtime.
257	///
258	/// Using a full config is useful for when someone wants to ensure that a change in the runtime
259	/// makes the deserialization fail and not silently add some default values.
260	Config(json::Value),
261	/// Represents a patch for the default runtime genesis config in JSON format which is
262	/// essentially a list of keys that are to be customized in runtime genesis config.
263	/// The contained value is a JSON blob that can be parsed by a compatible runtime.
264	Patch(json::Value),
265}
266
267/// Represents the different formats of the genesis state within chain spec JSON blob.
268#[derive(Serialize, Deserialize)]
269#[serde(rename_all = "camelCase")]
270#[serde(deny_unknown_fields)]
271enum Genesis {
272	/// The genesis storage as raw data. Typically raw key-value entries in state.
273	Raw(RawGenesis),
274	/// State root hash of the genesis storage.
275	StateRootHash(StorageData),
276	/// Represents the runtime genesis config in JSON format together with runtime code.
277	RuntimeGenesis(RuntimeGenesisInner),
278}
279
280/// A configuration of a client. Does not include runtime storage initialization.
281/// Note: `genesis` field is ignored due to way how the chain specification is serialized into
282/// JSON file. Refer to [`ChainSpecJsonContainer`], which flattens [`ClientSpec`] and denies unknown
283/// fields.
284#[derive(Serialize, Deserialize, Clone, Debug)]
285#[serde(rename_all = "camelCase")]
286// we cannot #[serde(deny_unknown_fields)]. Otherwise chain-spec-builder will fail on any
287// non-standard spec
288struct ClientSpec<E> {
289	name: String,
290	id: String,
291	#[serde(default)]
292	chain_type: ChainType,
293	boot_nodes: Vec<MultiaddrWithPeerId>,
294	telemetry_endpoints: Option<TelemetryEndpoints>,
295	protocol_id: Option<String>,
296	/// Arbitrary string. Nodes will only synchronize with other nodes that have the same value
297	/// in their `fork_id`. This can be used in order to segregate nodes in cases when multiple
298	/// chains have the same genesis hash.
299	#[serde(default = "Default::default", skip_serializing_if = "Option::is_none")]
300	fork_id: Option<String>,
301	properties: Option<Properties>,
302	#[serde(flatten)]
303	extensions: E,
304	// Never used, left only for backward compatibility.
305	#[serde(default, skip_serializing)]
306	#[allow(unused)]
307	consensus_engine: (),
308	#[serde(skip_serializing)]
309	#[allow(unused)]
310	genesis: serde::de::IgnoredAny,
311	/// Mapping from `block_number` to `wasm_code`.
312	///
313	/// The given `wasm_code` will be used to substitute the on-chain wasm code starting with the
314	/// given block number until the `spec_version` on chain changes.
315	#[serde(default)]
316	code_substitutes: BTreeMap<String, Bytes>,
317}
318
319/// A type denoting empty extensions.
320///
321/// We use `Option` here since `()` is not flattenable by serde.
322pub type NoExtension = Option<()>;
323
324/// Builder for creating [`ChainSpec`] instances.
325pub struct ChainSpecBuilder<E = NoExtension, EHF = ()> {
326	code: Vec<u8>,
327	extensions: E,
328	name: String,
329	id: String,
330	chain_type: ChainType,
331	genesis_build_action: GenesisBuildAction<EHF>,
332	boot_nodes: Option<Vec<MultiaddrWithPeerId>>,
333	telemetry_endpoints: Option<TelemetryEndpoints>,
334	protocol_id: Option<String>,
335	fork_id: Option<String>,
336	properties: Option<Properties>,
337}
338
339impl<E, EHF> ChainSpecBuilder<E, EHF> {
340	/// Creates a new builder instance with no defaults.
341	pub fn new(code: &[u8], extensions: E) -> Self {
342		Self {
343			code: code.into(),
344			extensions,
345			name: "Development".to_string(),
346			id: "dev".to_string(),
347			chain_type: ChainType::Local,
348			genesis_build_action: GenesisBuildAction::Patch(json::json!({})),
349			boot_nodes: None,
350			telemetry_endpoints: None,
351			protocol_id: None,
352			fork_id: None,
353			properties: None,
354		}
355	}
356
357	/// Sets the spec name.
358	pub fn with_name(mut self, name: &str) -> Self {
359		self.name = name.into();
360		self
361	}
362
363	/// Sets the spec ID.
364	pub fn with_id(mut self, id: &str) -> Self {
365		self.id = id.into();
366		self
367	}
368
369	/// Sets the type of the chain.
370	pub fn with_chain_type(mut self, chain_type: ChainType) -> Self {
371		self.chain_type = chain_type;
372		self
373	}
374
375	/// Sets a list of bootnode addresses.
376	pub fn with_boot_nodes(mut self, boot_nodes: Vec<MultiaddrWithPeerId>) -> Self {
377		self.boot_nodes = Some(boot_nodes);
378		self
379	}
380
381	/// Sets telemetry endpoints.
382	pub fn with_telemetry_endpoints(mut self, telemetry_endpoints: TelemetryEndpoints) -> Self {
383		self.telemetry_endpoints = Some(telemetry_endpoints);
384		self
385	}
386
387	/// Sets the network protocol ID.
388	pub fn with_protocol_id(mut self, protocol_id: &str) -> Self {
389		self.protocol_id = Some(protocol_id.into());
390		self
391	}
392
393	/// Sets an optional network fork identifier.
394	pub fn with_fork_id(mut self, fork_id: &str) -> Self {
395		self.fork_id = Some(fork_id.into());
396		self
397	}
398
399	/// Sets additional loosely-typed properties of the chain.
400	pub fn with_properties(mut self, properties: Properties) -> Self {
401		self.properties = Some(properties);
402		self
403	}
404
405	/// Sets chain spec extensions.
406	pub fn with_extensions(mut self, extensions: E) -> Self {
407		self.extensions = extensions;
408		self
409	}
410
411	/// Sets the code.
412	pub fn with_code(mut self, code: &[u8]) -> Self {
413		self.code = code.into();
414		self
415	}
416
417	/// Applies a patch to whatever genesis build action is set.
418	pub fn with_genesis_config_patch(mut self, patch: json::Value) -> Self {
419		self.genesis_build_action.merge_patch(patch);
420		self
421	}
422
423	/// Sets the name of runtime-provided JSON patch for runtime's GenesisConfig.
424	pub fn with_genesis_config_preset_name(mut self, name: &str) -> Self {
425		self.genesis_build_action =
426			GenesisBuildAction::NamedPreset(name.to_string(), json::json!({}), Default::default());
427		self
428	}
429
430	/// Sets the full runtime's GenesisConfig JSON.
431	pub fn with_genesis_config(mut self, config: json::Value) -> Self {
432		self.genesis_build_action = GenesisBuildAction::Full(config);
433		self
434	}
435
436	/// Builds a [`ChainSpec`] instance using the provided settings.
437	pub fn build(self) -> ChainSpec<E, EHF> {
438		let client_spec = ClientSpec {
439			name: self.name,
440			id: self.id,
441			chain_type: self.chain_type,
442			boot_nodes: self.boot_nodes.unwrap_or_default(),
443			telemetry_endpoints: self.telemetry_endpoints,
444			protocol_id: self.protocol_id,
445			fork_id: self.fork_id,
446			properties: self.properties,
447			extensions: self.extensions,
448			consensus_engine: (),
449			genesis: Default::default(),
450			code_substitutes: BTreeMap::new(),
451		};
452
453		ChainSpec {
454			client_spec,
455			genesis: GenesisSource::GenesisBuilderApi(self.genesis_build_action, self.code.into()),
456			_host_functions: Default::default(),
457		}
458	}
459}
460
461/// A configuration of a chain. Can be used to build a genesis block.
462///
463/// The chain spec is generic over the native `RuntimeGenesisConfig` struct (`G`). It is also
464/// possible to parametrize chain spec over the extended host functions (EHF). It should be use if
465/// runtime is using the non-standard host function during genesis state creation.
466pub struct ChainSpec<E = NoExtension, EHF = ()> {
467	client_spec: ClientSpec<E>,
468	genesis: GenesisSource<EHF>,
469	_host_functions: PhantomData<EHF>,
470}
471
472impl<E: Clone, EHF> Clone for ChainSpec<E, EHF> {
473	fn clone(&self) -> Self {
474		ChainSpec {
475			client_spec: self.client_spec.clone(),
476			genesis: self.genesis.clone(),
477			_host_functions: self._host_functions,
478		}
479	}
480}
481
482impl<E, EHF> ChainSpec<E, EHF> {
483	/// A list of bootnode addresses.
484	pub fn boot_nodes(&self) -> &[MultiaddrWithPeerId] {
485		&self.client_spec.boot_nodes
486	}
487
488	/// Spec name.
489	pub fn name(&self) -> &str {
490		&self.client_spec.name
491	}
492
493	/// Spec id.
494	pub fn id(&self) -> &str {
495		&self.client_spec.id
496	}
497
498	/// Telemetry endpoints (if any)
499	pub fn telemetry_endpoints(&self) -> &Option<TelemetryEndpoints> {
500		&self.client_spec.telemetry_endpoints
501	}
502
503	/// Network protocol id.
504	pub fn protocol_id(&self) -> Option<&str> {
505		self.client_spec.protocol_id.as_deref()
506	}
507
508	/// Optional network fork identifier.
509	pub fn fork_id(&self) -> Option<&str> {
510		self.client_spec.fork_id.as_deref()
511	}
512
513	/// Additional loosely-typed properties of the chain.
514	///
515	/// Returns an empty JSON object if 'properties' not defined in config
516	pub fn properties(&self) -> Properties {
517		self.client_spec.properties.as_ref().unwrap_or(&json::map::Map::new()).clone()
518	}
519
520	/// Add a bootnode to the list.
521	pub fn add_boot_node(&mut self, addr: MultiaddrWithPeerId) {
522		self.client_spec.boot_nodes.push(addr)
523	}
524
525	/// Returns a reference to the defined chain spec extensions.
526	pub fn extensions(&self) -> &E {
527		&self.client_spec.extensions
528	}
529
530	/// Returns a mutable reference to the defined chain spec extensions.
531	pub fn extensions_mut(&mut self) -> &mut E {
532		&mut self.client_spec.extensions
533	}
534
535	/// Type of the chain.
536	fn chain_type(&self) -> ChainType {
537		self.client_spec.chain_type.clone()
538	}
539
540	/// Provides a `ChainSpec` builder.
541	pub fn builder(code: &[u8], extensions: E) -> ChainSpecBuilder<E, EHF> {
542		ChainSpecBuilder::new(code, extensions)
543	}
544}
545
546impl<E: serde::de::DeserializeOwned, EHF> ChainSpec<E, EHF> {
547	/// Parse json content into a `ChainSpec`
548	pub fn from_json_bytes(json: impl Into<Cow<'static, [u8]>>) -> Result<Self, String> {
549		let json = json.into();
550		let client_spec = json::from_slice(json.as_ref())
551			.map_err(|e| format!("Error parsing spec file: {}", e))?;
552
553		Ok(ChainSpec {
554			client_spec,
555			genesis: GenesisSource::Binary(json),
556			_host_functions: Default::default(),
557		})
558	}
559
560	/// Parse json file into a `ChainSpec`
561	pub fn from_json_file(path: PathBuf) -> Result<Self, String> {
562		// We mmap the file into memory first, as this is *a lot* faster than using
563		// `serde_json::from_reader`. See https://github.com/serde-rs/json/issues/160
564		let file = File::open(&path)
565			.map_err(|e| format!("Error opening spec file `{}`: {}", path.display(), e))?;
566
567		// SAFETY: `mmap` is fundamentally unsafe since technically the file can change
568		//         underneath us while it is mapped; in practice it's unlikely to be a problem
569		let bytes = unsafe {
570			memmap2::Mmap::map(&file)
571				.map_err(|e| format!("Error mmaping spec file `{}`: {}", path.display(), e))?
572		};
573		let client_spec =
574			json::from_slice(&bytes).map_err(|e| format!("Error parsing spec file: {}", e))?;
575
576		Ok(ChainSpec {
577			client_spec,
578			genesis: GenesisSource::File(path),
579			_host_functions: Default::default(),
580		})
581	}
582}
583
584/// Helper structure for serializing (and only serializing) the ChainSpec into JSON file. It
585/// represents the layout of `ChainSpec` JSON file.
586#[derive(Serialize, Deserialize)]
587// we cannot #[serde(deny_unknown_fields)]. Otherwise chain-spec-builder will fail on any
588// non-standard spec.
589struct ChainSpecJsonContainer<E> {
590	#[serde(flatten)]
591	client_spec: ClientSpec<E>,
592	genesis: Genesis,
593}
594
595impl<E: serde::Serialize + Clone + 'static, EHF> ChainSpec<E, EHF>
596where
597	EHF: HostFunctions,
598{
599	fn json_container(&self, raw: bool) -> Result<ChainSpecJsonContainer<E>, String> {
600		let raw_genesis = match (raw, self.genesis.resolve()?) {
601			(
602				true,
603				Genesis::RuntimeGenesis(RuntimeGenesisInner {
604					json_blob: RuntimeGenesisConfigJson::Config(config),
605					code,
606				}),
607			) => {
608				let mut storage =
609					RuntimeCaller::<EHF>::new(&code[..]).get_storage_for_config(config)?;
610				storage.top.insert(sp_core::storage::well_known_keys::CODE.to_vec(), code);
611				RawGenesis::from(storage)
612			},
613			(
614				true,
615				Genesis::RuntimeGenesis(RuntimeGenesisInner {
616					json_blob: RuntimeGenesisConfigJson::Patch(patch),
617					code,
618				}),
619			) => {
620				let mut storage =
621					RuntimeCaller::<EHF>::new(&code[..]).get_storage_for_patch(patch)?;
622				storage.top.insert(sp_core::storage::well_known_keys::CODE.to_vec(), code);
623				RawGenesis::from(storage)
624			},
625			(true, Genesis::Raw(raw)) => raw,
626			(_, genesis) => {
627				return Ok(ChainSpecJsonContainer {
628					client_spec: self.client_spec.clone(),
629					genesis,
630				})
631			},
632		};
633
634		Ok(ChainSpecJsonContainer {
635			client_spec: self.client_spec.clone(),
636			genesis: Genesis::Raw(raw_genesis),
637		})
638	}
639
640	/// Dump the chain specification to JSON string.
641	pub fn as_json(&self, raw: bool) -> Result<String, String> {
642		let container = self.json_container(raw)?;
643		json::to_string_pretty(&container).map_err(|e| format!("Error generating spec json: {}", e))
644	}
645}
646
647impl<E, EHF> crate::ChainSpec for ChainSpec<E, EHF>
648where
649	E: GetExtension + serde::Serialize + Clone + Send + Sync + 'static,
650	EHF: HostFunctions,
651{
652	fn boot_nodes(&self) -> &[MultiaddrWithPeerId] {
653		ChainSpec::boot_nodes(self)
654	}
655
656	fn name(&self) -> &str {
657		ChainSpec::name(self)
658	}
659
660	fn id(&self) -> &str {
661		ChainSpec::id(self)
662	}
663
664	fn chain_type(&self) -> ChainType {
665		ChainSpec::chain_type(self)
666	}
667
668	fn telemetry_endpoints(&self) -> &Option<TelemetryEndpoints> {
669		ChainSpec::telemetry_endpoints(self)
670	}
671
672	fn protocol_id(&self) -> Option<&str> {
673		ChainSpec::protocol_id(self)
674	}
675
676	fn fork_id(&self) -> Option<&str> {
677		ChainSpec::fork_id(self)
678	}
679
680	fn properties(&self) -> Properties {
681		ChainSpec::properties(self)
682	}
683
684	fn add_boot_node(&mut self, addr: MultiaddrWithPeerId) {
685		ChainSpec::add_boot_node(self, addr)
686	}
687
688	fn extensions(&self) -> &dyn GetExtension {
689		ChainSpec::extensions(self) as &dyn GetExtension
690	}
691
692	fn extensions_mut(&mut self) -> &mut dyn GetExtension {
693		ChainSpec::extensions_mut(self) as &mut dyn GetExtension
694	}
695
696	fn as_json(&self, raw: bool) -> Result<String, String> {
697		ChainSpec::as_json(self, raw)
698	}
699
700	fn as_storage_builder(&self) -> &dyn BuildStorage {
701		self
702	}
703
704	fn cloned_box(&self) -> Box<dyn crate::ChainSpec> {
705		Box::new(self.clone())
706	}
707
708	fn set_storage(&mut self, storage: Storage) {
709		self.genesis = GenesisSource::Storage(storage);
710	}
711
712	fn code_substitutes(&self) -> std::collections::BTreeMap<String, Vec<u8>> {
713		self.client_spec
714			.code_substitutes
715			.iter()
716			.map(|(h, c)| (h.clone(), c.0.clone()))
717			.collect()
718	}
719}
720
721/// The `fun` will be called with the value at `path`.
722///
723/// If exists, the value at given `path` will be passed to the `fun` and the result of `fun`
724/// call will be returned. Otherwise false is returned.
725/// `path` will be modified.
726///
727/// # Examples
728/// ```ignore
729/// use serde_json::{from_str, json, Value};
730/// let doc = json!({"a":{"b":{"c":"5"}}});
731/// let mut path = ["a", "b", "c"].into();
732/// assert!(json_eval_value_at_key(&doc, &mut path, &|v| { assert_eq!(v,"5"); true }));
733/// ```
734fn json_eval_value_at_key(
735	doc: &json::Value,
736	path: &mut VecDeque<&str>,
737	fun: &dyn Fn(&json::Value) -> bool,
738) -> bool {
739	let Some(key) = path.pop_front() else { return false };
740
741	if path.is_empty() {
742		doc.as_object().map_or(false, |o| o.get(key).map_or(false, |v| fun(v)))
743	} else {
744		doc.as_object()
745			.map_or(false, |o| o.get(key).map_or(false, |v| json_eval_value_at_key(v, path, fun)))
746	}
747}
748
749macro_rules! json_path {
750	[ $($x:expr),+ ] => {
751		VecDeque::<&str>::from([$($x),+])
752	};
753}
754
755fn json_contains_path(doc: &json::Value, path: &mut VecDeque<&str>) -> bool {
756	json_eval_value_at_key(doc, path, &|_| true)
757}
758
759/// This function updates the code in given chain spec.
760///
761/// Function support updating the runtime code in provided JSON chain spec blob. `Genesis::Raw`
762/// and `Genesis::RuntimeGenesis` formats are supported.
763///
764/// If update was successful `true` is returned, otherwise `false`. Chain spec JSON is modified in
765/// place.
766pub fn update_code_in_json_chain_spec(chain_spec: &mut json::Value, code: &[u8]) -> bool {
767	let mut path = json_path!["genesis", "runtimeGenesis", "code"];
768	let mut raw_path = json_path!["genesis", "raw", "top"];
769
770	if json_contains_path(&chain_spec, &mut path) {
771		#[derive(Serialize)]
772		struct Container<'a> {
773			#[serde(with = "sp_core::bytes")]
774			code: &'a [u8],
775		}
776		let code_patch = json::json!({"genesis":{"runtimeGenesis": Container { code }}});
777		crate::json_patch::merge(chain_spec, code_patch);
778		true
779	} else if json_contains_path(&chain_spec, &mut raw_path) {
780		#[derive(Serialize)]
781		struct Container<'a> {
782			#[serde(with = "sp_core::bytes", rename = "0x3a636f6465")]
783			code: &'a [u8],
784		}
785		let code_patch = json::json!({"genesis":{"raw":{"top": Container { code }}}});
786		crate::json_patch::merge(chain_spec, code_patch);
787		true
788	} else {
789		false
790	}
791}
792
793/// This function sets a codeSubstitute in the chain spec.
794pub fn set_code_substitute_in_json_chain_spec(
795	chain_spec: &mut json::Value,
796	code: &[u8],
797	block_height: u64,
798) {
799	let substitutes = json::json!({"codeSubstitutes":{ &block_height.to_string(): sp_core::bytes::to_hex(code, false) }});
800	crate::json_patch::merge(chain_spec, substitutes);
801}
802
803#[cfg(test)]
804mod tests {
805	use super::*;
806	use pretty_assertions::assert_eq;
807	use serde_json::{from_str, json, Value};
808	use sp_application_crypto::Ss58Codec;
809	use sp_core::storage::well_known_keys;
810	use sp_keyring::Sr25519Keyring;
811
812	type TestSpec = ChainSpec;
813
814	#[test]
815	fn should_deserialize_example_chain_spec() {
816		let spec1 = TestSpec::from_json_bytes(Cow::Owned(
817			include_bytes!("../res/chain_spec.json").to_vec(),
818		))
819		.unwrap();
820		let spec2 = TestSpec::from_json_file(PathBuf::from("./res/chain_spec.json")).unwrap();
821
822		assert_eq!(spec1.as_json(false), spec2.as_json(false));
823		assert_eq!(spec2.chain_type(), ChainType::Live)
824	}
825
826	#[derive(Debug, Serialize, Deserialize, Clone)]
827	#[serde(rename_all = "camelCase")]
828	struct Extension1 {
829		my_property: String,
830	}
831
832	impl crate::Extension for Extension1 {
833		type Forks = Option<()>;
834
835		fn get<T: 'static>(&self) -> Option<&T> {
836			None
837		}
838
839		fn get_any(&self, _: std::any::TypeId) -> &dyn std::any::Any {
840			self
841		}
842
843		fn get_any_mut(&mut self, _: std::any::TypeId) -> &mut dyn std::any::Any {
844			self
845		}
846	}
847
848	type TestSpec2 = ChainSpec<Extension1>;
849
850	#[test]
851	fn should_deserialize_chain_spec_with_extensions() {
852		let spec = TestSpec2::from_json_bytes(Cow::Owned(
853			include_bytes!("../res/chain_spec2.json").to_vec(),
854		))
855		.unwrap();
856
857		assert_eq!(spec.extensions().my_property, "Test Extension");
858	}
859
860	#[test]
861	fn chain_spec_raw_output_should_be_deterministic() {
862		let mut spec = TestSpec2::from_json_bytes(Cow::Owned(
863			include_bytes!("../res/chain_spec2.json").to_vec(),
864		))
865		.unwrap();
866
867		let mut storage = spec.build_storage().unwrap();
868
869		// Add some extra data, so that storage "sorting" is tested.
870		let extra_data = &[("random_key", "val"), ("r@nd0m_key", "val"), ("aaarandom_key", "val")];
871		storage
872			.top
873			.extend(extra_data.iter().map(|(k, v)| (k.as_bytes().to_vec(), v.as_bytes().to_vec())));
874		crate::ChainSpec::set_storage(&mut spec, storage);
875
876		let json = spec.as_json(true).unwrap();
877
878		// Check multiple times that decoding and encoding the chain spec leads always to the same
879		// output.
880		for _ in 0..10 {
881			assert_eq!(
882				json,
883				TestSpec2::from_json_bytes(json.as_bytes().to_vec())
884					.unwrap()
885					.as_json(true)
886					.unwrap()
887			);
888		}
889	}
890
891	#[test]
892	// some tests for json path utils
893	fn test_json_eval_value_at_key() {
894		let doc = json!({"a":{"b1":"20","b":{"c":{"d":"10"}}}});
895
896		assert!(json_eval_value_at_key(&doc, &mut json_path!["a", "b1"], &|v| { *v == "20" }));
897		assert!(json_eval_value_at_key(&doc, &mut json_path!["a", "b", "c", "d"], &|v| {
898			*v == "10"
899		}));
900		assert!(!json_eval_value_at_key(&doc, &mut json_path!["a", "c", "d"], &|_| { true }));
901		assert!(!json_eval_value_at_key(&doc, &mut json_path!["d"], &|_| { true }));
902
903		assert!(json_contains_path(&doc, &mut json_path!["a", "b1"]));
904		assert!(json_contains_path(&doc, &mut json_path!["a", "b"]));
905		assert!(json_contains_path(&doc, &mut json_path!["a", "b", "c"]));
906		assert!(json_contains_path(&doc, &mut json_path!["a", "b", "c", "d"]));
907		assert!(!json_contains_path(&doc, &mut json_path!["a", "b", "c", "d", "e"]));
908		assert!(!json_contains_path(&doc, &mut json_path!["a", "b", "b1"]));
909		assert!(!json_contains_path(&doc, &mut json_path!["d"]));
910	}
911
912	fn zeroize_code_key_in_json(encoded: bool, json: &str) -> Value {
913		let mut json = from_str::<Value>(json).unwrap();
914		let (zeroing_patch, mut path) = if encoded {
915			(
916				json!({"genesis":{"raw":{"top":{"0x3a636f6465":"0x0"}}}}),
917				json_path!["genesis", "raw", "top", "0x3a636f6465"],
918			)
919		} else {
920			(
921				json!({"genesis":{"runtimeGenesis":{"code":"0x0"}}}),
922				json_path!["genesis", "runtimeGenesis", "code"],
923			)
924		};
925		assert!(json_contains_path(&json, &mut path));
926		crate::json_patch::merge(&mut json, zeroing_patch);
927		json
928	}
929
930	#[docify::export]
931	#[test]
932	fn build_chain_spec_with_patch_works() {
933		let output = ChainSpec::<()>::builder(
934			substrate_test_runtime::wasm_binary_unwrap().into(),
935			Default::default(),
936		)
937		.with_name("TestName")
938		.with_id("test_id")
939		.with_chain_type(ChainType::Local)
940		.with_genesis_config_patch(json!({
941			"babe": {
942				"epochConfig": {
943					"c": [
944						7,
945						10
946					],
947					"allowed_slots": "PrimaryAndSecondaryPlainSlots"
948				}
949			},
950			"substrateTest": {
951				"authorities": [
952					Sr25519Keyring::Ferdie.public().to_ss58check(),
953					Sr25519Keyring::Alice.public().to_ss58check()
954				],
955			}
956		}))
957		.build();
958
959		let raw_chain_spec = output.as_json(true);
960		assert!(raw_chain_spec.is_ok());
961	}
962
963	#[test]
964	fn generate_chain_spec_with_named_preset_works() {
965		sp_tracing::try_init_simple();
966		let output: ChainSpec<()> = ChainSpec::builder(
967			substrate_test_runtime::wasm_binary_unwrap().into(),
968			Default::default(),
969		)
970		.with_name("TestName")
971		.with_id("test_id")
972		.with_chain_type(ChainType::Local)
973		.with_genesis_config_preset_name("staging")
974		.build();
975
976		let actual = output.as_json(false).unwrap();
977		let expected =
978			from_str::<Value>(include_str!("../res/substrate_test_runtime_from_named_preset.json"))
979				.unwrap();
980
981		// wasm blob may change overtime so let's zero it. Also ensure it is there:
982		let actual = zeroize_code_key_in_json(false, actual.as_str());
983
984		assert_eq!(actual, expected);
985	}
986
987	#[test]
988	fn generate_chain_spec_with_patch_works() {
989		let output = ChainSpec::<()>::builder(
990			substrate_test_runtime::wasm_binary_unwrap().into(),
991			Default::default(),
992		)
993		.with_name("TestName")
994		.with_id("test_id")
995		.with_chain_type(ChainType::Local)
996		.with_genesis_config_patch(json!({
997			"babe": {
998				"epochConfig": {
999					"c": [
1000						7,
1001						10
1002					],
1003					"allowed_slots": "PrimaryAndSecondaryPlainSlots"
1004				}
1005			},
1006			"substrateTest": {
1007				"authorities": [
1008					Sr25519Keyring::Ferdie.public().to_ss58check(),
1009					Sr25519Keyring::Alice.public().to_ss58check()
1010				],
1011			}
1012		}))
1013		.build();
1014
1015		let actual = output.as_json(false).unwrap();
1016		let actual_raw = output.as_json(true).unwrap();
1017
1018		let expected =
1019			from_str::<Value>(include_str!("../res/substrate_test_runtime_from_patch.json"))
1020				.unwrap();
1021		let expected_raw =
1022			from_str::<Value>(include_str!("../res/substrate_test_runtime_from_patch_raw.json"))
1023				.unwrap();
1024
1025		// wasm blob may change overtime so let's zero it. Also ensure it is there:
1026		let actual = zeroize_code_key_in_json(false, actual.as_str());
1027		let actual_raw = zeroize_code_key_in_json(true, actual_raw.as_str());
1028
1029		assert_eq!(actual, expected);
1030		assert_eq!(expected_raw, actual_raw);
1031	}
1032
1033	#[test]
1034	fn generate_chain_spec_with_full_config_works() {
1035		let j = include_str!("../../../test-utils/runtime/res/default_genesis_config.json");
1036		let output = ChainSpec::<()>::builder(
1037			substrate_test_runtime::wasm_binary_unwrap().into(),
1038			Default::default(),
1039		)
1040		.with_name("TestName")
1041		.with_id("test_id")
1042		.with_chain_type(ChainType::Local)
1043		.with_genesis_config(from_str(j).unwrap())
1044		.build();
1045
1046		let actual = output.as_json(false).unwrap();
1047		let actual_raw = output.as_json(true).unwrap();
1048
1049		let expected =
1050			from_str::<Value>(include_str!("../res/substrate_test_runtime_from_config.json"))
1051				.unwrap();
1052		let expected_raw =
1053			from_str::<Value>(include_str!("../res/substrate_test_runtime_from_config_raw.json"))
1054				.unwrap();
1055
1056		// wasm blob may change overtime so let's zero it. Also ensure it is there:
1057		let actual = zeroize_code_key_in_json(false, actual.as_str());
1058		let actual_raw = zeroize_code_key_in_json(true, actual_raw.as_str());
1059
1060		assert_eq!(actual, expected);
1061		assert_eq!(expected_raw, actual_raw);
1062	}
1063
1064	#[test]
1065	fn chain_spec_as_json_fails_with_invalid_config() {
1066		let invalid_genesis_config = from_str::<Value>(include_str!(
1067			"../../../test-utils/runtime/res/default_genesis_config_invalid_2.json"
1068		))
1069		.unwrap();
1070		let output = ChainSpec::<()>::builder(
1071			substrate_test_runtime::wasm_binary_unwrap().into(),
1072			Default::default(),
1073		)
1074		.with_name("TestName")
1075		.with_id("test_id")
1076		.with_chain_type(ChainType::Local)
1077		.with_genesis_config(invalid_genesis_config.clone())
1078		.build();
1079
1080		let result = output.as_json(true).unwrap_err();
1081		let mut result = result.lines();
1082
1083		let result_header = result.next().unwrap();
1084		let result_body = result.collect::<Vec<&str>>().join("\n");
1085		let result_body: Value = serde_json::from_str(&result_body).unwrap();
1086
1087		let re = regex::Regex::new(concat!(
1088			r"^Invalid JSON blob: unknown field `babex`, expected one of `system`, `babe`, ",
1089			r"`substrateTest`, `balances` at line \d+ column \d+ for blob:$"
1090		))
1091		.unwrap();
1092
1093		assert_eq!(json!({"a":1,"b":2}), json!({"b":2,"a":1}));
1094		assert!(re.is_match(result_header));
1095		assert_eq!(invalid_genesis_config, result_body);
1096	}
1097
1098	#[test]
1099	fn chain_spec_as_json_fails_with_invalid_patch() {
1100		let output = ChainSpec::<()>::builder(
1101			substrate_test_runtime::wasm_binary_unwrap().into(),
1102			Default::default(),
1103		)
1104		.with_name("TestName")
1105		.with_id("test_id")
1106		.with_chain_type(ChainType::Local)
1107		.with_genesis_config_patch(json!({
1108			"invalid_pallet": {},
1109			"substrateTest": {
1110				"authorities": [
1111					Sr25519Keyring::Ferdie.public().to_ss58check(),
1112					Sr25519Keyring::Alice.public().to_ss58check()
1113				],
1114			}
1115		}))
1116		.build();
1117
1118		assert!(output.as_json(true).unwrap_err().contains("Invalid JSON blob: unknown field `invalid_pallet`, expected one of `system`, `babe`, `substrateTest`, `balances`"));
1119	}
1120
1121	#[test]
1122	fn check_if_code_is_valid_for_raw_without_code() {
1123		let spec = ChainSpec::<()>::from_json_bytes(Cow::Owned(
1124			include_bytes!("../res/raw_no_code.json").to_vec(),
1125		))
1126		.unwrap();
1127
1128		let j = from_str::<Value>(&spec.as_json(true).unwrap()).unwrap();
1129
1130		assert!(json_eval_value_at_key(
1131			&j,
1132			&mut json_path!["genesis", "raw", "top", "0x3a636f6465"],
1133			&|v| { *v == "0x010101" }
1134		));
1135		assert!(!json_contains_path(&j, &mut json_path!["code"]));
1136	}
1137
1138	#[test]
1139	fn check_code_in_assimilated_storage_for_raw_without_code() {
1140		let spec = ChainSpec::<()>::from_json_bytes(Cow::Owned(
1141			include_bytes!("../res/raw_no_code.json").to_vec(),
1142		))
1143		.unwrap();
1144
1145		let storage = spec.build_storage().unwrap();
1146		assert!(storage
1147			.top
1148			.get(&well_known_keys::CODE.to_vec())
1149			.map(|v| *v == vec![1, 1, 1])
1150			.unwrap())
1151	}
1152
1153	#[test]
1154	fn update_code_works_with_runtime_genesis_config() {
1155		let j = include_str!("../../../test-utils/runtime/res/default_genesis_config.json");
1156		let chain_spec = ChainSpec::<()>::builder(
1157			substrate_test_runtime::wasm_binary_unwrap().into(),
1158			Default::default(),
1159		)
1160		.with_name("TestName")
1161		.with_id("test_id")
1162		.with_chain_type(ChainType::Local)
1163		.with_genesis_config(from_str(j).unwrap())
1164		.build();
1165
1166		let mut chain_spec_json = from_str::<Value>(&chain_spec.as_json(false).unwrap()).unwrap();
1167		assert!(update_code_in_json_chain_spec(&mut chain_spec_json, &[0, 1, 2, 4, 5, 6]));
1168
1169		assert!(json_eval_value_at_key(
1170			&chain_spec_json,
1171			&mut json_path!["genesis", "runtimeGenesis", "code"],
1172			&|v| { *v == "0x000102040506" }
1173		));
1174	}
1175
1176	#[test]
1177	fn update_code_works_for_raw() {
1178		let j = include_str!("../../../test-utils/runtime/res/default_genesis_config.json");
1179		let chain_spec = ChainSpec::<()>::builder(
1180			substrate_test_runtime::wasm_binary_unwrap().into(),
1181			Default::default(),
1182		)
1183		.with_name("TestName")
1184		.with_id("test_id")
1185		.with_chain_type(ChainType::Local)
1186		.with_genesis_config(from_str(j).unwrap())
1187		.build();
1188
1189		let mut chain_spec_json = from_str::<Value>(&chain_spec.as_json(true).unwrap()).unwrap();
1190		assert!(update_code_in_json_chain_spec(&mut chain_spec_json, &[0, 1, 2, 4, 5, 6]));
1191
1192		assert!(json_eval_value_at_key(
1193			&chain_spec_json,
1194			&mut json_path!["genesis", "raw", "top", "0x3a636f6465"],
1195			&|v| { *v == "0x000102040506" }
1196		));
1197	}
1198
1199	#[test]
1200	fn update_code_works_with_runtime_genesis_patch() {
1201		let chain_spec = ChainSpec::<()>::builder(
1202			substrate_test_runtime::wasm_binary_unwrap().into(),
1203			Default::default(),
1204		)
1205		.with_name("TestName")
1206		.with_id("test_id")
1207		.with_chain_type(ChainType::Local)
1208		.with_genesis_config_patch(json!({}))
1209		.build();
1210
1211		let mut chain_spec_json = from_str::<Value>(&chain_spec.as_json(false).unwrap()).unwrap();
1212		assert!(update_code_in_json_chain_spec(&mut chain_spec_json, &[0, 1, 2, 4, 5, 6]));
1213
1214		assert!(json_eval_value_at_key(
1215			&chain_spec_json,
1216			&mut json_path!["genesis", "runtimeGenesis", "code"],
1217			&|v| { *v == "0x000102040506" }
1218		));
1219	}
1220}