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