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