zombienet_orchestrator/generators/
chain_spec.rs

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// TODO: (javier) move to state
30#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
31pub enum Context {
32    Relay,
33    Para { relay_chain: Chain, para_id: ParaId },
34}
35
36/// Posible chain-spec formats
37#[derive(Debug, Clone, Copy)]
38enum ChainSpecFormat {
39    Plain,
40    Raw,
41}
42/// Key types to replace in spec
43#[derive(Debug, Clone, Copy)]
44enum KeyType {
45    Session,
46    Aura,
47    Grandpa,
48}
49
50#[derive(Debug, Clone, Copy, Default)]
51enum SessionKeyType {
52    // Default derivarion (e.g `//`)
53    #[default]
54    Default,
55    // Stash detivarion (e.g `//<name>/stash`)
56    Stash,
57    // EVM session type
58    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
85/// Presets to check if is not set by the user.
86/// We check if the preset is valid for the runtime in order
87/// and if non of them are preset we fallback to the `default config`.
88const DEFAULT_PRESETS_TO_CHECK: [&str; 3] = ["local_testnet", "development", "dev"];
89
90/// Chain-spec builder representation
91///
92/// Multiple options are supported, and the current order is:
93/// IF [`asset_location`] is _some_ -> Use this chain_spec by copying the file from [`AssetLocation`]
94/// ELSE IF [`runtime_location`] is _some_ -> generate the chain-spec using the sc-chain-spec builder.
95/// ELSE -> Fallback to use the `default` or customized cmd.
96
97#[derive(Debug, Clone, Serialize, Deserialize)]
98pub struct ChainSpec {
99    // Name of the spec file, most of the times could be the same as the chain_name. (e.g rococo-local)
100    chain_spec_name: String,
101    // Location of the chain-spec to use
102    asset_location: Option<AssetLocation>,
103    // Location of the runtime to use
104    runtime: Option<ChainSpecRuntime>,
105    maybe_plain_path: Option<PathBuf>,
106    chain_name: Option<String>,
107    raw_path: Option<PathBuf>,
108    // The binary to build the chain-spec
109    command: Option<CommandInContext>,
110    // Imgae to use for build the chain-spec
111    image: Option<String>,
112    // Contex of the network (e.g relay or para)
113    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    /// Build the chain-spec
176    ///
177    /// Chain spec generation flow:
178    /// if chain_spec_path is set -> use this chain_spec
179    /// else if runtime_path is set and cmd is compatible with chain-spec-builder -> use the chain-spec-builder
180    /// else if chain_spec_command is set -> use this cmd for generate the chain_spec
181    /// else -> use the default command.
182    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 asset_location is some, then copy the asset to the `base_dir` of the ns with the name `<name>-plain.json`
200        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            // First dump the runtime into the ns scoped fs, since we want to easily reproduce
216            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            // list the presets to check if match with the supplied one or one of the defaults
230            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            // check the preset to use with this priorities:
241            // - IF user provide a preset (and if present) use it
242            // - else (user don't provide preset or the provided one isn't preset)
243            //     check the [`DEFAULT_PRESETS_TO_CHECK`] in order to find one valid
244            // - If we can't find any valid preset use the `default config` from the runtime
245
246            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                // default config
261                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            // we should create the chain-spec using command.
299            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            // SAFETY: we ensure that command is some with the first check of the fn
307            // default as empty
308            let sanitized_cmd = if replacement_value.is_empty() {
309                // we need to remove the `--chain` flag
310                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        // check if the _generated_ spec is in raw mode.
349        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        // raw path already set, no more work to do here...
379        let None = self.raw_path else {
380            return Ok(());
381        };
382
383        // expected raw path
384        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        // `build_raw` is always called after `build`, so `maybe_plain_path` must be set at this point
419        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        // fallback to use _cmd_ for raw creation
452        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        // TODO: we should get the full path from the scoped filesystem
472        let chain_spec_path_local = format!(
473            "{}/{}",
474            ns.base_dir().to_string_lossy(),
475            maybe_plain_path.display()
476        );
477        // Remote path to be injected
478        let chain_spec_path_in_pod = format!("{}/{}", NODE_CONFIG_DIR, maybe_plain_path.display());
479        // Path in the context of the node, this can be different in the context of the providers (e.g native)
480        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            // In native
485            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    /// Override the :code in chain-spec raw version
600    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        // first ensure we have the raw version of the chain-spec
609        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        // read override wasm
616        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        // read spec  to json value
623        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        // override :code
629        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        // save it
645        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        // first ensure we have the raw version of the chain-spec
659        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        // read overrides to json value
668        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        // read spec to json value
675        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 overrides with existing spec
681        merge(&mut chain_spec_json, &override_content);
682
683        // save changes
684        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                // if we have both paths return the raw
723                (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                // if we have both paths return the raw
751                (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    // TODO: (javier) move this fns to state aware
767    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            // make genesis overrides first.
798            if let Some(overrides) = &para.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            // Get validators to add as authorities
815            let validators: Vec<&NodeSpec> = para
816                .collators
817                .iter()
818                .filter(|node| node.is_validator)
819                .collect();
820
821            // check chain key types
822            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            // Add nodes to collator
837            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 `parachainInfo/parachainId`
851            override_parachain_info(&pointer, &mut chain_spec_json, para.id);
852
853            // write spec
854            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            // get the tokenDecimals property or set the default (12)
883            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            // get the config pointer
895            let pointer = get_runtime_config_pointer(&chain_spec_json)
896                .map_err(GeneratorError::ChainSpecGeneration)?;
897
898            // make genesis overrides first.
899            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            // get min stake (to store if neede later)
908            let staking_min = get_staking_min(&pointer, &mut chain_spec_json);
909
910            // Clear authorities
911            clear_authorities(&pointer, &mut chain_spec_json, &self.context);
912
913            // add balances
914            add_balances(
915                &pointer,
916                &mut chain_spec_json,
917                &relaychain.nodes,
918                token_decimals,
919                staking_min,
920            );
921
922            // add staking
923            add_staking(
924                &pointer,
925                &mut chain_spec_json,
926                &relaychain.nodes,
927                staking_min,
928            );
929
930            // Get validators to add as authorities
931            let validators: Vec<&NodeSpec> = relaychain
932                .nodes
933                .iter()
934                .filter(|node| node.is_validator)
935                .collect();
936
937            // check chain key types
938            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            // staking && nominators
954
955            if !hrmp_channels.is_empty() {
956                add_hrmp_channels(&pointer, &mut chain_spec_json, hrmp_channels);
957            }
958
959            // paras
960            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            // TODO:
972            // - staking
973            // - nominators
974
975            // write spec
976            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        // write spec
1020        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    /// Get the chain_is from the json content of a chain-spec file.
1029    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    // generate_command.
1061
1062    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
1117// Internal Chain-spec customizations
1118
1119async 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            // For retro-compatibility with substrate pre Polkadot 0.9.5
1134            "/parachainsParas/paras"
1135        } else {
1136            // The config may not contain paras. Since chainspec allows to contain the RuntimeGenesisConfig patch we can inject it.
1137            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    // runtime_genesis_config is no longer in ChainSpec after rococo runtime rework (refer to: https://github.com/paritytech/polkadot-sdk/pull/1256)
1168    // ChainSpec may contain a RuntimeGenesisConfigPatch
1169    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        // top level key is at end of the pointer
1209        let p = format!("/{}", pointer_parts[i]);
1210        trace!("overrides pointer {p}");
1211        p
1212    } else {
1213        // example: pointer is `/genesis/runtimeGenesis/patch` and the overrides start at  `runtimeGenesis`
1214        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        // overrides already start with /genesis
1230        return Ok("/genesis".into());
1231    } else {
1232        // check if we are one level inner
1233        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
1256// Merge `patch_section` with `overrides`.
1257fn 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            // we only want to override keys present in the genesis object
1266            if let Some(genesis_value) = genesis_obj.get_mut(overrides_key) {
1267                match (&genesis_value, overrides_obj.get(overrides_key)) {
1268                    // recurse if genesis value is an object
1269                    (serde_json::Value::Object(_), Some(overrides_value))
1270                        if overrides_value.is_object() =>
1271                    {
1272                        merge(genesis_value, overrides_value);
1273                    },
1274                    // override if genesis value not an object
1275                    (_, 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                // Allow to add keys, see (https://github.com/paritytech/zombienet/issues/1614)
1285                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        // clear keys (session, aura, grandpa)
1305        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        // clear collatorSelector
1318        if val.get("collatorSelection").is_some() {
1319            val["collatorSelection"]["invulnerables"] = json!([]);
1320        }
1321
1322        // clear staking but not `validatorCount` if `devStakers` is set
1323        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    // get min staking
1338    let staking_ptr = format!("{runtime_config_ptr}/staking/stakers");
1339    if let Some(stakers) = chain_spec_json.pointer(&staking_ptr) {
1340        // stakers should be an array
1341        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            // should be a info log
1358            warn!("NO 'balances' key in runtime config, skipping...");
1359            return;
1360        };
1361
1362        // create a balance map
1363        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            // TODO: handle error here and check the `accounts.accounts` design
1370            // Double down the minimal stake defined
1371            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        // ensure zombie account (//Zombie) have funds
1379        // we will use for internal usage (e.g new validators)
1380        balances_map.insert(
1381            "5FTcLfwFc7ctvqp3RhbEig6UuHLHcHVRujuUm8r21wy4dAR8".to_string(),
1382            1000 * 10_u128.pow(token_decimals as u32),
1383        );
1384
1385        // convert the map and store again
1386        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            // should be a info log
1560            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
1593// TODO: (team)
1594// fn add_nominators() {}
1595
1596// // TODO: (team) we should think a better way to use the decorators from
1597// // current version (ts).
1598// fn para_custom() { todo!() }
1599fn 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            // Add warning here!
1609        }
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        // collatorSelection.invulnerables
1641        if let Some(invulnerables) = val.pointer_mut("/collatorSelection/invulnerables") {
1642            *invulnerables = json!(keys);
1643        } else {
1644            // TODO: add a nice warning here.
1645            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
1652// Helpers
1653fn generate_balance_map(balances: &serde_json::Value) -> HashMap<String, u128> {
1654    // SAFETY: balances is always an array in chain-spec with items [k,v]
1655    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        // Could also be  something like [genesis.runtimeGenesis.patch.balances]
1793        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        // sr and sr_stash keys exists
1844        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        // sr and sr_stash keys exists
1872        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 'balances' is not created
1905        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        // stakers should be one (with the luca sr_stash accounts)
1936        let sr_stash = nodes[0].accounts.accounts.get("sr_stash").unwrap();
1937        assert_eq!(new_staking["stakers"][0][0], json!(sr_stash.address));
1938        // with the calculated minimal bound
1939        assert_eq!(new_staking["stakers"][0][2], json!(min));
1940        // and only one
1941        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 should be empty
1953            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 'preopenHrmpChannels' is not created
2003        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        // Stash
2041        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        // Non-stash
2047        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}