Skip to main content

soil_chain_spec/
chain_spec.rs

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