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