zombienet_orchestrator/generators/
chain_spec.rs

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