1use std::{
2 collections::HashMap,
3 path::{Path, PathBuf},
4};
5
6use anyhow::anyhow;
7use configuration::{
8 types::{AssetLocation, Chain, ChainSpecRuntime, JsonOverrides, ParaId},
9 HrmpChannelConfig,
10};
11use provider::{
12 constants::NODE_CONFIG_DIR,
13 types::{GenerateFileCommand, GenerateFilesOptions, TransferedFile},
14 DynNamespace, ProviderError,
15};
16use sc_chain_spec::{GenericChainSpec, GenesisConfigBuilderRuntimeCaller};
17use serde::{Deserialize, Serialize};
18use serde_json::json;
19use support::{constants::THIS_IS_A_BUG, fs::FileSystem, replacer::apply_replacements};
20use tokio::process::Command;
21use tracing::{debug, info, trace, warn};
22
23use super::errors::GeneratorError;
24use crate::{
25 network_spec::{node::NodeSpec, parachain::ParachainSpec, relaychain::RelaychainSpec},
26 ScopedFilesystem,
27};
28
29#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
31pub enum Context {
32 Relay,
33 Para { relay_chain: Chain, para_id: ParaId },
34}
35
36#[derive(Debug, Clone, Copy)]
38enum ChainSpecFormat {
39 Plain,
40 Raw,
41}
42#[derive(Debug, Clone, Copy)]
44enum KeyType {
45 Session,
46 Aura,
47 Grandpa,
48}
49
50#[derive(Debug, Clone, Copy)]
51enum SessionKeyType {
52 Default,
54 Stash,
56 Evm,
58}
59
60impl Default for SessionKeyType {
61 fn default() -> Self {
62 Self::Default
63 }
64}
65
66#[derive(Debug, Clone, Serialize, Deserialize)]
67pub enum CommandInContext {
68 Local(String),
69 Remote(String),
70}
71
72impl CommandInContext {
73 fn cmd(&self) -> &str {
74 match self {
75 CommandInContext::Local(cmd) | CommandInContext::Remote(cmd) => cmd.as_ref(),
76 }
77 }
78}
79
80#[derive(Debug)]
81pub struct ParaGenesisConfig<T: AsRef<Path>> {
82 pub(crate) state_path: T,
83 pub(crate) wasm_path: T,
84 pub(crate) id: u32,
85 pub(crate) as_parachain: bool,
86}
87
88const DEFAULT_PRESETS_TO_CHECK: [&str; 3] = ["local_testnet", "development", "dev"];
92
93#[derive(Debug, Clone, Serialize, Deserialize)]
101pub struct ChainSpec {
102 chain_spec_name: String,
104 asset_location: Option<AssetLocation>,
106 runtime: Option<ChainSpecRuntime>,
108 maybe_plain_path: Option<PathBuf>,
109 chain_name: Option<String>,
110 raw_path: Option<PathBuf>,
111 command: Option<CommandInContext>,
113 image: Option<String>,
115 context: Context,
117}
118
119impl ChainSpec {
120 pub(crate) fn new(chain_spec_name: impl Into<String>, context: Context) -> Self {
121 Self {
122 chain_spec_name: chain_spec_name.into(),
123 chain_name: None,
124 maybe_plain_path: None,
125 asset_location: None,
126 runtime: None,
127 raw_path: None,
128 command: None,
129 image: None,
130 context,
131 }
132 }
133
134 pub(crate) fn chain_spec_name(&self) -> &str {
135 self.chain_spec_name.as_ref()
136 }
137
138 pub(crate) fn chain_name(&self) -> Option<&str> {
139 self.chain_name.as_deref()
140 }
141
142 pub(crate) fn set_chain_name(mut self, chain_name: impl Into<String>) -> Self {
143 self.chain_name = Some(chain_name.into());
144 self
145 }
146
147 pub(crate) fn asset_location(mut self, location: AssetLocation) -> Self {
148 self.asset_location = Some(location);
149 self
150 }
151
152 pub(crate) fn runtime(mut self, chain_spec_runtime: ChainSpecRuntime) -> Self {
153 self.runtime = Some(chain_spec_runtime);
154 self
155 }
156
157 pub(crate) fn command(mut self, command: impl Into<String>, is_local: bool) -> Self {
158 let cmd = if is_local {
159 CommandInContext::Local(command.into())
160 } else {
161 CommandInContext::Remote(command.into())
162 };
163 self.command = Some(cmd);
164 self
165 }
166
167 pub(crate) fn image(mut self, image: Option<String>) -> Self {
168 self.image = image;
169 self
170 }
171
172 pub async fn build<'a, T>(
180 &mut self,
181 ns: &DynNamespace,
182 scoped_fs: &ScopedFilesystem<'a, T>,
183 ) -> Result<(), GeneratorError>
184 where
185 T: FileSystem,
186 {
187 if self.asset_location.is_none() && self.command.is_none() && self.runtime.is_none() {
188 return Err(GeneratorError::ChainSpecGeneration(
189 "Can not build the chain spec without set the command, asset_location or runtime"
190 .to_string(),
191 ));
192 }
193
194 let maybe_plain_spec_path = PathBuf::from(format!("{}-plain.json", self.chain_spec_name));
195
196 if let Some(location) = self.asset_location.as_ref() {
198 let maybe_plain_spec_full_path = scoped_fs.full_path(maybe_plain_spec_path.as_path());
199 location
200 .dump_asset(maybe_plain_spec_full_path)
201 .await
202 .map_err(|e| {
203 GeneratorError::ChainSpecGeneration(format!(
204 "Error {e} dumping location {location:?}"
205 ))
206 })?;
207 } else if let Some(runtime) = self.runtime.as_ref() {
208 trace!(
209 "Creating chain-spec with runtime from localtion: {}",
210 runtime.location
211 );
212 let runtime_file_name = PathBuf::from(format!("{}-runtime.wasm", self.chain_spec_name));
214 let runtime_path_ns = scoped_fs.full_path(runtime_file_name.as_path());
215 runtime
216 .location
217 .dump_asset(runtime_path_ns)
218 .await
219 .map_err(|e| {
220 GeneratorError::ChainSpecGeneration(format!(
221 "Error {e} dumping location {:?}",
222 runtime.location
223 ))
224 })?;
225
226 let runtime_code = scoped_fs.read(runtime_file_name.as_path()).await?;
228
229 let caller: GenesisConfigBuilderRuntimeCaller =
230 GenesisConfigBuilderRuntimeCaller::new(&runtime_code[..]);
231 let presets = caller.preset_names().map_err(|e| {
232 GeneratorError::ChainSpecGeneration(format!(
233 "getting default config from runtime should work: {e}"
234 ))
235 })?;
236
237 let preset = DEFAULT_PRESETS_TO_CHECK
244 .iter()
245 .find(|preset| presets.iter().any(|item| item == *preset));
246
247 trace!("presets: {:?} - preset to use: {:?}", presets, preset);
248 let builder = if let Some(preset) = preset {
249 GenericChainSpec::<()>::builder(&runtime_code[..], ())
250 .with_genesis_config_preset_name(preset)
251 } else {
252 let default_config = caller.get_default_config().map_err(|e| {
254 GeneratorError::ChainSpecGeneration(format!(
255 "getting default config from runtime should work: {e}"
256 ))
257 })?;
258
259 GenericChainSpec::<()>::builder(&runtime_code[..], ())
260 .with_genesis_config(default_config)
261 };
262
263 let builder = if let Context::Para {
264 relay_chain: _,
265 para_id,
266 } = &self.context
267 {
268 builder.with_id(¶_id.to_string())
269 } else {
270 builder
271 };
272
273 let builder = if let Some(chain_name) = self.chain_name.as_ref() {
274 builder.with_name(chain_name)
275 } else {
276 builder
277 };
278
279 let chain_spec = builder.build();
280
281 let contents = chain_spec.as_json(false).map_err(|e| {
282 GeneratorError::ChainSpecGeneration(format!(
283 "getting chain-spec as json should work, err: {e}"
284 ))
285 })?;
286
287 scoped_fs.write(&maybe_plain_spec_path, contents).await?;
288 } else {
289 trace!("Creating chain-spec with command");
290 let mut replacement_value = String::default();
292 if let Some(chain_name) = self.chain_name.as_ref() {
293 if !chain_name.is_empty() {
294 replacement_value.clone_from(chain_name);
295 }
296 };
297
298 let sanitized_cmd = if replacement_value.is_empty() {
301 self.command.as_ref().unwrap().cmd().replace("--chain", "")
303 } else {
304 self.command.as_ref().unwrap().cmd().to_owned()
305 };
306
307 let full_cmd = apply_replacements(
308 &sanitized_cmd,
309 &HashMap::from([("chainName", replacement_value.as_str())]),
310 );
311 trace!("full_cmd: {:?}", full_cmd);
312
313 let parts: Vec<&str> = full_cmd.split_whitespace().collect();
314 let Some((cmd, args)) = parts.split_first() else {
315 return Err(GeneratorError::ChainSpecGeneration(format!(
316 "Invalid generator command: {full_cmd}"
317 )));
318 };
319 trace!("cmd: {:?} - args: {:?}", cmd, args);
320
321 let generate_command =
322 GenerateFileCommand::new(cmd, maybe_plain_spec_path.clone()).args(args);
323 if let Some(CommandInContext::Local(_)) = self.command {
324 build_locally(generate_command, scoped_fs).await?;
326 } else {
327 let options = GenerateFilesOptions::new(vec![generate_command], self.image.clone());
329 ns.generate_files(options).await?;
330 }
331 }
332
333 if is_raw(maybe_plain_spec_path.clone(), scoped_fs).await? {
335 let spec_path = PathBuf::from(format!("{}.json", self.chain_spec_name));
336 let tf_file = TransferedFile::new(
337 &PathBuf::from_iter([ns.base_dir(), &maybe_plain_spec_path]),
338 &spec_path,
339 );
340 scoped_fs.copy_files(vec![&tf_file]).await.map_err(|e| {
341 GeneratorError::ChainSpecGeneration(format!(
342 "Error copying file: {tf_file}, err: {e}"
343 ))
344 })?;
345
346 self.raw_path = Some(spec_path);
347 } else {
348 self.maybe_plain_path = Some(maybe_plain_spec_path);
349 }
350 Ok(())
351 }
352
353 pub async fn build_raw<'a, T>(
354 &mut self,
355 ns: &DynNamespace,
356 scoped_fs: &ScopedFilesystem<'a, T>,
357 relay_chain_id: Option<Chain>,
358 ) -> Result<(), GeneratorError>
359 where
360 T: FileSystem,
361 {
362 let None = self.raw_path else {
364 return Ok(());
365 };
366
367 let raw_spec_path = PathBuf::from(format!("{}.json", self.chain_spec_name));
369 self.raw_path = Some(raw_spec_path.clone());
370
371 if self.runtime.is_some() && self.asset_location.is_none() {
372 let (json_content, _) = self.read_spec(scoped_fs).await?;
376 let json_bytes: Vec<u8> = json_content.as_bytes().into();
377 let chain_spec = GenericChainSpec::<()>::from_json_bytes(json_bytes).map_err(|e| {
378 GeneratorError::ChainSpecGeneration(format!(
379 "Error loading chain-spec from json_bytes, err: {e}"
380 ))
381 })?;
382
383 let contents = chain_spec.as_json(true).map_err(|e| {
384 GeneratorError::ChainSpecGeneration(format!(
385 "getting chain-spec as json should work, err: {e}"
386 ))
387 })?;
388
389 let contents = if let Context::Para {
390 relay_chain: _,
391 para_id: _,
392 } = &self.context
393 {
394 let mut contents_json: serde_json::Value = serde_json::from_str(&contents)
395 .map_err(|e| {
396 GeneratorError::ChainSpecGeneration(format!(
397 "getting chain-spec as json should work, err: {e}"
398 ))
399 })?;
400 if contents_json["relay_chain"].is_null() {
401 contents_json["relay_chain"] = json!(relay_chain_id);
402 }
403
404 serde_json::to_string_pretty(&contents_json).map_err(|e| {
405 GeneratorError::ChainSpecGeneration(format!(
406 "getting chain-spec json as pretty string should work, err: {e}"
407 ))
408 })?
409 } else {
410 contents
411 };
412
413 self.write_spec(scoped_fs, contents).await?;
414 } else {
415 let temp_name = format!(
417 "temp-build-raw-{}-{}",
418 self.chain_spec_name,
419 rand::random::<u8>()
420 );
421
422 let cmd = self
423 .command
424 .as_ref()
425 .ok_or(GeneratorError::ChainSpecGeneration(
426 "Invalid command".into(),
427 ))?;
428 let maybe_plain_path =
429 self.maybe_plain_path
430 .as_ref()
431 .ok_or(GeneratorError::ChainSpecGeneration(
432 "Invalid plain path".into(),
433 ))?;
434
435 let chain_spec_path_local = format!(
437 "{}/{}",
438 ns.base_dir().to_string_lossy(),
439 maybe_plain_path.display()
440 );
441 let chain_spec_path_in_pod =
443 format!("{}/{}", NODE_CONFIG_DIR, maybe_plain_path.display());
444 let chain_spec_path_in_args =
446 if matches!(self.command, Some(CommandInContext::Local(_))) {
447 chain_spec_path_local.clone()
448 } else if ns.capabilities().prefix_with_full_path {
449 format!(
451 "{}/{}{}",
452 ns.base_dir().to_string_lossy(),
453 &temp_name,
454 &chain_spec_path_in_pod
455 )
456 } else {
457 chain_spec_path_in_pod.clone()
458 };
459
460 let mut full_cmd = apply_replacements(
461 cmd.cmd(),
462 &HashMap::from([("chainName", chain_spec_path_in_args.as_str())]),
463 );
464
465 if !full_cmd.contains("--raw") {
466 full_cmd = format!("{full_cmd} --raw");
467 }
468 trace!("full_cmd: {:?}", full_cmd);
469
470 let parts: Vec<&str> = full_cmd.split_whitespace().collect();
471 let Some((cmd, args)) = parts.split_first() else {
472 return Err(GeneratorError::ChainSpecGeneration(format!(
473 "Invalid generator command: {full_cmd}"
474 )));
475 };
476 trace!("cmd: {:?} - args: {:?}", cmd, args);
477
478 let generate_command = GenerateFileCommand::new(cmd, raw_spec_path).args(args);
479
480 if let Some(CommandInContext::Local(_)) = self.command {
481 build_locally(generate_command, scoped_fs).await?;
483 } else {
484 let options = GenerateFilesOptions::with_files(
486 vec![generate_command],
487 self.image.clone(),
488 &[TransferedFile::new(
489 chain_spec_path_local,
490 chain_spec_path_in_pod,
491 )],
492 )
493 .temp_name(temp_name);
494 trace!("calling generate_files with options: {:#?}", options);
495 ns.generate_files(options).await?;
496 }
497 }
498
499 Ok(())
500 }
501
502 pub async fn override_code<'a, T>(
504 &mut self,
505 scoped_fs: &ScopedFilesystem<'a, T>,
506 wasm_override: &AssetLocation,
507 ) -> Result<(), GeneratorError>
508 where
509 T: FileSystem,
510 {
511 let Some(_) = self.raw_path else {
513 return Err(GeneratorError::OverridingWasm(String::from(
514 "Raw path should be set at this point.",
515 )));
516 };
517 let (content, _) = self.read_spec(scoped_fs).await?;
518 let override_content = wasm_override.get_asset().await.map_err(|_| {
520 GeneratorError::OverridingWasm(format!(
521 "Can not get asset to override wasm, asset: {wasm_override}"
522 ))
523 })?;
524
525 let mut chain_spec_json: serde_json::Value =
527 serde_json::from_str(&content).map_err(|_| {
528 GeneratorError::ChainSpecGeneration("Can not parse chain-spec as json".into())
529 })?;
530
531 let Some(code) = chain_spec_json.pointer_mut("/genesis/raw/top/0x3a636f6465") else {
533 return Err(GeneratorError::OverridingWasm(String::from(
534 "Pointer '/genesis/raw/top/0x3a636f6465' should be valid in the raw spec.",
535 )));
536 };
537
538 info!(
539 "🖋 Overriding ':code' (0x3a636f6465) in raw chain-spec with content of {}",
540 wasm_override
541 );
542 *code = json!(format!("0x{}", hex::encode(override_content)));
543
544 let overrided_content = serde_json::to_string_pretty(&chain_spec_json).map_err(|_| {
545 GeneratorError::ChainSpecGeneration("can not parse chain-spec value as json".into())
546 })?;
547 self.write_spec(scoped_fs, overrided_content).await?;
549
550 Ok(())
551 }
552
553 pub async fn override_raw_spec<'a, T>(
554 &mut self,
555 scoped_fs: &ScopedFilesystem<'a, T>,
556 raw_spec_overrides: &JsonOverrides,
557 ) -> Result<(), GeneratorError>
558 where
559 T: FileSystem,
560 {
561 let Some(_) = self.raw_path else {
563 return Err(GeneratorError::OverridingRawSpec(String::from(
564 "Raw path should be set at this point.",
565 )));
566 };
567
568 let (content, _) = self.read_spec(scoped_fs).await?;
569
570 let override_content: serde_json::Value = raw_spec_overrides.get().await.map_err(|_| {
572 GeneratorError::OverridingRawSpec(format!(
573 "Can not parse raw_spec_override contents as json: {raw_spec_overrides}"
574 ))
575 })?;
576
577 let mut chain_spec_json: serde_json::Value =
579 serde_json::from_str(&content).map_err(|_| {
580 GeneratorError::ChainSpecGeneration("Can not parse chain-spec as json".into())
581 })?;
582
583 merge(&mut chain_spec_json, &override_content);
585
586 let overrided_content = serde_json::to_string_pretty(&chain_spec_json).map_err(|_| {
588 GeneratorError::ChainSpecGeneration("can not parse chain-spec value as json".into())
589 })?;
590 self.write_spec(scoped_fs, overrided_content).await?;
591
592 Ok(())
593 }
594
595 pub fn raw_path(&self) -> Option<&Path> {
596 self.raw_path.as_deref()
597 }
598
599 pub fn set_asset_location(&mut self, location: AssetLocation) {
600 self.asset_location = Some(location)
601 }
602
603 pub async fn read_chain_id<'a, T>(
604 &self,
605 scoped_fs: &ScopedFilesystem<'a, T>,
606 ) -> Result<String, GeneratorError>
607 where
608 T: FileSystem,
609 {
610 let (content, _) = self.read_spec(scoped_fs).await?;
611 ChainSpec::chain_id_from_spec(&content)
612 }
613
614 async fn read_spec<'a, T>(
615 &self,
616 scoped_fs: &ScopedFilesystem<'a, T>,
617 ) -> Result<(String, ChainSpecFormat), GeneratorError>
618 where
619 T: FileSystem,
620 {
621 let (path, format) = match (self.maybe_plain_path.as_ref(), self.raw_path.as_ref()) {
622 (Some(path), None) => (path, ChainSpecFormat::Plain),
623 (None, Some(path)) => (path, ChainSpecFormat::Raw),
624 (Some(_), Some(path)) => {
625 (path, ChainSpecFormat::Raw)
627 },
628 (None, None) => unreachable!(),
629 };
630
631 let content = scoped_fs.read_to_string(path.clone()).await.map_err(|_| {
632 GeneratorError::ChainSpecGeneration(format!(
633 "Can not read chain-spec from {}",
634 path.to_string_lossy()
635 ))
636 })?;
637
638 Ok((content, format))
639 }
640
641 async fn write_spec<'a, T>(
642 &self,
643 scoped_fs: &ScopedFilesystem<'a, T>,
644 content: impl Into<String>,
645 ) -> Result<(), GeneratorError>
646 where
647 T: FileSystem,
648 {
649 let (path, _format) = match (self.maybe_plain_path.as_ref(), self.raw_path.as_ref()) {
650 (Some(path), None) => (path, ChainSpecFormat::Plain),
651 (None, Some(path)) => (path, ChainSpecFormat::Raw),
652 (Some(_), Some(path)) => {
653 (path, ChainSpecFormat::Raw)
655 },
656 (None, None) => unreachable!(),
657 };
658
659 scoped_fs.write(path, content.into()).await.map_err(|_| {
660 GeneratorError::ChainSpecGeneration(format!(
661 "Can not write chain-spec from {}",
662 path.to_string_lossy()
663 ))
664 })?;
665
666 Ok(())
667 }
668
669 pub async fn customize_para<'a, T>(
671 &self,
672 para: &ParachainSpec,
673 relay_chain_id: &str,
674 scoped_fs: &ScopedFilesystem<'a, T>,
675 ) -> Result<(), GeneratorError>
676 where
677 T: FileSystem,
678 {
679 let (content, format) = self.read_spec(scoped_fs).await?;
680 let mut chain_spec_json: serde_json::Value =
681 serde_json::from_str(&content).map_err(|_| {
682 GeneratorError::ChainSpecGeneration("Can not parse chain-spec as json".into())
683 })?;
684
685 if let Some(para_id) = chain_spec_json.get_mut("para_id") {
686 *para_id = json!(para.id);
687 };
688 if let Some(para_id) = chain_spec_json.get_mut("paraId") {
689 *para_id = json!(para.id);
690 };
691
692 if let Some(relay_chain_id_field) = chain_spec_json.get_mut("relay_chain") {
693 *relay_chain_id_field = json!(relay_chain_id);
694 };
695
696 if let ChainSpecFormat::Plain = format {
697 let pointer = get_runtime_config_pointer(&chain_spec_json)
698 .map_err(GeneratorError::ChainSpecGeneration)?;
699
700 if let Some(overrides) = ¶.genesis_overrides {
702 let percolated_overrides = percolate_overrides(&pointer, overrides)
703 .map_err(|e| GeneratorError::ChainSpecGeneration(e.to_string()))?;
704 if let Some(genesis) = chain_spec_json.pointer_mut(&pointer) {
705 merge(genesis, percolated_overrides);
706 }
707 }
708
709 clear_authorities(&pointer, &mut chain_spec_json, &self.context);
710
711 let key_type_to_use = if para.is_evm_based {
712 SessionKeyType::Evm
713 } else {
714 SessionKeyType::Default
715 };
716
717 let validators: Vec<&NodeSpec> = para
719 .collators
720 .iter()
721 .filter(|node| node.is_validator)
722 .collect();
723
724 if chain_spec_json
726 .pointer(&format!("{pointer}/session"))
727 .is_some()
728 {
729 add_authorities(&pointer, &mut chain_spec_json, &validators, key_type_to_use);
730 } else if chain_spec_json
731 .pointer(&format!("{pointer}/aura"))
732 .is_some()
733 {
734 add_aura_authorities(&pointer, &mut chain_spec_json, &validators, KeyType::Aura);
735 } else {
736 warn!("Can't customize keys, not `session` or `aura` find in the chain-spec file");
737 };
738
739 let invulnerables: Vec<&NodeSpec> = para
741 .collators
742 .iter()
743 .filter(|node| node.is_invulnerable)
744 .collect();
745
746 add_collator_selection(
747 &pointer,
748 &mut chain_spec_json,
749 &invulnerables,
750 key_type_to_use,
751 );
752
753 override_parachain_info(&pointer, &mut chain_spec_json, para.id);
755
756 let content = serde_json::to_string_pretty(&chain_spec_json).map_err(|_| {
758 GeneratorError::ChainSpecGeneration("can not parse chain-spec value as json".into())
759 })?;
760 self.write_spec(scoped_fs, content).await?;
761 } else {
762 warn!("⚠️ Chain spec for para_id: {} is in raw mode", para.id);
763 }
764 Ok(())
765 }
766
767 pub async fn customize_relay<'a, T, U>(
768 &self,
769 relaychain: &RelaychainSpec,
770 hrmp_channels: &[HrmpChannelConfig],
771 para_artifacts: Vec<ParaGenesisConfig<U>>,
772 scoped_fs: &ScopedFilesystem<'a, T>,
773 ) -> Result<(), GeneratorError>
774 where
775 T: FileSystem,
776 U: AsRef<Path>,
777 {
778 let (content, format) = self.read_spec(scoped_fs).await?;
779 let mut chain_spec_json: serde_json::Value =
780 serde_json::from_str(&content).map_err(|_| {
781 GeneratorError::ChainSpecGeneration("Can not parse chain-spec as json".into())
782 })?;
783
784 if let ChainSpecFormat::Plain = format {
785 let token_decimals =
787 if let Some(val) = chain_spec_json.pointer("/properties/tokenDecimals") {
788 let val = val.as_u64().unwrap_or(12);
789 if val > u8::MAX as u64 {
790 12
791 } else {
792 val as u8
793 }
794 } else {
795 12
796 };
797 let pointer = get_runtime_config_pointer(&chain_spec_json)
799 .map_err(GeneratorError::ChainSpecGeneration)?;
800
801 if let Some(overrides) = &relaychain.runtime_genesis_patch {
803 let percolated_overrides = percolate_overrides(&pointer, overrides)
804 .map_err(|e| GeneratorError::ChainSpecGeneration(e.to_string()))?;
805 if let Some(patch_section) = chain_spec_json.pointer_mut(&pointer) {
806 merge(patch_section, percolated_overrides);
807 }
808 }
809
810 let staking_min = get_staking_min(&pointer, &mut chain_spec_json);
812
813 clear_authorities(&pointer, &mut chain_spec_json, &self.context);
815
816 add_balances(
818 &pointer,
819 &mut chain_spec_json,
820 &relaychain.nodes,
821 token_decimals,
822 staking_min,
823 );
824
825 add_staking(
827 &pointer,
828 &mut chain_spec_json,
829 &relaychain.nodes,
830 staking_min,
831 );
832
833 let validators: Vec<&NodeSpec> = relaychain
835 .nodes
836 .iter()
837 .filter(|node| node.is_validator)
838 .collect();
839
840 if chain_spec_json
842 .pointer(&format!("{pointer}/session"))
843 .is_some()
844 {
845 add_authorities(
846 &pointer,
847 &mut chain_spec_json,
848 &validators,
849 SessionKeyType::Stash,
850 );
851 } else {
852 add_aura_authorities(&pointer, &mut chain_spec_json, &validators, KeyType::Aura);
853 add_grandpa_authorities(&pointer, &mut chain_spec_json, &validators, KeyType::Aura);
854 }
855
856 if !hrmp_channels.is_empty() {
859 add_hrmp_channels(&pointer, &mut chain_spec_json, hrmp_channels);
860 }
861
862 for para_genesis_config in para_artifacts.iter() {
864 add_parachain_to_genesis(
865 &pointer,
866 &mut chain_spec_json,
867 para_genesis_config,
868 scoped_fs,
869 )
870 .await
871 .map_err(|e| GeneratorError::ChainSpecGeneration(e.to_string()))?;
872 }
873
874 let content = serde_json::to_string_pretty(&chain_spec_json).map_err(|_| {
880 GeneratorError::ChainSpecGeneration("can not parse chain-spec value as json".into())
881 })?;
882 self.write_spec(scoped_fs, content).await?;
883 } else {
884 warn!(
885 "⚠️ Chain Spec for chain {} is in raw mode, can't customize.",
886 self.chain_spec_name
887 );
888 }
889 Ok(())
890 }
891
892 pub async fn add_bootnodes<'a, T>(
893 &self,
894 scoped_fs: &ScopedFilesystem<'a, T>,
895 bootnodes: &[String],
896 ) -> Result<(), GeneratorError>
897 where
898 T: FileSystem,
899 {
900 let (content, _) = self.read_spec(scoped_fs).await?;
901 let mut chain_spec_json: serde_json::Value =
902 serde_json::from_str(&content).map_err(|_| {
903 GeneratorError::ChainSpecGeneration("Can not parse chain-spec as json".into())
904 })?;
905
906 if let Some(bootnodes_on_file) = chain_spec_json.get_mut("bootNodes") {
907 if let Some(bootnodes_on_file) = bootnodes_on_file.as_array_mut() {
908 let mut bootnodes_to_add =
909 bootnodes.iter().map(|bootnode| json!(bootnode)).collect();
910 bootnodes_on_file.append(&mut bootnodes_to_add);
911 } else {
912 return Err(GeneratorError::ChainSpecGeneration(
913 "id should be an string in the chain-spec, this is a bug".into(),
914 ));
915 };
916 } else {
917 return Err(GeneratorError::ChainSpecGeneration(
918 "'bootNodes' should be a fields in the chain-spec of the relaychain".into(),
919 ));
920 };
921
922 let content = serde_json::to_string_pretty(&chain_spec_json).map_err(|_| {
924 GeneratorError::ChainSpecGeneration("can not parse chain-spec value as json".into())
925 })?;
926 self.write_spec(scoped_fs, content).await?;
927
928 Ok(())
929 }
930
931 pub fn chain_id_from_spec(spec_content: &str) -> Result<String, GeneratorError> {
933 let chain_spec_json: serde_json::Value =
934 serde_json::from_str(spec_content).map_err(|_| {
935 GeneratorError::ChainSpecGeneration("Can not parse chain-spec as json".into())
936 })?;
937 if let Some(chain_id) = chain_spec_json.get("id") {
938 if let Some(chain_id) = chain_id.as_str() {
939 Ok(chain_id.to_string())
940 } else {
941 Err(GeneratorError::ChainSpecGeneration(
942 "id should be an string in the chain-spec, this is a bug".into(),
943 ))
944 }
945 } else {
946 Err(GeneratorError::ChainSpecGeneration(
947 "'id' should be a fields in the chain-spec of the relaychain".into(),
948 ))
949 }
950 }
951}
952
953type GenesisNodeKey = (String, String, HashMap<String, String>);
954
955async fn build_locally<'a, T>(
956 generate_command: GenerateFileCommand,
957 scoped_fs: &ScopedFilesystem<'a, T>,
958) -> Result<(), GeneratorError>
959where
960 T: FileSystem,
961{
962 let result = Command::new(generate_command.program.clone())
965 .args(generate_command.args.clone())
966 .current_dir(scoped_fs.base_dir)
967 .output()
968 .await
969 .map_err(|err| {
970 GeneratorError::ChainSpecGeneration(format!(
971 "Error running cmd: {} args: {}, err: {}",
972 &generate_command.program,
973 &generate_command.args.join(" "),
974 err
975 ))
976 })?;
977
978 if result.status.success() {
979 scoped_fs
980 .write(
981 generate_command.local_output_path,
982 String::from_utf8_lossy(&result.stdout).to_string(),
983 )
984 .await?;
985 Ok(())
986 } else {
987 Err(GeneratorError::ChainSpecGeneration(format!(
988 "Error running cmd: {} args: {}, err: {}",
989 &generate_command.program,
990 &generate_command.args.join(" "),
991 String::from_utf8_lossy(&result.stderr)
992 )))
993 }
994}
995
996async fn is_raw<'a, T>(
997 file: PathBuf,
998 scoped_fs: &ScopedFilesystem<'a, T>,
999) -> Result<bool, ProviderError>
1000where
1001 T: FileSystem,
1002{
1003 let content = scoped_fs.read_to_string(file).await?;
1004 let chain_spec_json: serde_json::Value = serde_json::from_str(&content).unwrap();
1005
1006 Ok(chain_spec_json.pointer("/genesis/raw/top").is_some())
1007}
1008
1009async fn add_parachain_to_genesis<'a, T, U>(
1012 runtime_config_ptr: &str,
1013 chain_spec_json: &mut serde_json::Value,
1014 para_genesis_config: &ParaGenesisConfig<U>,
1015 scoped_fs: &ScopedFilesystem<'a, T>,
1016) -> Result<(), anyhow::Error>
1017where
1018 T: FileSystem,
1019 U: AsRef<Path>,
1020{
1021 if let Some(val) = chain_spec_json.pointer_mut(runtime_config_ptr) {
1022 let paras_pointer = if val.get("paras").is_some() {
1023 "/paras/paras"
1024 } else if val.get("parachainsParas").is_some() {
1025 "/parachainsParas/paras"
1027 } else {
1028 val["paras"] = json!({ "paras": [] });
1030 "/paras/paras"
1031 };
1032
1033 let paras = val
1034 .pointer_mut(paras_pointer)
1035 .ok_or(anyhow!("paras pointer should be valid {paras_pointer:?} "))?;
1036 let paras_vec = paras
1037 .as_array_mut()
1038 .ok_or(anyhow!("paras should be an array"))?;
1039
1040 let head = scoped_fs
1041 .read_to_string(para_genesis_config.state_path.as_ref())
1042 .await?;
1043 let wasm = scoped_fs
1044 .read_to_string(para_genesis_config.wasm_path.as_ref())
1045 .await?;
1046
1047 paras_vec.push(json!([
1048 para_genesis_config.id,
1049 [head.trim(), wasm.trim(), para_genesis_config.as_parachain]
1050 ]));
1051
1052 Ok(())
1053 } else {
1054 unreachable!("pointer to runtime config should be valid!")
1055 }
1056}
1057
1058fn get_runtime_config_pointer(chain_spec_json: &serde_json::Value) -> Result<String, String> {
1059 let pointers = [
1062 "/genesis/runtimeGenesis/config",
1063 "/genesis/runtimeGenesis/patch",
1064 "/genesis/runtimeGenesisConfigPatch",
1065 "/genesis/runtime/runtime_genesis_config",
1066 "/genesis/runtime",
1067 ];
1068
1069 for pointer in pointers {
1070 if chain_spec_json.pointer(pointer).is_some() {
1071 return Ok(pointer.to_string());
1072 }
1073 }
1074
1075 Err("Can not find the runtime pointer".into())
1076}
1077
1078fn percolate_overrides<'a>(
1079 pointer: &str,
1080 overrides: &'a serde_json::Value,
1081) -> Result<&'a serde_json::Value, anyhow::Error> {
1082 let pointer_parts = pointer.split('/').collect::<Vec<&str>>();
1083 trace!("pointer_parts: {pointer_parts:?}");
1084
1085 let top_level = overrides
1086 .as_object()
1087 .ok_or_else(|| anyhow!("Overrides must be an object"))?;
1088 let top_level_key = top_level
1089 .keys()
1090 .next()
1091 .ok_or_else(|| anyhow!("Invalid override value: {overrides:?}"))?;
1092 trace!("top_level_key: {top_level_key}");
1093 let index = pointer_parts.iter().position(|x| *x == top_level_key);
1094 let Some(i) = index else {
1095 warn!("Top level key '{top_level_key}' isn't part of the pointer ({pointer}), returning without percolating");
1096 return Ok(overrides);
1097 };
1098
1099 let p = if i == pointer_parts.len() - 1 {
1100 let p = format!("/{}", pointer_parts[i]);
1102 trace!("overrides pointer {p}");
1103 p
1104 } else {
1105 let p = format!("/{}", pointer_parts[i..].join("/"));
1107 trace!("overrides pointer {p}");
1108 p
1109 };
1110 let overrides_to_use = overrides
1111 .pointer(&p)
1112 .ok_or_else(|| anyhow!("Invalid override value: {overrides:?}"))?;
1113 Ok(overrides_to_use)
1114}
1115
1116#[allow(dead_code)]
1117fn construct_runtime_pointer_from_overrides(
1118 overrides: &serde_json::Value,
1119) -> Result<String, anyhow::Error> {
1120 if overrides.get("genesis").is_some() {
1121 return Ok("/genesis".into());
1123 } else {
1124 if let Some(top_level) = overrides.as_object() {
1126 let k = top_level
1127 .keys()
1128 .next()
1129 .ok_or_else(|| anyhow!("Invalid override value: {overrides:?}"))?;
1130 match k.as_str() {
1131 "runtimeGenesisConfigPatch" | "runtime" | "runtimeGenesis" => {
1132 return Ok(("/genesis").into())
1133 },
1134 "config" | "path" => {
1135 return Ok(("/genesis/runtimeGenesis").into());
1136 },
1137 "runtime_genesis_config" => {
1138 return Ok(("/genesis/runtime").into());
1139 },
1140 _ => {},
1141 }
1142 }
1143 }
1144
1145 Err(anyhow!("Can not find the runtime pointer"))
1146}
1147
1148fn merge(patch_section: &mut serde_json::Value, overrides: &serde_json::Value) {
1150 trace!("patch: {:?}", patch_section);
1151 trace!("overrides: {:?}", overrides);
1152 if let (Some(genesis_obj), Some(overrides_obj)) =
1153 (patch_section.as_object_mut(), overrides.as_object())
1154 {
1155 for overrides_key in overrides_obj.keys() {
1156 trace!("overrides_key: {:?}", overrides_key);
1157 if let Some(genesis_value) = genesis_obj.get_mut(overrides_key) {
1159 match (&genesis_value, overrides_obj.get(overrides_key)) {
1160 (serde_json::Value::Object(_), Some(overrides_value))
1162 if overrides_value.is_object() =>
1163 {
1164 merge(genesis_value, overrides_value);
1165 },
1166 (_, Some(overrides_value)) => {
1168 trace!("overriding: {:?} / {:?}", genesis_value, overrides_value);
1169 *genesis_value = overrides_value.clone();
1170 },
1171 _ => {
1172 trace!("not match!");
1173 },
1174 }
1175 } else {
1176 warn!(
1178 "key: {overrides_key} not present in genesis_obj: {:?} (adding key)",
1179 genesis_obj
1180 );
1181 let overrides_value = overrides_obj.get(overrides_key).expect(&format!(
1182 "overrides_key {overrides_key} should be present in the overrides obj. qed"
1183 ));
1184 genesis_obj.insert(overrides_key.clone(), overrides_value.clone());
1185 }
1186 }
1187 }
1188}
1189
1190fn clear_authorities(
1191 runtime_config_ptr: &str,
1192 chain_spec_json: &mut serde_json::Value,
1193 ctx: &Context,
1194) {
1195 if let Some(val) = chain_spec_json.pointer_mut(runtime_config_ptr) {
1196 if val.get("session").is_some() {
1198 val["session"]["keys"] = json!([]);
1199 }
1200
1201 if val.get("aura").is_some() {
1202 val["aura"]["authorities"] = json!([]);
1203 }
1204
1205 if val.get("grandpa").is_some() {
1206 val["grandpa"]["authorities"] = json!([]);
1207 }
1208
1209 if val.get("collatorSelection").is_some() {
1211 val["collatorSelection"]["invulnerables"] = json!([]);
1212 }
1213
1214 if val.get("staking").is_some() && ctx == &Context::Relay {
1216 val["staking"]["invulnerables"] = json!([]);
1217 val["staking"]["stakers"] = json!([]);
1218
1219 if val["staking"]["devStakers"] == json!(null) {
1220 val["staking"]["validatorCount"] = json!(0);
1221 }
1222 }
1223 } else {
1224 unreachable!("pointer to runtime config should be valid!")
1225 }
1226}
1227
1228fn get_staking_min(runtime_config_ptr: &str, chain_spec_json: &mut serde_json::Value) -> u128 {
1229 let staking_ptr = format!("{runtime_config_ptr}/staking/stakers");
1231 if let Some(stakers) = chain_spec_json.pointer(&staking_ptr) {
1232 let min = stakers[0][2].clone();
1234 min.as_u64().unwrap_or(0).into()
1235 } else {
1236 0
1237 }
1238}
1239
1240fn add_balances(
1241 runtime_config_ptr: &str,
1242 chain_spec_json: &mut serde_json::Value,
1243 nodes: &Vec<NodeSpec>,
1244 token_decimals: u8,
1245 staking_min: u128,
1246) {
1247 if let Some(val) = chain_spec_json.pointer_mut(runtime_config_ptr) {
1248 let Some(balances) = val.pointer("/balances/balances") else {
1249 warn!("NO 'balances' key in runtime config, skipping...");
1251 return;
1252 };
1253
1254 let mut balances_map = generate_balance_map(balances);
1256 for node in nodes {
1257 if node.initial_balance.eq(&0) {
1258 continue;
1259 };
1260
1261 let balance = std::cmp::max(node.initial_balance, staking_min * 2);
1264 for k in ["sr", "sr_stash"] {
1265 let account = node.accounts.accounts.get(k).unwrap();
1266 balances_map.insert(account.address.clone(), balance);
1267 }
1268 }
1269
1270 balances_map.insert(
1273 "5FTcLfwFc7ctvqp3RhbEig6UuHLHcHVRujuUm8r21wy4dAR8".to_string(),
1274 1000 * 10_u128.pow(token_decimals as u32),
1275 );
1276
1277 let new_balances: Vec<(&String, &u128)> =
1279 balances_map.iter().collect::<Vec<(&String, &u128)>>();
1280
1281 val["balances"]["balances"] = json!(new_balances);
1282 } else {
1283 unreachable!("pointer to runtime config should be valid!")
1284 }
1285}
1286
1287fn get_node_keys(
1288 node: &NodeSpec,
1289 session_key: SessionKeyType,
1290 asset_hub_polkadot: bool,
1291) -> GenesisNodeKey {
1292 let sr_account = node.accounts.accounts.get("sr").unwrap();
1293 let sr_stash = node.accounts.accounts.get("sr_stash").unwrap();
1294 let ed_account = node.accounts.accounts.get("ed").unwrap();
1295 let ec_account = node.accounts.accounts.get("ec").unwrap();
1296 let eth_account = node.accounts.accounts.get("eth").unwrap();
1297 let mut keys = HashMap::new();
1298 for k in [
1299 "babe",
1300 "im_online",
1301 "parachain_validator",
1302 "authority_discovery",
1303 "para_validator",
1304 "para_assignment",
1305 "aura",
1306 "nimbus",
1307 "vrf",
1308 ] {
1309 if k == "aura" && asset_hub_polkadot {
1310 keys.insert(k.to_string(), ed_account.address.clone());
1311 continue;
1312 }
1313 keys.insert(k.to_string(), sr_account.address.clone());
1314 }
1315
1316 keys.insert("grandpa".to_string(), ed_account.address.clone());
1317 keys.insert("beefy".to_string(), ec_account.address.clone());
1318 keys.insert("eth".to_string(), eth_account.public_key.clone());
1319
1320 let account_to_use = match session_key {
1321 SessionKeyType::Default => sr_account.address.clone(),
1322 SessionKeyType::Stash => sr_stash.address.clone(),
1323 SessionKeyType::Evm => format!("0x{}", eth_account.public_key),
1324 };
1325
1326 (account_to_use.clone(), account_to_use, keys)
1327}
1328fn add_authorities(
1329 runtime_config_ptr: &str,
1330 chain_spec_json: &mut serde_json::Value,
1331 nodes: &[&NodeSpec],
1332 session_key: SessionKeyType,
1333) {
1334 let asset_hub_polkadot = chain_spec_json
1335 .get("id")
1336 .and_then(|v| v.as_str())
1337 .map(|id| id.starts_with("asset-hub-polkadot"))
1338 .unwrap_or_default();
1339 if let Some(val) = chain_spec_json.pointer_mut(runtime_config_ptr) {
1340 if let Some(session_keys) = val.pointer_mut("/session/keys") {
1341 let keys: Vec<GenesisNodeKey> = nodes
1342 .iter()
1343 .map(|node| get_node_keys(node, session_key, asset_hub_polkadot))
1344 .collect();
1345 *session_keys = json!(keys);
1346 } else {
1347 warn!("⚠️ 'session/keys' key not present in runtime config.");
1348 }
1349 } else {
1350 unreachable!("pointer to runtime config should be valid!")
1351 }
1352}
1353fn add_hrmp_channels(
1354 runtime_config_ptr: &str,
1355 chain_spec_json: &mut serde_json::Value,
1356 hrmp_channels: &[HrmpChannelConfig],
1357) {
1358 if let Some(val) = chain_spec_json.pointer_mut(runtime_config_ptr) {
1359 if let Some(preopen_hrmp_channels) = val.pointer_mut("/hrmp/preopenHrmpChannels") {
1360 let hrmp_channels = hrmp_channels
1361 .iter()
1362 .map(|c| {
1363 (
1364 c.sender(),
1365 c.recipient(),
1366 c.max_capacity(),
1367 c.max_message_size(),
1368 )
1369 })
1370 .collect::<Vec<_>>();
1371 *preopen_hrmp_channels = json!(hrmp_channels);
1372 } else {
1373 warn!("⚠️ 'hrmp/preopenHrmpChannels' key not present in runtime config.");
1374 }
1375 } else {
1376 unreachable!("pointer to runtime config should be valid!")
1377 }
1378}
1379
1380fn add_aura_authorities(
1381 runtime_config_ptr: &str,
1382 chain_spec_json: &mut serde_json::Value,
1383 nodes: &[&NodeSpec],
1384 _key_type: KeyType,
1385) {
1386 if let Some(val) = chain_spec_json.pointer_mut(runtime_config_ptr) {
1387 if let Some(aura_authorities) = val.pointer_mut("/aura/authorities") {
1388 let keys: Vec<String> = nodes
1389 .iter()
1390 .map(|node| {
1391 node.accounts
1392 .accounts
1393 .get("sr")
1394 .expect(&format!(
1395 "'sr' account should be set at spec computation {THIS_IS_A_BUG}"
1396 ))
1397 .address
1398 .clone()
1399 })
1400 .collect();
1401 *aura_authorities = json!(keys);
1402 } else {
1403 warn!("⚠️ 'aura/authorities' key not present in runtime config.");
1404 }
1405 } else {
1406 unreachable!("pointer to runtime config should be valid!")
1407 }
1408}
1409
1410fn add_grandpa_authorities(
1411 runtime_config_ptr: &str,
1412 chain_spec_json: &mut serde_json::Value,
1413 nodes: &[&NodeSpec],
1414 _key_type: KeyType,
1415) {
1416 if let Some(val) = chain_spec_json.pointer_mut(runtime_config_ptr) {
1417 if let Some(grandpa_authorities) = val.pointer_mut("/grandpa/authorities") {
1418 let keys: Vec<(String, usize)> = nodes
1419 .iter()
1420 .map(|node| {
1421 (
1422 node.accounts
1423 .accounts
1424 .get("ed")
1425 .expect(&format!(
1426 "'ed' account should be set at spec computation {THIS_IS_A_BUG}"
1427 ))
1428 .address
1429 .clone(),
1430 1,
1431 )
1432 })
1433 .collect();
1434 *grandpa_authorities = json!(keys);
1435 } else {
1436 warn!("⚠️ 'grandpa/authorities' key not present in runtime config.");
1437 }
1438 } else {
1439 unreachable!("pointer to runtime config should be valid!")
1440 }
1441}
1442
1443fn add_staking(
1444 runtime_config_ptr: &str,
1445 chain_spec_json: &mut serde_json::Value,
1446 nodes: &Vec<NodeSpec>,
1447 staking_min: u128,
1448) {
1449 if let Some(val) = chain_spec_json.pointer_mut(runtime_config_ptr) {
1450 let Some(_) = val.pointer("/staking") else {
1451 warn!("NO 'staking' key in runtime config, skipping...");
1453 return;
1454 };
1455
1456 let mut stakers = vec![];
1457 let mut invulnerables = vec![];
1458 for node in nodes {
1459 let sr_stash_addr = &node
1460 .accounts
1461 .accounts
1462 .get("sr_stash")
1463 .expect("'sr_stash account should be defined for the node. qed")
1464 .address;
1465 stakers.push(json!([
1466 sr_stash_addr,
1467 sr_stash_addr,
1468 staking_min,
1469 "Validator"
1470 ]));
1471
1472 if node.is_invulnerable {
1473 invulnerables.push(sr_stash_addr);
1474 }
1475 }
1476
1477 val["staking"]["validatorCount"] = json!(stakers.len());
1478 val["staking"]["stakers"] = json!(stakers);
1479 val["staking"]["invulnerables"] = json!(invulnerables);
1480 } else {
1481 unreachable!("pointer to runtime config should be valid!")
1482 }
1483}
1484
1485fn override_parachain_info(
1492 runtime_config_ptr: &str,
1493 chain_spec_json: &mut serde_json::Value,
1494 para_id: u32,
1495) {
1496 if let Some(val) = chain_spec_json.pointer_mut(runtime_config_ptr) {
1497 if let Some(parachain_id) = val.pointer_mut("/parachainInfo/parachainId") {
1498 *parachain_id = json!(para_id)
1499 } else {
1500 }
1502 } else {
1503 unreachable!("pointer to runtime config should be valid!")
1504 }
1505}
1506fn add_collator_selection(
1507 runtime_config_ptr: &str,
1508 chain_spec_json: &mut serde_json::Value,
1509 nodes: &[&NodeSpec],
1510 session_key: SessionKeyType,
1511) {
1512 if let Some(val) = chain_spec_json.pointer_mut(runtime_config_ptr) {
1513 let key_type = if let SessionKeyType::Evm = session_key {
1514 "eth"
1515 } else {
1516 "sr"
1517 };
1518 let keys: Vec<String> = nodes
1519 .iter()
1520 .map(|node| {
1521 node.accounts
1522 .accounts
1523 .get(key_type)
1524 .expect(&format!(
1525 "'sr' account should be set at spec computation {THIS_IS_A_BUG}"
1526 ))
1527 .address
1528 .clone()
1529 })
1530 .collect();
1531
1532 if let Some(invulnerables) = val.pointer_mut("/collatorSelection/invulnerables") {
1534 *invulnerables = json!(keys);
1535 } else {
1536 debug!("⚠️ 'invulnerables' not present in spec, will not be customized");
1538 }
1539 } else {
1540 unreachable!("pointer to runtime config should be valid!")
1541 }
1542}
1543
1544fn generate_balance_map(balances: &serde_json::Value) -> HashMap<String, u128> {
1546 let balances_map: HashMap<String, u128> =
1548 serde_json::from_value::<Vec<(String, u128)>>(balances.to_owned())
1549 .unwrap()
1550 .iter()
1551 .fold(HashMap::new(), |mut memo, balance| {
1552 memo.insert(balance.0.clone(), balance.1);
1553 memo
1554 });
1555 balances_map
1556}
1557
1558#[cfg(test)]
1559mod tests {
1560 use std::fs;
1561
1562 use configuration::HrmpChannelConfigBuilder;
1563
1564 use super::*;
1565 use crate::{generators, shared::types::NodeAccounts};
1566
1567 const ROCOCO_LOCAL_PLAIN_TESTING: &str = "./testing/rococo-local-plain.json";
1568
1569 fn chain_spec_test(file: &str) -> serde_json::Value {
1570 let content = fs::read_to_string(file).unwrap();
1571 serde_json::from_str(&content).unwrap()
1572 }
1573
1574 fn chain_spec_with_stake() -> serde_json::Value {
1575 json!({"genesis": {
1576 "runtimeGenesis" : {
1577 "patch": {
1578 "staking": {
1579 "forceEra": "NotForcing",
1580 "invulnerables": [
1581 "5GNJqTPyNqANBkUVMN1LPPrxXnFouWXoe2wNSmmEoLctxiZY",
1582 "5HpG9w8EBLe5XCrbczpwq5TSXvedjrBGCwqxK1iQ7qUsSWFc"
1583 ],
1584 "minimumValidatorCount": 1,
1585 "slashRewardFraction": 100000000,
1586 "stakers": [
1587 [
1588 "5GNJqTPyNqANBkUVMN1LPPrxXnFouWXoe2wNSmmEoLctxiZY",
1589 "5GNJqTPyNqANBkUVMN1LPPrxXnFouWXoe2wNSmmEoLctxiZY",
1590 100000000000001_u128,
1591 "Validator"
1592 ],
1593 [
1594 "5HpG9w8EBLe5XCrbczpwq5TSXvedjrBGCwqxK1iQ7qUsSWFc",
1595 "5HpG9w8EBLe5XCrbczpwq5TSXvedjrBGCwqxK1iQ7qUsSWFc",
1596 100000000000000_u128,
1597 "Validator"
1598 ]
1599 ],
1600 "validatorCount": 2
1601 },
1602 }
1603 }
1604 }})
1605 }
1606
1607 fn chain_spec_with_dev_stakers() -> serde_json::Value {
1608 json!({"genesis": {
1609 "runtimeGenesis" : {
1610 "patch": {
1611 "staking": {
1612 "activeEra": [
1613 0,
1614 0,
1615 0
1616 ],
1617 "canceledPayout": 0,
1618 "devStakers": [
1619 2000,
1620 25000
1621 ],
1622 "forceEra": "NotForcing",
1623 "invulnerables": [],
1624 "maxNominatorCount": null,
1625 "maxValidatorCount": null,
1626 "minNominatorBond": 0,
1627 "minValidatorBond": 0,
1628 "slashRewardFraction": 0,
1629 "stakers": [],
1630 "validatorCount": 500
1631 },
1632 }
1633 }
1634 }})
1635 }
1636
1637 #[test]
1638 fn get_min_stake_works() {
1639 let mut chain_spec_json = chain_spec_with_stake();
1640
1641 let pointer = get_runtime_config_pointer(&chain_spec_json).unwrap();
1642 let min = get_staking_min(&pointer, &mut chain_spec_json);
1643
1644 assert_eq!(100000000000001, min);
1645 }
1646
1647 #[test]
1648 fn dev_stakers_not_override_count_works() {
1649 let mut chain_spec_json = chain_spec_with_dev_stakers();
1650
1651 let pointer = get_runtime_config_pointer(&chain_spec_json).unwrap();
1652 clear_authorities(&pointer, &mut chain_spec_json, &Context::Relay);
1653
1654 let validator_count = chain_spec_json
1655 .pointer(&format!("{pointer}/staking/validatorCount"))
1656 .unwrap();
1657 assert_eq!(validator_count, &json!(500));
1658 }
1659
1660 #[test]
1661 fn dev_stakers_override_count_works() {
1662 let mut chain_spec_json = chain_spec_with_stake();
1663
1664 let pointer = get_runtime_config_pointer(&chain_spec_json).unwrap();
1665 clear_authorities(&pointer, &mut chain_spec_json, &Context::Relay);
1666
1667 let validator_count = chain_spec_json
1668 .pointer(&format!("{pointer}/staking/validatorCount"))
1669 .unwrap();
1670 assert_eq!(validator_count, &json!(0));
1671 }
1672
1673 #[test]
1674 fn overrides_from_toml_works() {
1675 use serde::{Deserialize, Serialize};
1676
1677 #[derive(Debug, Serialize, Deserialize)]
1678 struct MockConfig {
1679 #[serde(rename = "genesis", skip_serializing_if = "Option::is_none")]
1680 genesis_overrides: Option<serde_json::Value>,
1681 }
1682
1683 let mut chain_spec_json = chain_spec_test(ROCOCO_LOCAL_PLAIN_TESTING);
1684 const TOML: &str = "[genesis.runtime.balances]
1686 devAccounts = [
1687 20000,
1688 1000000000000000000,
1689 \"//Sender//{}\"
1690 ]";
1691 let override_toml: MockConfig = toml::from_str(TOML).unwrap();
1692 let overrides = override_toml.genesis_overrides.unwrap();
1693 let pointer = get_runtime_config_pointer(&chain_spec_json).unwrap();
1694
1695 let percolated_overrides = percolate_overrides(&pointer, &overrides)
1696 .map_err(|e| GeneratorError::ChainSpecGeneration(e.to_string()))
1697 .unwrap();
1698 trace!("percolated_overrides: {:#?}", percolated_overrides);
1699 if let Some(genesis) = chain_spec_json.pointer_mut(&pointer) {
1700 merge(genesis, percolated_overrides);
1701 }
1702
1703 trace!("chain spec: {chain_spec_json:#?}");
1704 assert!(chain_spec_json
1705 .pointer("/genesis/runtime/balances/devAccounts")
1706 .is_some());
1707 }
1708
1709 #[test]
1710 fn add_balances_works() {
1711 let mut spec_plain = chain_spec_test(ROCOCO_LOCAL_PLAIN_TESTING);
1712 let mut name = String::from("luca");
1713 let initial_balance = 1_000_000_000_000_u128;
1714 let seed = format!("//{}{name}", name.remove(0).to_uppercase());
1715 let accounts = NodeAccounts {
1716 accounts: generators::generate_node_keys(&seed).unwrap(),
1717 seed,
1718 };
1719 let node = NodeSpec {
1720 name,
1721 accounts,
1722 initial_balance,
1723 ..Default::default()
1724 };
1725
1726 let nodes = vec![node];
1727 add_balances("/genesis/runtime", &mut spec_plain, &nodes, 12, 0);
1728
1729 let new_balances = spec_plain
1730 .pointer("/genesis/runtime/balances/balances")
1731 .unwrap();
1732
1733 let balances_map = generate_balance_map(new_balances);
1734
1735 let sr = nodes[0].accounts.accounts.get("sr").unwrap();
1737 let sr_stash = nodes[0].accounts.accounts.get("sr_stash").unwrap();
1738 assert_eq!(balances_map.get(&sr.address).unwrap(), &initial_balance);
1739 assert_eq!(
1740 balances_map.get(&sr_stash.address).unwrap(),
1741 &initial_balance
1742 );
1743 }
1744
1745 #[test]
1746 fn add_balances_ensure_zombie_account() {
1747 let mut spec_plain = chain_spec_test(ROCOCO_LOCAL_PLAIN_TESTING);
1748
1749 let balances = spec_plain
1750 .pointer("/genesis/runtime/balances/balances")
1751 .unwrap();
1752 let balances_map = generate_balance_map(balances);
1753
1754 let nodes: Vec<NodeSpec> = vec![];
1755 add_balances("/genesis/runtime", &mut spec_plain, &nodes, 12, 0);
1756
1757 let new_balances = spec_plain
1758 .pointer("/genesis/runtime/balances/balances")
1759 .unwrap();
1760
1761 let new_balances_map = generate_balance_map(new_balances);
1762
1763 assert!(new_balances_map.contains_key("5FTcLfwFc7ctvqp3RhbEig6UuHLHcHVRujuUm8r21wy4dAR8"));
1765 assert_eq!(new_balances_map.len(), balances_map.len() + 1);
1766 }
1767
1768 #[test]
1769 fn add_balances_spec_without_balances() {
1770 let mut spec_plain = chain_spec_test(ROCOCO_LOCAL_PLAIN_TESTING);
1771
1772 {
1773 let balances = spec_plain.pointer_mut("/genesis/runtime/balances").unwrap();
1774 *balances = json!(serde_json::Value::Null);
1775 }
1776
1777 let mut name = String::from("luca");
1778 let initial_balance = 1_000_000_000_000_u128;
1779 let seed = format!("//{}{name}", name.remove(0).to_uppercase());
1780 let accounts = NodeAccounts {
1781 accounts: generators::generate_node_keys(&seed).unwrap(),
1782 seed,
1783 };
1784 let node = NodeSpec {
1785 name,
1786 accounts,
1787 initial_balance,
1788 ..Default::default()
1789 };
1790
1791 let nodes = vec![node];
1792 add_balances("/genesis/runtime", &mut spec_plain, &nodes, 12, 0);
1793
1794 let new_balances = spec_plain.pointer("/genesis/runtime/balances/balances");
1795
1796 assert_eq!(new_balances, None);
1798 }
1799
1800 #[test]
1801 fn add_staking_works() {
1802 let mut chain_spec_json = chain_spec_with_stake();
1803 let mut name = String::from("luca");
1804 let initial_balance = 1_000_000_000_000_u128;
1805 let seed = format!("//{}{name}", name.remove(0).to_uppercase());
1806 let accounts = NodeAccounts {
1807 accounts: generators::generate_node_keys(&seed).unwrap(),
1808 seed,
1809 };
1810 let node = NodeSpec {
1811 name,
1812 accounts,
1813 initial_balance,
1814 ..Default::default()
1815 };
1816
1817 let pointer = get_runtime_config_pointer(&chain_spec_json).unwrap();
1818 let min = get_staking_min(&pointer, &mut chain_spec_json);
1819
1820 let nodes = vec![node];
1821 add_staking(&pointer, &mut chain_spec_json, &nodes, min);
1822
1823 let new_staking = chain_spec_json
1824 .pointer("/genesis/runtimeGenesis/patch/staking")
1825 .unwrap();
1826
1827 let sr_stash = nodes[0].accounts.accounts.get("sr_stash").unwrap();
1829 assert_eq!(new_staking["stakers"][0][0], json!(sr_stash.address));
1830 assert_eq!(new_staking["stakers"][0][2], json!(min));
1832 assert_eq!(new_staking["stakers"].as_array().unwrap().len(), 1);
1834 }
1835
1836 #[test]
1837 fn adding_hrmp_channels_works() {
1838 let mut spec_plain = chain_spec_test(ROCOCO_LOCAL_PLAIN_TESTING);
1839
1840 {
1841 let current_hrmp_channels = spec_plain
1842 .pointer("/genesis/runtime/hrmp/preopenHrmpChannels")
1843 .unwrap();
1844 assert_eq!(current_hrmp_channels, &json!([]));
1846 }
1847
1848 let para_100_101 = HrmpChannelConfigBuilder::new()
1849 .with_sender(100)
1850 .with_recipient(101)
1851 .build();
1852 let para_101_100 = HrmpChannelConfigBuilder::new()
1853 .with_sender(101)
1854 .with_recipient(100)
1855 .build();
1856 let channels = vec![para_100_101, para_101_100];
1857
1858 add_hrmp_channels("/genesis/runtime", &mut spec_plain, &channels);
1859 let new_hrmp_channels = spec_plain
1860 .pointer("/genesis/runtime/hrmp/preopenHrmpChannels")
1861 .unwrap()
1862 .as_array()
1863 .unwrap();
1864
1865 assert_eq!(new_hrmp_channels.len(), 2);
1866 assert_eq!(new_hrmp_channels.first().unwrap()[0], 100);
1867 assert_eq!(new_hrmp_channels.first().unwrap()[1], 101);
1868 assert_eq!(new_hrmp_channels.last().unwrap()[0], 101);
1869 assert_eq!(new_hrmp_channels.last().unwrap()[1], 100);
1870 }
1871
1872 #[test]
1873 fn adding_hrmp_channels_to_an_spec_without_channels() {
1874 let mut spec_plain = chain_spec_test("./testing/rococo-local-plain.json");
1875
1876 {
1877 let hrmp = spec_plain.pointer_mut("/genesis/runtime/hrmp").unwrap();
1878 *hrmp = json!(serde_json::Value::Null);
1879 }
1880
1881 let para_100_101 = HrmpChannelConfigBuilder::new()
1882 .with_sender(100)
1883 .with_recipient(101)
1884 .build();
1885 let para_101_100 = HrmpChannelConfigBuilder::new()
1886 .with_sender(101)
1887 .with_recipient(100)
1888 .build();
1889 let channels = vec![para_100_101, para_101_100];
1890
1891 add_hrmp_channels("/genesis/runtime", &mut spec_plain, &channels);
1892 let new_hrmp_channels = spec_plain.pointer("/genesis/runtime/hrmp/preopenHrmpChannels");
1893
1894 assert_eq!(new_hrmp_channels, None);
1896 }
1897
1898 #[test]
1899 fn get_node_keys_works() {
1900 let mut name = String::from("luca");
1901 let seed = format!("//{}{name}", name.remove(0).to_uppercase());
1902 let accounts = NodeAccounts {
1903 accounts: generators::generate_node_keys(&seed).unwrap(),
1904 seed,
1905 };
1906 let node = NodeSpec {
1907 name,
1908 accounts,
1909 ..Default::default()
1910 };
1911
1912 let sr = &node.accounts.accounts["sr"];
1913 let keys = [
1914 ("babe".into(), sr.address.clone()),
1915 ("im_online".into(), sr.address.clone()),
1916 ("parachain_validator".into(), sr.address.clone()),
1917 ("authority_discovery".into(), sr.address.clone()),
1918 ("para_validator".into(), sr.address.clone()),
1919 ("para_assignment".into(), sr.address.clone()),
1920 ("aura".into(), sr.address.clone()),
1921 ("nimbus".into(), sr.address.clone()),
1922 ("vrf".into(), sr.address.clone()),
1923 (
1924 "grandpa".into(),
1925 node.accounts.accounts["ed"].address.clone(),
1926 ),
1927 ("beefy".into(), node.accounts.accounts["ec"].address.clone()),
1928 ("eth".into(), node.accounts.accounts["eth"].address.clone()),
1929 ]
1930 .into();
1931
1932 let sr_stash = &node.accounts.accounts["sr_stash"];
1934 let node_key = get_node_keys(&node, SessionKeyType::Stash, false);
1935 assert_eq!(node_key.0, sr_stash.address);
1936 assert_eq!(node_key.1, sr_stash.address);
1937 assert_eq!(node_key.2, keys);
1938 let node_key = get_node_keys(&node, SessionKeyType::Default, false);
1940 assert_eq!(node_key.0, sr.address);
1941 assert_eq!(node_key.1, sr.address);
1942 assert_eq!(node_key.2, keys);
1943 }
1944
1945 #[test]
1946 fn get_node_keys_supports_asset_hub_polkadot() {
1947 let mut name = String::from("luca");
1948 let seed = format!("//{}{name}", name.remove(0).to_uppercase());
1949 let accounts = NodeAccounts {
1950 accounts: generators::generate_node_keys(&seed).unwrap(),
1951 seed,
1952 };
1953 let node = NodeSpec {
1954 name,
1955 accounts,
1956 ..Default::default()
1957 };
1958
1959 let node_key = get_node_keys(&node, SessionKeyType::default(), false);
1960 assert_eq!(node_key.2["aura"], node.accounts.accounts["sr"].address);
1961
1962 let node_key = get_node_keys(&node, SessionKeyType::default(), true);
1963 assert_eq!(node_key.2["aura"], node.accounts.accounts["ed"].address);
1964 }
1965}