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