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