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