1#![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 Patch(json::Value),
35 Full(json::Value),
37 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 Storage(Storage),
68 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 #[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 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 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#[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#[derive(Serialize, Deserialize, Debug)]
228struct RuntimeGenesisInner {
229 #[serde(default, with = "subsoil::core::bytes")]
232 code: Vec<u8>,
233 #[serde(flatten)]
235 json_blob: RuntimeGenesisConfigJson,
236}
237
238#[derive(Serialize, Deserialize, Debug)]
241#[serde(rename_all = "camelCase")]
242enum RuntimeGenesisConfigJson {
243 Config(json::Value),
249 Patch(json::Value),
253}
254
255#[derive(Serialize, Deserialize)]
257#[serde(rename_all = "camelCase")]
258#[serde(deny_unknown_fields)]
259enum Genesis {
260 Raw(RawGenesis),
262 StateRootHash(StorageData),
264 RuntimeGenesis(RuntimeGenesisInner),
266}
267
268#[derive(Serialize, Deserialize, Clone, Debug)]
273#[serde(rename_all = "camelCase")]
274struct 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 #[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 #[serde(default, skip_serializing)]
294 #[allow(unused)]
295 consensus_engine: (),
296 #[serde(skip_serializing)]
297 #[allow(unused)]
298 genesis: serde::de::IgnoredAny,
299 #[serde(default)]
304 code_substitutes: BTreeMap<String, Bytes>,
305}
306
307pub type NoExtension = Option<()>;
311
312pub 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 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 pub fn with_name(mut self, name: &str) -> Self {
347 self.name = name.into();
348 self
349 }
350
351 pub fn with_id(mut self, id: &str) -> Self {
353 self.id = id.into();
354 self
355 }
356
357 pub fn with_chain_type(mut self, chain_type: ChainType) -> Self {
359 self.chain_type = chain_type;
360 self
361 }
362
363 pub fn with_boot_nodes(mut self, boot_nodes: Vec<MultiaddrWithPeerId>) -> Self {
365 self.boot_nodes = Some(boot_nodes);
366 self
367 }
368
369 pub fn with_telemetry_endpoints(mut self, telemetry_endpoints: TelemetryEndpoints) -> Self {
371 self.telemetry_endpoints = Some(telemetry_endpoints);
372 self
373 }
374
375 pub fn with_protocol_id(mut self, protocol_id: &str) -> Self {
377 self.protocol_id = Some(protocol_id.into());
378 self
379 }
380
381 pub fn with_fork_id(mut self, fork_id: &str) -> Self {
383 self.fork_id = Some(fork_id.into());
384 self
385 }
386
387 pub fn with_properties(mut self, properties: Properties) -> Self {
389 self.properties = Some(properties);
390 self
391 }
392
393 pub fn with_extensions(mut self, extensions: E) -> Self {
395 self.extensions = extensions;
396 self
397 }
398
399 pub fn with_code(mut self, code: &[u8]) -> Self {
401 self.code = code.into();
402 self
403 }
404
405 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 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 pub fn with_genesis_config(mut self, config: json::Value) -> Self {
420 self.genesis_build_action = GenesisBuildAction::Full(config);
421 self
422 }
423
424 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
449pub 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 pub fn boot_nodes(&self) -> &[MultiaddrWithPeerId] {
473 &self.client_spec.boot_nodes
474 }
475
476 pub fn name(&self) -> &str {
478 &self.client_spec.name
479 }
480
481 pub fn id(&self) -> &str {
483 &self.client_spec.id
484 }
485
486 pub fn telemetry_endpoints(&self) -> &Option<TelemetryEndpoints> {
488 &self.client_spec.telemetry_endpoints
489 }
490
491 pub fn protocol_id(&self) -> Option<&str> {
493 self.client_spec.protocol_id.as_deref()
494 }
495
496 pub fn fork_id(&self) -> Option<&str> {
498 self.client_spec.fork_id.as_deref()
499 }
500
501 pub fn properties(&self) -> Properties {
505 self.client_spec.properties.as_ref().unwrap_or(&json::map::Map::new()).clone()
506 }
507
508 pub fn add_boot_node(&mut self, addr: MultiaddrWithPeerId) {
510 self.client_spec.boot_nodes.push(addr)
511 }
512
513 pub fn extensions(&self) -> &E {
515 &self.client_spec.extensions
516 }
517
518 pub fn extensions_mut(&mut self) -> &mut E {
520 &mut self.client_spec.extensions
521 }
522
523 fn chain_type(&self) -> ChainType {
525 self.client_spec.chain_type.clone()
526 }
527
528 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 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 pub fn from_json_file(path: PathBuf) -> Result<Self, String> {
550 let file = File::open(&path)
553 .map_err(|e| format!("Error opening spec file `{}`: {}", path.display(), e))?;
554
555 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#[derive(Serialize, Deserialize)]
575struct 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 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
709fn 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
747pub 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
781pub 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 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 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 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 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 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 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}