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::{
24 chain_spec_key_types::{parse_chain_spec_key_types, ChainSpecKeyType},
25 errors::GeneratorError,
26};
27use crate::{
28 generators::keystore_key_types::KeyScheme,
29 network_spec::{node::NodeSpec, parachain::ParachainSpec, relaychain::RelaychainSpec},
30 ScopedFilesystem,
31};
32
33const ZOMBIE_KEY: &str = "5FTcLfwFc7ctvqp3RhbEig6UuHLHcHVRujuUm8r21wy4dAR8";
35
36#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
38pub enum Context {
39 Relay,
40 Para { relay_chain: Chain, para_id: ParaId },
41}
42
43#[derive(Debug, Clone, Copy)]
45enum ChainSpecFormat {
46 Plain,
47 Raw,
48}
49#[derive(Debug, Clone, Copy)]
51enum KeyType {
52 Session,
53 Aura,
54 Grandpa,
55}
56
57#[derive(Debug, Clone, Copy, Default)]
58enum SessionKeyType {
59 #[default]
61 Default,
62 Stash,
64 Evm,
66}
67
68type MaybeExpectedPath = Option<PathBuf>;
69
70#[derive(Debug, Clone, Serialize, Deserialize)]
71pub enum CommandInContext {
72 Local(String, MaybeExpectedPath),
73 Remote(String, MaybeExpectedPath),
74}
75
76impl CommandInContext {
77 fn cmd(&self) -> &str {
78 match self {
79 CommandInContext::Local(cmd, _) | CommandInContext::Remote(cmd, _) => cmd.as_ref(),
80 }
81 }
82}
83
84#[derive(Debug)]
85pub struct ParaGenesisConfig<T: AsRef<Path>> {
86 pub(crate) state_path: T,
87 pub(crate) wasm_path: T,
88 pub(crate) id: u32,
89 pub(crate) as_parachain: bool,
90}
91
92const DEFAULT_PRESETS_TO_CHECK: [&str; 3] = ["local_testnet", "development", "dev"];
96
97#[derive(Debug, Clone, Serialize, Deserialize)]
105pub struct ChainSpec {
106 chain_spec_name: String,
108 asset_location: Option<AssetLocation>,
110 runtime: Option<ChainSpecRuntime>,
112 maybe_plain_path: Option<PathBuf>,
113 chain_name: Option<String>,
114 raw_path: Option<PathBuf>,
115 command: Option<CommandInContext>,
117 image: Option<String>,
119 context: Context,
121}
122
123impl ChainSpec {
124 pub(crate) fn new(chain_spec_name: impl Into<String>, context: Context) -> Self {
125 Self {
126 chain_spec_name: chain_spec_name.into(),
127 chain_name: None,
128 maybe_plain_path: None,
129 asset_location: None,
130 runtime: None,
131 raw_path: None,
132 command: None,
133 image: None,
134 context,
135 }
136 }
137
138 pub(crate) fn chain_spec_name(&self) -> &str {
139 self.chain_spec_name.as_ref()
140 }
141
142 pub(crate) fn chain_name(&self) -> Option<&str> {
143 self.chain_name.as_deref()
144 }
145
146 pub(crate) fn set_chain_name(mut self, chain_name: impl Into<String>) -> Self {
147 self.chain_name = Some(chain_name.into());
148 self
149 }
150
151 pub(crate) fn asset_location(mut self, location: AssetLocation) -> Self {
152 self.asset_location = Some(location);
153 self
154 }
155
156 pub(crate) fn runtime(mut self, chain_spec_runtime: ChainSpecRuntime) -> Self {
157 self.runtime = Some(chain_spec_runtime);
158 self
159 }
160
161 pub(crate) fn command(
162 mut self,
163 command: impl Into<String>,
164 is_local: bool,
165 expected_path: Option<&str>,
166 ) -> Self {
167 let maybe_expected_path = expected_path.map(PathBuf::from);
168 let cmd = if is_local {
169 CommandInContext::Local(command.into(), maybe_expected_path)
170 } else {
171 CommandInContext::Remote(command.into(), maybe_expected_path)
172 };
173 self.command = Some(cmd);
174 self
175 }
176
177 pub(crate) fn image(mut self, image: Option<String>) -> Self {
178 self.image = image;
179 self
180 }
181
182 pub async fn build<'a, T>(
190 &mut self,
191 ns: &DynNamespace,
192 scoped_fs: &ScopedFilesystem<'a, T>,
193 ) -> Result<(), GeneratorError>
194 where
195 T: FileSystem,
196 {
197 if self.asset_location.is_none() && self.command.is_none() && self.runtime.is_none() {
198 return Err(GeneratorError::ChainSpecGeneration(
199 "Can not build the chain spec without set the command, asset_location or runtime"
200 .to_string(),
201 ));
202 }
203
204 let maybe_plain_spec_path = PathBuf::from(format!("{}-plain.json", self.chain_spec_name));
205
206 if let Some(location) = self.asset_location.as_ref() {
208 let maybe_plain_spec_full_path = scoped_fs.full_path(maybe_plain_spec_path.as_path());
209 location
210 .dump_asset(maybe_plain_spec_full_path)
211 .await
212 .map_err(|e| {
213 GeneratorError::ChainSpecGeneration(format!(
214 "Error {e} dumping location {location:?}"
215 ))
216 })?;
217 } else if let Some(runtime) = self.runtime.as_ref() {
218 trace!(
219 "Creating chain-spec with runtime from localtion: {}",
220 runtime.location
221 );
222 let runtime_file_name = PathBuf::from(format!("{}-runtime.wasm", self.chain_spec_name));
224 let runtime_path_ns = scoped_fs.full_path(runtime_file_name.as_path());
225 runtime
226 .location
227 .dump_asset(runtime_path_ns)
228 .await
229 .map_err(|e| {
230 GeneratorError::ChainSpecGeneration(format!(
231 "Error {e} dumping location {:?}",
232 runtime.location
233 ))
234 })?;
235
236 let runtime_code = scoped_fs.read(runtime_file_name.as_path()).await?;
238
239 let caller: GenesisConfigBuilderRuntimeCaller =
240 GenesisConfigBuilderRuntimeCaller::new(&runtime_code[..]);
241 let presets = caller.preset_names().map_err(|e| {
242 GeneratorError::ChainSpecGeneration(format!(
243 "getting default config from runtime should work: {e}"
244 ))
245 })?;
246
247 let preset_to_check = if let Some(preset) = &runtime.preset {
254 [vec![preset.as_str()], DEFAULT_PRESETS_TO_CHECK.to_vec()].concat()
255 } else {
256 DEFAULT_PRESETS_TO_CHECK.to_vec()
257 };
258 let preset = preset_to_check
259 .iter()
260 .find(|preset| presets.iter().any(|item| item == *preset));
261
262 trace!("presets: {:?} - preset to use: {:?}", presets, preset);
263 let builder = if let Some(preset) = preset {
264 GenericChainSpec::<()>::builder(&runtime_code[..], ())
265 .with_genesis_config_preset_name(preset)
266 } else {
267 let default_config = caller.get_default_config().map_err(|e| {
269 GeneratorError::ChainSpecGeneration(format!(
270 "getting default config from runtime should work: {e}"
271 ))
272 })?;
273
274 GenericChainSpec::<()>::builder(&runtime_code[..], ())
275 .with_genesis_config(default_config)
276 };
277
278 let builder = if let Context::Para {
279 relay_chain: _,
280 para_id: _,
281 } = &self.context
282 {
283 builder.with_id(self.chain_spec_name())
284 } else {
285 builder
286 };
287
288 let builder = if let Some(chain_name) = self.chain_name.as_ref() {
289 builder.with_name(chain_name)
290 } else {
291 builder
292 };
293
294 let chain_spec = builder.build();
295
296 let contents = chain_spec.as_json(false).map_err(|e| {
297 GeneratorError::ChainSpecGeneration(format!(
298 "getting chain-spec as json should work, err: {e}"
299 ))
300 })?;
301
302 scoped_fs.write(&maybe_plain_spec_path, contents).await?;
303 } else {
304 trace!("Creating chain-spec with command");
305 let mut replacement_value = String::default();
307 if let Some(chain_name) = self.chain_name.as_ref() {
308 if !chain_name.is_empty() {
309 replacement_value.clone_from(chain_name);
310 }
311 };
312
313 let sanitized_cmd = if replacement_value.is_empty() {
316 self.command.as_ref().unwrap().cmd().replace("--chain", "")
318 } else {
319 self.command.as_ref().unwrap().cmd().to_owned()
320 };
321
322 let full_cmd = apply_replacements(
323 &sanitized_cmd,
324 &HashMap::from([("chainName", replacement_value.as_str())]),
325 );
326 trace!("full_cmd: {:?}", full_cmd);
327
328 let parts: Vec<&str> = full_cmd.split_whitespace().collect();
329 let Some((cmd, args)) = parts.split_first() else {
330 return Err(GeneratorError::ChainSpecGeneration(format!(
331 "Invalid generator command: {full_cmd}"
332 )));
333 };
334 trace!("cmd: {:?} - args: {:?}", cmd, args);
335
336 let generate_command =
337 GenerateFileCommand::new(cmd, maybe_plain_spec_path.clone()).args(args);
338 if let Some(cmd) = &self.command {
339 match cmd {
340 CommandInContext::Local(_, expected_path) => {
341 build_locally(generate_command, scoped_fs, expected_path.as_deref()).await?
342 },
343 CommandInContext::Remote(_, expected_path) => {
344 let options = GenerateFilesOptions::new(
345 vec![generate_command],
346 self.image.clone(),
347 expected_path.clone(),
348 );
349 ns.generate_files(options).await?;
350 },
351 }
352 }
353 }
354
355 if is_raw(maybe_plain_spec_path.clone(), scoped_fs).await? {
357 let spec_path = PathBuf::from(format!("{}.json", self.chain_spec_name));
358 let tf_file = TransferedFile::new(
359 &PathBuf::from_iter([ns.base_dir(), &maybe_plain_spec_path]),
360 &spec_path,
361 );
362 scoped_fs.copy_files(vec![&tf_file]).await.map_err(|e| {
363 GeneratorError::ChainSpecGeneration(format!(
364 "Error copying file: {tf_file}, err: {e}"
365 ))
366 })?;
367
368 self.raw_path = Some(spec_path);
369 } else {
370 self.maybe_plain_path = Some(maybe_plain_spec_path);
371 }
372 Ok(())
373 }
374
375 pub async fn build_raw<'a, T>(
376 &mut self,
377 ns: &DynNamespace,
378 scoped_fs: &ScopedFilesystem<'a, T>,
379 relay_chain_id: Option<Chain>,
380 ) -> Result<(), GeneratorError>
381 where
382 T: FileSystem,
383 {
384 warn!("Building raw version from {:?}", self);
385 let None = self.raw_path else {
387 return Ok(());
388 };
389
390 let raw_spec_path = PathBuf::from(format!("{}.json", self.chain_spec_name));
392
393 match self
394 .try_build_raw_with_generic(scoped_fs, relay_chain_id.clone(), raw_spec_path.as_path())
395 .await
396 {
397 Ok(_) => return Ok(()),
398 Err(err) => {
399 if Self::should_retry_with_command(&err) && self.command.is_some() {
400 warn!(
401 "GenericChainSpec raw generation failed ({}). Falling back to command execution.",
402 err
403 );
404 } else {
405 return Err(err);
406 }
407 },
408 }
409
410 self.build_raw_with_command(ns, scoped_fs, raw_spec_path, relay_chain_id)
411 .await?;
412
413 Ok(())
414 }
415
416 async fn try_build_raw_with_generic<'a, T>(
417 &mut self,
418 scoped_fs: &ScopedFilesystem<'a, T>,
419 relay_chain_id: Option<Chain>,
420 raw_spec_path: &Path,
421 ) -> Result<(), GeneratorError>
422 where
423 T: FileSystem,
424 {
425 let (json_content, _) = self.read_spec(scoped_fs).await?;
427 let json_bytes: Vec<u8> = json_content.as_bytes().into();
428 let chain_spec = GenericChainSpec::<()>::from_json_bytes(json_bytes).map_err(|e| {
429 GeneratorError::ChainSpecGeneration(format!(
430 "Error loading chain-spec from json_bytes, err: {e}"
431 ))
432 })?;
433
434 self.raw_path = Some(raw_spec_path.to_path_buf());
435 let contents = chain_spec.as_json(true).map_err(|e| {
436 GeneratorError::ChainSpecGeneration(format!(
437 "getting chain-spec as json should work, err: {e}"
438 ))
439 })?;
440 let contents = self
441 .ensure_para_fields_in_raw(&contents, relay_chain_id)
442 .await?;
443 self.write_spec(scoped_fs, contents).await?;
444
445 Ok(())
446 }
447
448 async fn build_raw_with_command<'a, T>(
449 &mut self,
450 ns: &DynNamespace,
451 scoped_fs: &ScopedFilesystem<'a, T>,
452 raw_spec_path: PathBuf,
453 relay_chain_id: Option<Chain>,
454 ) -> Result<(), GeneratorError>
455 where
456 T: FileSystem,
457 {
458 let temp_name = format!(
460 "temp-build-raw-{}-{}",
461 self.chain_spec_name,
462 rand::random::<u8>()
463 );
464
465 let cmd = self
466 .command
467 .as_ref()
468 .ok_or(GeneratorError::ChainSpecGeneration(
469 "Invalid command".into(),
470 ))?;
471 let maybe_plain_path =
472 self.maybe_plain_path
473 .as_ref()
474 .ok_or(GeneratorError::ChainSpecGeneration(
475 "Invalid plain path".into(),
476 ))?;
477
478 let chain_spec_path_local = format!(
480 "{}/{}",
481 ns.base_dir().to_string_lossy(),
482 maybe_plain_path.display()
483 );
484 let chain_spec_path_in_pod = format!("{}/{}", NODE_CONFIG_DIR, maybe_plain_path.display());
486 let chain_spec_path_in_args = if matches!(self.command, Some(CommandInContext::Local(_, _)))
488 {
489 chain_spec_path_local.clone()
490 } else if ns.capabilities().prefix_with_full_path {
491 format!(
493 "{}/{}{}",
494 ns.base_dir().to_string_lossy(),
495 &temp_name,
496 &chain_spec_path_in_pod
497 )
498 } else {
499 chain_spec_path_in_pod.clone()
500 };
501
502 let mut full_cmd = apply_replacements(
503 cmd.cmd(),
504 &HashMap::from([("chainName", chain_spec_path_in_args.as_str())]),
505 );
506
507 if !full_cmd.contains("--raw") {
508 full_cmd = format!("{full_cmd} --raw");
509 }
510 trace!("full_cmd: {:?}", full_cmd);
511
512 let parts: Vec<&str> = full_cmd.split_whitespace().collect();
513 let Some((cmd, args)) = parts.split_first() else {
514 return Err(GeneratorError::ChainSpecGeneration(format!(
515 "Invalid generator command: {full_cmd}"
516 )));
517 };
518 trace!("cmd: {:?} - args: {:?}", cmd, args);
519
520 let generate_command = GenerateFileCommand::new(cmd, raw_spec_path.clone()).args(args);
521
522 if let Some(cmd) = &self.command {
523 match cmd {
524 CommandInContext::Local(_, expected_path) => {
525 build_locally(generate_command, scoped_fs, expected_path.as_deref()).await?
526 },
527 CommandInContext::Remote(_, expected_path) => {
528 let options = GenerateFilesOptions::with_files(
529 vec![generate_command],
530 self.image.clone(),
531 &[TransferedFile::new(
532 chain_spec_path_local,
533 chain_spec_path_in_pod,
534 )],
535 expected_path.clone(),
536 )
537 .temp_name(temp_name);
538 trace!("calling generate_files with options: {:#?}", options);
539 ns.generate_files(options).await?;
540 },
541 }
542 }
543
544 self.raw_path = Some(raw_spec_path.clone());
545 let (content, _) = self.read_spec(scoped_fs).await?;
546 let content = self
547 .ensure_para_fields_in_raw(&content, relay_chain_id)
548 .await?;
549 self.write_spec(scoped_fs, content).await?;
550
551 Ok(())
552 }
553
554 async fn ensure_para_fields_in_raw(
555 &mut self,
556 content: &str,
557 relay_chain_id: Option<Chain>,
558 ) -> Result<String, GeneratorError> {
559 if let Context::Para {
560 relay_chain: _,
561 para_id,
562 } = &self.context
563 {
564 let mut chain_spec_json: serde_json::Value =
565 serde_json::from_str(content).map_err(|e| {
566 GeneratorError::ChainSpecGeneration(format!(
567 "getting chain-spec as json should work, err: {e}"
568 ))
569 })?;
570
571 let mut needs_write = false;
572
573 if chain_spec_json["relay_chain"].is_null() {
574 chain_spec_json["relay_chain"] = json!(relay_chain_id);
575 needs_write = true;
576 }
577
578 if chain_spec_json["para_id"].is_null() {
579 chain_spec_json["para_id"] = json!(para_id);
580 needs_write = true;
581 }
582
583 if needs_write {
584 let contents = serde_json::to_string_pretty(&chain_spec_json).map_err(|e| {
585 GeneratorError::ChainSpecGeneration(format!(
586 "getting chain-spec json as pretty string should work, err: {e}"
587 ))
588 })?;
589 return Ok(contents);
590 }
591 }
592
593 Ok(content.to_string())
594 }
595
596 fn should_retry_with_command(err: &GeneratorError) -> bool {
597 match err {
598 GeneratorError::ChainSpecGeneration(msg) => {
599 let msg_lower = msg.to_lowercase();
600 msg_lower.contains("genesisbuilder_get_preset") || msg_lower.contains("_get_preset")
601 },
602 _ => false,
603 }
604 }
605
606 pub async fn override_code<'a, T>(
608 &mut self,
609 scoped_fs: &ScopedFilesystem<'a, T>,
610 wasm_override: &AssetLocation,
611 ) -> Result<(), GeneratorError>
612 where
613 T: FileSystem,
614 {
615 let Some(_) = self.raw_path else {
617 return Err(GeneratorError::OverridingWasm(String::from(
618 "Raw path should be set at this point.",
619 )));
620 };
621 let (content, _) = self.read_spec(scoped_fs).await?;
622 let override_content = wasm_override.get_asset().await.map_err(|_| {
624 GeneratorError::OverridingWasm(format!(
625 "Can not get asset to override wasm, asset: {wasm_override}"
626 ))
627 })?;
628
629 let mut chain_spec_json: serde_json::Value =
631 serde_json::from_str(&content).map_err(|_| {
632 GeneratorError::ChainSpecGeneration("Can not parse chain-spec as json".into())
633 })?;
634
635 let Some(code) = chain_spec_json.pointer_mut("/genesis/raw/top/0x3a636f6465") else {
637 return Err(GeneratorError::OverridingWasm(String::from(
638 "Pointer '/genesis/raw/top/0x3a636f6465' should be valid in the raw spec.",
639 )));
640 };
641
642 info!(
643 "🖋 Overriding ':code' (0x3a636f6465) in raw chain-spec with content of {}",
644 wasm_override
645 );
646 *code = json!(format!("0x{}", hex::encode(override_content)));
647
648 let overrided_content = serde_json::to_string_pretty(&chain_spec_json).map_err(|_| {
649 GeneratorError::ChainSpecGeneration("can not parse chain-spec value as json".into())
650 })?;
651 self.write_spec(scoped_fs, overrided_content).await?;
653
654 Ok(())
655 }
656
657 pub async fn override_raw_spec<'a, T>(
658 &mut self,
659 scoped_fs: &ScopedFilesystem<'a, T>,
660 raw_spec_overrides: &JsonOverrides,
661 ) -> Result<(), GeneratorError>
662 where
663 T: FileSystem,
664 {
665 let Some(_) = self.raw_path else {
667 return Err(GeneratorError::OverridingRawSpec(String::from(
668 "Raw path should be set at this point.",
669 )));
670 };
671
672 let (content, _) = self.read_spec(scoped_fs).await?;
673
674 let override_content: serde_json::Value = raw_spec_overrides.get().await.map_err(|_| {
676 GeneratorError::OverridingRawSpec(format!(
677 "Can not parse raw_spec_override contents as json: {raw_spec_overrides}"
678 ))
679 })?;
680
681 let mut chain_spec_json: serde_json::Value =
683 serde_json::from_str(&content).map_err(|_| {
684 GeneratorError::ChainSpecGeneration("Can not parse chain-spec as json".into())
685 })?;
686
687 merge(&mut chain_spec_json, &override_content);
689
690 let overrided_content = serde_json::to_string_pretty(&chain_spec_json).map_err(|_| {
692 GeneratorError::ChainSpecGeneration("can not parse chain-spec value as json".into())
693 })?;
694 self.write_spec(scoped_fs, overrided_content).await?;
695
696 Ok(())
697 }
698
699 pub fn raw_path(&self) -> Option<&Path> {
700 self.raw_path.as_deref()
701 }
702
703 pub fn set_asset_location(&mut self, location: AssetLocation) {
704 self.asset_location = Some(location)
705 }
706
707 pub async fn read_chain_id<'a, T>(
708 &self,
709 scoped_fs: &ScopedFilesystem<'a, T>,
710 ) -> Result<String, GeneratorError>
711 where
712 T: FileSystem,
713 {
714 let (content, _) = self.read_spec(scoped_fs).await?;
715 ChainSpec::chain_id_from_spec(&content)
716 }
717
718 async fn read_spec<'a, T>(
719 &self,
720 scoped_fs: &ScopedFilesystem<'a, T>,
721 ) -> Result<(String, ChainSpecFormat), GeneratorError>
722 where
723 T: FileSystem,
724 {
725 let (path, format) = match (self.maybe_plain_path.as_ref(), self.raw_path.as_ref()) {
726 (Some(path), None) => (path, ChainSpecFormat::Plain),
727 (None, Some(path)) => (path, ChainSpecFormat::Raw),
728 (Some(_), Some(path)) => {
729 (path, ChainSpecFormat::Raw)
731 },
732 (None, None) => unreachable!(),
733 };
734
735 let content = scoped_fs.read_to_string(path.clone()).await.map_err(|_| {
736 GeneratorError::ChainSpecGeneration(format!(
737 "Can not read chain-spec from {}",
738 path.to_string_lossy()
739 ))
740 })?;
741
742 Ok((content, format))
743 }
744
745 async fn write_spec<'a, T>(
746 &self,
747 scoped_fs: &ScopedFilesystem<'a, T>,
748 content: impl Into<String>,
749 ) -> Result<(), GeneratorError>
750 where
751 T: FileSystem,
752 {
753 let (path, _format) = match (self.maybe_plain_path.as_ref(), self.raw_path.as_ref()) {
754 (Some(path), None) => (path, ChainSpecFormat::Plain),
755 (None, Some(path)) => (path, ChainSpecFormat::Raw),
756 (Some(_), Some(path)) => {
757 (path, ChainSpecFormat::Raw)
759 },
760 (None, None) => unreachable!(),
761 };
762
763 scoped_fs.write(path, content.into()).await.map_err(|_| {
764 GeneratorError::ChainSpecGeneration(format!(
765 "Can not write chain-spec from {}",
766 path.to_string_lossy()
767 ))
768 })?;
769
770 Ok(())
771 }
772
773 pub async fn customize_para<'a, T>(
775 &self,
776 para: &ParachainSpec,
777 relay_chain_id: &str,
778 scoped_fs: &ScopedFilesystem<'a, T>,
779 ) -> Result<(), GeneratorError>
780 where
781 T: FileSystem,
782 {
783 let (content, format) = self.read_spec(scoped_fs).await?;
784 let mut chain_spec_json: serde_json::Value =
785 serde_json::from_str(&content).map_err(|_| {
786 GeneratorError::ChainSpecGeneration("Can not parse chain-spec as json".into())
787 })?;
788
789 if let Some(para_id) = chain_spec_json.get_mut("para_id") {
790 *para_id = json!(para.id);
791 };
792 if let Some(para_id) = chain_spec_json.get_mut("paraId") {
793 *para_id = json!(para.id);
794 };
795
796 if let Some(relay_chain_id_field) = chain_spec_json.get_mut("relay_chain") {
797 *relay_chain_id_field = json!(relay_chain_id);
798 };
799
800 if let ChainSpecFormat::Plain = format {
801 let pointer = get_runtime_config_pointer(&chain_spec_json)
802 .map_err(GeneratorError::ChainSpecGeneration)?;
803
804 if let Some(overrides) = ¶.genesis_overrides {
806 let percolated_overrides = percolate_overrides(&pointer, overrides)
807 .map_err(|e| GeneratorError::ChainSpecGeneration(e.to_string()))?;
808 if let Some(genesis) = chain_spec_json.pointer_mut(&pointer) {
809 merge(genesis, percolated_overrides);
810 }
811 }
812
813 clear_authorities(&pointer, &mut chain_spec_json, &self.context);
814
815 let key_type_to_use = if para.is_evm_based {
816 SessionKeyType::Evm
817 } else {
818 SessionKeyType::Default
819 };
820
821 let validators: Vec<&NodeSpec> = para
823 .collators
824 .iter()
825 .filter(|node| node.is_validator)
826 .collect();
827
828 if chain_spec_json
830 .pointer(&format!("{pointer}/session"))
831 .is_some()
832 {
833 add_authorities(&pointer, &mut chain_spec_json, &validators, key_type_to_use);
834 } else if chain_spec_json
835 .pointer(&format!("{pointer}/aura"))
836 .is_some()
837 {
838 add_aura_authorities(&pointer, &mut chain_spec_json, &validators, KeyType::Aura);
839 } else {
840 warn!("Can't customize keys, not `session` or `aura` find in the chain-spec file");
841 };
842
843 let invulnerables: Vec<&NodeSpec> = para
845 .collators
846 .iter()
847 .filter(|node| node.is_invulnerable)
848 .collect();
849
850 add_collator_selection(
851 &pointer,
852 &mut chain_spec_json,
853 &invulnerables,
854 key_type_to_use,
855 );
856
857 override_parachain_info(&pointer, &mut chain_spec_json, para.id);
859
860 let balances_to_add =
862 generate_balance_to_add_from_assets_pallet(&pointer, &chain_spec_json);
863 add_balances(&pointer, &mut chain_spec_json, balances_to_add);
864
865 let content = serde_json::to_string_pretty(&chain_spec_json).map_err(|_| {
867 GeneratorError::ChainSpecGeneration("can not parse chain-spec value as json".into())
868 })?;
869 self.write_spec(scoped_fs, content).await?;
870 } else {
871 warn!("⚠️ Chain spec for para_id: {} is in raw mode", para.id);
872 }
873 Ok(())
874 }
875
876 pub async fn customize_relay<'a, T, U>(
877 &self,
878 relaychain: &RelaychainSpec,
879 hrmp_channels: &[HrmpChannelConfig],
880 para_artifacts: Vec<ParaGenesisConfig<U>>,
881 scoped_fs: &ScopedFilesystem<'a, T>,
882 ) -> Result<(), GeneratorError>
883 where
884 T: FileSystem,
885 U: AsRef<Path>,
886 {
887 let (content, format) = self.read_spec(scoped_fs).await?;
888 let mut chain_spec_json: serde_json::Value =
889 serde_json::from_str(&content).map_err(|_| {
890 GeneratorError::ChainSpecGeneration("Can not parse chain-spec as json".into())
891 })?;
892
893 if let ChainSpecFormat::Plain = format {
894 let token_decimals =
896 if let Some(val) = chain_spec_json.pointer("/properties/tokenDecimals") {
897 let val = val.as_u64().unwrap_or(12);
898 if val > u8::MAX as u64 {
899 12
900 } else {
901 val as u8
902 }
903 } else {
904 12
905 };
906 let pointer = get_runtime_config_pointer(&chain_spec_json)
908 .map_err(GeneratorError::ChainSpecGeneration)?;
909
910 if let Some(overrides) = &relaychain.runtime_genesis_patch {
912 let percolated_overrides = percolate_overrides(&pointer, overrides)
913 .map_err(|e| GeneratorError::ChainSpecGeneration(e.to_string()))?;
914 if let Some(patch_section) = chain_spec_json.pointer_mut(&pointer) {
915 merge(patch_section, percolated_overrides);
916 }
917 }
918
919 let staking_min = get_staking_min(&pointer, &mut chain_spec_json);
921
922 clear_authorities(&pointer, &mut chain_spec_json, &self.context);
924
925 let mut balances_to_add =
927 generate_balance_to_add_from_nodes(&relaychain.nodes, staking_min);
928
929 balances_to_add.push((
932 ZOMBIE_KEY.to_string(),
933 1000 * 10_u128.pow(token_decimals as u32),
934 ));
935
936 add_balances(&pointer, &mut chain_spec_json, balances_to_add);
937
938 add_staking(
940 &pointer,
941 &mut chain_spec_json,
942 &relaychain.nodes,
943 staking_min,
944 );
945
946 let validators: Vec<&NodeSpec> = relaychain
948 .nodes
949 .iter()
950 .filter(|node| node.is_validator)
951 .collect();
952
953 if chain_spec_json
955 .pointer(&format!("{pointer}/session"))
956 .is_some()
957 {
958 add_authorities(
959 &pointer,
960 &mut chain_spec_json,
961 &validators,
962 SessionKeyType::Stash,
963 );
964 } else {
965 add_aura_authorities(&pointer, &mut chain_spec_json, &validators, KeyType::Aura);
966 add_grandpa_authorities(&pointer, &mut chain_spec_json, &validators, KeyType::Aura);
967 }
968
969 if !hrmp_channels.is_empty() {
972 add_hrmp_channels(&pointer, &mut chain_spec_json, hrmp_channels);
973 }
974
975 for para_genesis_config in para_artifacts.iter() {
977 add_parachain_to_genesis(
978 &pointer,
979 &mut chain_spec_json,
980 para_genesis_config,
981 scoped_fs,
982 )
983 .await
984 .map_err(|e| GeneratorError::ChainSpecGeneration(e.to_string()))?;
985 }
986
987 let content = serde_json::to_string_pretty(&chain_spec_json).map_err(|_| {
993 GeneratorError::ChainSpecGeneration("can not parse chain-spec value as json".into())
994 })?;
995 self.write_spec(scoped_fs, content).await?;
996 } else {
997 warn!(
998 "⚠️ Chain Spec for chain {} is in raw mode, can't customize.",
999 self.chain_spec_name
1000 );
1001 }
1002 Ok(())
1003 }
1004
1005 pub async fn add_bootnodes<'a, T>(
1006 &self,
1007 scoped_fs: &ScopedFilesystem<'a, T>,
1008 bootnodes: &[String],
1009 ) -> Result<(), GeneratorError>
1010 where
1011 T: FileSystem,
1012 {
1013 let (content, _) = self.read_spec(scoped_fs).await?;
1014 let mut chain_spec_json: serde_json::Value =
1015 serde_json::from_str(&content).map_err(|_| {
1016 GeneratorError::ChainSpecGeneration("Can not parse chain-spec as json".into())
1017 })?;
1018
1019 if let Some(bootnodes_on_file) = chain_spec_json.get_mut("bootNodes") {
1020 if let Some(bootnodes_on_file) = bootnodes_on_file.as_array_mut() {
1021 let mut bootnodes_to_add =
1022 bootnodes.iter().map(|bootnode| json!(bootnode)).collect();
1023 bootnodes_on_file.append(&mut bootnodes_to_add);
1024 } else {
1025 return Err(GeneratorError::ChainSpecGeneration(
1026 "id should be an string in the chain-spec, this is a bug".into(),
1027 ));
1028 };
1029 } else {
1030 return Err(GeneratorError::ChainSpecGeneration(
1031 "'bootNodes' should be a fields in the chain-spec of the relaychain".into(),
1032 ));
1033 };
1034
1035 let content = serde_json::to_string_pretty(&chain_spec_json).map_err(|_| {
1037 GeneratorError::ChainSpecGeneration("can not parse chain-spec value as json".into())
1038 })?;
1039 self.write_spec(scoped_fs, content).await?;
1040
1041 Ok(())
1042 }
1043
1044 pub fn chain_id_from_spec(spec_content: &str) -> Result<String, GeneratorError> {
1046 let chain_spec_json: serde_json::Value =
1047 serde_json::from_str(spec_content).map_err(|_| {
1048 GeneratorError::ChainSpecGeneration("Can not parse chain-spec as json".into())
1049 })?;
1050 if let Some(chain_id) = chain_spec_json.get("id") {
1051 if let Some(chain_id) = chain_id.as_str() {
1052 Ok(chain_id.to_string())
1053 } else {
1054 Err(GeneratorError::ChainSpecGeneration(
1055 "id should be an string in the chain-spec, this is a bug".into(),
1056 ))
1057 }
1058 } else {
1059 Err(GeneratorError::ChainSpecGeneration(
1060 "'id' should be a fields in the chain-spec of the relaychain".into(),
1061 ))
1062 }
1063 }
1064}
1065
1066type GenesisNodeKey = (String, String, HashMap<String, String>);
1067
1068async fn build_locally<'a, T>(
1069 generate_command: GenerateFileCommand,
1070 scoped_fs: &ScopedFilesystem<'a, T>,
1071 maybe_output: Option<&Path>,
1072) -> Result<(), GeneratorError>
1073where
1074 T: FileSystem,
1075{
1076 let result = Command::new(generate_command.program.clone())
1079 .args(generate_command.args.clone())
1080 .output()
1081 .await
1082 .map_err(|err| {
1083 GeneratorError::ChainSpecGeneration(format!(
1084 "Error running cmd: {} args: {}, err: {}",
1085 &generate_command.program,
1086 &generate_command.args.join(" "),
1087 err
1088 ))
1089 })?;
1090
1091 if result.status.success() {
1092 let raw_output = if let Some(output_path) = maybe_output {
1093 tokio::fs::read(output_path).await.map_err(|err| {
1094 GeneratorError::ChainSpecGeneration(format!(
1095 "Error reading output file at {}: {}",
1096 output_path.display(),
1097 err
1098 ))
1099 })?
1100 } else {
1101 result.stdout
1102 };
1103 scoped_fs
1104 .write(
1105 generate_command.local_output_path,
1106 String::from_utf8_lossy(&raw_output).to_string(),
1107 )
1108 .await?;
1109 Ok(())
1110 } else {
1111 Err(GeneratorError::ChainSpecGeneration(format!(
1112 "Error running cmd: {} args: {}, err: {}",
1113 &generate_command.program,
1114 &generate_command.args.join(" "),
1115 String::from_utf8_lossy(&result.stderr)
1116 )))
1117 }
1118}
1119
1120async fn is_raw<'a, T>(
1121 file: PathBuf,
1122 scoped_fs: &ScopedFilesystem<'a, T>,
1123) -> Result<bool, ProviderError>
1124where
1125 T: FileSystem,
1126{
1127 let content = scoped_fs.read_to_string(file).await?;
1128 let chain_spec_json: serde_json::Value = serde_json::from_str(&content).unwrap();
1129
1130 Ok(chain_spec_json.pointer("/genesis/raw/top").is_some())
1131}
1132
1133async fn add_parachain_to_genesis<'a, T, U>(
1136 runtime_config_ptr: &str,
1137 chain_spec_json: &mut serde_json::Value,
1138 para_genesis_config: &ParaGenesisConfig<U>,
1139 scoped_fs: &ScopedFilesystem<'a, T>,
1140) -> Result<(), anyhow::Error>
1141where
1142 T: FileSystem,
1143 U: AsRef<Path>,
1144{
1145 if let Some(val) = chain_spec_json.pointer_mut(runtime_config_ptr) {
1146 let paras_pointer = if val.get("paras").is_some() {
1147 "/paras/paras"
1148 } else if val.get("parachainsParas").is_some() {
1149 "/parachainsParas/paras"
1151 } else {
1152 val["paras"] = json!({ "paras": [] });
1154 "/paras/paras"
1155 };
1156
1157 let paras = val
1158 .pointer_mut(paras_pointer)
1159 .ok_or(anyhow!("paras pointer should be valid {paras_pointer:?} "))?;
1160 let paras_vec = paras
1161 .as_array_mut()
1162 .ok_or(anyhow!("paras should be an array"))?;
1163
1164 let head = scoped_fs
1165 .read_to_string(para_genesis_config.state_path.as_ref())
1166 .await?;
1167 let wasm = scoped_fs
1168 .read_to_string(para_genesis_config.wasm_path.as_ref())
1169 .await?;
1170
1171 paras_vec.push(json!([
1172 para_genesis_config.id,
1173 [head.trim(), wasm.trim(), para_genesis_config.as_parachain]
1174 ]));
1175
1176 Ok(())
1177 } else {
1178 unreachable!("pointer to runtime config should be valid!")
1179 }
1180}
1181
1182fn get_runtime_config_pointer(chain_spec_json: &serde_json::Value) -> Result<String, String> {
1183 let pointers = [
1186 "/genesis/runtimeGenesis/config",
1187 "/genesis/runtimeGenesis/patch",
1188 "/genesis/runtimeGenesisConfigPatch",
1189 "/genesis/runtime/runtime_genesis_config",
1190 "/genesis/runtime",
1191 ];
1192
1193 for pointer in pointers {
1194 if chain_spec_json.pointer(pointer).is_some() {
1195 return Ok(pointer.to_string());
1196 }
1197 }
1198
1199 Err("Can not find the runtime pointer".into())
1200}
1201
1202fn percolate_overrides<'a>(
1203 pointer: &str,
1204 overrides: &'a serde_json::Value,
1205) -> Result<&'a serde_json::Value, anyhow::Error> {
1206 let pointer_parts = pointer.split('/').collect::<Vec<&str>>();
1207 trace!("pointer_parts: {pointer_parts:?}");
1208
1209 let top_level = overrides
1210 .as_object()
1211 .ok_or_else(|| anyhow!("Overrides must be an object"))?;
1212 let top_level_key = top_level
1213 .keys()
1214 .next()
1215 .ok_or_else(|| anyhow!("Invalid override value: {overrides:?}"))?;
1216 trace!("top_level_key: {top_level_key}");
1217 let index = pointer_parts.iter().position(|x| *x == top_level_key);
1218 let Some(i) = index else {
1219 warn!("Top level key '{top_level_key}' isn't part of the pointer ({pointer}), returning without percolating");
1220 return Ok(overrides);
1221 };
1222
1223 let p = if i == pointer_parts.len() - 1 {
1224 let p = format!("/{}", pointer_parts[i]);
1226 trace!("overrides pointer {p}");
1227 p
1228 } else {
1229 let p = format!("/{}", pointer_parts[i..].join("/"));
1231 trace!("overrides pointer {p}");
1232 p
1233 };
1234 let overrides_to_use = overrides
1235 .pointer(&p)
1236 .ok_or_else(|| anyhow!("Invalid override value: {overrides:?}"))?;
1237 Ok(overrides_to_use)
1238}
1239
1240#[allow(dead_code)]
1241fn construct_runtime_pointer_from_overrides(
1242 overrides: &serde_json::Value,
1243) -> Result<String, anyhow::Error> {
1244 if overrides.get("genesis").is_some() {
1245 return Ok("/genesis".into());
1247 } else {
1248 if let Some(top_level) = overrides.as_object() {
1250 let k = top_level
1251 .keys()
1252 .next()
1253 .ok_or_else(|| anyhow!("Invalid override value: {overrides:?}"))?;
1254 match k.as_str() {
1255 "runtimeGenesisConfigPatch" | "runtime" | "runtimeGenesis" => {
1256 return Ok(("/genesis").into())
1257 },
1258 "config" | "path" => {
1259 return Ok(("/genesis/runtimeGenesis").into());
1260 },
1261 "runtime_genesis_config" => {
1262 return Ok(("/genesis/runtime").into());
1263 },
1264 _ => {},
1265 }
1266 }
1267 }
1268
1269 Err(anyhow!("Can not find the runtime pointer"))
1270}
1271
1272fn merge(patch_section: &mut serde_json::Value, overrides: &serde_json::Value) {
1274 trace!("patch: {:?}", patch_section);
1275 trace!("overrides: {:?}", overrides);
1276 if let (Some(genesis_obj), Some(overrides_obj)) =
1277 (patch_section.as_object_mut(), overrides.as_object())
1278 {
1279 for overrides_key in overrides_obj.keys() {
1280 trace!("overrides_key: {:?}", overrides_key);
1281 if let Some(genesis_value) = genesis_obj.get_mut(overrides_key) {
1283 match (&genesis_value, overrides_obj.get(overrides_key)) {
1284 (serde_json::Value::Object(_), Some(overrides_value))
1286 if overrides_value.is_object() =>
1287 {
1288 merge(genesis_value, overrides_value);
1289 },
1290 (_, Some(overrides_value)) => {
1292 trace!("overriding: {:?} / {:?}", genesis_value, overrides_value);
1293 *genesis_value = overrides_value.clone();
1294 },
1295 _ => {
1296 trace!("not match!");
1297 },
1298 }
1299 } else {
1300 warn!(
1302 "key: {overrides_key} not present in genesis_obj: {:?} (adding key)",
1303 genesis_obj
1304 );
1305 let overrides_value = overrides_obj.get(overrides_key).expect(&format!(
1306 "overrides_key {overrides_key} should be present in the overrides obj. qed"
1307 ));
1308 genesis_obj.insert(overrides_key.clone(), overrides_value.clone());
1309 }
1310 }
1311 }
1312}
1313
1314fn clear_authorities(
1315 runtime_config_ptr: &str,
1316 chain_spec_json: &mut serde_json::Value,
1317 ctx: &Context,
1318) {
1319 if let Some(val) = chain_spec_json.pointer_mut(runtime_config_ptr) {
1320 if val.get("session").is_some() {
1322 val["session"]["keys"] = json!([]);
1323 }
1324
1325 if val.get("aura").is_some() {
1326 val["aura"]["authorities"] = json!([]);
1327 }
1328
1329 if val.get("grandpa").is_some() {
1330 val["grandpa"]["authorities"] = json!([]);
1331 }
1332
1333 if val.get("collatorSelection").is_some() {
1335 val["collatorSelection"]["invulnerables"] = json!([]);
1336 }
1337
1338 if val.get("staking").is_some() && ctx == &Context::Relay {
1340 val["staking"]["invulnerables"] = json!([]);
1341 val["staking"]["stakers"] = json!([]);
1342
1343 if val["staking"]["devStakers"] == json!(null) {
1344 val["staking"]["validatorCount"] = json!(0);
1345 }
1346 }
1347 } else {
1348 unreachable!("pointer to runtime config should be valid!")
1349 }
1350}
1351
1352fn get_staking_min(runtime_config_ptr: &str, chain_spec_json: &mut serde_json::Value) -> u128 {
1353 let staking_ptr = format!("{runtime_config_ptr}/staking/stakers");
1355 if let Some(stakers) = chain_spec_json.pointer(&staking_ptr) {
1356 let min = stakers[0][2].clone();
1358 min.as_u64().unwrap_or(0).into()
1359 } else {
1360 0
1361 }
1362}
1363
1364fn add_balances(
1365 runtime_config_ptr: &str,
1366 chain_spec_json: &mut serde_json::Value,
1367 balances_to_add: Vec<(String, u128)>,
1368 ) {
1370 if let Some(val) = chain_spec_json.pointer_mut(runtime_config_ptr) {
1371 let Some(balances) = val.pointer("/balances/balances") else {
1372 warn!("NO 'balances' key in runtime config, skipping...");
1374 return;
1375 };
1376
1377 let mut balances_map = generate_balance_map(balances);
1379 for balance in balances_to_add {
1380 balances_map.insert(balance.0, balance.1);
1381 }
1382
1383 let new_balances: Vec<(&String, &u128)> =
1385 balances_map.iter().collect::<Vec<(&String, &u128)>>();
1386
1387 val["balances"]["balances"] = json!(new_balances);
1388 } else {
1389 unreachable!("pointer to runtime config should be valid!")
1390 }
1391}
1392
1393fn get_address_for_scheme(node: &NodeSpec, scheme: KeyScheme) -> String {
1395 let account_key = scheme.account_key();
1396 node.accounts
1397 .accounts
1398 .get(account_key)
1399 .expect(&format!(
1400 "'{}' account should be set at spec computation {THIS_IS_A_BUG}",
1401 account_key
1402 ))
1403 .address
1404 .clone()
1405}
1406
1407fn get_node_keys(
1408 node: &NodeSpec,
1409 session_key: SessionKeyType,
1410 asset_hub_polkadot: bool,
1411) -> GenesisNodeKey {
1412 let sr_account = node.accounts.accounts.get("sr").unwrap();
1413 let sr_stash = node.accounts.accounts.get("sr_stash").unwrap();
1414 let ed_account = node.accounts.accounts.get("ed").unwrap();
1415 let ec_account = node.accounts.accounts.get("ec").unwrap();
1416 let eth_account = node.accounts.accounts.get("eth").unwrap();
1417 let mut keys = HashMap::new();
1418 for k in [
1419 "babe",
1420 "im_online",
1421 "parachain_validator",
1422 "authority_discovery",
1423 "para_validator",
1424 "para_assignment",
1425 "aura",
1426 "nimbus",
1427 "vrf",
1428 ] {
1429 if k == "aura" && asset_hub_polkadot {
1430 keys.insert(k.to_string(), ed_account.address.clone());
1431 continue;
1432 }
1433 keys.insert(k.to_string(), sr_account.address.clone());
1434 }
1435
1436 keys.insert("grandpa".to_string(), ed_account.address.clone());
1437 keys.insert("beefy".to_string(), ec_account.address.clone());
1438 keys.insert("eth".to_string(), eth_account.public_key.clone());
1439
1440 let account_to_use = match session_key {
1441 SessionKeyType::Default => sr_account.address.clone(),
1442 SessionKeyType::Stash => sr_stash.address.clone(),
1443 SessionKeyType::Evm => format!("0x{}", eth_account.public_key),
1444 };
1445
1446 (account_to_use.clone(), account_to_use, keys)
1447}
1448
1449fn get_node_keys_with_custom_types(
1452 node: &NodeSpec,
1453 session_key: SessionKeyType,
1454 custom_key_types: &[ChainSpecKeyType],
1455) -> GenesisNodeKey {
1456 let sr_account = node.accounts.accounts.get("sr").unwrap();
1457 let sr_stash = node.accounts.accounts.get("sr_stash").unwrap();
1458 let eth_account = node.accounts.accounts.get("eth").unwrap();
1459
1460 let mut keys = HashMap::new();
1462 for key_type in custom_key_types {
1463 let scheme = key_type.scheme;
1464 let account_key = scheme.account_key();
1465 let address = node
1466 .accounts
1467 .accounts
1468 .get(account_key)
1469 .expect(&format!(
1470 "'{}' account should be set at spec computation {THIS_IS_A_BUG}",
1471 account_key
1472 ))
1473 .address
1474 .clone();
1475 keys.insert(key_type.key_name.clone(), address);
1476 }
1477
1478 let account_to_use = match session_key {
1479 SessionKeyType::Default => sr_account.address.clone(),
1480 SessionKeyType::Stash => sr_stash.address.clone(),
1481 SessionKeyType::Evm => format!("0x{}", eth_account.public_key),
1482 };
1483
1484 (account_to_use.clone(), account_to_use, keys)
1485}
1486
1487fn add_authorities(
1488 runtime_config_ptr: &str,
1489 chain_spec_json: &mut serde_json::Value,
1490 nodes: &[&NodeSpec],
1491 session_key: SessionKeyType,
1492) {
1493 let asset_hub_polkadot = chain_spec_json
1494 .get("id")
1495 .and_then(|v| v.as_str())
1496 .map(|id| id.starts_with("asset-hub-polkadot"))
1497 .unwrap_or_default();
1498 if let Some(val) = chain_spec_json.pointer_mut(runtime_config_ptr) {
1499 if let Some(session_keys) = val.pointer_mut("/session/keys") {
1500 let keys: Vec<GenesisNodeKey> = nodes
1501 .iter()
1502 .map(|node| {
1503 if let Some(custom_key_types) =
1504 parse_chain_spec_key_types(&node.chain_spec_key_types, asset_hub_polkadot)
1505 {
1506 get_node_keys_with_custom_types(node, session_key, &custom_key_types)
1507 } else {
1508 get_node_keys(node, session_key, asset_hub_polkadot)
1509 }
1510 })
1511 .collect();
1512 *session_keys = json!(keys);
1513 } else {
1514 warn!("⚠️ 'session/keys' key not present in runtime config.");
1515 }
1516 } else {
1517 unreachable!("pointer to runtime config should be valid!")
1518 }
1519}
1520fn add_hrmp_channels(
1521 runtime_config_ptr: &str,
1522 chain_spec_json: &mut serde_json::Value,
1523 hrmp_channels: &[HrmpChannelConfig],
1524) {
1525 if let Some(val) = chain_spec_json.pointer_mut(runtime_config_ptr) {
1526 if let Some(preopen_hrmp_channels) = val.pointer_mut("/hrmp/preopenHrmpChannels") {
1527 let hrmp_channels = hrmp_channels
1528 .iter()
1529 .map(|c| {
1530 (
1531 c.sender(),
1532 c.recipient(),
1533 c.max_capacity(),
1534 c.max_message_size(),
1535 )
1536 })
1537 .collect::<Vec<_>>();
1538 *preopen_hrmp_channels = json!(hrmp_channels);
1539 } else {
1540 warn!("⚠️ 'hrmp/preopenHrmpChannels' key not present in runtime config.");
1541 }
1542 } else {
1543 unreachable!("pointer to runtime config should be valid!")
1544 }
1545}
1546
1547fn add_aura_authorities(
1548 runtime_config_ptr: &str,
1549 chain_spec_json: &mut serde_json::Value,
1550 nodes: &[&NodeSpec],
1551 _key_type: KeyType,
1552) {
1553 if let Some(val) = chain_spec_json.pointer_mut(runtime_config_ptr) {
1554 if let Some(aura_authorities) = val.pointer_mut("/aura/authorities") {
1555 let keys: Vec<String> = nodes
1556 .iter()
1557 .map(|node| {
1558 node.accounts
1559 .accounts
1560 .get("sr")
1561 .expect(&format!(
1562 "'sr' account should be set at spec computation {THIS_IS_A_BUG}"
1563 ))
1564 .address
1565 .clone()
1566 })
1567 .collect();
1568 *aura_authorities = json!(keys);
1569 } else {
1570 warn!("⚠️ 'aura/authorities' key not present in runtime config.");
1571 }
1572 } else {
1573 unreachable!("pointer to runtime config should be valid!")
1574 }
1575}
1576
1577fn add_grandpa_authorities(
1578 runtime_config_ptr: &str,
1579 chain_spec_json: &mut serde_json::Value,
1580 nodes: &[&NodeSpec],
1581 _key_type: KeyType,
1582) {
1583 if let Some(val) = chain_spec_json.pointer_mut(runtime_config_ptr) {
1584 if let Some(grandpa_authorities) = val.pointer_mut("/grandpa/authorities") {
1585 let keys: Vec<(String, usize)> = nodes
1586 .iter()
1587 .map(|node| {
1588 (
1589 node.accounts
1590 .accounts
1591 .get("ed")
1592 .expect(&format!(
1593 "'ed' account should be set at spec computation {THIS_IS_A_BUG}"
1594 ))
1595 .address
1596 .clone(),
1597 1,
1598 )
1599 })
1600 .collect();
1601 *grandpa_authorities = json!(keys);
1602 } else {
1603 warn!("⚠️ 'grandpa/authorities' key not present in runtime config.");
1604 }
1605 } else {
1606 unreachable!("pointer to runtime config should be valid!")
1607 }
1608}
1609
1610fn add_staking(
1611 runtime_config_ptr: &str,
1612 chain_spec_json: &mut serde_json::Value,
1613 nodes: &Vec<NodeSpec>,
1614 staking_min: u128,
1615) {
1616 if let Some(val) = chain_spec_json.pointer_mut(runtime_config_ptr) {
1617 let Some(_) = val.pointer("/staking") else {
1618 warn!("NO 'staking' key in runtime config, skipping...");
1620 return;
1621 };
1622
1623 let mut stakers = vec![];
1624 let mut invulnerables = vec![];
1625 for node in nodes {
1626 let sr_stash_addr = &node
1627 .accounts
1628 .accounts
1629 .get("sr_stash")
1630 .expect("'sr_stash account should be defined for the node. qed")
1631 .address;
1632 stakers.push(json!([
1633 sr_stash_addr,
1634 sr_stash_addr,
1635 staking_min,
1636 "Validator"
1637 ]));
1638
1639 if node.is_invulnerable {
1640 invulnerables.push(sr_stash_addr);
1641 }
1642 }
1643
1644 val["staking"]["validatorCount"] = json!(stakers.len());
1645 val["staking"]["stakers"] = json!(stakers);
1646 val["staking"]["invulnerables"] = json!(invulnerables);
1647 } else {
1648 unreachable!("pointer to runtime config should be valid!")
1649 }
1650}
1651
1652fn override_parachain_info(
1659 runtime_config_ptr: &str,
1660 chain_spec_json: &mut serde_json::Value,
1661 para_id: u32,
1662) {
1663 if let Some(val) = chain_spec_json.pointer_mut(runtime_config_ptr) {
1664 if let Some(parachain_id) = val.pointer_mut("/parachainInfo/parachainId") {
1665 *parachain_id = json!(para_id)
1666 } else {
1667 }
1669 } else {
1670 unreachable!("pointer to runtime config should be valid!")
1671 }
1672}
1673
1674fn generate_balance_to_add_from_assets_pallet(
1675 runtime_config_ptr: &str,
1676 chain_spec_json: &serde_json::Value,
1677) -> Vec<(String, u128)> {
1678 if let Some(val) = chain_spec_json.pointer(runtime_config_ptr) {
1679 if let Some(assets_accounts) = val.pointer("/assets/accounts") {
1680 let assets_accounts = assets_accounts
1681 .as_array()
1682 .expect("assets_accounts config should be an array, qed");
1683 let accounts_to_add: Vec<(String, u128)> = assets_accounts
1684 .iter()
1685 .map(|account| {
1686 let account = account
1687 .as_array()
1688 .expect("assets_accounts config should be an array, qed");
1689 (
1691 account[1]
1692 .as_str()
1693 .expect("account should be a valid string. qed")
1694 .to_string(),
1695 account[2]
1696 .as_number()
1697 .expect("balance should be a valid str")
1698 .to_string()
1699 .parse::<u128>()
1700 .expect("balance should be a valid u128"),
1701 )
1702 })
1703 .collect();
1704 accounts_to_add
1705 } else {
1706 vec![]
1707 }
1708 } else {
1709 unreachable!("pointer to runtime config should be valid!")
1710 }
1711}
1712
1713fn add_collator_selection(
1714 runtime_config_ptr: &str,
1715 chain_spec_json: &mut serde_json::Value,
1716 nodes: &[&NodeSpec],
1717 session_key: SessionKeyType,
1718) {
1719 if let Some(val) = chain_spec_json.pointer_mut(runtime_config_ptr) {
1720 let key_type = if let SessionKeyType::Evm = session_key {
1721 "eth"
1722 } else {
1723 "sr"
1724 };
1725 let keys: Vec<String> = nodes
1726 .iter()
1727 .map(|node| {
1728 node.accounts
1729 .accounts
1730 .get(key_type)
1731 .expect(&format!(
1732 "'sr' account should be set at spec computation {THIS_IS_A_BUG}"
1733 ))
1734 .address
1735 .clone()
1736 })
1737 .collect();
1738
1739 if let Some(invulnerables) = val.pointer_mut("/collatorSelection/invulnerables") {
1741 *invulnerables = json!(keys);
1742 } else {
1743 debug!("⚠️ 'invulnerables' not present in spec, will not be customized");
1745 }
1746 } else {
1747 unreachable!("pointer to runtime config should be valid!")
1748 }
1749}
1750
1751fn generate_balance_map(balances: &serde_json::Value) -> HashMap<String, u128> {
1753 let balances_map: HashMap<String, u128> =
1755 serde_json::from_value::<Vec<(String, u128)>>(balances.to_owned())
1756 .unwrap()
1757 .iter()
1758 .fold(HashMap::new(), |mut memo, balance| {
1759 memo.insert(balance.0.clone(), balance.1);
1760 memo
1761 });
1762 balances_map
1763}
1764
1765fn generate_balance_to_add_from_nodes(
1766 nodes: &[NodeSpec],
1767 staking_min: u128,
1768) -> Vec<(String, u128)> {
1769 let mut balances_to_add = vec![];
1771
1772 for node in nodes {
1773 if node.initial_balance.eq(&0) {
1774 continue;
1775 };
1776
1777 let balance = std::cmp::max(node.initial_balance, staking_min * 2);
1779 for k in ["sr", "sr_stash"] {
1780 let account = node.accounts.accounts.get(k).unwrap();
1781 balances_to_add.push((account.address.clone(), balance));
1782 }
1783 }
1784 balances_to_add
1785}
1786
1787#[cfg(test)]
1788mod tests {
1789 use std::fs;
1790
1791 use configuration::HrmpChannelConfigBuilder;
1792
1793 use super::*;
1794 use crate::{generators, shared::types::NodeAccounts};
1795
1796 const ROCOCO_LOCAL_PLAIN_TESTING: &str = "./testing/rococo-local-plain.json";
1797 const ROCOCO_PENPAL_LOCAL_PLAIN_TESTING: &str = "./testing/rococo-penpal-local-plain.json";
1798
1799 fn chain_spec_test(file: &str) -> serde_json::Value {
1800 let content = fs::read_to_string(file).unwrap();
1801 serde_json::from_str(&content).unwrap()
1802 }
1803
1804 fn chain_spec_with_stake() -> serde_json::Value {
1805 json!({"genesis": {
1806 "runtimeGenesis" : {
1807 "patch": {
1808 "staking": {
1809 "forceEra": "NotForcing",
1810 "invulnerables": [
1811 "5GNJqTPyNqANBkUVMN1LPPrxXnFouWXoe2wNSmmEoLctxiZY",
1812 "5HpG9w8EBLe5XCrbczpwq5TSXvedjrBGCwqxK1iQ7qUsSWFc"
1813 ],
1814 "minimumValidatorCount": 1,
1815 "slashRewardFraction": 100000000,
1816 "stakers": [
1817 [
1818 "5GNJqTPyNqANBkUVMN1LPPrxXnFouWXoe2wNSmmEoLctxiZY",
1819 "5GNJqTPyNqANBkUVMN1LPPrxXnFouWXoe2wNSmmEoLctxiZY",
1820 100000000000001_u128,
1821 "Validator"
1822 ],
1823 [
1824 "5HpG9w8EBLe5XCrbczpwq5TSXvedjrBGCwqxK1iQ7qUsSWFc",
1825 "5HpG9w8EBLe5XCrbczpwq5TSXvedjrBGCwqxK1iQ7qUsSWFc",
1826 100000000000000_u128,
1827 "Validator"
1828 ]
1829 ],
1830 "validatorCount": 2
1831 },
1832 }
1833 }
1834 }})
1835 }
1836
1837 fn chain_spec_with_dev_stakers() -> serde_json::Value {
1838 json!({"genesis": {
1839 "runtimeGenesis" : {
1840 "patch": {
1841 "staking": {
1842 "activeEra": [
1843 0,
1844 0,
1845 0
1846 ],
1847 "canceledPayout": 0,
1848 "devStakers": [
1849 2000,
1850 25000
1851 ],
1852 "forceEra": "NotForcing",
1853 "invulnerables": [],
1854 "maxNominatorCount": null,
1855 "maxValidatorCount": null,
1856 "minNominatorBond": 0,
1857 "minValidatorBond": 0,
1858 "slashRewardFraction": 0,
1859 "stakers": [],
1860 "validatorCount": 500
1861 },
1862 }
1863 }
1864 }})
1865 }
1866
1867 #[test]
1868 fn get_min_stake_works() {
1869 let mut chain_spec_json = chain_spec_with_stake();
1870
1871 let pointer = get_runtime_config_pointer(&chain_spec_json).unwrap();
1872 let min = get_staking_min(&pointer, &mut chain_spec_json);
1873
1874 assert_eq!(100000000000001, min);
1875 }
1876
1877 #[test]
1878 fn dev_stakers_not_override_count_works() {
1879 let mut chain_spec_json = chain_spec_with_dev_stakers();
1880
1881 let pointer = get_runtime_config_pointer(&chain_spec_json).unwrap();
1882 clear_authorities(&pointer, &mut chain_spec_json, &Context::Relay);
1883
1884 let validator_count = chain_spec_json
1885 .pointer(&format!("{pointer}/staking/validatorCount"))
1886 .unwrap();
1887 assert_eq!(validator_count, &json!(500));
1888 }
1889
1890 #[test]
1891 fn dev_stakers_override_count_works() {
1892 let mut chain_spec_json = chain_spec_with_stake();
1893
1894 let pointer = get_runtime_config_pointer(&chain_spec_json).unwrap();
1895 clear_authorities(&pointer, &mut chain_spec_json, &Context::Relay);
1896
1897 let validator_count = chain_spec_json
1898 .pointer(&format!("{pointer}/staking/validatorCount"))
1899 .unwrap();
1900 assert_eq!(validator_count, &json!(0));
1901 }
1902
1903 #[test]
1904 fn overrides_from_toml_works() {
1905 use serde::{Deserialize, Serialize};
1906
1907 #[derive(Debug, Serialize, Deserialize)]
1908 struct MockConfig {
1909 #[serde(rename = "genesis", skip_serializing_if = "Option::is_none")]
1910 genesis_overrides: Option<serde_json::Value>,
1911 }
1912
1913 let mut chain_spec_json = chain_spec_test(ROCOCO_LOCAL_PLAIN_TESTING);
1914 const TOML: &str = "[genesis.runtime.balances]
1916 devAccounts = [
1917 20000,
1918 1000000000000000000,
1919 \"//Sender//{}\"
1920 ]";
1921 let override_toml: MockConfig = toml::from_str(TOML).unwrap();
1922 let overrides = override_toml.genesis_overrides.unwrap();
1923 let pointer = get_runtime_config_pointer(&chain_spec_json).unwrap();
1924
1925 let percolated_overrides = percolate_overrides(&pointer, &overrides)
1926 .map_err(|e| GeneratorError::ChainSpecGeneration(e.to_string()))
1927 .unwrap();
1928 trace!("percolated_overrides: {:#?}", percolated_overrides);
1929 if let Some(genesis) = chain_spec_json.pointer_mut(&pointer) {
1930 merge(genesis, percolated_overrides);
1931 }
1932
1933 trace!("chain spec: {chain_spec_json:#?}");
1934 assert!(chain_spec_json
1935 .pointer("/genesis/runtime/balances/devAccounts")
1936 .is_some());
1937 }
1938
1939 #[test]
1940 fn add_balances_works() {
1941 let mut spec_plain = chain_spec_test(ROCOCO_LOCAL_PLAIN_TESTING);
1942 let mut name = String::from("luca");
1943 let initial_balance = 1_000_000_000_000_u128;
1944 let seed = format!("//{}{name}", name.remove(0).to_uppercase());
1945 let accounts = NodeAccounts {
1946 accounts: generators::generate_node_keys(&seed).unwrap(),
1947 seed,
1948 };
1949 let node = NodeSpec {
1950 name,
1951 accounts,
1952 initial_balance,
1953 ..Default::default()
1954 };
1955
1956 let nodes = vec![node];
1957 let balances_to_add = generate_balance_to_add_from_nodes(&nodes, 0);
1958 add_balances("/genesis/runtime", &mut spec_plain, balances_to_add);
1959
1960 let new_balances = spec_plain
1961 .pointer("/genesis/runtime/balances/balances")
1962 .unwrap();
1963
1964 let balances_map = generate_balance_map(new_balances);
1965
1966 let sr = nodes[0].accounts.accounts.get("sr").unwrap();
1968 let sr_stash = nodes[0].accounts.accounts.get("sr_stash").unwrap();
1969 assert_eq!(balances_map.get(&sr.address).unwrap(), &initial_balance);
1970 assert_eq!(
1971 balances_map.get(&sr_stash.address).unwrap(),
1972 &initial_balance
1973 );
1974 }
1975
1976 #[test]
1977 fn add_balances_ensure_zombie_account() {
1978 let mut spec_plain = chain_spec_test(ROCOCO_LOCAL_PLAIN_TESTING);
1979
1980 let balances = spec_plain
1981 .pointer("/genesis/runtime/balances/balances")
1982 .unwrap();
1983 let balances_map = generate_balance_map(balances);
1984
1985 let nodes: Vec<NodeSpec> = vec![];
1986 let mut balances_to_add = generate_balance_to_add_from_nodes(&nodes, 0);
1987 balances_to_add.push((ZOMBIE_KEY.to_string(), 1000 * 10_u128.pow(12)));
1988 add_balances("/genesis/runtime", &mut spec_plain, balances_to_add);
1989
1990 let new_balances = spec_plain
1991 .pointer("/genesis/runtime/balances/balances")
1992 .unwrap();
1993
1994 let new_balances_map = generate_balance_map(new_balances);
1995
1996 assert!(new_balances_map.contains_key(ZOMBIE_KEY));
1998 assert_eq!(
1999 new_balances_map.len(),
2000 balances_map.len() + 1,
2001 "Number of balances should includes one more key (zombie key)."
2002 );
2003 }
2004
2005 #[test]
2006 fn add_balances_spec_without_balances() {
2007 let mut spec_plain = chain_spec_test(ROCOCO_LOCAL_PLAIN_TESTING);
2008
2009 {
2010 let balances = spec_plain.pointer_mut("/genesis/runtime/balances").unwrap();
2011 *balances = json!(serde_json::Value::Null);
2012 }
2013
2014 let mut name = String::from("luca");
2015 let initial_balance = 1_000_000_000_000_u128;
2016 let seed = format!("//{}{name}", name.remove(0).to_uppercase());
2017 let accounts = NodeAccounts {
2018 accounts: generators::generate_node_keys(&seed).unwrap(),
2019 seed,
2020 };
2021 let node = NodeSpec {
2022 name,
2023 accounts,
2024 initial_balance,
2025 ..Default::default()
2026 };
2027
2028 let nodes = vec![node];
2029 let balances_to_add = generate_balance_to_add_from_nodes(&nodes, 0);
2030 add_balances("/genesis/runtime", &mut spec_plain, balances_to_add);
2031
2032 let new_balances = spec_plain.pointer("/genesis/runtime/balances/balances");
2033
2034 assert_eq!(new_balances, None);
2036 }
2037
2038 #[test]
2039 fn add_staking_works() {
2040 let mut chain_spec_json = chain_spec_with_stake();
2041 let mut name = String::from("luca");
2042 let initial_balance = 1_000_000_000_000_u128;
2043 let seed = format!("//{}{name}", name.remove(0).to_uppercase());
2044 let accounts = NodeAccounts {
2045 accounts: generators::generate_node_keys(&seed).unwrap(),
2046 seed,
2047 };
2048 let node = NodeSpec {
2049 name,
2050 accounts,
2051 initial_balance,
2052 ..Default::default()
2053 };
2054
2055 let pointer = get_runtime_config_pointer(&chain_spec_json).unwrap();
2056 let min = get_staking_min(&pointer, &mut chain_spec_json);
2057
2058 let nodes = vec![node];
2059 add_staking(&pointer, &mut chain_spec_json, &nodes, min);
2060
2061 let new_staking = chain_spec_json
2062 .pointer("/genesis/runtimeGenesis/patch/staking")
2063 .unwrap();
2064
2065 let sr_stash = nodes[0].accounts.accounts.get("sr_stash").unwrap();
2067 assert_eq!(new_staking["stakers"][0][0], json!(sr_stash.address));
2068 assert_eq!(new_staking["stakers"][0][2], json!(min));
2070 assert_eq!(new_staking["stakers"].as_array().unwrap().len(), 1);
2072 }
2073
2074 #[test]
2075 fn adding_hrmp_channels_works() {
2076 let mut spec_plain = chain_spec_test(ROCOCO_LOCAL_PLAIN_TESTING);
2077
2078 {
2079 let current_hrmp_channels = spec_plain
2080 .pointer("/genesis/runtime/hrmp/preopenHrmpChannels")
2081 .unwrap();
2082 assert_eq!(current_hrmp_channels, &json!([]));
2084 }
2085
2086 let para_100_101 = HrmpChannelConfigBuilder::new()
2087 .with_sender(100)
2088 .with_recipient(101)
2089 .build();
2090 let para_101_100 = HrmpChannelConfigBuilder::new()
2091 .with_sender(101)
2092 .with_recipient(100)
2093 .build();
2094 let channels = vec![para_100_101, para_101_100];
2095
2096 add_hrmp_channels("/genesis/runtime", &mut spec_plain, &channels);
2097 let new_hrmp_channels = spec_plain
2098 .pointer("/genesis/runtime/hrmp/preopenHrmpChannels")
2099 .unwrap()
2100 .as_array()
2101 .unwrap();
2102
2103 assert_eq!(new_hrmp_channels.len(), 2);
2104 assert_eq!(new_hrmp_channels.first().unwrap()[0], 100);
2105 assert_eq!(new_hrmp_channels.first().unwrap()[1], 101);
2106 assert_eq!(new_hrmp_channels.last().unwrap()[0], 101);
2107 assert_eq!(new_hrmp_channels.last().unwrap()[1], 100);
2108 }
2109
2110 #[test]
2111 fn adding_hrmp_channels_to_an_spec_without_channels() {
2112 let mut spec_plain = chain_spec_test("./testing/rococo-local-plain.json");
2113
2114 {
2115 let hrmp = spec_plain.pointer_mut("/genesis/runtime/hrmp").unwrap();
2116 *hrmp = json!(serde_json::Value::Null);
2117 }
2118
2119 let para_100_101 = HrmpChannelConfigBuilder::new()
2120 .with_sender(100)
2121 .with_recipient(101)
2122 .build();
2123 let para_101_100 = HrmpChannelConfigBuilder::new()
2124 .with_sender(101)
2125 .with_recipient(100)
2126 .build();
2127 let channels = vec![para_100_101, para_101_100];
2128
2129 add_hrmp_channels("/genesis/runtime", &mut spec_plain, &channels);
2130 let new_hrmp_channels = spec_plain.pointer("/genesis/runtime/hrmp/preopenHrmpChannels");
2131
2132 assert_eq!(new_hrmp_channels, None);
2134 }
2135
2136 #[test]
2137 fn get_node_keys_works() {
2138 let mut name = String::from("luca");
2139 let seed = format!("//{}{name}", name.remove(0).to_uppercase());
2140 let accounts = NodeAccounts {
2141 accounts: generators::generate_node_keys(&seed).unwrap(),
2142 seed,
2143 };
2144 let node = NodeSpec {
2145 name,
2146 accounts,
2147 ..Default::default()
2148 };
2149
2150 let sr = &node.accounts.accounts["sr"];
2151 let keys = [
2152 ("babe".into(), sr.address.clone()),
2153 ("im_online".into(), sr.address.clone()),
2154 ("parachain_validator".into(), sr.address.clone()),
2155 ("authority_discovery".into(), sr.address.clone()),
2156 ("para_validator".into(), sr.address.clone()),
2157 ("para_assignment".into(), sr.address.clone()),
2158 ("aura".into(), sr.address.clone()),
2159 ("nimbus".into(), sr.address.clone()),
2160 ("vrf".into(), sr.address.clone()),
2161 (
2162 "grandpa".into(),
2163 node.accounts.accounts["ed"].address.clone(),
2164 ),
2165 ("beefy".into(), node.accounts.accounts["ec"].address.clone()),
2166 ("eth".into(), node.accounts.accounts["eth"].address.clone()),
2167 ]
2168 .into();
2169
2170 let sr_stash = &node.accounts.accounts["sr_stash"];
2172 let node_key = get_node_keys(&node, SessionKeyType::Stash, false);
2173 assert_eq!(node_key.0, sr_stash.address);
2174 assert_eq!(node_key.1, sr_stash.address);
2175 assert_eq!(node_key.2, keys);
2176 let node_key = get_node_keys(&node, SessionKeyType::Default, false);
2178 assert_eq!(node_key.0, sr.address);
2179 assert_eq!(node_key.1, sr.address);
2180 assert_eq!(node_key.2, keys);
2181 }
2182
2183 #[test]
2184 fn get_node_keys_supports_asset_hub_polkadot() {
2185 let mut name = String::from("luca");
2186 let seed = format!("//{}{name}", name.remove(0).to_uppercase());
2187 let accounts = NodeAccounts {
2188 accounts: generators::generate_node_keys(&seed).unwrap(),
2189 seed,
2190 };
2191 let node = NodeSpec {
2192 name,
2193 accounts,
2194 ..Default::default()
2195 };
2196
2197 let node_key = get_node_keys(&node, SessionKeyType::default(), false);
2198 assert_eq!(node_key.2["aura"], node.accounts.accounts["sr"].address);
2199
2200 let node_key = get_node_keys(&node, SessionKeyType::default(), true);
2201 assert_eq!(node_key.2["aura"], node.accounts.accounts["ed"].address);
2202 }
2203
2204 #[test]
2205 fn ensure_penpal_assets_works() {
2206 let mut spec_plain = chain_spec_test(ROCOCO_PENPAL_LOCAL_PLAIN_TESTING);
2207
2208 let balances = spec_plain
2209 .pointer("/genesis/runtimeGenesis/patch/balances/balances")
2210 .unwrap();
2211 let balances_map = generate_balance_map(balances);
2212 println!("balance {balances_map:?}");
2213
2214 let nodes: Vec<NodeSpec> = vec![];
2215 let balances_to_add = generate_balance_to_add_from_nodes(&nodes, 0);
2216 add_balances(
2217 "/genesis/runtimeGenesis/patch",
2218 &mut spec_plain,
2219 balances_to_add,
2220 );
2221
2222 let balances_to_add_from_assets = generate_balance_to_add_from_assets_pallet(
2224 "/genesis/runtimeGenesis/patch",
2225 &spec_plain,
2226 );
2227 println!("to add : {balances_to_add_from_assets:?}");
2228 add_balances(
2229 "/genesis/runtimeGenesis/patch",
2230 &mut spec_plain,
2231 balances_to_add_from_assets,
2232 );
2233
2234 let new_balances = spec_plain
2235 .pointer("/genesis/runtimeGenesis/patch/balances/balances")
2236 .unwrap();
2237
2238 let new_balances_map = generate_balance_map(new_balances);
2239 println!("balance {new_balances_map:?}");
2240
2241 assert_eq!(new_balances_map.len(), balances_map.len() + 1);
2242 }
2243
2244 #[test]
2245 fn get_node_keys_with_custom_types_works() {
2246 use super::super::{chain_spec_key_types::ChainSpecKeyType, keystore_key_types::KeyScheme};
2247
2248 let mut name = String::from("alice");
2249 let seed = format!("//{}{name}", name.remove(0).to_uppercase());
2250 let accounts = NodeAccounts {
2251 accounts: generators::generate_node_keys(&seed).unwrap(),
2252 seed,
2253 };
2254 let node = NodeSpec {
2255 name,
2256 accounts,
2257 ..Default::default()
2258 };
2259
2260 let custom_key_types = vec![
2261 ChainSpecKeyType::new("aura", KeyScheme::Ed),
2262 ChainSpecKeyType::new("grandpa", KeyScheme::Sr),
2263 ];
2264
2265 let node_key =
2266 get_node_keys_with_custom_types(&node, SessionKeyType::Default, &custom_key_types);
2267
2268 assert_eq!(node_key.0, node.accounts.accounts["sr"].address);
2270 assert_eq!(node_key.1, node.accounts.accounts["sr"].address);
2271
2272 assert_eq!(node_key.2["aura"], node.accounts.accounts["ed"].address);
2274 assert_eq!(node_key.2["grandpa"], node.accounts.accounts["sr"].address);
2275 }
2276
2277 #[test]
2278 fn get_node_keys_with_custom_types_stash_works() {
2279 use super::super::{chain_spec_key_types::ChainSpecKeyType, keystore_key_types::KeyScheme};
2280
2281 let mut name = String::from("alice");
2282 let seed = format!("//{}{name}", name.remove(0).to_uppercase());
2283 let accounts = NodeAccounts {
2284 accounts: generators::generate_node_keys(&seed).unwrap(),
2285 seed,
2286 };
2287 let node = NodeSpec {
2288 name,
2289 accounts,
2290 ..Default::default()
2291 };
2292
2293 let custom_key_types = vec![ChainSpecKeyType::new("aura", KeyScheme::Sr)];
2294
2295 let node_key =
2296 get_node_keys_with_custom_types(&node, SessionKeyType::Stash, &custom_key_types);
2297
2298 assert_eq!(node_key.0, node.accounts.accounts["sr_stash"].address);
2300 assert_eq!(node_key.1, node.accounts.accounts["sr_stash"].address);
2301 assert_eq!(node_key.2["aura"], node.accounts.accounts["sr"].address);
2302 }
2303}