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// Zombie key to insert (//Zombie)
30const ZOMBIE_KEY: &str = "5FTcLfwFc7ctvqp3RhbEig6UuHLHcHVRujuUm8r21wy4dAR8";
31
32// TODO: (javier) move to state
33#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
34pub enum Context {
35    Relay,
36    Para { relay_chain: Chain, para_id: ParaId },
37}
38
39/// Posible chain-spec formats
40#[derive(Debug, Clone, Copy)]
41enum ChainSpecFormat {
42    Plain,
43    Raw,
44}
45/// Key types to replace in spec
46#[derive(Debug, Clone, Copy)]
47enum KeyType {
48    Session,
49    Aura,
50    Grandpa,
51}
52
53#[derive(Debug, Clone, Copy, Default)]
54enum SessionKeyType {
55    // Default derivarion (e.g `//`)
56    #[default]
57    Default,
58    // Stash detivarion (e.g `//<name>/stash`)
59    Stash,
60    // EVM session type
61    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
88/// Presets to check if is not set by the user.
89/// We check if the preset is valid for the runtime in order
90/// and if non of them are preset we fallback to the `default config`.
91const DEFAULT_PRESETS_TO_CHECK: [&str; 3] = ["local_testnet", "development", "dev"];
92
93/// Chain-spec builder representation
94///
95/// Multiple options are supported, and the current order is:
96/// IF [`asset_location`] is _some_ -> Use this chain_spec by copying the file from [`AssetLocation`]
97/// ELSE IF [`runtime_location`] is _some_ -> generate the chain-spec using the sc-chain-spec builder.
98/// ELSE -> Fallback to use the `default` or customized cmd.
99
100#[derive(Debug, Clone, Serialize, Deserialize)]
101pub struct ChainSpec {
102    // Name of the spec file, most of the times could be the same as the chain_name. (e.g rococo-local)
103    chain_spec_name: String,
104    // Location of the chain-spec to use
105    asset_location: Option<AssetLocation>,
106    // Location of the runtime to use
107    runtime: Option<ChainSpecRuntime>,
108    maybe_plain_path: Option<PathBuf>,
109    chain_name: Option<String>,
110    raw_path: Option<PathBuf>,
111    // The binary to build the chain-spec
112    command: Option<CommandInContext>,
113    // Imgae to use for build the chain-spec
114    image: Option<String>,
115    // Contex of the network (e.g relay or para)
116    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    /// Build the chain-spec
179    ///
180    /// Chain spec generation flow:
181    /// if chain_spec_path is set -> use this chain_spec
182    /// else if runtime_path is set and cmd is compatible with chain-spec-builder -> use the chain-spec-builder
183    /// else if chain_spec_command is set -> use this cmd for generate the chain_spec
184    /// else -> use the default command.
185    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 asset_location is some, then copy the asset to the `base_dir` of the ns with the name `<name>-plain.json`
203        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            // First dump the runtime into the ns scoped fs, since we want to easily reproduce
219            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            // list the presets to check if match with the supplied one or one of the defaults
233            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            // check the preset to use with this priorities:
244            // - IF user provide a preset (and if present) use it
245            // - else (user don't provide preset or the provided one isn't preset)
246            //     check the [`DEFAULT_PRESETS_TO_CHECK`] in order to find one valid
247            // - If we can't find any valid preset use the `default config` from the runtime
248
249            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                // default config
264                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            // we should create the chain-spec using command.
302            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            // SAFETY: we ensure that command is some with the first check of the fn
310            // default as empty
311            let sanitized_cmd = if replacement_value.is_empty() {
312                // we need to remove the `--chain` flag
313                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        // check if the _generated_ spec is in raw mode.
352        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        // raw path already set, no more work to do here...
382        let None = self.raw_path else {
383            return Ok(());
384        };
385
386        // expected raw path
387        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        // `build_raw` is always called after `build`, so `maybe_plain_path` must be set at this point
422        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        // fallback to use _cmd_ for raw creation
455        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        // TODO: we should get the full path from the scoped filesystem
475        let chain_spec_path_local = format!(
476            "{}/{}",
477            ns.base_dir().to_string_lossy(),
478            maybe_plain_path.display()
479        );
480        // Remote path to be injected
481        let chain_spec_path_in_pod = format!("{}/{}", NODE_CONFIG_DIR, maybe_plain_path.display());
482        // Path in the context of the node, this can be different in the context of the providers (e.g native)
483        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            // In native
488            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    /// Override the :code in chain-spec raw version
603    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        // first ensure we have the raw version of the chain-spec
612        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        // read override wasm
619        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        // read spec  to json value
626        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        // override :code
632        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        // save it
648        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        // first ensure we have the raw version of the chain-spec
662        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        // read overrides to json value
671        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        // read spec to json value
678        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 overrides with existing spec
684        merge(&mut chain_spec_json, &override_content);
685
686        // save changes
687        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                // if we have both paths return the raw
726                (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                // if we have both paths return the raw
754                (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    // TODO: (javier) move this fns to state aware
770    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            // make genesis overrides first.
801            if let Some(overrides) = &para.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            // Get validators to add as authorities
818            let validators: Vec<&NodeSpec> = para
819                .collators
820                .iter()
821                .filter(|node| node.is_validator)
822                .collect();
823
824            // check chain key types
825            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            // Add nodes to collator
840            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 `parachainInfo/parachainId`
854            override_parachain_info(&pointer, &mut chain_spec_json, para.id);
855
856            // check if `assets` pallet config
857            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            // write spec
862            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            // get the tokenDecimals property or set the default (12)
891            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            // get the config pointer
903            let pointer = get_runtime_config_pointer(&chain_spec_json)
904                .map_err(GeneratorError::ChainSpecGeneration)?;
905
906            // make genesis overrides first.
907            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            // get min stake (to store if neede later)
916            let staking_min = get_staking_min(&pointer, &mut chain_spec_json);
917
918            // Clear authorities
919            clear_authorities(&pointer, &mut chain_spec_json, &self.context);
920
921            // add balances
922            let mut balances_to_add =
923                generate_balance_to_add_from_nodes(&relaychain.nodes, staking_min);
924
925            // ensure zombie account (//Zombie) have funds
926            // we will use for internal usage (e.g new validators)
927            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
935            add_staking(
936                &pointer,
937                &mut chain_spec_json,
938                &relaychain.nodes,
939                staking_min,
940            );
941
942            // Get validators to add as authorities
943            let validators: Vec<&NodeSpec> = relaychain
944                .nodes
945                .iter()
946                .filter(|node| node.is_validator)
947                .collect();
948
949            // check chain key types
950            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            // staking && nominators
966
967            if !hrmp_channels.is_empty() {
968                add_hrmp_channels(&pointer, &mut chain_spec_json, hrmp_channels);
969            }
970
971            // paras
972            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            // TODO:
984            // - staking
985            // - nominators
986
987            // write spec
988            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        // write spec
1032        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    /// Get the chain_is from the json content of a chain-spec file.
1041    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    // generate_command.
1073
1074    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
1129// Internal Chain-spec customizations
1130
1131async 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            // For retro-compatibility with substrate pre Polkadot 0.9.5
1146            "/parachainsParas/paras"
1147        } else {
1148            // The config may not contain paras. Since chainspec allows to contain the RuntimeGenesisConfig patch we can inject it.
1149            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    // runtime_genesis_config is no longer in ChainSpec after rococo runtime rework (refer to: https://github.com/paritytech/polkadot-sdk/pull/1256)
1180    // ChainSpec may contain a RuntimeGenesisConfigPatch
1181    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        // top level key is at end of the pointer
1221        let p = format!("/{}", pointer_parts[i]);
1222        trace!("overrides pointer {p}");
1223        p
1224    } else {
1225        // example: pointer is `/genesis/runtimeGenesis/patch` and the overrides start at  `runtimeGenesis`
1226        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        // overrides already start with /genesis
1242        return Ok("/genesis".into());
1243    } else {
1244        // check if we are one level inner
1245        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
1268// Merge `patch_section` with `overrides`.
1269fn 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            // we only want to override keys present in the genesis object
1278            if let Some(genesis_value) = genesis_obj.get_mut(overrides_key) {
1279                match (&genesis_value, overrides_obj.get(overrides_key)) {
1280                    // recurse if genesis value is an object
1281                    (serde_json::Value::Object(_), Some(overrides_value))
1282                        if overrides_value.is_object() =>
1283                    {
1284                        merge(genesis_value, overrides_value);
1285                    },
1286                    // override if genesis value not an object
1287                    (_, 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                // Allow to add keys, see (https://github.com/paritytech/zombienet/issues/1614)
1297                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        // clear keys (session, aura, grandpa)
1317        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        // clear collatorSelector
1330        if val.get("collatorSelection").is_some() {
1331            val["collatorSelection"]["invulnerables"] = json!([]);
1332        }
1333
1334        // clear staking but not `validatorCount` if `devStakers` is set
1335        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    // get min staking
1350    let staking_ptr = format!("{runtime_config_ptr}/staking/stakers");
1351    if let Some(stakers) = chain_spec_json.pointer(&staking_ptr) {
1352        // stakers should be an array
1353        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    // token_decimals: u8,
1365) {
1366    if let Some(val) = chain_spec_json.pointer_mut(runtime_config_ptr) {
1367        let Some(balances) = val.pointer("/balances/balances") else {
1368            // should be a info log
1369            warn!("NO 'balances' key in runtime config, skipping...");
1370            return;
1371        };
1372
1373        // create a balance map
1374        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        // convert the map and store again
1380        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            // should be a info log
1554            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
1587// TODO: (team)
1588// fn add_nominators() {}
1589
1590// // TODO: (team) we should think a better way to use the decorators from
1591// // current version (ts).
1592// fn para_custom() { todo!() }
1593fn 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            // Add warning here!
1603        }
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                    // map account / balance
1625                    (
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        // collatorSelection.invulnerables
1675        if let Some(invulnerables) = val.pointer_mut("/collatorSelection/invulnerables") {
1676            *invulnerables = json!(keys);
1677        } else {
1678            // TODO: add a nice warning here.
1679            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
1686// Helpers
1687fn generate_balance_map(balances: &serde_json::Value) -> HashMap<String, u128> {
1688    // SAFETY: balances is always an array in chain-spec with items [k,v]
1689    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    // generate balances to add
1705    let mut balances_to_add = vec![];
1706
1707    for node in nodes {
1708        if node.initial_balance.eq(&0) {
1709            continue;
1710        };
1711
1712        // Double down the minimal stake defined
1713        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        // Could also be  something like [genesis.runtimeGenesis.patch.balances]
1850        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        // sr and sr_stash keys exists
1902        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        // sr and sr_stash keys exists
1932        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 'balances' is not created
1970        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        // stakers should be one (with the luca sr_stash accounts)
2001        let sr_stash = nodes[0].accounts.accounts.get("sr_stash").unwrap();
2002        assert_eq!(new_staking["stakers"][0][0], json!(sr_stash.address));
2003        // with the calculated minimal bound
2004        assert_eq!(new_staking["stakers"][0][2], json!(min));
2005        // and only one
2006        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 should be empty
2018            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 'preopenHrmpChannels' is not created
2068        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        // Stash
2106        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        // Non-stash
2112        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        //
2158        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}