1#![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 Patch(json::Value),
47 Full(json::Value),
49 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
74enum GenesisSource<EHF> {
75 File(PathBuf),
76 Binary(Cow<'static, [u8]>),
77 Storage(Storage),
79 GenesisBuilderApi(GenesisBuildAction<EHF>, Vec<u8>),
81}
82
83impl<EHF> Clone for GenesisSource<EHF> {
84 fn clone(&self) -> Self {
85 match *self {
86 Self::File(ref path) => Self::File(path.clone()),
87 Self::Binary(ref d) => Self::Binary(d.clone()),
88 Self::Storage(ref s) => Self::Storage(s.clone()),
89 Self::GenesisBuilderApi(ref s, ref c) => Self::GenesisBuilderApi(s.clone(), c.clone()),
90 }
91 }
92}
93
94impl<EHF: HostFunctions> GenesisSource<EHF> {
95 fn resolve(&self) -> Result<Genesis, String> {
96 #[derive(Serialize, Deserialize)]
99 struct GenesisContainer {
100 genesis: Genesis,
101 }
102
103 match self {
104 Self::File(path) => {
105 let file = File::open(path).map_err(|e| {
106 format!("Error opening spec file at `{}`: {}", path.display(), e)
107 })?;
108 let bytes = unsafe {
112 memmap2::Mmap::map(&file).map_err(|e| {
113 format!("Error mmaping spec file `{}`: {}", path.display(), e)
114 })?
115 };
116
117 let genesis: GenesisContainer = json::from_slice(&bytes)
118 .map_err(|e| format!("Error parsing spec file: {}", e))?;
119 Ok(genesis.genesis)
120 },
121 Self::Binary(buf) => {
122 let genesis: GenesisContainer = json::from_reader(buf.as_ref())
123 .map_err(|e| format!("Error parsing embedded file: {}", e))?;
124 Ok(genesis.genesis)
125 },
126 Self::Storage(storage) => Ok(Genesis::Raw(RawGenesis::from(storage.clone()))),
127 Self::GenesisBuilderApi(GenesisBuildAction::Full(config), code) =>
128 Ok(Genesis::RuntimeGenesis(RuntimeGenesisInner {
129 json_blob: RuntimeGenesisConfigJson::Config(config.clone()),
130 code: code.clone(),
131 })),
132 Self::GenesisBuilderApi(GenesisBuildAction::Patch(patch), code) =>
133 Ok(Genesis::RuntimeGenesis(RuntimeGenesisInner {
134 json_blob: RuntimeGenesisConfigJson::Patch(patch.clone()),
135 code: code.clone(),
136 })),
137 Self::GenesisBuilderApi(GenesisBuildAction::NamedPreset(name, patch, _), code) => {
138 let mut preset =
139 RuntimeCaller::<EHF>::new(&code[..]).get_named_preset(Some(name))?;
140 json_merge(&mut preset, patch.clone());
141 Ok(Genesis::RuntimeGenesis(RuntimeGenesisInner {
142 json_blob: RuntimeGenesisConfigJson::Patch(preset),
143 code: code.clone(),
144 }))
145 },
146 }
147 }
148}
149
150impl<E, EHF> BuildStorage for ChainSpec<E, EHF>
151where
152 EHF: HostFunctions,
153{
154 fn assimilate_storage(&self, storage: &mut Storage) -> Result<(), String> {
155 match self.genesis.resolve()? {
156 Genesis::Raw(RawGenesis { top: map, children_default: children_map }) => {
157 storage.top.extend(map.into_iter().map(|(k, v)| (k.0, v.0)));
158 children_map.into_iter().for_each(|(k, v)| {
159 let child_info = ChildInfo::new_default(k.0.as_slice());
160 storage
161 .children_default
162 .entry(k.0)
163 .or_insert_with(|| StorageChild { data: Default::default(), child_info })
164 .data
165 .extend(v.into_iter().map(|(k, v)| (k.0, v.0)));
166 });
167 },
168 Genesis::StateRootHash(_) =>
172 return Err("Genesis storage in hash format not supported".into()),
173 Genesis::RuntimeGenesis(RuntimeGenesisInner {
174 json_blob: RuntimeGenesisConfigJson::Config(config),
175 code,
176 }) => {
177 RuntimeCaller::<EHF>::new(&code[..])
178 .get_storage_for_config(config)?
179 .assimilate_storage(storage)?;
180 storage
181 .top
182 .insert(sp_core::storage::well_known_keys::CODE.to_vec(), code.clone());
183 },
184 Genesis::RuntimeGenesis(RuntimeGenesisInner {
185 json_blob: RuntimeGenesisConfigJson::Patch(patch),
186 code,
187 }) => {
188 RuntimeCaller::<EHF>::new(&code[..])
189 .get_storage_for_patch(patch)?
190 .assimilate_storage(storage)?;
191 storage
192 .top
193 .insert(sp_core::storage::well_known_keys::CODE.to_vec(), code.clone());
194 },
195 };
196
197 Ok(())
198 }
199}
200
201pub type GenesisStorage = BTreeMap<StorageKey, StorageData>;
202
203#[derive(Serialize, Deserialize)]
205#[serde(rename_all = "camelCase")]
206#[serde(deny_unknown_fields)]
207pub struct RawGenesis {
208 pub top: GenesisStorage,
209 pub children_default: BTreeMap<StorageKey, GenesisStorage>,
210}
211
212impl From<sp_core::storage::Storage> for RawGenesis {
213 fn from(value: sp_core::storage::Storage) -> Self {
214 Self {
215 top: value.top.into_iter().map(|(k, v)| (StorageKey(k), StorageData(v))).collect(),
216 children_default: value
217 .children_default
218 .into_iter()
219 .map(|(sk, child)| {
220 (
221 StorageKey(sk),
222 child
223 .data
224 .into_iter()
225 .map(|(k, v)| (StorageKey(k), StorageData(v)))
226 .collect(),
227 )
228 })
229 .collect(),
230 }
231 }
232}
233
234#[derive(Serialize, Deserialize, Debug)]
236struct RuntimeGenesisInner {
237 #[serde(default, with = "sp_core::bytes")]
240 code: Vec<u8>,
241 #[serde(flatten)]
243 json_blob: RuntimeGenesisConfigJson,
244}
245
246#[derive(Serialize, Deserialize, Debug)]
249#[serde(rename_all = "camelCase")]
250enum RuntimeGenesisConfigJson {
251 Config(json::Value),
257 Patch(json::Value),
261}
262
263#[derive(Serialize, Deserialize)]
265#[serde(rename_all = "camelCase")]
266#[serde(deny_unknown_fields)]
267enum Genesis {
268 Raw(RawGenesis),
270 StateRootHash(StorageData),
272 RuntimeGenesis(RuntimeGenesisInner),
274}
275
276#[derive(Serialize, Deserialize, Clone, Debug)]
281#[serde(rename_all = "camelCase")]
282struct ClientSpec<E> {
285 name: String,
286 id: String,
287 #[serde(default)]
288 chain_type: ChainType,
289 boot_nodes: Vec<MultiaddrWithPeerId>,
290 telemetry_endpoints: Option<TelemetryEndpoints>,
291 protocol_id: Option<String>,
292 #[serde(default = "Default::default", skip_serializing_if = "Option::is_none")]
296 fork_id: Option<String>,
297 properties: Option<Properties>,
298 #[serde(flatten)]
299 extensions: E,
300 #[serde(default, skip_serializing)]
302 #[allow(unused)]
303 consensus_engine: (),
304 #[serde(skip_serializing)]
305 #[allow(unused)]
306 genesis: serde::de::IgnoredAny,
307 #[serde(default)]
312 code_substitutes: BTreeMap<String, Bytes>,
313}
314
315pub type NoExtension = Option<()>;
319
320pub struct ChainSpecBuilder<E = NoExtension, EHF = ()> {
322 code: Vec<u8>,
323 extensions: E,
324 name: String,
325 id: String,
326 chain_type: ChainType,
327 genesis_build_action: GenesisBuildAction<EHF>,
328 boot_nodes: Option<Vec<MultiaddrWithPeerId>>,
329 telemetry_endpoints: Option<TelemetryEndpoints>,
330 protocol_id: Option<String>,
331 fork_id: Option<String>,
332 properties: Option<Properties>,
333}
334
335impl<E, EHF> ChainSpecBuilder<E, EHF> {
336 pub fn new(code: &[u8], extensions: E) -> Self {
338 Self {
339 code: code.into(),
340 extensions,
341 name: "Development".to_string(),
342 id: "dev".to_string(),
343 chain_type: ChainType::Local,
344 genesis_build_action: GenesisBuildAction::Patch(json::json!({})),
345 boot_nodes: None,
346 telemetry_endpoints: None,
347 protocol_id: None,
348 fork_id: None,
349 properties: None,
350 }
351 }
352
353 pub fn with_name(mut self, name: &str) -> Self {
355 self.name = name.into();
356 self
357 }
358
359 pub fn with_id(mut self, id: &str) -> Self {
361 self.id = id.into();
362 self
363 }
364
365 pub fn with_chain_type(mut self, chain_type: ChainType) -> Self {
367 self.chain_type = chain_type;
368 self
369 }
370
371 pub fn with_boot_nodes(mut self, boot_nodes: Vec<MultiaddrWithPeerId>) -> Self {
373 self.boot_nodes = Some(boot_nodes);
374 self
375 }
376
377 pub fn with_telemetry_endpoints(mut self, telemetry_endpoints: TelemetryEndpoints) -> Self {
379 self.telemetry_endpoints = Some(telemetry_endpoints);
380 self
381 }
382
383 pub fn with_protocol_id(mut self, protocol_id: &str) -> Self {
385 self.protocol_id = Some(protocol_id.into());
386 self
387 }
388
389 pub fn with_fork_id(mut self, fork_id: &str) -> Self {
391 self.fork_id = Some(fork_id.into());
392 self
393 }
394
395 pub fn with_properties(mut self, properties: Properties) -> Self {
397 self.properties = Some(properties);
398 self
399 }
400
401 pub fn with_extensions(mut self, extensions: E) -> Self {
403 self.extensions = extensions;
404 self
405 }
406
407 pub fn with_code(mut self, code: &[u8]) -> Self {
409 self.code = code.into();
410 self
411 }
412
413 pub fn with_genesis_config_patch(mut self, patch: json::Value) -> Self {
415 self.genesis_build_action.merge_patch(patch);
416 self
417 }
418
419 pub fn with_genesis_config_preset_name(mut self, name: &str) -> Self {
421 self.genesis_build_action =
422 GenesisBuildAction::NamedPreset(name.to_string(), json::json!({}), Default::default());
423 self
424 }
425
426 pub fn with_genesis_config(mut self, config: json::Value) -> Self {
428 self.genesis_build_action = GenesisBuildAction::Full(config);
429 self
430 }
431
432 pub fn build(self) -> ChainSpec<E, EHF> {
434 let client_spec = ClientSpec {
435 name: self.name,
436 id: self.id,
437 chain_type: self.chain_type,
438 boot_nodes: self.boot_nodes.unwrap_or_default(),
439 telemetry_endpoints: self.telemetry_endpoints,
440 protocol_id: self.protocol_id,
441 fork_id: self.fork_id,
442 properties: self.properties,
443 extensions: self.extensions,
444 consensus_engine: (),
445 genesis: Default::default(),
446 code_substitutes: BTreeMap::new(),
447 };
448
449 ChainSpec {
450 client_spec,
451 genesis: GenesisSource::GenesisBuilderApi(self.genesis_build_action, self.code.into()),
452 _host_functions: Default::default(),
453 }
454 }
455}
456
457pub struct ChainSpec<E = NoExtension, EHF = ()> {
463 client_spec: ClientSpec<E>,
464 genesis: GenesisSource<EHF>,
465 _host_functions: PhantomData<EHF>,
466}
467
468impl<E: Clone, EHF> Clone for ChainSpec<E, EHF> {
469 fn clone(&self) -> Self {
470 ChainSpec {
471 client_spec: self.client_spec.clone(),
472 genesis: self.genesis.clone(),
473 _host_functions: self._host_functions,
474 }
475 }
476}
477
478impl<E, EHF> ChainSpec<E, EHF> {
479 pub fn boot_nodes(&self) -> &[MultiaddrWithPeerId] {
481 &self.client_spec.boot_nodes
482 }
483
484 pub fn name(&self) -> &str {
486 &self.client_spec.name
487 }
488
489 pub fn id(&self) -> &str {
491 &self.client_spec.id
492 }
493
494 pub fn telemetry_endpoints(&self) -> &Option<TelemetryEndpoints> {
496 &self.client_spec.telemetry_endpoints
497 }
498
499 pub fn protocol_id(&self) -> Option<&str> {
501 self.client_spec.protocol_id.as_deref()
502 }
503
504 pub fn fork_id(&self) -> Option<&str> {
506 self.client_spec.fork_id.as_deref()
507 }
508
509 pub fn properties(&self) -> Properties {
513 self.client_spec.properties.as_ref().unwrap_or(&json::map::Map::new()).clone()
514 }
515
516 pub fn add_boot_node(&mut self, addr: MultiaddrWithPeerId) {
518 self.client_spec.boot_nodes.push(addr)
519 }
520
521 pub fn extensions(&self) -> &E {
523 &self.client_spec.extensions
524 }
525
526 pub fn extensions_mut(&mut self) -> &mut E {
528 &mut self.client_spec.extensions
529 }
530
531 fn chain_type(&self) -> ChainType {
533 self.client_spec.chain_type.clone()
534 }
535
536 pub fn builder(code: &[u8], extensions: E) -> ChainSpecBuilder<E, EHF> {
538 ChainSpecBuilder::new(code, extensions)
539 }
540}
541
542impl<E: serde::de::DeserializeOwned, EHF> ChainSpec<E, EHF> {
543 pub fn from_json_bytes(json: impl Into<Cow<'static, [u8]>>) -> Result<Self, String> {
545 let json = json.into();
546 let client_spec = json::from_slice(json.as_ref())
547 .map_err(|e| format!("Error parsing spec file: {}", e))?;
548
549 Ok(ChainSpec {
550 client_spec,
551 genesis: GenesisSource::Binary(json),
552 _host_functions: Default::default(),
553 })
554 }
555
556 pub fn from_json_file(path: PathBuf) -> Result<Self, String> {
558 let file = File::open(&path)
561 .map_err(|e| format!("Error opening spec file `{}`: {}", path.display(), e))?;
562
563 let bytes = unsafe {
566 memmap2::Mmap::map(&file)
567 .map_err(|e| format!("Error mmaping spec file `{}`: {}", path.display(), e))?
568 };
569 let client_spec =
570 json::from_slice(&bytes).map_err(|e| format!("Error parsing spec file: {}", e))?;
571
572 Ok(ChainSpec {
573 client_spec,
574 genesis: GenesisSource::File(path),
575 _host_functions: Default::default(),
576 })
577 }
578}
579
580#[derive(Serialize, Deserialize)]
583struct ChainSpecJsonContainer<E> {
586 #[serde(flatten)]
587 client_spec: ClientSpec<E>,
588 genesis: Genesis,
589}
590
591impl<E: serde::Serialize + Clone + 'static, EHF> ChainSpec<E, EHF>
592where
593 EHF: HostFunctions,
594{
595 fn json_container(&self, raw: bool) -> Result<ChainSpecJsonContainer<E>, String> {
596 let raw_genesis = match (raw, self.genesis.resolve()?) {
597 (
598 true,
599 Genesis::RuntimeGenesis(RuntimeGenesisInner {
600 json_blob: RuntimeGenesisConfigJson::Config(config),
601 code,
602 }),
603 ) => {
604 let mut storage =
605 RuntimeCaller::<EHF>::new(&code[..]).get_storage_for_config(config)?;
606 storage.top.insert(sp_core::storage::well_known_keys::CODE.to_vec(), code);
607 RawGenesis::from(storage)
608 },
609 (
610 true,
611 Genesis::RuntimeGenesis(RuntimeGenesisInner {
612 json_blob: RuntimeGenesisConfigJson::Patch(patch),
613 code,
614 }),
615 ) => {
616 let mut storage =
617 RuntimeCaller::<EHF>::new(&code[..]).get_storage_for_patch(patch)?;
618 storage.top.insert(sp_core::storage::well_known_keys::CODE.to_vec(), code);
619 RawGenesis::from(storage)
620 },
621 (true, Genesis::Raw(raw)) => raw,
622 (_, genesis) =>
623 return Ok(ChainSpecJsonContainer { client_spec: self.client_spec.clone(), genesis }),
624 };
625
626 Ok(ChainSpecJsonContainer {
627 client_spec: self.client_spec.clone(),
628 genesis: Genesis::Raw(raw_genesis),
629 })
630 }
631
632 pub fn as_json(&self, raw: bool) -> Result<String, String> {
634 let container = self.json_container(raw)?;
635 json::to_string_pretty(&container).map_err(|e| format!("Error generating spec json: {}", e))
636 }
637}
638
639impl<E, EHF> crate::ChainSpec for ChainSpec<E, EHF>
640where
641 E: GetExtension + serde::Serialize + Clone + Send + Sync + 'static,
642 EHF: HostFunctions,
643{
644 fn boot_nodes(&self) -> &[MultiaddrWithPeerId] {
645 ChainSpec::boot_nodes(self)
646 }
647
648 fn name(&self) -> &str {
649 ChainSpec::name(self)
650 }
651
652 fn id(&self) -> &str {
653 ChainSpec::id(self)
654 }
655
656 fn chain_type(&self) -> ChainType {
657 ChainSpec::chain_type(self)
658 }
659
660 fn telemetry_endpoints(&self) -> &Option<TelemetryEndpoints> {
661 ChainSpec::telemetry_endpoints(self)
662 }
663
664 fn protocol_id(&self) -> Option<&str> {
665 ChainSpec::protocol_id(self)
666 }
667
668 fn fork_id(&self) -> Option<&str> {
669 ChainSpec::fork_id(self)
670 }
671
672 fn properties(&self) -> Properties {
673 ChainSpec::properties(self)
674 }
675
676 fn add_boot_node(&mut self, addr: MultiaddrWithPeerId) {
677 ChainSpec::add_boot_node(self, addr)
678 }
679
680 fn extensions(&self) -> &dyn GetExtension {
681 ChainSpec::extensions(self) as &dyn GetExtension
682 }
683
684 fn extensions_mut(&mut self) -> &mut dyn GetExtension {
685 ChainSpec::extensions_mut(self) as &mut dyn GetExtension
686 }
687
688 fn as_json(&self, raw: bool) -> Result<String, String> {
689 ChainSpec::as_json(self, raw)
690 }
691
692 fn as_storage_builder(&self) -> &dyn BuildStorage {
693 self
694 }
695
696 fn cloned_box(&self) -> Box<dyn crate::ChainSpec> {
697 Box::new(self.clone())
698 }
699
700 fn set_storage(&mut self, storage: Storage) {
701 self.genesis = GenesisSource::Storage(storage);
702 }
703
704 fn code_substitutes(&self) -> std::collections::BTreeMap<String, Vec<u8>> {
705 self.client_spec
706 .code_substitutes
707 .iter()
708 .map(|(h, c)| (h.clone(), c.0.clone()))
709 .collect()
710 }
711}
712
713fn json_eval_value_at_key(
727 doc: &json::Value,
728 path: &mut VecDeque<&str>,
729 fun: &dyn Fn(&json::Value) -> bool,
730) -> bool {
731 let Some(key) = path.pop_front() else { return false };
732
733 if path.is_empty() {
734 doc.as_object().map_or(false, |o| o.get(key).map_or(false, |v| fun(v)))
735 } else {
736 doc.as_object()
737 .map_or(false, |o| o.get(key).map_or(false, |v| json_eval_value_at_key(v, path, fun)))
738 }
739}
740
741macro_rules! json_path {
742 [ $($x:expr),+ ] => {
743 VecDeque::<&str>::from([$($x),+])
744 };
745}
746
747fn json_contains_path(doc: &json::Value, path: &mut VecDeque<&str>) -> bool {
748 json_eval_value_at_key(doc, path, &|_| true)
749}
750
751pub fn update_code_in_json_chain_spec(chain_spec: &mut json::Value, code: &[u8]) -> bool {
759 let mut path = json_path!["genesis", "runtimeGenesis", "code"];
760 let mut raw_path = json_path!["genesis", "raw", "top"];
761
762 if json_contains_path(&chain_spec, &mut path) {
763 #[derive(Serialize)]
764 struct Container<'a> {
765 #[serde(with = "sp_core::bytes")]
766 code: &'a [u8],
767 }
768 let code_patch = json::json!({"genesis":{"runtimeGenesis": Container { code }}});
769 crate::json_patch::merge(chain_spec, code_patch);
770 true
771 } else if json_contains_path(&chain_spec, &mut raw_path) {
772 #[derive(Serialize)]
773 struct Container<'a> {
774 #[serde(with = "sp_core::bytes", rename = "0x3a636f6465")]
775 code: &'a [u8],
776 }
777 let code_patch = json::json!({"genesis":{"raw":{"top": Container { code }}}});
778 crate::json_patch::merge(chain_spec, code_patch);
779 true
780 } else {
781 false
782 }
783}
784
785pub fn set_code_substitute_in_json_chain_spec(
787 chain_spec: &mut json::Value,
788 code: &[u8],
789 block_height: u64,
790) {
791 let substitutes = json::json!({"codeSubstitutes":{ &block_height.to_string(): sp_core::bytes::to_hex(code, false) }});
792 crate::json_patch::merge(chain_spec, substitutes);
793}
794
795#[cfg(test)]
796mod tests {
797 use super::*;
798 use pretty_assertions::assert_eq;
799 use serde_json::{from_str, json, Value};
800 use sp_application_crypto::Ss58Codec;
801 use sp_core::storage::well_known_keys;
802 use sp_keyring::Sr25519Keyring;
803
804 type TestSpec = ChainSpec;
805
806 #[test]
807 fn should_deserialize_example_chain_spec() {
808 let spec1 = TestSpec::from_json_bytes(Cow::Owned(
809 include_bytes!("../res/chain_spec.json").to_vec(),
810 ))
811 .unwrap();
812 let spec2 = TestSpec::from_json_file(PathBuf::from("./res/chain_spec.json")).unwrap();
813
814 assert_eq!(spec1.as_json(false), spec2.as_json(false));
815 assert_eq!(spec2.chain_type(), ChainType::Live)
816 }
817
818 #[derive(Debug, Serialize, Deserialize, Clone)]
819 #[serde(rename_all = "camelCase")]
820 struct Extension1 {
821 my_property: String,
822 }
823
824 impl crate::Extension for Extension1 {
825 type Forks = Option<()>;
826
827 fn get<T: 'static>(&self) -> Option<&T> {
828 None
829 }
830
831 fn get_any(&self, _: std::any::TypeId) -> &dyn std::any::Any {
832 self
833 }
834
835 fn get_any_mut(&mut self, _: std::any::TypeId) -> &mut dyn std::any::Any {
836 self
837 }
838 }
839
840 type TestSpec2 = ChainSpec<Extension1>;
841
842 #[test]
843 fn should_deserialize_chain_spec_with_extensions() {
844 let spec = TestSpec2::from_json_bytes(Cow::Owned(
845 include_bytes!("../res/chain_spec2.json").to_vec(),
846 ))
847 .unwrap();
848
849 assert_eq!(spec.extensions().my_property, "Test Extension");
850 }
851
852 #[test]
853 fn chain_spec_raw_output_should_be_deterministic() {
854 let mut spec = TestSpec2::from_json_bytes(Cow::Owned(
855 include_bytes!("../res/chain_spec2.json").to_vec(),
856 ))
857 .unwrap();
858
859 let mut storage = spec.build_storage().unwrap();
860
861 let extra_data = &[("random_key", "val"), ("r@nd0m_key", "val"), ("aaarandom_key", "val")];
863 storage
864 .top
865 .extend(extra_data.iter().map(|(k, v)| (k.as_bytes().to_vec(), v.as_bytes().to_vec())));
866 crate::ChainSpec::set_storage(&mut spec, storage);
867
868 let json = spec.as_json(true).unwrap();
869
870 for _ in 0..10 {
873 assert_eq!(
874 json,
875 TestSpec2::from_json_bytes(json.as_bytes().to_vec())
876 .unwrap()
877 .as_json(true)
878 .unwrap()
879 );
880 }
881 }
882
883 #[test]
884 fn test_json_eval_value_at_key() {
886 let doc = json!({"a":{"b1":"20","b":{"c":{"d":"10"}}}});
887
888 assert!(json_eval_value_at_key(&doc, &mut json_path!["a", "b1"], &|v| { *v == "20" }));
889 assert!(json_eval_value_at_key(&doc, &mut json_path!["a", "b", "c", "d"], &|v| {
890 *v == "10"
891 }));
892 assert!(!json_eval_value_at_key(&doc, &mut json_path!["a", "c", "d"], &|_| { true }));
893 assert!(!json_eval_value_at_key(&doc, &mut json_path!["d"], &|_| { true }));
894
895 assert!(json_contains_path(&doc, &mut json_path!["a", "b1"]));
896 assert!(json_contains_path(&doc, &mut json_path!["a", "b"]));
897 assert!(json_contains_path(&doc, &mut json_path!["a", "b", "c"]));
898 assert!(json_contains_path(&doc, &mut json_path!["a", "b", "c", "d"]));
899 assert!(!json_contains_path(&doc, &mut json_path!["a", "b", "c", "d", "e"]));
900 assert!(!json_contains_path(&doc, &mut json_path!["a", "b", "b1"]));
901 assert!(!json_contains_path(&doc, &mut json_path!["d"]));
902 }
903
904 fn zeroize_code_key_in_json(encoded: bool, json: &str) -> Value {
905 let mut json = from_str::<Value>(json).unwrap();
906 let (zeroing_patch, mut path) = if encoded {
907 (
908 json!({"genesis":{"raw":{"top":{"0x3a636f6465":"0x0"}}}}),
909 json_path!["genesis", "raw", "top", "0x3a636f6465"],
910 )
911 } else {
912 (
913 json!({"genesis":{"runtimeGenesis":{"code":"0x0"}}}),
914 json_path!["genesis", "runtimeGenesis", "code"],
915 )
916 };
917 assert!(json_contains_path(&json, &mut path));
918 crate::json_patch::merge(&mut json, zeroing_patch);
919 json
920 }
921
922 #[docify::export]
923 #[test]
924 fn build_chain_spec_with_patch_works() {
925 let output = ChainSpec::<()>::builder(
926 substrate_test_runtime::wasm_binary_unwrap().into(),
927 Default::default(),
928 )
929 .with_name("TestName")
930 .with_id("test_id")
931 .with_chain_type(ChainType::Local)
932 .with_genesis_config_patch(json!({
933 "babe": {
934 "epochConfig": {
935 "c": [
936 7,
937 10
938 ],
939 "allowed_slots": "PrimaryAndSecondaryPlainSlots"
940 }
941 },
942 "substrateTest": {
943 "authorities": [
944 Sr25519Keyring::Ferdie.public().to_ss58check(),
945 Sr25519Keyring::Alice.public().to_ss58check()
946 ],
947 }
948 }))
949 .build();
950
951 let raw_chain_spec = output.as_json(true);
952 assert!(raw_chain_spec.is_ok());
953 }
954
955 #[test]
956 fn generate_chain_spec_with_named_preset_works() {
957 sp_tracing::try_init_simple();
958 let output: ChainSpec<()> = ChainSpec::builder(
959 substrate_test_runtime::wasm_binary_unwrap().into(),
960 Default::default(),
961 )
962 .with_name("TestName")
963 .with_id("test_id")
964 .with_chain_type(ChainType::Local)
965 .with_genesis_config_preset_name("staging")
966 .build();
967
968 let actual = output.as_json(false).unwrap();
969 let expected =
970 from_str::<Value>(include_str!("../res/substrate_test_runtime_from_named_preset.json"))
971 .unwrap();
972
973 let actual = zeroize_code_key_in_json(false, actual.as_str());
975
976 assert_eq!(actual, expected);
977 }
978
979 #[test]
980 fn generate_chain_spec_with_patch_works() {
981 let output = ChainSpec::<()>::builder(
982 substrate_test_runtime::wasm_binary_unwrap().into(),
983 Default::default(),
984 )
985 .with_name("TestName")
986 .with_id("test_id")
987 .with_chain_type(ChainType::Local)
988 .with_genesis_config_patch(json!({
989 "babe": {
990 "epochConfig": {
991 "c": [
992 7,
993 10
994 ],
995 "allowed_slots": "PrimaryAndSecondaryPlainSlots"
996 }
997 },
998 "substrateTest": {
999 "authorities": [
1000 Sr25519Keyring::Ferdie.public().to_ss58check(),
1001 Sr25519Keyring::Alice.public().to_ss58check()
1002 ],
1003 }
1004 }))
1005 .build();
1006
1007 let actual = output.as_json(false).unwrap();
1008 let actual_raw = output.as_json(true).unwrap();
1009
1010 let expected =
1011 from_str::<Value>(include_str!("../res/substrate_test_runtime_from_patch.json"))
1012 .unwrap();
1013 let expected_raw =
1014 from_str::<Value>(include_str!("../res/substrate_test_runtime_from_patch_raw.json"))
1015 .unwrap();
1016
1017 let actual = zeroize_code_key_in_json(false, actual.as_str());
1019 let actual_raw = zeroize_code_key_in_json(true, actual_raw.as_str());
1020
1021 assert_eq!(actual, expected);
1022 assert_eq!(expected_raw, actual_raw);
1023 }
1024
1025 #[test]
1026 fn generate_chain_spec_with_full_config_works() {
1027 let j = include_str!("../../../test-utils/runtime/res/default_genesis_config.json");
1028 let output = ChainSpec::<()>::builder(
1029 substrate_test_runtime::wasm_binary_unwrap().into(),
1030 Default::default(),
1031 )
1032 .with_name("TestName")
1033 .with_id("test_id")
1034 .with_chain_type(ChainType::Local)
1035 .with_genesis_config(from_str(j).unwrap())
1036 .build();
1037
1038 let actual = output.as_json(false).unwrap();
1039 let actual_raw = output.as_json(true).unwrap();
1040
1041 let expected =
1042 from_str::<Value>(include_str!("../res/substrate_test_runtime_from_config.json"))
1043 .unwrap();
1044 let expected_raw =
1045 from_str::<Value>(include_str!("../res/substrate_test_runtime_from_config_raw.json"))
1046 .unwrap();
1047
1048 let actual = zeroize_code_key_in_json(false, actual.as_str());
1050 let actual_raw = zeroize_code_key_in_json(true, actual_raw.as_str());
1051
1052 assert_eq!(actual, expected);
1053 assert_eq!(expected_raw, actual_raw);
1054 }
1055
1056 #[test]
1057 fn chain_spec_as_json_fails_with_invalid_config() {
1058 let invalid_genesis_config = from_str::<Value>(include_str!(
1059 "../../../test-utils/runtime/res/default_genesis_config_invalid_2.json"
1060 ))
1061 .unwrap();
1062 let output = ChainSpec::<()>::builder(
1063 substrate_test_runtime::wasm_binary_unwrap().into(),
1064 Default::default(),
1065 )
1066 .with_name("TestName")
1067 .with_id("test_id")
1068 .with_chain_type(ChainType::Local)
1069 .with_genesis_config(invalid_genesis_config.clone())
1070 .build();
1071
1072 let result = output.as_json(true).unwrap_err();
1073 let mut result = result.lines();
1074
1075 let result_header = result.next().unwrap();
1076 let result_body = result.collect::<Vec<&str>>().join("\n");
1077 let result_body: Value = serde_json::from_str(&result_body).unwrap();
1078
1079 let re = regex::Regex::new(concat!(
1080 r"^Invalid JSON blob: unknown field `babex`, expected one of `system`, `babe`, ",
1081 r"`substrateTest`, `balances` at line \d+ column \d+ for blob:$"
1082 ))
1083 .unwrap();
1084
1085 assert_eq!(json!({"a":1,"b":2}), json!({"b":2,"a":1}));
1086 assert!(re.is_match(result_header));
1087 assert_eq!(invalid_genesis_config, result_body);
1088 }
1089
1090 #[test]
1091 fn chain_spec_as_json_fails_with_invalid_patch() {
1092 let output = ChainSpec::<()>::builder(
1093 substrate_test_runtime::wasm_binary_unwrap().into(),
1094 Default::default(),
1095 )
1096 .with_name("TestName")
1097 .with_id("test_id")
1098 .with_chain_type(ChainType::Local)
1099 .with_genesis_config_patch(json!({
1100 "invalid_pallet": {},
1101 "substrateTest": {
1102 "authorities": [
1103 Sr25519Keyring::Ferdie.public().to_ss58check(),
1104 Sr25519Keyring::Alice.public().to_ss58check()
1105 ],
1106 }
1107 }))
1108 .build();
1109
1110 assert!(output.as_json(true).unwrap_err().contains("Invalid JSON blob: unknown field `invalid_pallet`, expected one of `system`, `babe`, `substrateTest`, `balances`"));
1111 }
1112
1113 #[test]
1114 fn check_if_code_is_valid_for_raw_without_code() {
1115 let spec = ChainSpec::<()>::from_json_bytes(Cow::Owned(
1116 include_bytes!("../res/raw_no_code.json").to_vec(),
1117 ))
1118 .unwrap();
1119
1120 let j = from_str::<Value>(&spec.as_json(true).unwrap()).unwrap();
1121
1122 assert!(json_eval_value_at_key(
1123 &j,
1124 &mut json_path!["genesis", "raw", "top", "0x3a636f6465"],
1125 &|v| { *v == "0x010101" }
1126 ));
1127 assert!(!json_contains_path(&j, &mut json_path!["code"]));
1128 }
1129
1130 #[test]
1131 fn check_code_in_assimilated_storage_for_raw_without_code() {
1132 let spec = ChainSpec::<()>::from_json_bytes(Cow::Owned(
1133 include_bytes!("../res/raw_no_code.json").to_vec(),
1134 ))
1135 .unwrap();
1136
1137 let storage = spec.build_storage().unwrap();
1138 assert!(storage
1139 .top
1140 .get(&well_known_keys::CODE.to_vec())
1141 .map(|v| *v == vec![1, 1, 1])
1142 .unwrap())
1143 }
1144
1145 #[test]
1146 fn update_code_works_with_runtime_genesis_config() {
1147 let j = include_str!("../../../test-utils/runtime/res/default_genesis_config.json");
1148 let chain_spec = ChainSpec::<()>::builder(
1149 substrate_test_runtime::wasm_binary_unwrap().into(),
1150 Default::default(),
1151 )
1152 .with_name("TestName")
1153 .with_id("test_id")
1154 .with_chain_type(ChainType::Local)
1155 .with_genesis_config(from_str(j).unwrap())
1156 .build();
1157
1158 let mut chain_spec_json = from_str::<Value>(&chain_spec.as_json(false).unwrap()).unwrap();
1159 assert!(update_code_in_json_chain_spec(&mut chain_spec_json, &[0, 1, 2, 4, 5, 6]));
1160
1161 assert!(json_eval_value_at_key(
1162 &chain_spec_json,
1163 &mut json_path!["genesis", "runtimeGenesis", "code"],
1164 &|v| { *v == "0x000102040506" }
1165 ));
1166 }
1167
1168 #[test]
1169 fn update_code_works_for_raw() {
1170 let j = include_str!("../../../test-utils/runtime/res/default_genesis_config.json");
1171 let chain_spec = ChainSpec::<()>::builder(
1172 substrate_test_runtime::wasm_binary_unwrap().into(),
1173 Default::default(),
1174 )
1175 .with_name("TestName")
1176 .with_id("test_id")
1177 .with_chain_type(ChainType::Local)
1178 .with_genesis_config(from_str(j).unwrap())
1179 .build();
1180
1181 let mut chain_spec_json = from_str::<Value>(&chain_spec.as_json(true).unwrap()).unwrap();
1182 assert!(update_code_in_json_chain_spec(&mut chain_spec_json, &[0, 1, 2, 4, 5, 6]));
1183
1184 assert!(json_eval_value_at_key(
1185 &chain_spec_json,
1186 &mut json_path!["genesis", "raw", "top", "0x3a636f6465"],
1187 &|v| { *v == "0x000102040506" }
1188 ));
1189 }
1190
1191 #[test]
1192 fn update_code_works_with_runtime_genesis_patch() {
1193 let chain_spec = ChainSpec::<()>::builder(
1194 substrate_test_runtime::wasm_binary_unwrap().into(),
1195 Default::default(),
1196 )
1197 .with_name("TestName")
1198 .with_id("test_id")
1199 .with_chain_type(ChainType::Local)
1200 .with_genesis_config_patch(json!({}))
1201 .build();
1202
1203 let mut chain_spec_json = from_str::<Value>(&chain_spec.as_json(false).unwrap()).unwrap();
1204 assert!(update_code_in_json_chain_spec(&mut chain_spec_json, &[0, 1, 2, 4, 5, 6]));
1205
1206 assert!(json_eval_value_at_key(
1207 &chain_spec_json,
1208 &mut json_path!["genesis", "runtimeGenesis", "code"],
1209 &|v| { *v == "0x000102040506" }
1210 ));
1211 }
1212}