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