1use std::{
2 collections::HashMap,
3 path::{Path, PathBuf},
4};
5
6use anyhow::anyhow;
7use configuration::{
8 types::{AssetLocation, Chain, ChainSpecRuntime, JsonOverrides, ParaId},
9 HrmpChannelConfig,
10};
11use provider::{
12 constants::NODE_CONFIG_DIR,
13 types::{GenerateFileCommand, GenerateFilesOptions, TransferedFile},
14 DynNamespace, ProviderError,
15};
16use sc_chain_spec::{GenericChainSpec, GenesisConfigBuilderRuntimeCaller};
17use serde::{Deserialize, Serialize};
18use serde_json::json;
19use support::{constants::THIS_IS_A_BUG, fs::FileSystem, replacer::apply_replacements};
20use tokio::process::Command;
21use tracing::{debug, info, trace, warn};
22
23use super::errors::GeneratorError;
24use crate::{
25 network_spec::{node::NodeSpec, parachain::ParachainSpec, relaychain::RelaychainSpec},
26 ScopedFilesystem,
27};
28
29#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
31pub enum Context {
32 Relay,
33 Para { relay_chain: Chain, para_id: ParaId },
34}
35
36#[derive(Debug, Clone, Copy)]
38enum ChainSpecFormat {
39 Plain,
40 Raw,
41}
42#[derive(Debug, Clone, Copy)]
44enum KeyType {
45 Session,
46 Aura,
47 Grandpa,
48}
49
50#[derive(Debug, Clone, Copy, Default)]
51enum SessionKeyType {
52 #[default]
54 Default,
55 Stash,
57 Evm,
59}
60
61type MaybeExpectedPath = Option<PathBuf>;
62
63#[derive(Debug, Clone, Serialize, Deserialize)]
64pub enum CommandInContext {
65 Local(String, MaybeExpectedPath),
66 Remote(String, MaybeExpectedPath),
67}
68
69impl CommandInContext {
70 fn cmd(&self) -> &str {
71 match self {
72 CommandInContext::Local(cmd, _) | CommandInContext::Remote(cmd, _) => cmd.as_ref(),
73 }
74 }
75}
76
77#[derive(Debug)]
78pub struct ParaGenesisConfig<T: AsRef<Path>> {
79 pub(crate) state_path: T,
80 pub(crate) wasm_path: T,
81 pub(crate) id: u32,
82 pub(crate) as_parachain: bool,
83}
84
85const DEFAULT_PRESETS_TO_CHECK: [&str; 3] = ["local_testnet", "development", "dev"];
89
90#[derive(Debug, Clone, Serialize, Deserialize)]
98pub struct ChainSpec {
99 chain_spec_name: String,
101 asset_location: Option<AssetLocation>,
103 runtime: Option<ChainSpecRuntime>,
105 maybe_plain_path: Option<PathBuf>,
106 chain_name: Option<String>,
107 raw_path: Option<PathBuf>,
108 command: Option<CommandInContext>,
110 image: Option<String>,
112 context: Context,
114}
115
116impl ChainSpec {
117 pub(crate) fn new(chain_spec_name: impl Into<String>, context: Context) -> Self {
118 Self {
119 chain_spec_name: chain_spec_name.into(),
120 chain_name: None,
121 maybe_plain_path: None,
122 asset_location: None,
123 runtime: None,
124 raw_path: None,
125 command: None,
126 image: None,
127 context,
128 }
129 }
130
131 pub(crate) fn chain_spec_name(&self) -> &str {
132 self.chain_spec_name.as_ref()
133 }
134
135 pub(crate) fn chain_name(&self) -> Option<&str> {
136 self.chain_name.as_deref()
137 }
138
139 pub(crate) fn set_chain_name(mut self, chain_name: impl Into<String>) -> Self {
140 self.chain_name = Some(chain_name.into());
141 self
142 }
143
144 pub(crate) fn asset_location(mut self, location: AssetLocation) -> Self {
145 self.asset_location = Some(location);
146 self
147 }
148
149 pub(crate) fn runtime(mut self, chain_spec_runtime: ChainSpecRuntime) -> Self {
150 self.runtime = Some(chain_spec_runtime);
151 self
152 }
153
154 pub(crate) fn command(
155 mut self,
156 command: impl Into<String>,
157 is_local: bool,
158 expected_path: Option<&str>,
159 ) -> Self {
160 let maybe_expected_path = expected_path.map(PathBuf::from);
161 let cmd = if is_local {
162 CommandInContext::Local(command.into(), maybe_expected_path)
163 } else {
164 CommandInContext::Remote(command.into(), maybe_expected_path)
165 };
166 self.command = Some(cmd);
167 self
168 }
169
170 pub(crate) fn image(mut self, image: Option<String>) -> Self {
171 self.image = image;
172 self
173 }
174
175 pub async fn build<'a, T>(
183 &mut self,
184 ns: &DynNamespace,
185 scoped_fs: &ScopedFilesystem<'a, T>,
186 ) -> Result<(), GeneratorError>
187 where
188 T: FileSystem,
189 {
190 if self.asset_location.is_none() && self.command.is_none() && self.runtime.is_none() {
191 return Err(GeneratorError::ChainSpecGeneration(
192 "Can not build the chain spec without set the command, asset_location or runtime"
193 .to_string(),
194 ));
195 }
196
197 let maybe_plain_spec_path = PathBuf::from(format!("{}-plain.json", self.chain_spec_name));
198
199 if let Some(location) = self.asset_location.as_ref() {
201 let maybe_plain_spec_full_path = scoped_fs.full_path(maybe_plain_spec_path.as_path());
202 location
203 .dump_asset(maybe_plain_spec_full_path)
204 .await
205 .map_err(|e| {
206 GeneratorError::ChainSpecGeneration(format!(
207 "Error {e} dumping location {location:?}"
208 ))
209 })?;
210 } else if let Some(runtime) = self.runtime.as_ref() {
211 trace!(
212 "Creating chain-spec with runtime from localtion: {}",
213 runtime.location
214 );
215 let runtime_file_name = PathBuf::from(format!("{}-runtime.wasm", self.chain_spec_name));
217 let runtime_path_ns = scoped_fs.full_path(runtime_file_name.as_path());
218 runtime
219 .location
220 .dump_asset(runtime_path_ns)
221 .await
222 .map_err(|e| {
223 GeneratorError::ChainSpecGeneration(format!(
224 "Error {e} dumping location {:?}",
225 runtime.location
226 ))
227 })?;
228
229 let runtime_code = scoped_fs.read(runtime_file_name.as_path()).await?;
231
232 let caller: GenesisConfigBuilderRuntimeCaller =
233 GenesisConfigBuilderRuntimeCaller::new(&runtime_code[..]);
234 let presets = caller.preset_names().map_err(|e| {
235 GeneratorError::ChainSpecGeneration(format!(
236 "getting default config from runtime should work: {e}"
237 ))
238 })?;
239
240 let preset_to_check = if let Some(preset) = &runtime.preset {
247 [vec![preset.as_str()], DEFAULT_PRESETS_TO_CHECK.to_vec()].concat()
248 } else {
249 DEFAULT_PRESETS_TO_CHECK.to_vec()
250 };
251 let preset = preset_to_check
252 .iter()
253 .find(|preset| presets.iter().any(|item| item == *preset));
254
255 trace!("presets: {:?} - preset to use: {:?}", presets, preset);
256 let builder = if let Some(preset) = preset {
257 GenericChainSpec::<()>::builder(&runtime_code[..], ())
258 .with_genesis_config_preset_name(preset)
259 } else {
260 let default_config = caller.get_default_config().map_err(|e| {
262 GeneratorError::ChainSpecGeneration(format!(
263 "getting default config from runtime should work: {e}"
264 ))
265 })?;
266
267 GenericChainSpec::<()>::builder(&runtime_code[..], ())
268 .with_genesis_config(default_config)
269 };
270
271 let builder = if let Context::Para {
272 relay_chain: _,
273 para_id: _,
274 } = &self.context
275 {
276 builder.with_id(self.chain_spec_name())
277 } else {
278 builder
279 };
280
281 let builder = if let Some(chain_name) = self.chain_name.as_ref() {
282 builder.with_name(chain_name)
283 } else {
284 builder
285 };
286
287 let chain_spec = builder.build();
288
289 let contents = chain_spec.as_json(false).map_err(|e| {
290 GeneratorError::ChainSpecGeneration(format!(
291 "getting chain-spec as json should work, err: {e}"
292 ))
293 })?;
294
295 scoped_fs.write(&maybe_plain_spec_path, contents).await?;
296 } else {
297 trace!("Creating chain-spec with command");
298 let mut replacement_value = String::default();
300 if let Some(chain_name) = self.chain_name.as_ref() {
301 if !chain_name.is_empty() {
302 replacement_value.clone_from(chain_name);
303 }
304 };
305
306 let sanitized_cmd = if replacement_value.is_empty() {
309 self.command.as_ref().unwrap().cmd().replace("--chain", "")
311 } else {
312 self.command.as_ref().unwrap().cmd().to_owned()
313 };
314
315 let full_cmd = apply_replacements(
316 &sanitized_cmd,
317 &HashMap::from([("chainName", replacement_value.as_str())]),
318 );
319 trace!("full_cmd: {:?}", full_cmd);
320
321 let parts: Vec<&str> = full_cmd.split_whitespace().collect();
322 let Some((cmd, args)) = parts.split_first() else {
323 return Err(GeneratorError::ChainSpecGeneration(format!(
324 "Invalid generator command: {full_cmd}"
325 )));
326 };
327 trace!("cmd: {:?} - args: {:?}", cmd, args);
328
329 let generate_command =
330 GenerateFileCommand::new(cmd, maybe_plain_spec_path.clone()).args(args);
331 if let Some(cmd) = &self.command {
332 match cmd {
333 CommandInContext::Local(_, expected_path) => {
334 build_locally(generate_command, scoped_fs, expected_path.as_deref()).await?
335 },
336 CommandInContext::Remote(_, expected_path) => {
337 let options = GenerateFilesOptions::new(
338 vec![generate_command],
339 self.image.clone(),
340 expected_path.clone(),
341 );
342 ns.generate_files(options).await?;
343 },
344 }
345 }
346 }
347
348 if is_raw(maybe_plain_spec_path.clone(), scoped_fs).await? {
350 let spec_path = PathBuf::from(format!("{}.json", self.chain_spec_name));
351 let tf_file = TransferedFile::new(
352 &PathBuf::from_iter([ns.base_dir(), &maybe_plain_spec_path]),
353 &spec_path,
354 );
355 scoped_fs.copy_files(vec![&tf_file]).await.map_err(|e| {
356 GeneratorError::ChainSpecGeneration(format!(
357 "Error copying file: {tf_file}, err: {e}"
358 ))
359 })?;
360
361 self.raw_path = Some(spec_path);
362 } else {
363 self.maybe_plain_path = Some(maybe_plain_spec_path);
364 }
365 Ok(())
366 }
367
368 pub async fn build_raw<'a, T>(
369 &mut self,
370 ns: &DynNamespace,
371 scoped_fs: &ScopedFilesystem<'a, T>,
372 relay_chain_id: Option<Chain>,
373 ) -> Result<(), GeneratorError>
374 where
375 T: FileSystem,
376 {
377 warn!("Building raw version from {:?}", self);
378 let None = self.raw_path else {
380 return Ok(());
381 };
382
383 let raw_spec_path = PathBuf::from(format!("{}.json", self.chain_spec_name));
385
386 match self
387 .try_build_raw_with_generic(scoped_fs, relay_chain_id.clone(), raw_spec_path.as_path())
388 .await
389 {
390 Ok(_) => return Ok(()),
391 Err(err) => {
392 if Self::should_retry_with_command(&err) && self.command.is_some() {
393 warn!(
394 "GenericChainSpec raw generation failed ({}). Falling back to command execution.",
395 err
396 );
397 } else {
398 return Err(err);
399 }
400 },
401 }
402
403 self.build_raw_with_command(ns, scoped_fs, raw_spec_path, relay_chain_id)
404 .await?;
405
406 Ok(())
407 }
408
409 async fn try_build_raw_with_generic<'a, T>(
410 &mut self,
411 scoped_fs: &ScopedFilesystem<'a, T>,
412 relay_chain_id: Option<Chain>,
413 raw_spec_path: &Path,
414 ) -> Result<(), GeneratorError>
415 where
416 T: FileSystem,
417 {
418 let (json_content, _) = self.read_spec(scoped_fs).await?;
420 let json_bytes: Vec<u8> = json_content.as_bytes().into();
421 let chain_spec = GenericChainSpec::<()>::from_json_bytes(json_bytes).map_err(|e| {
422 GeneratorError::ChainSpecGeneration(format!(
423 "Error loading chain-spec from json_bytes, err: {e}"
424 ))
425 })?;
426
427 self.raw_path = Some(raw_spec_path.to_path_buf());
428 let contents = chain_spec.as_json(true).map_err(|e| {
429 GeneratorError::ChainSpecGeneration(format!(
430 "getting chain-spec as json should work, err: {e}"
431 ))
432 })?;
433 let contents = self
434 .ensure_para_fields_in_raw(&contents, relay_chain_id)
435 .await?;
436 self.write_spec(scoped_fs, contents).await?;
437
438 Ok(())
439 }
440
441 async fn build_raw_with_command<'a, T>(
442 &mut self,
443 ns: &DynNamespace,
444 scoped_fs: &ScopedFilesystem<'a, T>,
445 raw_spec_path: PathBuf,
446 relay_chain_id: Option<Chain>,
447 ) -> Result<(), GeneratorError>
448 where
449 T: FileSystem,
450 {
451 let temp_name = format!(
453 "temp-build-raw-{}-{}",
454 self.chain_spec_name,
455 rand::random::<u8>()
456 );
457
458 let cmd = self
459 .command
460 .as_ref()
461 .ok_or(GeneratorError::ChainSpecGeneration(
462 "Invalid command".into(),
463 ))?;
464 let maybe_plain_path =
465 self.maybe_plain_path
466 .as_ref()
467 .ok_or(GeneratorError::ChainSpecGeneration(
468 "Invalid plain path".into(),
469 ))?;
470
471 let chain_spec_path_local = format!(
473 "{}/{}",
474 ns.base_dir().to_string_lossy(),
475 maybe_plain_path.display()
476 );
477 let chain_spec_path_in_pod = format!("{}/{}", NODE_CONFIG_DIR, maybe_plain_path.display());
479 let chain_spec_path_in_args = if matches!(self.command, Some(CommandInContext::Local(_, _)))
481 {
482 chain_spec_path_local.clone()
483 } else if ns.capabilities().prefix_with_full_path {
484 format!(
486 "{}/{}{}",
487 ns.base_dir().to_string_lossy(),
488 &temp_name,
489 &chain_spec_path_in_pod
490 )
491 } else {
492 chain_spec_path_in_pod.clone()
493 };
494
495 let mut full_cmd = apply_replacements(
496 cmd.cmd(),
497 &HashMap::from([("chainName", chain_spec_path_in_args.as_str())]),
498 );
499
500 if !full_cmd.contains("--raw") {
501 full_cmd = format!("{full_cmd} --raw");
502 }
503 trace!("full_cmd: {:?}", full_cmd);
504
505 let parts: Vec<&str> = full_cmd.split_whitespace().collect();
506 let Some((cmd, args)) = parts.split_first() else {
507 return Err(GeneratorError::ChainSpecGeneration(format!(
508 "Invalid generator command: {full_cmd}"
509 )));
510 };
511 trace!("cmd: {:?} - args: {:?}", cmd, args);
512
513 let generate_command = GenerateFileCommand::new(cmd, raw_spec_path.clone()).args(args);
514
515 if let Some(cmd) = &self.command {
516 match cmd {
517 CommandInContext::Local(_, expected_path) => {
518 build_locally(generate_command, scoped_fs, expected_path.as_deref()).await?
519 },
520 CommandInContext::Remote(_, expected_path) => {
521 let options = GenerateFilesOptions::with_files(
522 vec![generate_command],
523 self.image.clone(),
524 &[TransferedFile::new(
525 chain_spec_path_local,
526 chain_spec_path_in_pod,
527 )],
528 expected_path.clone(),
529 )
530 .temp_name(temp_name);
531 trace!("calling generate_files with options: {:#?}", options);
532 ns.generate_files(options).await?;
533 },
534 }
535 }
536
537 self.raw_path = Some(raw_spec_path.clone());
538 let (content, _) = self.read_spec(scoped_fs).await?;
539 let content = self
540 .ensure_para_fields_in_raw(&content, relay_chain_id)
541 .await?;
542 self.write_spec(scoped_fs, content).await?;
543
544 Ok(())
545 }
546
547 async fn ensure_para_fields_in_raw(
548 &mut self,
549 content: &str,
550 relay_chain_id: Option<Chain>,
551 ) -> Result<String, GeneratorError> {
552 if let Context::Para {
553 relay_chain: _,
554 para_id,
555 } = &self.context
556 {
557 let mut chain_spec_json: serde_json::Value =
558 serde_json::from_str(content).map_err(|e| {
559 GeneratorError::ChainSpecGeneration(format!(
560 "getting chain-spec as json should work, err: {e}"
561 ))
562 })?;
563
564 let mut needs_write = false;
565
566 if chain_spec_json["relay_chain"].is_null() {
567 chain_spec_json["relay_chain"] = json!(relay_chain_id);
568 needs_write = true;
569 }
570
571 if chain_spec_json["para_id"].is_null() {
572 chain_spec_json["para_id"] = json!(para_id);
573 needs_write = true;
574 }
575
576 if needs_write {
577 let contents = serde_json::to_string_pretty(&chain_spec_json).map_err(|e| {
578 GeneratorError::ChainSpecGeneration(format!(
579 "getting chain-spec json as pretty string should work, err: {e}"
580 ))
581 })?;
582 return Ok(contents);
583 }
584 }
585
586 Ok(content.to_string())
587 }
588
589 fn should_retry_with_command(err: &GeneratorError) -> bool {
590 match err {
591 GeneratorError::ChainSpecGeneration(msg) => {
592 let msg_lower = msg.to_lowercase();
593 msg_lower.contains("genesisbuilder_get_preset") || msg_lower.contains("_get_preset")
594 },
595 _ => false,
596 }
597 }
598
599 pub async fn override_code<'a, T>(
601 &mut self,
602 scoped_fs: &ScopedFilesystem<'a, T>,
603 wasm_override: &AssetLocation,
604 ) -> Result<(), GeneratorError>
605 where
606 T: FileSystem,
607 {
608 let Some(_) = self.raw_path else {
610 return Err(GeneratorError::OverridingWasm(String::from(
611 "Raw path should be set at this point.",
612 )));
613 };
614 let (content, _) = self.read_spec(scoped_fs).await?;
615 let override_content = wasm_override.get_asset().await.map_err(|_| {
617 GeneratorError::OverridingWasm(format!(
618 "Can not get asset to override wasm, asset: {wasm_override}"
619 ))
620 })?;
621
622 let mut chain_spec_json: serde_json::Value =
624 serde_json::from_str(&content).map_err(|_| {
625 GeneratorError::ChainSpecGeneration("Can not parse chain-spec as json".into())
626 })?;
627
628 let Some(code) = chain_spec_json.pointer_mut("/genesis/raw/top/0x3a636f6465") else {
630 return Err(GeneratorError::OverridingWasm(String::from(
631 "Pointer '/genesis/raw/top/0x3a636f6465' should be valid in the raw spec.",
632 )));
633 };
634
635 info!(
636 "🖋 Overriding ':code' (0x3a636f6465) in raw chain-spec with content of {}",
637 wasm_override
638 );
639 *code = json!(format!("0x{}", hex::encode(override_content)));
640
641 let overrided_content = serde_json::to_string_pretty(&chain_spec_json).map_err(|_| {
642 GeneratorError::ChainSpecGeneration("can not parse chain-spec value as json".into())
643 })?;
644 self.write_spec(scoped_fs, overrided_content).await?;
646
647 Ok(())
648 }
649
650 pub async fn override_raw_spec<'a, T>(
651 &mut self,
652 scoped_fs: &ScopedFilesystem<'a, T>,
653 raw_spec_overrides: &JsonOverrides,
654 ) -> Result<(), GeneratorError>
655 where
656 T: FileSystem,
657 {
658 let Some(_) = self.raw_path else {
660 return Err(GeneratorError::OverridingRawSpec(String::from(
661 "Raw path should be set at this point.",
662 )));
663 };
664
665 let (content, _) = self.read_spec(scoped_fs).await?;
666
667 let override_content: serde_json::Value = raw_spec_overrides.get().await.map_err(|_| {
669 GeneratorError::OverridingRawSpec(format!(
670 "Can not parse raw_spec_override contents as json: {raw_spec_overrides}"
671 ))
672 })?;
673
674 let mut chain_spec_json: serde_json::Value =
676 serde_json::from_str(&content).map_err(|_| {
677 GeneratorError::ChainSpecGeneration("Can not parse chain-spec as json".into())
678 })?;
679
680 merge(&mut chain_spec_json, &override_content);
682
683 let overrided_content = serde_json::to_string_pretty(&chain_spec_json).map_err(|_| {
685 GeneratorError::ChainSpecGeneration("can not parse chain-spec value as json".into())
686 })?;
687 self.write_spec(scoped_fs, overrided_content).await?;
688
689 Ok(())
690 }
691
692 pub fn raw_path(&self) -> Option<&Path> {
693 self.raw_path.as_deref()
694 }
695
696 pub fn set_asset_location(&mut self, location: AssetLocation) {
697 self.asset_location = Some(location)
698 }
699
700 pub async fn read_chain_id<'a, T>(
701 &self,
702 scoped_fs: &ScopedFilesystem<'a, T>,
703 ) -> Result<String, GeneratorError>
704 where
705 T: FileSystem,
706 {
707 let (content, _) = self.read_spec(scoped_fs).await?;
708 ChainSpec::chain_id_from_spec(&content)
709 }
710
711 async fn read_spec<'a, T>(
712 &self,
713 scoped_fs: &ScopedFilesystem<'a, T>,
714 ) -> Result<(String, ChainSpecFormat), GeneratorError>
715 where
716 T: FileSystem,
717 {
718 let (path, format) = match (self.maybe_plain_path.as_ref(), self.raw_path.as_ref()) {
719 (Some(path), None) => (path, ChainSpecFormat::Plain),
720 (None, Some(path)) => (path, ChainSpecFormat::Raw),
721 (Some(_), Some(path)) => {
722 (path, ChainSpecFormat::Raw)
724 },
725 (None, None) => unreachable!(),
726 };
727
728 let content = scoped_fs.read_to_string(path.clone()).await.map_err(|_| {
729 GeneratorError::ChainSpecGeneration(format!(
730 "Can not read chain-spec from {}",
731 path.to_string_lossy()
732 ))
733 })?;
734
735 Ok((content, format))
736 }
737
738 async fn write_spec<'a, T>(
739 &self,
740 scoped_fs: &ScopedFilesystem<'a, T>,
741 content: impl Into<String>,
742 ) -> Result<(), GeneratorError>
743 where
744 T: FileSystem,
745 {
746 let (path, _format) = match (self.maybe_plain_path.as_ref(), self.raw_path.as_ref()) {
747 (Some(path), None) => (path, ChainSpecFormat::Plain),
748 (None, Some(path)) => (path, ChainSpecFormat::Raw),
749 (Some(_), Some(path)) => {
750 (path, ChainSpecFormat::Raw)
752 },
753 (None, None) => unreachable!(),
754 };
755
756 scoped_fs.write(path, content.into()).await.map_err(|_| {
757 GeneratorError::ChainSpecGeneration(format!(
758 "Can not write chain-spec from {}",
759 path.to_string_lossy()
760 ))
761 })?;
762
763 Ok(())
764 }
765
766 pub async fn customize_para<'a, T>(
768 &self,
769 para: &ParachainSpec,
770 relay_chain_id: &str,
771 scoped_fs: &ScopedFilesystem<'a, T>,
772 ) -> Result<(), GeneratorError>
773 where
774 T: FileSystem,
775 {
776 let (content, format) = self.read_spec(scoped_fs).await?;
777 let mut chain_spec_json: serde_json::Value =
778 serde_json::from_str(&content).map_err(|_| {
779 GeneratorError::ChainSpecGeneration("Can not parse chain-spec as json".into())
780 })?;
781
782 if let Some(para_id) = chain_spec_json.get_mut("para_id") {
783 *para_id = json!(para.id);
784 };
785 if let Some(para_id) = chain_spec_json.get_mut("paraId") {
786 *para_id = json!(para.id);
787 };
788
789 if let Some(relay_chain_id_field) = chain_spec_json.get_mut("relay_chain") {
790 *relay_chain_id_field = json!(relay_chain_id);
791 };
792
793 if let ChainSpecFormat::Plain = format {
794 let pointer = get_runtime_config_pointer(&chain_spec_json)
795 .map_err(GeneratorError::ChainSpecGeneration)?;
796
797 if let Some(overrides) = ¶.genesis_overrides {
799 let percolated_overrides = percolate_overrides(&pointer, overrides)
800 .map_err(|e| GeneratorError::ChainSpecGeneration(e.to_string()))?;
801 if let Some(genesis) = chain_spec_json.pointer_mut(&pointer) {
802 merge(genesis, percolated_overrides);
803 }
804 }
805
806 clear_authorities(&pointer, &mut chain_spec_json, &self.context);
807
808 let key_type_to_use = if para.is_evm_based {
809 SessionKeyType::Evm
810 } else {
811 SessionKeyType::Default
812 };
813
814 let validators: Vec<&NodeSpec> = para
816 .collators
817 .iter()
818 .filter(|node| node.is_validator)
819 .collect();
820
821 if chain_spec_json
823 .pointer(&format!("{pointer}/session"))
824 .is_some()
825 {
826 add_authorities(&pointer, &mut chain_spec_json, &validators, key_type_to_use);
827 } else if chain_spec_json
828 .pointer(&format!("{pointer}/aura"))
829 .is_some()
830 {
831 add_aura_authorities(&pointer, &mut chain_spec_json, &validators, KeyType::Aura);
832 } else {
833 warn!("Can't customize keys, not `session` or `aura` find in the chain-spec file");
834 };
835
836 let invulnerables: Vec<&NodeSpec> = para
838 .collators
839 .iter()
840 .filter(|node| node.is_invulnerable)
841 .collect();
842
843 add_collator_selection(
844 &pointer,
845 &mut chain_spec_json,
846 &invulnerables,
847 key_type_to_use,
848 );
849
850 override_parachain_info(&pointer, &mut chain_spec_json, para.id);
852
853 let content = serde_json::to_string_pretty(&chain_spec_json).map_err(|_| {
855 GeneratorError::ChainSpecGeneration("can not parse chain-spec value as json".into())
856 })?;
857 self.write_spec(scoped_fs, content).await?;
858 } else {
859 warn!("⚠️ Chain spec for para_id: {} is in raw mode", para.id);
860 }
861 Ok(())
862 }
863
864 pub async fn customize_relay<'a, T, U>(
865 &self,
866 relaychain: &RelaychainSpec,
867 hrmp_channels: &[HrmpChannelConfig],
868 para_artifacts: Vec<ParaGenesisConfig<U>>,
869 scoped_fs: &ScopedFilesystem<'a, T>,
870 ) -> Result<(), GeneratorError>
871 where
872 T: FileSystem,
873 U: AsRef<Path>,
874 {
875 let (content, format) = self.read_spec(scoped_fs).await?;
876 let mut chain_spec_json: serde_json::Value =
877 serde_json::from_str(&content).map_err(|_| {
878 GeneratorError::ChainSpecGeneration("Can not parse chain-spec as json".into())
879 })?;
880
881 if let ChainSpecFormat::Plain = format {
882 let token_decimals =
884 if let Some(val) = chain_spec_json.pointer("/properties/tokenDecimals") {
885 let val = val.as_u64().unwrap_or(12);
886 if val > u8::MAX as u64 {
887 12
888 } else {
889 val as u8
890 }
891 } else {
892 12
893 };
894 let pointer = get_runtime_config_pointer(&chain_spec_json)
896 .map_err(GeneratorError::ChainSpecGeneration)?;
897
898 if let Some(overrides) = &relaychain.runtime_genesis_patch {
900 let percolated_overrides = percolate_overrides(&pointer, overrides)
901 .map_err(|e| GeneratorError::ChainSpecGeneration(e.to_string()))?;
902 if let Some(patch_section) = chain_spec_json.pointer_mut(&pointer) {
903 merge(patch_section, percolated_overrides);
904 }
905 }
906
907 let staking_min = get_staking_min(&pointer, &mut chain_spec_json);
909
910 clear_authorities(&pointer, &mut chain_spec_json, &self.context);
912
913 add_balances(
915 &pointer,
916 &mut chain_spec_json,
917 &relaychain.nodes,
918 token_decimals,
919 staking_min,
920 );
921
922 add_staking(
924 &pointer,
925 &mut chain_spec_json,
926 &relaychain.nodes,
927 staking_min,
928 );
929
930 let validators: Vec<&NodeSpec> = relaychain
932 .nodes
933 .iter()
934 .filter(|node| node.is_validator)
935 .collect();
936
937 if chain_spec_json
939 .pointer(&format!("{pointer}/session"))
940 .is_some()
941 {
942 add_authorities(
943 &pointer,
944 &mut chain_spec_json,
945 &validators,
946 SessionKeyType::Stash,
947 );
948 } else {
949 add_aura_authorities(&pointer, &mut chain_spec_json, &validators, KeyType::Aura);
950 add_grandpa_authorities(&pointer, &mut chain_spec_json, &validators, KeyType::Aura);
951 }
952
953 if !hrmp_channels.is_empty() {
956 add_hrmp_channels(&pointer, &mut chain_spec_json, hrmp_channels);
957 }
958
959 for para_genesis_config in para_artifacts.iter() {
961 add_parachain_to_genesis(
962 &pointer,
963 &mut chain_spec_json,
964 para_genesis_config,
965 scoped_fs,
966 )
967 .await
968 .map_err(|e| GeneratorError::ChainSpecGeneration(e.to_string()))?;
969 }
970
971 let content = serde_json::to_string_pretty(&chain_spec_json).map_err(|_| {
977 GeneratorError::ChainSpecGeneration("can not parse chain-spec value as json".into())
978 })?;
979 self.write_spec(scoped_fs, content).await?;
980 } else {
981 warn!(
982 "⚠️ Chain Spec for chain {} is in raw mode, can't customize.",
983 self.chain_spec_name
984 );
985 }
986 Ok(())
987 }
988
989 pub async fn add_bootnodes<'a, T>(
990 &self,
991 scoped_fs: &ScopedFilesystem<'a, T>,
992 bootnodes: &[String],
993 ) -> Result<(), GeneratorError>
994 where
995 T: FileSystem,
996 {
997 let (content, _) = self.read_spec(scoped_fs).await?;
998 let mut chain_spec_json: serde_json::Value =
999 serde_json::from_str(&content).map_err(|_| {
1000 GeneratorError::ChainSpecGeneration("Can not parse chain-spec as json".into())
1001 })?;
1002
1003 if let Some(bootnodes_on_file) = chain_spec_json.get_mut("bootNodes") {
1004 if let Some(bootnodes_on_file) = bootnodes_on_file.as_array_mut() {
1005 let mut bootnodes_to_add =
1006 bootnodes.iter().map(|bootnode| json!(bootnode)).collect();
1007 bootnodes_on_file.append(&mut bootnodes_to_add);
1008 } else {
1009 return Err(GeneratorError::ChainSpecGeneration(
1010 "id should be an string in the chain-spec, this is a bug".into(),
1011 ));
1012 };
1013 } else {
1014 return Err(GeneratorError::ChainSpecGeneration(
1015 "'bootNodes' should be a fields in the chain-spec of the relaychain".into(),
1016 ));
1017 };
1018
1019 let content = serde_json::to_string_pretty(&chain_spec_json).map_err(|_| {
1021 GeneratorError::ChainSpecGeneration("can not parse chain-spec value as json".into())
1022 })?;
1023 self.write_spec(scoped_fs, content).await?;
1024
1025 Ok(())
1026 }
1027
1028 pub fn chain_id_from_spec(spec_content: &str) -> Result<String, GeneratorError> {
1030 let chain_spec_json: serde_json::Value =
1031 serde_json::from_str(spec_content).map_err(|_| {
1032 GeneratorError::ChainSpecGeneration("Can not parse chain-spec as json".into())
1033 })?;
1034 if let Some(chain_id) = chain_spec_json.get("id") {
1035 if let Some(chain_id) = chain_id.as_str() {
1036 Ok(chain_id.to_string())
1037 } else {
1038 Err(GeneratorError::ChainSpecGeneration(
1039 "id should be an string in the chain-spec, this is a bug".into(),
1040 ))
1041 }
1042 } else {
1043 Err(GeneratorError::ChainSpecGeneration(
1044 "'id' should be a fields in the chain-spec of the relaychain".into(),
1045 ))
1046 }
1047 }
1048}
1049
1050type GenesisNodeKey = (String, String, HashMap<String, String>);
1051
1052async fn build_locally<'a, T>(
1053 generate_command: GenerateFileCommand,
1054 scoped_fs: &ScopedFilesystem<'a, T>,
1055 maybe_output: Option<&Path>,
1056) -> Result<(), GeneratorError>
1057where
1058 T: FileSystem,
1059{
1060 let result = Command::new(generate_command.program.clone())
1063 .args(generate_command.args.clone())
1064 .output()
1065 .await
1066 .map_err(|err| {
1067 GeneratorError::ChainSpecGeneration(format!(
1068 "Error running cmd: {} args: {}, err: {}",
1069 &generate_command.program,
1070 &generate_command.args.join(" "),
1071 err
1072 ))
1073 })?;
1074
1075 if result.status.success() {
1076 let raw_output = if let Some(output_path) = maybe_output {
1077 tokio::fs::read(output_path).await.map_err(|err| {
1078 GeneratorError::ChainSpecGeneration(format!(
1079 "Error reading output file at {}: {}",
1080 output_path.display(),
1081 err
1082 ))
1083 })?
1084 } else {
1085 result.stdout
1086 };
1087 scoped_fs
1088 .write(
1089 generate_command.local_output_path,
1090 String::from_utf8_lossy(&raw_output).to_string(),
1091 )
1092 .await?;
1093 Ok(())
1094 } else {
1095 Err(GeneratorError::ChainSpecGeneration(format!(
1096 "Error running cmd: {} args: {}, err: {}",
1097 &generate_command.program,
1098 &generate_command.args.join(" "),
1099 String::from_utf8_lossy(&result.stderr)
1100 )))
1101 }
1102}
1103
1104async fn is_raw<'a, T>(
1105 file: PathBuf,
1106 scoped_fs: &ScopedFilesystem<'a, T>,
1107) -> Result<bool, ProviderError>
1108where
1109 T: FileSystem,
1110{
1111 let content = scoped_fs.read_to_string(file).await?;
1112 let chain_spec_json: serde_json::Value = serde_json::from_str(&content).unwrap();
1113
1114 Ok(chain_spec_json.pointer("/genesis/raw/top").is_some())
1115}
1116
1117async fn add_parachain_to_genesis<'a, T, U>(
1120 runtime_config_ptr: &str,
1121 chain_spec_json: &mut serde_json::Value,
1122 para_genesis_config: &ParaGenesisConfig<U>,
1123 scoped_fs: &ScopedFilesystem<'a, T>,
1124) -> Result<(), anyhow::Error>
1125where
1126 T: FileSystem,
1127 U: AsRef<Path>,
1128{
1129 if let Some(val) = chain_spec_json.pointer_mut(runtime_config_ptr) {
1130 let paras_pointer = if val.get("paras").is_some() {
1131 "/paras/paras"
1132 } else if val.get("parachainsParas").is_some() {
1133 "/parachainsParas/paras"
1135 } else {
1136 val["paras"] = json!({ "paras": [] });
1138 "/paras/paras"
1139 };
1140
1141 let paras = val
1142 .pointer_mut(paras_pointer)
1143 .ok_or(anyhow!("paras pointer should be valid {paras_pointer:?} "))?;
1144 let paras_vec = paras
1145 .as_array_mut()
1146 .ok_or(anyhow!("paras should be an array"))?;
1147
1148 let head = scoped_fs
1149 .read_to_string(para_genesis_config.state_path.as_ref())
1150 .await?;
1151 let wasm = scoped_fs
1152 .read_to_string(para_genesis_config.wasm_path.as_ref())
1153 .await?;
1154
1155 paras_vec.push(json!([
1156 para_genesis_config.id,
1157 [head.trim(), wasm.trim(), para_genesis_config.as_parachain]
1158 ]));
1159
1160 Ok(())
1161 } else {
1162 unreachable!("pointer to runtime config should be valid!")
1163 }
1164}
1165
1166fn get_runtime_config_pointer(chain_spec_json: &serde_json::Value) -> Result<String, String> {
1167 let pointers = [
1170 "/genesis/runtimeGenesis/config",
1171 "/genesis/runtimeGenesis/patch",
1172 "/genesis/runtimeGenesisConfigPatch",
1173 "/genesis/runtime/runtime_genesis_config",
1174 "/genesis/runtime",
1175 ];
1176
1177 for pointer in pointers {
1178 if chain_spec_json.pointer(pointer).is_some() {
1179 return Ok(pointer.to_string());
1180 }
1181 }
1182
1183 Err("Can not find the runtime pointer".into())
1184}
1185
1186fn percolate_overrides<'a>(
1187 pointer: &str,
1188 overrides: &'a serde_json::Value,
1189) -> Result<&'a serde_json::Value, anyhow::Error> {
1190 let pointer_parts = pointer.split('/').collect::<Vec<&str>>();
1191 trace!("pointer_parts: {pointer_parts:?}");
1192
1193 let top_level = overrides
1194 .as_object()
1195 .ok_or_else(|| anyhow!("Overrides must be an object"))?;
1196 let top_level_key = top_level
1197 .keys()
1198 .next()
1199 .ok_or_else(|| anyhow!("Invalid override value: {overrides:?}"))?;
1200 trace!("top_level_key: {top_level_key}");
1201 let index = pointer_parts.iter().position(|x| *x == top_level_key);
1202 let Some(i) = index else {
1203 warn!("Top level key '{top_level_key}' isn't part of the pointer ({pointer}), returning without percolating");
1204 return Ok(overrides);
1205 };
1206
1207 let p = if i == pointer_parts.len() - 1 {
1208 let p = format!("/{}", pointer_parts[i]);
1210 trace!("overrides pointer {p}");
1211 p
1212 } else {
1213 let p = format!("/{}", pointer_parts[i..].join("/"));
1215 trace!("overrides pointer {p}");
1216 p
1217 };
1218 let overrides_to_use = overrides
1219 .pointer(&p)
1220 .ok_or_else(|| anyhow!("Invalid override value: {overrides:?}"))?;
1221 Ok(overrides_to_use)
1222}
1223
1224#[allow(dead_code)]
1225fn construct_runtime_pointer_from_overrides(
1226 overrides: &serde_json::Value,
1227) -> Result<String, anyhow::Error> {
1228 if overrides.get("genesis").is_some() {
1229 return Ok("/genesis".into());
1231 } else {
1232 if let Some(top_level) = overrides.as_object() {
1234 let k = top_level
1235 .keys()
1236 .next()
1237 .ok_or_else(|| anyhow!("Invalid override value: {overrides:?}"))?;
1238 match k.as_str() {
1239 "runtimeGenesisConfigPatch" | "runtime" | "runtimeGenesis" => {
1240 return Ok(("/genesis").into())
1241 },
1242 "config" | "path" => {
1243 return Ok(("/genesis/runtimeGenesis").into());
1244 },
1245 "runtime_genesis_config" => {
1246 return Ok(("/genesis/runtime").into());
1247 },
1248 _ => {},
1249 }
1250 }
1251 }
1252
1253 Err(anyhow!("Can not find the runtime pointer"))
1254}
1255
1256fn merge(patch_section: &mut serde_json::Value, overrides: &serde_json::Value) {
1258 trace!("patch: {:?}", patch_section);
1259 trace!("overrides: {:?}", overrides);
1260 if let (Some(genesis_obj), Some(overrides_obj)) =
1261 (patch_section.as_object_mut(), overrides.as_object())
1262 {
1263 for overrides_key in overrides_obj.keys() {
1264 trace!("overrides_key: {:?}", overrides_key);
1265 if let Some(genesis_value) = genesis_obj.get_mut(overrides_key) {
1267 match (&genesis_value, overrides_obj.get(overrides_key)) {
1268 (serde_json::Value::Object(_), Some(overrides_value))
1270 if overrides_value.is_object() =>
1271 {
1272 merge(genesis_value, overrides_value);
1273 },
1274 (_, Some(overrides_value)) => {
1276 trace!("overriding: {:?} / {:?}", genesis_value, overrides_value);
1277 *genesis_value = overrides_value.clone();
1278 },
1279 _ => {
1280 trace!("not match!");
1281 },
1282 }
1283 } else {
1284 warn!(
1286 "key: {overrides_key} not present in genesis_obj: {:?} (adding key)",
1287 genesis_obj
1288 );
1289 let overrides_value = overrides_obj.get(overrides_key).expect(&format!(
1290 "overrides_key {overrides_key} should be present in the overrides obj. qed"
1291 ));
1292 genesis_obj.insert(overrides_key.clone(), overrides_value.clone());
1293 }
1294 }
1295 }
1296}
1297
1298fn clear_authorities(
1299 runtime_config_ptr: &str,
1300 chain_spec_json: &mut serde_json::Value,
1301 ctx: &Context,
1302) {
1303 if let Some(val) = chain_spec_json.pointer_mut(runtime_config_ptr) {
1304 if val.get("session").is_some() {
1306 val["session"]["keys"] = json!([]);
1307 }
1308
1309 if val.get("aura").is_some() {
1310 val["aura"]["authorities"] = json!([]);
1311 }
1312
1313 if val.get("grandpa").is_some() {
1314 val["grandpa"]["authorities"] = json!([]);
1315 }
1316
1317 if val.get("collatorSelection").is_some() {
1319 val["collatorSelection"]["invulnerables"] = json!([]);
1320 }
1321
1322 if val.get("staking").is_some() && ctx == &Context::Relay {
1324 val["staking"]["invulnerables"] = json!([]);
1325 val["staking"]["stakers"] = json!([]);
1326
1327 if val["staking"]["devStakers"] == json!(null) {
1328 val["staking"]["validatorCount"] = json!(0);
1329 }
1330 }
1331 } else {
1332 unreachable!("pointer to runtime config should be valid!")
1333 }
1334}
1335
1336fn get_staking_min(runtime_config_ptr: &str, chain_spec_json: &mut serde_json::Value) -> u128 {
1337 let staking_ptr = format!("{runtime_config_ptr}/staking/stakers");
1339 if let Some(stakers) = chain_spec_json.pointer(&staking_ptr) {
1340 let min = stakers[0][2].clone();
1342 min.as_u64().unwrap_or(0).into()
1343 } else {
1344 0
1345 }
1346}
1347
1348fn add_balances(
1349 runtime_config_ptr: &str,
1350 chain_spec_json: &mut serde_json::Value,
1351 nodes: &Vec<NodeSpec>,
1352 token_decimals: u8,
1353 staking_min: u128,
1354) {
1355 if let Some(val) = chain_spec_json.pointer_mut(runtime_config_ptr) {
1356 let Some(balances) = val.pointer("/balances/balances") else {
1357 warn!("NO 'balances' key in runtime config, skipping...");
1359 return;
1360 };
1361
1362 let mut balances_map = generate_balance_map(balances);
1364 for node in nodes {
1365 if node.initial_balance.eq(&0) {
1366 continue;
1367 };
1368
1369 let balance = std::cmp::max(node.initial_balance, staking_min * 2);
1372 for k in ["sr", "sr_stash"] {
1373 let account = node.accounts.accounts.get(k).unwrap();
1374 balances_map.insert(account.address.clone(), balance);
1375 }
1376 }
1377
1378 balances_map.insert(
1381 "5FTcLfwFc7ctvqp3RhbEig6UuHLHcHVRujuUm8r21wy4dAR8".to_string(),
1382 1000 * 10_u128.pow(token_decimals as u32),
1383 );
1384
1385 let new_balances: Vec<(&String, &u128)> =
1387 balances_map.iter().collect::<Vec<(&String, &u128)>>();
1388
1389 val["balances"]["balances"] = json!(new_balances);
1390 } else {
1391 unreachable!("pointer to runtime config should be valid!")
1392 }
1393}
1394
1395fn get_node_keys(
1396 node: &NodeSpec,
1397 session_key: SessionKeyType,
1398 asset_hub_polkadot: bool,
1399) -> GenesisNodeKey {
1400 let sr_account = node.accounts.accounts.get("sr").unwrap();
1401 let sr_stash = node.accounts.accounts.get("sr_stash").unwrap();
1402 let ed_account = node.accounts.accounts.get("ed").unwrap();
1403 let ec_account = node.accounts.accounts.get("ec").unwrap();
1404 let eth_account = node.accounts.accounts.get("eth").unwrap();
1405 let mut keys = HashMap::new();
1406 for k in [
1407 "babe",
1408 "im_online",
1409 "parachain_validator",
1410 "authority_discovery",
1411 "para_validator",
1412 "para_assignment",
1413 "aura",
1414 "nimbus",
1415 "vrf",
1416 ] {
1417 if k == "aura" && asset_hub_polkadot {
1418 keys.insert(k.to_string(), ed_account.address.clone());
1419 continue;
1420 }
1421 keys.insert(k.to_string(), sr_account.address.clone());
1422 }
1423
1424 keys.insert("grandpa".to_string(), ed_account.address.clone());
1425 keys.insert("beefy".to_string(), ec_account.address.clone());
1426 keys.insert("eth".to_string(), eth_account.public_key.clone());
1427
1428 let account_to_use = match session_key {
1429 SessionKeyType::Default => sr_account.address.clone(),
1430 SessionKeyType::Stash => sr_stash.address.clone(),
1431 SessionKeyType::Evm => format!("0x{}", eth_account.public_key),
1432 };
1433
1434 (account_to_use.clone(), account_to_use, keys)
1435}
1436fn add_authorities(
1437 runtime_config_ptr: &str,
1438 chain_spec_json: &mut serde_json::Value,
1439 nodes: &[&NodeSpec],
1440 session_key: SessionKeyType,
1441) {
1442 let asset_hub_polkadot = chain_spec_json
1443 .get("id")
1444 .and_then(|v| v.as_str())
1445 .map(|id| id.starts_with("asset-hub-polkadot"))
1446 .unwrap_or_default();
1447 if let Some(val) = chain_spec_json.pointer_mut(runtime_config_ptr) {
1448 if let Some(session_keys) = val.pointer_mut("/session/keys") {
1449 let keys: Vec<GenesisNodeKey> = nodes
1450 .iter()
1451 .map(|node| get_node_keys(node, session_key, asset_hub_polkadot))
1452 .collect();
1453 *session_keys = json!(keys);
1454 } else {
1455 warn!("⚠️ 'session/keys' key not present in runtime config.");
1456 }
1457 } else {
1458 unreachable!("pointer to runtime config should be valid!")
1459 }
1460}
1461fn add_hrmp_channels(
1462 runtime_config_ptr: &str,
1463 chain_spec_json: &mut serde_json::Value,
1464 hrmp_channels: &[HrmpChannelConfig],
1465) {
1466 if let Some(val) = chain_spec_json.pointer_mut(runtime_config_ptr) {
1467 if let Some(preopen_hrmp_channels) = val.pointer_mut("/hrmp/preopenHrmpChannels") {
1468 let hrmp_channels = hrmp_channels
1469 .iter()
1470 .map(|c| {
1471 (
1472 c.sender(),
1473 c.recipient(),
1474 c.max_capacity(),
1475 c.max_message_size(),
1476 )
1477 })
1478 .collect::<Vec<_>>();
1479 *preopen_hrmp_channels = json!(hrmp_channels);
1480 } else {
1481 warn!("⚠️ 'hrmp/preopenHrmpChannels' key not present in runtime config.");
1482 }
1483 } else {
1484 unreachable!("pointer to runtime config should be valid!")
1485 }
1486}
1487
1488fn add_aura_authorities(
1489 runtime_config_ptr: &str,
1490 chain_spec_json: &mut serde_json::Value,
1491 nodes: &[&NodeSpec],
1492 _key_type: KeyType,
1493) {
1494 if let Some(val) = chain_spec_json.pointer_mut(runtime_config_ptr) {
1495 if let Some(aura_authorities) = val.pointer_mut("/aura/authorities") {
1496 let keys: Vec<String> = nodes
1497 .iter()
1498 .map(|node| {
1499 node.accounts
1500 .accounts
1501 .get("sr")
1502 .expect(&format!(
1503 "'sr' account should be set at spec computation {THIS_IS_A_BUG}"
1504 ))
1505 .address
1506 .clone()
1507 })
1508 .collect();
1509 *aura_authorities = json!(keys);
1510 } else {
1511 warn!("⚠️ 'aura/authorities' key not present in runtime config.");
1512 }
1513 } else {
1514 unreachable!("pointer to runtime config should be valid!")
1515 }
1516}
1517
1518fn add_grandpa_authorities(
1519 runtime_config_ptr: &str,
1520 chain_spec_json: &mut serde_json::Value,
1521 nodes: &[&NodeSpec],
1522 _key_type: KeyType,
1523) {
1524 if let Some(val) = chain_spec_json.pointer_mut(runtime_config_ptr) {
1525 if let Some(grandpa_authorities) = val.pointer_mut("/grandpa/authorities") {
1526 let keys: Vec<(String, usize)> = nodes
1527 .iter()
1528 .map(|node| {
1529 (
1530 node.accounts
1531 .accounts
1532 .get("ed")
1533 .expect(&format!(
1534 "'ed' account should be set at spec computation {THIS_IS_A_BUG}"
1535 ))
1536 .address
1537 .clone(),
1538 1,
1539 )
1540 })
1541 .collect();
1542 *grandpa_authorities = json!(keys);
1543 } else {
1544 warn!("⚠️ 'grandpa/authorities' key not present in runtime config.");
1545 }
1546 } else {
1547 unreachable!("pointer to runtime config should be valid!")
1548 }
1549}
1550
1551fn add_staking(
1552 runtime_config_ptr: &str,
1553 chain_spec_json: &mut serde_json::Value,
1554 nodes: &Vec<NodeSpec>,
1555 staking_min: u128,
1556) {
1557 if let Some(val) = chain_spec_json.pointer_mut(runtime_config_ptr) {
1558 let Some(_) = val.pointer("/staking") else {
1559 warn!("NO 'staking' key in runtime config, skipping...");
1561 return;
1562 };
1563
1564 let mut stakers = vec![];
1565 let mut invulnerables = vec![];
1566 for node in nodes {
1567 let sr_stash_addr = &node
1568 .accounts
1569 .accounts
1570 .get("sr_stash")
1571 .expect("'sr_stash account should be defined for the node. qed")
1572 .address;
1573 stakers.push(json!([
1574 sr_stash_addr,
1575 sr_stash_addr,
1576 staking_min,
1577 "Validator"
1578 ]));
1579
1580 if node.is_invulnerable {
1581 invulnerables.push(sr_stash_addr);
1582 }
1583 }
1584
1585 val["staking"]["validatorCount"] = json!(stakers.len());
1586 val["staking"]["stakers"] = json!(stakers);
1587 val["staking"]["invulnerables"] = json!(invulnerables);
1588 } else {
1589 unreachable!("pointer to runtime config should be valid!")
1590 }
1591}
1592
1593fn override_parachain_info(
1600 runtime_config_ptr: &str,
1601 chain_spec_json: &mut serde_json::Value,
1602 para_id: u32,
1603) {
1604 if let Some(val) = chain_spec_json.pointer_mut(runtime_config_ptr) {
1605 if let Some(parachain_id) = val.pointer_mut("/parachainInfo/parachainId") {
1606 *parachain_id = json!(para_id)
1607 } else {
1608 }
1610 } else {
1611 unreachable!("pointer to runtime config should be valid!")
1612 }
1613}
1614fn add_collator_selection(
1615 runtime_config_ptr: &str,
1616 chain_spec_json: &mut serde_json::Value,
1617 nodes: &[&NodeSpec],
1618 session_key: SessionKeyType,
1619) {
1620 if let Some(val) = chain_spec_json.pointer_mut(runtime_config_ptr) {
1621 let key_type = if let SessionKeyType::Evm = session_key {
1622 "eth"
1623 } else {
1624 "sr"
1625 };
1626 let keys: Vec<String> = nodes
1627 .iter()
1628 .map(|node| {
1629 node.accounts
1630 .accounts
1631 .get(key_type)
1632 .expect(&format!(
1633 "'sr' account should be set at spec computation {THIS_IS_A_BUG}"
1634 ))
1635 .address
1636 .clone()
1637 })
1638 .collect();
1639
1640 if let Some(invulnerables) = val.pointer_mut("/collatorSelection/invulnerables") {
1642 *invulnerables = json!(keys);
1643 } else {
1644 debug!("⚠️ 'invulnerables' not present in spec, will not be customized");
1646 }
1647 } else {
1648 unreachable!("pointer to runtime config should be valid!")
1649 }
1650}
1651
1652fn generate_balance_map(balances: &serde_json::Value) -> HashMap<String, u128> {
1654 let balances_map: HashMap<String, u128> =
1656 serde_json::from_value::<Vec<(String, u128)>>(balances.to_owned())
1657 .unwrap()
1658 .iter()
1659 .fold(HashMap::new(), |mut memo, balance| {
1660 memo.insert(balance.0.clone(), balance.1);
1661 memo
1662 });
1663 balances_map
1664}
1665
1666#[cfg(test)]
1667mod tests {
1668 use std::fs;
1669
1670 use configuration::HrmpChannelConfigBuilder;
1671
1672 use super::*;
1673 use crate::{generators, shared::types::NodeAccounts};
1674
1675 const ROCOCO_LOCAL_PLAIN_TESTING: &str = "./testing/rococo-local-plain.json";
1676
1677 fn chain_spec_test(file: &str) -> serde_json::Value {
1678 let content = fs::read_to_string(file).unwrap();
1679 serde_json::from_str(&content).unwrap()
1680 }
1681
1682 fn chain_spec_with_stake() -> serde_json::Value {
1683 json!({"genesis": {
1684 "runtimeGenesis" : {
1685 "patch": {
1686 "staking": {
1687 "forceEra": "NotForcing",
1688 "invulnerables": [
1689 "5GNJqTPyNqANBkUVMN1LPPrxXnFouWXoe2wNSmmEoLctxiZY",
1690 "5HpG9w8EBLe5XCrbczpwq5TSXvedjrBGCwqxK1iQ7qUsSWFc"
1691 ],
1692 "minimumValidatorCount": 1,
1693 "slashRewardFraction": 100000000,
1694 "stakers": [
1695 [
1696 "5GNJqTPyNqANBkUVMN1LPPrxXnFouWXoe2wNSmmEoLctxiZY",
1697 "5GNJqTPyNqANBkUVMN1LPPrxXnFouWXoe2wNSmmEoLctxiZY",
1698 100000000000001_u128,
1699 "Validator"
1700 ],
1701 [
1702 "5HpG9w8EBLe5XCrbczpwq5TSXvedjrBGCwqxK1iQ7qUsSWFc",
1703 "5HpG9w8EBLe5XCrbczpwq5TSXvedjrBGCwqxK1iQ7qUsSWFc",
1704 100000000000000_u128,
1705 "Validator"
1706 ]
1707 ],
1708 "validatorCount": 2
1709 },
1710 }
1711 }
1712 }})
1713 }
1714
1715 fn chain_spec_with_dev_stakers() -> serde_json::Value {
1716 json!({"genesis": {
1717 "runtimeGenesis" : {
1718 "patch": {
1719 "staking": {
1720 "activeEra": [
1721 0,
1722 0,
1723 0
1724 ],
1725 "canceledPayout": 0,
1726 "devStakers": [
1727 2000,
1728 25000
1729 ],
1730 "forceEra": "NotForcing",
1731 "invulnerables": [],
1732 "maxNominatorCount": null,
1733 "maxValidatorCount": null,
1734 "minNominatorBond": 0,
1735 "minValidatorBond": 0,
1736 "slashRewardFraction": 0,
1737 "stakers": [],
1738 "validatorCount": 500
1739 },
1740 }
1741 }
1742 }})
1743 }
1744
1745 #[test]
1746 fn get_min_stake_works() {
1747 let mut chain_spec_json = chain_spec_with_stake();
1748
1749 let pointer = get_runtime_config_pointer(&chain_spec_json).unwrap();
1750 let min = get_staking_min(&pointer, &mut chain_spec_json);
1751
1752 assert_eq!(100000000000001, min);
1753 }
1754
1755 #[test]
1756 fn dev_stakers_not_override_count_works() {
1757 let mut chain_spec_json = chain_spec_with_dev_stakers();
1758
1759 let pointer = get_runtime_config_pointer(&chain_spec_json).unwrap();
1760 clear_authorities(&pointer, &mut chain_spec_json, &Context::Relay);
1761
1762 let validator_count = chain_spec_json
1763 .pointer(&format!("{pointer}/staking/validatorCount"))
1764 .unwrap();
1765 assert_eq!(validator_count, &json!(500));
1766 }
1767
1768 #[test]
1769 fn dev_stakers_override_count_works() {
1770 let mut chain_spec_json = chain_spec_with_stake();
1771
1772 let pointer = get_runtime_config_pointer(&chain_spec_json).unwrap();
1773 clear_authorities(&pointer, &mut chain_spec_json, &Context::Relay);
1774
1775 let validator_count = chain_spec_json
1776 .pointer(&format!("{pointer}/staking/validatorCount"))
1777 .unwrap();
1778 assert_eq!(validator_count, &json!(0));
1779 }
1780
1781 #[test]
1782 fn overrides_from_toml_works() {
1783 use serde::{Deserialize, Serialize};
1784
1785 #[derive(Debug, Serialize, Deserialize)]
1786 struct MockConfig {
1787 #[serde(rename = "genesis", skip_serializing_if = "Option::is_none")]
1788 genesis_overrides: Option<serde_json::Value>,
1789 }
1790
1791 let mut chain_spec_json = chain_spec_test(ROCOCO_LOCAL_PLAIN_TESTING);
1792 const TOML: &str = "[genesis.runtime.balances]
1794 devAccounts = [
1795 20000,
1796 1000000000000000000,
1797 \"//Sender//{}\"
1798 ]";
1799 let override_toml: MockConfig = toml::from_str(TOML).unwrap();
1800 let overrides = override_toml.genesis_overrides.unwrap();
1801 let pointer = get_runtime_config_pointer(&chain_spec_json).unwrap();
1802
1803 let percolated_overrides = percolate_overrides(&pointer, &overrides)
1804 .map_err(|e| GeneratorError::ChainSpecGeneration(e.to_string()))
1805 .unwrap();
1806 trace!("percolated_overrides: {:#?}", percolated_overrides);
1807 if let Some(genesis) = chain_spec_json.pointer_mut(&pointer) {
1808 merge(genesis, percolated_overrides);
1809 }
1810
1811 trace!("chain spec: {chain_spec_json:#?}");
1812 assert!(chain_spec_json
1813 .pointer("/genesis/runtime/balances/devAccounts")
1814 .is_some());
1815 }
1816
1817 #[test]
1818 fn add_balances_works() {
1819 let mut spec_plain = chain_spec_test(ROCOCO_LOCAL_PLAIN_TESTING);
1820 let mut name = String::from("luca");
1821 let initial_balance = 1_000_000_000_000_u128;
1822 let seed = format!("//{}{name}", name.remove(0).to_uppercase());
1823 let accounts = NodeAccounts {
1824 accounts: generators::generate_node_keys(&seed).unwrap(),
1825 seed,
1826 };
1827 let node = NodeSpec {
1828 name,
1829 accounts,
1830 initial_balance,
1831 ..Default::default()
1832 };
1833
1834 let nodes = vec![node];
1835 add_balances("/genesis/runtime", &mut spec_plain, &nodes, 12, 0);
1836
1837 let new_balances = spec_plain
1838 .pointer("/genesis/runtime/balances/balances")
1839 .unwrap();
1840
1841 let balances_map = generate_balance_map(new_balances);
1842
1843 let sr = nodes[0].accounts.accounts.get("sr").unwrap();
1845 let sr_stash = nodes[0].accounts.accounts.get("sr_stash").unwrap();
1846 assert_eq!(balances_map.get(&sr.address).unwrap(), &initial_balance);
1847 assert_eq!(
1848 balances_map.get(&sr_stash.address).unwrap(),
1849 &initial_balance
1850 );
1851 }
1852
1853 #[test]
1854 fn add_balances_ensure_zombie_account() {
1855 let mut spec_plain = chain_spec_test(ROCOCO_LOCAL_PLAIN_TESTING);
1856
1857 let balances = spec_plain
1858 .pointer("/genesis/runtime/balances/balances")
1859 .unwrap();
1860 let balances_map = generate_balance_map(balances);
1861
1862 let nodes: Vec<NodeSpec> = vec![];
1863 add_balances("/genesis/runtime", &mut spec_plain, &nodes, 12, 0);
1864
1865 let new_balances = spec_plain
1866 .pointer("/genesis/runtime/balances/balances")
1867 .unwrap();
1868
1869 let new_balances_map = generate_balance_map(new_balances);
1870
1871 assert!(new_balances_map.contains_key("5FTcLfwFc7ctvqp3RhbEig6UuHLHcHVRujuUm8r21wy4dAR8"));
1873 assert_eq!(new_balances_map.len(), balances_map.len() + 1);
1874 }
1875
1876 #[test]
1877 fn add_balances_spec_without_balances() {
1878 let mut spec_plain = chain_spec_test(ROCOCO_LOCAL_PLAIN_TESTING);
1879
1880 {
1881 let balances = spec_plain.pointer_mut("/genesis/runtime/balances").unwrap();
1882 *balances = json!(serde_json::Value::Null);
1883 }
1884
1885 let mut name = String::from("luca");
1886 let initial_balance = 1_000_000_000_000_u128;
1887 let seed = format!("//{}{name}", name.remove(0).to_uppercase());
1888 let accounts = NodeAccounts {
1889 accounts: generators::generate_node_keys(&seed).unwrap(),
1890 seed,
1891 };
1892 let node = NodeSpec {
1893 name,
1894 accounts,
1895 initial_balance,
1896 ..Default::default()
1897 };
1898
1899 let nodes = vec![node];
1900 add_balances("/genesis/runtime", &mut spec_plain, &nodes, 12, 0);
1901
1902 let new_balances = spec_plain.pointer("/genesis/runtime/balances/balances");
1903
1904 assert_eq!(new_balances, None);
1906 }
1907
1908 #[test]
1909 fn add_staking_works() {
1910 let mut chain_spec_json = chain_spec_with_stake();
1911 let mut name = String::from("luca");
1912 let initial_balance = 1_000_000_000_000_u128;
1913 let seed = format!("//{}{name}", name.remove(0).to_uppercase());
1914 let accounts = NodeAccounts {
1915 accounts: generators::generate_node_keys(&seed).unwrap(),
1916 seed,
1917 };
1918 let node = NodeSpec {
1919 name,
1920 accounts,
1921 initial_balance,
1922 ..Default::default()
1923 };
1924
1925 let pointer = get_runtime_config_pointer(&chain_spec_json).unwrap();
1926 let min = get_staking_min(&pointer, &mut chain_spec_json);
1927
1928 let nodes = vec![node];
1929 add_staking(&pointer, &mut chain_spec_json, &nodes, min);
1930
1931 let new_staking = chain_spec_json
1932 .pointer("/genesis/runtimeGenesis/patch/staking")
1933 .unwrap();
1934
1935 let sr_stash = nodes[0].accounts.accounts.get("sr_stash").unwrap();
1937 assert_eq!(new_staking["stakers"][0][0], json!(sr_stash.address));
1938 assert_eq!(new_staking["stakers"][0][2], json!(min));
1940 assert_eq!(new_staking["stakers"].as_array().unwrap().len(), 1);
1942 }
1943
1944 #[test]
1945 fn adding_hrmp_channels_works() {
1946 let mut spec_plain = chain_spec_test(ROCOCO_LOCAL_PLAIN_TESTING);
1947
1948 {
1949 let current_hrmp_channels = spec_plain
1950 .pointer("/genesis/runtime/hrmp/preopenHrmpChannels")
1951 .unwrap();
1952 assert_eq!(current_hrmp_channels, &json!([]));
1954 }
1955
1956 let para_100_101 = HrmpChannelConfigBuilder::new()
1957 .with_sender(100)
1958 .with_recipient(101)
1959 .build();
1960 let para_101_100 = HrmpChannelConfigBuilder::new()
1961 .with_sender(101)
1962 .with_recipient(100)
1963 .build();
1964 let channels = vec![para_100_101, para_101_100];
1965
1966 add_hrmp_channels("/genesis/runtime", &mut spec_plain, &channels);
1967 let new_hrmp_channels = spec_plain
1968 .pointer("/genesis/runtime/hrmp/preopenHrmpChannels")
1969 .unwrap()
1970 .as_array()
1971 .unwrap();
1972
1973 assert_eq!(new_hrmp_channels.len(), 2);
1974 assert_eq!(new_hrmp_channels.first().unwrap()[0], 100);
1975 assert_eq!(new_hrmp_channels.first().unwrap()[1], 101);
1976 assert_eq!(new_hrmp_channels.last().unwrap()[0], 101);
1977 assert_eq!(new_hrmp_channels.last().unwrap()[1], 100);
1978 }
1979
1980 #[test]
1981 fn adding_hrmp_channels_to_an_spec_without_channels() {
1982 let mut spec_plain = chain_spec_test("./testing/rococo-local-plain.json");
1983
1984 {
1985 let hrmp = spec_plain.pointer_mut("/genesis/runtime/hrmp").unwrap();
1986 *hrmp = json!(serde_json::Value::Null);
1987 }
1988
1989 let para_100_101 = HrmpChannelConfigBuilder::new()
1990 .with_sender(100)
1991 .with_recipient(101)
1992 .build();
1993 let para_101_100 = HrmpChannelConfigBuilder::new()
1994 .with_sender(101)
1995 .with_recipient(100)
1996 .build();
1997 let channels = vec![para_100_101, para_101_100];
1998
1999 add_hrmp_channels("/genesis/runtime", &mut spec_plain, &channels);
2000 let new_hrmp_channels = spec_plain.pointer("/genesis/runtime/hrmp/preopenHrmpChannels");
2001
2002 assert_eq!(new_hrmp_channels, None);
2004 }
2005
2006 #[test]
2007 fn get_node_keys_works() {
2008 let mut name = String::from("luca");
2009 let seed = format!("//{}{name}", name.remove(0).to_uppercase());
2010 let accounts = NodeAccounts {
2011 accounts: generators::generate_node_keys(&seed).unwrap(),
2012 seed,
2013 };
2014 let node = NodeSpec {
2015 name,
2016 accounts,
2017 ..Default::default()
2018 };
2019
2020 let sr = &node.accounts.accounts["sr"];
2021 let keys = [
2022 ("babe".into(), sr.address.clone()),
2023 ("im_online".into(), sr.address.clone()),
2024 ("parachain_validator".into(), sr.address.clone()),
2025 ("authority_discovery".into(), sr.address.clone()),
2026 ("para_validator".into(), sr.address.clone()),
2027 ("para_assignment".into(), sr.address.clone()),
2028 ("aura".into(), sr.address.clone()),
2029 ("nimbus".into(), sr.address.clone()),
2030 ("vrf".into(), sr.address.clone()),
2031 (
2032 "grandpa".into(),
2033 node.accounts.accounts["ed"].address.clone(),
2034 ),
2035 ("beefy".into(), node.accounts.accounts["ec"].address.clone()),
2036 ("eth".into(), node.accounts.accounts["eth"].address.clone()),
2037 ]
2038 .into();
2039
2040 let sr_stash = &node.accounts.accounts["sr_stash"];
2042 let node_key = get_node_keys(&node, SessionKeyType::Stash, false);
2043 assert_eq!(node_key.0, sr_stash.address);
2044 assert_eq!(node_key.1, sr_stash.address);
2045 assert_eq!(node_key.2, keys);
2046 let node_key = get_node_keys(&node, SessionKeyType::Default, false);
2048 assert_eq!(node_key.0, sr.address);
2049 assert_eq!(node_key.1, sr.address);
2050 assert_eq!(node_key.2, keys);
2051 }
2052
2053 #[test]
2054 fn get_node_keys_supports_asset_hub_polkadot() {
2055 let mut name = String::from("luca");
2056 let seed = format!("//{}{name}", name.remove(0).to_uppercase());
2057 let accounts = NodeAccounts {
2058 accounts: generators::generate_node_keys(&seed).unwrap(),
2059 seed,
2060 };
2061 let node = NodeSpec {
2062 name,
2063 accounts,
2064 ..Default::default()
2065 };
2066
2067 let node_key = get_node_keys(&node, SessionKeyType::default(), false);
2068 assert_eq!(node_key.2["aura"], node.accounts.accounts["sr"].address);
2069
2070 let node_key = get_node_keys(&node, SessionKeyType::default(), true);
2071 assert_eq!(node_key.2["aura"], node.accounts.accounts["ed"].address);
2072 }
2073}