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}
74
75enum GenesisSource<EHF> {
76 File(PathBuf),
77 Binary(Cow<'static, [u8]>),
78 Storage(Storage),
80 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 #[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 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 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#[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#[derive(Serialize, Deserialize, Debug)]
240struct RuntimeGenesisInner {
241 #[serde(default, with = "sp_core::bytes")]
244 code: Vec<u8>,
245 #[serde(flatten)]
247 json_blob: RuntimeGenesisConfigJson,
248}
249
250#[derive(Serialize, Deserialize, Debug)]
253#[serde(rename_all = "camelCase")]
254enum RuntimeGenesisConfigJson {
255 Config(json::Value),
261 Patch(json::Value),
265}
266
267#[derive(Serialize, Deserialize)]
269#[serde(rename_all = "camelCase")]
270#[serde(deny_unknown_fields)]
271enum Genesis {
272 Raw(RawGenesis),
274 StateRootHash(StorageData),
276 RuntimeGenesis(RuntimeGenesisInner),
278}
279
280#[derive(Serialize, Deserialize, Clone, Debug)]
285#[serde(rename_all = "camelCase")]
286struct 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 #[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 #[serde(default, skip_serializing)]
306 #[allow(unused)]
307 consensus_engine: (),
308 #[serde(skip_serializing)]
309 #[allow(unused)]
310 genesis: serde::de::IgnoredAny,
311 #[serde(default)]
316 code_substitutes: BTreeMap<String, Bytes>,
317}
318
319pub type NoExtension = Option<()>;
323
324pub 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 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 pub fn with_name(mut self, name: &str) -> Self {
359 self.name = name.into();
360 self
361 }
362
363 pub fn with_id(mut self, id: &str) -> Self {
365 self.id = id.into();
366 self
367 }
368
369 pub fn with_chain_type(mut self, chain_type: ChainType) -> Self {
371 self.chain_type = chain_type;
372 self
373 }
374
375 pub fn with_boot_nodes(mut self, boot_nodes: Vec<MultiaddrWithPeerId>) -> Self {
377 self.boot_nodes = Some(boot_nodes);
378 self
379 }
380
381 pub fn with_telemetry_endpoints(mut self, telemetry_endpoints: TelemetryEndpoints) -> Self {
383 self.telemetry_endpoints = Some(telemetry_endpoints);
384 self
385 }
386
387 pub fn with_protocol_id(mut self, protocol_id: &str) -> Self {
389 self.protocol_id = Some(protocol_id.into());
390 self
391 }
392
393 pub fn with_fork_id(mut self, fork_id: &str) -> Self {
395 self.fork_id = Some(fork_id.into());
396 self
397 }
398
399 pub fn with_properties(mut self, properties: Properties) -> Self {
401 self.properties = Some(properties);
402 self
403 }
404
405 pub fn with_extensions(mut self, extensions: E) -> Self {
407 self.extensions = extensions;
408 self
409 }
410
411 pub fn with_code(mut self, code: &[u8]) -> Self {
413 self.code = code.into();
414 self
415 }
416
417 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 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 pub fn with_genesis_config(mut self, config: json::Value) -> Self {
432 self.genesis_build_action = GenesisBuildAction::Full(config);
433 self
434 }
435
436 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
461pub 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 pub fn boot_nodes(&self) -> &[MultiaddrWithPeerId] {
485 &self.client_spec.boot_nodes
486 }
487
488 pub fn name(&self) -> &str {
490 &self.client_spec.name
491 }
492
493 pub fn id(&self) -> &str {
495 &self.client_spec.id
496 }
497
498 pub fn telemetry_endpoints(&self) -> &Option<TelemetryEndpoints> {
500 &self.client_spec.telemetry_endpoints
501 }
502
503 pub fn protocol_id(&self) -> Option<&str> {
505 self.client_spec.protocol_id.as_deref()
506 }
507
508 pub fn fork_id(&self) -> Option<&str> {
510 self.client_spec.fork_id.as_deref()
511 }
512
513 pub fn properties(&self) -> Properties {
517 self.client_spec.properties.as_ref().unwrap_or(&json::map::Map::new()).clone()
518 }
519
520 pub fn add_boot_node(&mut self, addr: MultiaddrWithPeerId) {
522 self.client_spec.boot_nodes.push(addr)
523 }
524
525 pub fn extensions(&self) -> &E {
527 &self.client_spec.extensions
528 }
529
530 pub fn extensions_mut(&mut self) -> &mut E {
532 &mut self.client_spec.extensions
533 }
534
535 fn chain_type(&self) -> ChainType {
537 self.client_spec.chain_type.clone()
538 }
539
540 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 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 pub fn from_json_file(path: PathBuf) -> Result<Self, String> {
562 let file = File::open(&path)
565 .map_err(|e| format!("Error opening spec file `{}`: {}", path.display(), e))?;
566
567 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#[derive(Serialize, Deserialize)]
587struct 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 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
721fn 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
759pub 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
793pub 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 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 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 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 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 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 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}