Skip to main content

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 read_raw_spec<'a, T>(
659        &self,
660        scoped_fs: &ScopedFilesystem<'a, T>,
661    ) -> Result<serde_json::Value, GeneratorError>
662    where
663        T: FileSystem,
664    {
665        // first ensure we have the raw version of the chain-spec
666        let Some(_) = self.raw_path else {
667            return Err(GeneratorError::OverridingRawSpec(String::from(
668                "Raw path should be set at this point.",
669            )));
670        };
671
672        let (content, _) = self.read_spec(scoped_fs).await?;
673
674        // read spec to json value
675        let chain_spec_json: serde_json::Value = serde_json::from_str(&content).map_err(|_| {
676            GeneratorError::ChainSpecGeneration("Can not parse chain-spec as json".into())
677        })?;
678
679        Ok(chain_spec_json)
680    }
681
682    pub async fn override_raw_spec<'a, T>(
683        &mut self,
684        scoped_fs: &ScopedFilesystem<'a, T>,
685        raw_spec_overrides: &JsonOverrides,
686    ) -> Result<(), GeneratorError>
687    where
688        T: FileSystem,
689    {
690        // read spec to json value
691        let mut chain_spec_json: serde_json::Value = self.read_raw_spec(scoped_fs).await?;
692
693        // read overrides to json value
694        let override_content: serde_json::Value = raw_spec_overrides.get().await.map_err(|_| {
695            GeneratorError::OverridingRawSpec(format!(
696                "Can not parse raw_spec_override contents as json: {raw_spec_overrides}"
697            ))
698        })?;
699
700        // merge overrides with existing spec
701        merge(&mut chain_spec_json, &override_content);
702
703        // save changes
704        let overrided_content = serde_json::to_string_pretty(&chain_spec_json).map_err(|_| {
705            GeneratorError::ChainSpecGeneration("can not parse chain-spec value as json".into())
706        })?;
707        self.write_spec(scoped_fs, overrided_content).await?;
708
709        Ok(())
710    }
711
712    // find from genesis.raw.top
713    pub async fn find_raw_key<'a, T>(
714        &self,
715        scoped_fs: &ScopedFilesystem<'a, T>,
716        key: &str,
717    ) -> Result<bool, GeneratorError>
718    where
719        T: FileSystem,
720    {
721        // first ensure we have the raw version of the chain-spec
722        let Some(_) = self.raw_path else {
723            return Err(GeneratorError::OverridingRawSpec(String::from(
724                "Raw path should be set at this point.",
725            )));
726        };
727
728        let (content, _) = self.read_spec(scoped_fs).await?;
729
730        // read spec to json value
731        let chain_spec_json: serde_json::Value = serde_json::from_str(&content).map_err(|_| {
732            GeneratorError::ChainSpecGeneration("Can not parse chain-spec as json".into())
733        })?;
734
735        let val = &chain_spec_json["genesis"]["raw"]["top"][key];
736
737        Ok(val != &serde_json::Value::Null)
738    }
739
740    pub fn raw_path(&self) -> Option<&Path> {
741        self.raw_path.as_deref()
742    }
743
744    pub fn set_asset_location(&mut self, location: AssetLocation) {
745        self.asset_location = Some(location)
746    }
747
748    pub async fn read_chain_id<'a, T>(
749        &self,
750        scoped_fs: &ScopedFilesystem<'a, T>,
751    ) -> Result<String, GeneratorError>
752    where
753        T: FileSystem,
754    {
755        let (content, _) = self.read_spec(scoped_fs).await?;
756        ChainSpec::chain_id_from_spec(&content)
757    }
758
759    async fn read_spec<'a, T>(
760        &self,
761        scoped_fs: &ScopedFilesystem<'a, T>,
762    ) -> Result<(String, ChainSpecFormat), GeneratorError>
763    where
764        T: FileSystem,
765    {
766        let (path, format) = match (self.maybe_plain_path.as_ref(), self.raw_path.as_ref()) {
767            (Some(path), None) => (path, ChainSpecFormat::Plain),
768            (None, Some(path)) => (path, ChainSpecFormat::Raw),
769            (Some(_), Some(path)) => {
770                // if we have both paths return the raw
771                (path, ChainSpecFormat::Raw)
772            },
773            (None, None) => unreachable!(),
774        };
775
776        let content = scoped_fs.read_to_string(path.clone()).await.map_err(|_| {
777            GeneratorError::ChainSpecGeneration(format!(
778                "Can not read chain-spec from {}",
779                path.to_string_lossy()
780            ))
781        })?;
782
783        Ok((content, format))
784    }
785
786    async fn write_spec<'a, T>(
787        &self,
788        scoped_fs: &ScopedFilesystem<'a, T>,
789        content: impl Into<String>,
790    ) -> Result<(), GeneratorError>
791    where
792        T: FileSystem,
793    {
794        let (path, _format) = match (self.maybe_plain_path.as_ref(), self.raw_path.as_ref()) {
795            (Some(path), None) => (path, ChainSpecFormat::Plain),
796            (None, Some(path)) => (path, ChainSpecFormat::Raw),
797            (Some(_), Some(path)) => {
798                // if we have both paths return the raw
799                (path, ChainSpecFormat::Raw)
800            },
801            (None, None) => unreachable!(),
802        };
803
804        scoped_fs.write(path, content.into()).await.map_err(|_| {
805            GeneratorError::ChainSpecGeneration(format!(
806                "Can not write chain-spec from {}",
807                path.to_string_lossy()
808            ))
809        })?;
810
811        Ok(())
812    }
813
814    // TODO: (javier) move this fns to state aware
815    pub async fn customize_para<'a, T>(
816        &self,
817        para: &ParachainSpec,
818        relay_chain_id: &str,
819        scoped_fs: &ScopedFilesystem<'a, T>,
820    ) -> Result<(), GeneratorError>
821    where
822        T: FileSystem,
823    {
824        let (content, format) = self.read_spec(scoped_fs).await?;
825        let mut chain_spec_json: serde_json::Value =
826            serde_json::from_str(&content).map_err(|_| {
827                GeneratorError::ChainSpecGeneration("Can not parse chain-spec as json".into())
828            })?;
829
830        if let Some(para_id) = chain_spec_json.get_mut("para_id") {
831            *para_id = json!(para.id);
832        };
833        if let Some(para_id) = chain_spec_json.get_mut("paraId") {
834            *para_id = json!(para.id);
835        };
836
837        if let Some(relay_chain_id_field) = chain_spec_json.get_mut("relay_chain") {
838            *relay_chain_id_field = json!(relay_chain_id);
839        };
840
841        if let ChainSpecFormat::Plain = format {
842            let pointer = get_runtime_config_pointer(&chain_spec_json)
843                .map_err(GeneratorError::ChainSpecGeneration)?;
844
845            // make genesis overrides first.
846            if let Some(overrides) = &para.genesis_overrides {
847                let percolated_overrides = percolate_overrides(&pointer, overrides)
848                    .map_err(|e| GeneratorError::ChainSpecGeneration(e.to_string()))?;
849                if let Some(genesis) = chain_spec_json.pointer_mut(&pointer) {
850                    merge(genesis, percolated_overrides);
851                }
852            }
853
854            clear_authorities(&pointer, &mut chain_spec_json, &self.context);
855
856            let key_type_to_use = if para.is_evm_based {
857                SessionKeyType::Evm
858            } else {
859                SessionKeyType::Default
860            };
861
862            // Get validators to add as authorities
863            let validators: Vec<&NodeSpec> = para
864                .collators
865                .iter()
866                .filter(|node| node.is_validator)
867                .collect();
868
869            // check chain key types
870            if chain_spec_json
871                .pointer(&format!("{pointer}/session"))
872                .is_some()
873            {
874                add_authorities(&pointer, &mut chain_spec_json, &validators, key_type_to_use);
875            } else if chain_spec_json
876                .pointer(&format!("{pointer}/aura"))
877                .is_some()
878            {
879                add_aura_authorities(&pointer, &mut chain_spec_json, &validators, KeyType::Aura);
880            } else {
881                warn!("Can't customize keys, not `session` or `aura` find in the chain-spec file");
882            };
883
884            // Add nodes to collator
885            let invulnerables: Vec<&NodeSpec> = para
886                .collators
887                .iter()
888                .filter(|node| node.is_invulnerable)
889                .collect();
890
891            add_collator_selection(
892                &pointer,
893                &mut chain_spec_json,
894                &invulnerables,
895                key_type_to_use,
896            );
897
898            // override `parachainInfo/parachainId`
899            override_parachain_info(&pointer, &mut chain_spec_json, para.id);
900
901            // check if `assets` pallet config
902            let balances_to_add =
903                generate_balance_to_add_from_assets_pallet(&pointer, &chain_spec_json);
904            add_balances(&pointer, &mut chain_spec_json, balances_to_add);
905
906            // write spec
907            let content = serde_json::to_string_pretty(&chain_spec_json).map_err(|_| {
908                GeneratorError::ChainSpecGeneration("can not parse chain-spec value as json".into())
909            })?;
910            self.write_spec(scoped_fs, content).await?;
911        } else {
912            warn!("⚠️ Chain spec for para_id: {} is in raw mode", para.id);
913        }
914        Ok(())
915    }
916
917    pub async fn customize_relay<'a, T, U>(
918        &self,
919        relaychain: &RelaychainSpec,
920        hrmp_channels: &[HrmpChannelConfig],
921        para_artifacts: Vec<ParaGenesisConfig<U>>,
922        scoped_fs: &ScopedFilesystem<'a, T>,
923    ) -> Result<(), GeneratorError>
924    where
925        T: FileSystem,
926        U: AsRef<Path>,
927    {
928        let (content, format) = self.read_spec(scoped_fs).await?;
929        let mut chain_spec_json: serde_json::Value =
930            serde_json::from_str(&content).map_err(|_| {
931                GeneratorError::ChainSpecGeneration("Can not parse chain-spec as json".into())
932            })?;
933
934        if let ChainSpecFormat::Plain = format {
935            // get the tokenDecimals property or set the default (12)
936            let token_decimals =
937                if let Some(val) = chain_spec_json.pointer("/properties/tokenDecimals") {
938                    let val = val.as_u64().unwrap_or(12);
939                    if val > u8::MAX as u64 {
940                        12
941                    } else {
942                        val as u8
943                    }
944                } else {
945                    12
946                };
947            // get the config pointer
948            let pointer = get_runtime_config_pointer(&chain_spec_json)
949                .map_err(GeneratorError::ChainSpecGeneration)?;
950
951            // make genesis overrides first.
952            if let Some(overrides) = &relaychain.runtime_genesis_patch {
953                let percolated_overrides = percolate_overrides(&pointer, overrides)
954                    .map_err(|e| GeneratorError::ChainSpecGeneration(e.to_string()))?;
955                if let Some(patch_section) = chain_spec_json.pointer_mut(&pointer) {
956                    merge(patch_section, percolated_overrides);
957                }
958            }
959
960            // get min stake (to store if neede later)
961            let staking_min = get_staking_min(&pointer, &mut chain_spec_json);
962
963            // Clear authorities
964            clear_authorities(&pointer, &mut chain_spec_json, &self.context);
965
966            // add balances
967            let mut balances_to_add =
968                generate_balance_to_add_from_nodes(&relaychain.nodes, staking_min);
969
970            // ensure zombie account (//Zombie) have funds
971            // we will use for internal usage (e.g new validators)
972            balances_to_add.push((
973                ZOMBIE_KEY.to_string(),
974                1000 * 10_u128.pow(token_decimals as u32),
975            ));
976
977            add_balances(&pointer, &mut chain_spec_json, balances_to_add);
978
979            // add staking
980            add_staking(
981                &pointer,
982                &mut chain_spec_json,
983                &relaychain.nodes,
984                staking_min,
985            );
986
987            // Get validators to add as authorities
988            let validators: Vec<&NodeSpec> = relaychain
989                .nodes
990                .iter()
991                .filter(|node| node.is_validator)
992                .collect();
993
994            // check chain key types
995            if chain_spec_json
996                .pointer(&format!("{pointer}/session"))
997                .is_some()
998            {
999                add_authorities(
1000                    &pointer,
1001                    &mut chain_spec_json,
1002                    &validators,
1003                    SessionKeyType::Stash,
1004                );
1005            } else {
1006                add_aura_authorities(&pointer, &mut chain_spec_json, &validators, KeyType::Aura);
1007                add_grandpa_authorities(&pointer, &mut chain_spec_json, &validators, KeyType::Aura);
1008            }
1009
1010            // staking && nominators
1011
1012            if !hrmp_channels.is_empty() {
1013                add_hrmp_channels(&pointer, &mut chain_spec_json, hrmp_channels);
1014            }
1015
1016            // paras
1017            for para_genesis_config in para_artifacts.iter() {
1018                add_parachain_to_genesis(
1019                    &pointer,
1020                    &mut chain_spec_json,
1021                    para_genesis_config,
1022                    scoped_fs,
1023                )
1024                .await
1025                .map_err(|e| GeneratorError::ChainSpecGeneration(e.to_string()))?;
1026            }
1027
1028            // TODO:
1029            // - staking
1030            // - nominators
1031
1032            // write spec
1033            let content = serde_json::to_string_pretty(&chain_spec_json).map_err(|_| {
1034                GeneratorError::ChainSpecGeneration("can not parse chain-spec value as json".into())
1035            })?;
1036            self.write_spec(scoped_fs, content).await?;
1037        } else {
1038            warn!(
1039                "⚠️ Chain Spec for chain {} is in raw mode, can't customize.",
1040                self.chain_spec_name
1041            );
1042        }
1043        Ok(())
1044    }
1045
1046    pub(crate) async fn apply_genesis_override<'a, T>(
1047        &self,
1048        scoped_fs: &ScopedFilesystem<'a, T>,
1049        overrides: &serde_json::Value,
1050    ) -> Result<(), GeneratorError>
1051    where
1052        T: FileSystem,
1053    {
1054        let (content, _) = self.read_spec(scoped_fs).await?;
1055        let mut chain_spec_json: serde_json::Value =
1056            serde_json::from_str(&content).map_err(|_| {
1057                GeneratorError::ChainSpecGeneration("Can not parse chain-spec as json".into())
1058            })?;
1059
1060        // get the config pointer
1061        let pointer = get_runtime_config_pointer(&chain_spec_json)
1062            .map_err(GeneratorError::ChainSpecGeneration)?;
1063
1064        let percolated_overrides = percolate_overrides(&pointer, overrides)
1065            .map_err(|e| GeneratorError::ChainSpecGeneration(e.to_string()))?;
1066        if let Some(patch_section) = chain_spec_json.pointer_mut(&pointer) {
1067            merge(patch_section, percolated_overrides);
1068        }
1069
1070        // write spec
1071        let content = serde_json::to_string_pretty(&chain_spec_json).map_err(|_| {
1072            GeneratorError::ChainSpecGeneration("can not parse chain-spec value as json".into())
1073        })?;
1074        self.write_spec(scoped_fs, content).await?;
1075
1076        Ok(())
1077    }
1078
1079    pub async fn add_bootnodes<'a, T>(
1080        &self,
1081        scoped_fs: &ScopedFilesystem<'a, T>,
1082        bootnodes: &[String],
1083    ) -> Result<(), GeneratorError>
1084    where
1085        T: FileSystem,
1086    {
1087        let (content, _) = self.read_spec(scoped_fs).await?;
1088        let mut chain_spec_json: serde_json::Value =
1089            serde_json::from_str(&content).map_err(|_| {
1090                GeneratorError::ChainSpecGeneration("Can not parse chain-spec as json".into())
1091            })?;
1092
1093        if let Some(bootnodes_on_file) = chain_spec_json.get_mut("bootNodes") {
1094            if let Some(bootnodes_on_file) = bootnodes_on_file.as_array_mut() {
1095                let mut bootnodes_to_add =
1096                    bootnodes.iter().map(|bootnode| json!(bootnode)).collect();
1097                bootnodes_on_file.append(&mut bootnodes_to_add);
1098            } else {
1099                return Err(GeneratorError::ChainSpecGeneration(
1100                    "id should be an string in the chain-spec, this is a bug".into(),
1101                ));
1102            };
1103        } else {
1104            return Err(GeneratorError::ChainSpecGeneration(
1105                "'bootNodes' should be a fields in the chain-spec of the relaychain".into(),
1106            ));
1107        };
1108
1109        // write spec
1110        let content = serde_json::to_string_pretty(&chain_spec_json).map_err(|_| {
1111            GeneratorError::ChainSpecGeneration("can not parse chain-spec value as json".into())
1112        })?;
1113        self.write_spec(scoped_fs, content).await?;
1114
1115        Ok(())
1116    }
1117
1118    /// Get the chain_is from the json content of a chain-spec file.
1119    pub fn chain_id_from_spec(spec_content: &str) -> Result<String, GeneratorError> {
1120        let chain_spec_json: serde_json::Value =
1121            serde_json::from_str(spec_content).map_err(|_| {
1122                GeneratorError::ChainSpecGeneration("Can not parse chain-spec as json".into())
1123            })?;
1124        if let Some(chain_id) = chain_spec_json.get("id") {
1125            if let Some(chain_id) = chain_id.as_str() {
1126                Ok(chain_id.to_string())
1127            } else {
1128                Err(GeneratorError::ChainSpecGeneration(
1129                    "id should be an string in the chain-spec, this is a bug".into(),
1130                ))
1131            }
1132        } else {
1133            Err(GeneratorError::ChainSpecGeneration(
1134                "'id' should be a fields in the chain-spec of the relaychain".into(),
1135            ))
1136        }
1137    }
1138
1139    /// Run a post-processing script on the chain-spec file.
1140    /// The script receives the path to the chain-spec as argument.
1141    pub async fn run_post_process_script<'a, T>(
1142        &self,
1143        script_command: &str,
1144        scoped_fs: &ScopedFilesystem<'a, T>,
1145    ) -> Result<(), GeneratorError>
1146    where
1147        T: FileSystem,
1148    {
1149        let spec_path =
1150            self.maybe_plain_path
1151                .as_ref()
1152                .ok_or(GeneratorError::ChainSpecGeneration(
1153                    "Chain-spec path not found for post-process script".into(),
1154                ))?;
1155        let full_path = scoped_fs.full_path(spec_path);
1156
1157        info!(
1158            "🔧 Running chain-spec post-process script: {} {}",
1159            script_command,
1160            full_path.display()
1161        );
1162
1163        // Read the current spec content and pass it to the script stdin.
1164        let spec_content = scoped_fs.read_to_string(spec_path).await.map_err(|e| {
1165            GeneratorError::ChainSpecGeneration(format!("Failed to read spec: {}", e))
1166        })?;
1167
1168        let mut child = Command::new(script_command)
1169            .stdin(Stdio::piped())
1170            .stdout(Stdio::piped())
1171            .stderr(Stdio::piped())
1172            .spawn()
1173            .map_err(|e| {
1174                GeneratorError::ChainSpecGeneration(format!(
1175                    "Failed to execute chain-spec post-process script: {}",
1176                    e
1177                ))
1178            })?;
1179
1180        if let Some(mut stdin) = child.stdin.take() {
1181            stdin
1182                .write_all(spec_content.as_bytes())
1183                .await
1184                .map_err(|e| {
1185                    GeneratorError::ChainSpecGeneration(format!(
1186                        "Failed to write to script stdin: {}",
1187                        e
1188                    ))
1189                })?;
1190        }
1191
1192        let output = child.wait_with_output().await.map_err(|e| {
1193            GeneratorError::ChainSpecGeneration(format!("Failed to wait for script output: {}", e))
1194        })?;
1195
1196        let stderr = String::from_utf8_lossy(&output.stderr);
1197        if !stderr.trim().is_empty() {
1198            info!("Script stderr: {}", stderr.trim());
1199        }
1200
1201        if !output.status.success() {
1202            return Err(GeneratorError::ChainSpecGeneration(format!(
1203                "Chain-spec post-process script failed with exit code {:?}: {}",
1204                output.status.code(),
1205                stderr
1206            )));
1207        }
1208
1209        let stdout = String::from_utf8_lossy(&output.stdout);
1210        let stdout_trimmed = stdout.trim();
1211        if !stdout_trimmed.is_empty() {
1212            // Validate JSON before overwriting the spec. If invalid, log and skip applying.
1213            if let Err(e) = serde_json::from_str::<serde_json::Value>(stdout_trimmed) {
1214                warn!(
1215                    "Script produced invalid JSON; output will NOT be applied: {}",
1216                    e
1217                );
1218                return Ok(());
1219            }
1220
1221            // Write to a temporary file inside the scoped fs, then copy into place via provider
1222            let tmp_path = PathBuf::from(format!("{}.postproc.tmp", spec_path.to_string_lossy()));
1223            scoped_fs
1224                .write(&tmp_path, stdout_trimmed.to_string())
1225                .await
1226                .map_err(|e| {
1227                    GeneratorError::ChainSpecGeneration(format!(
1228                        "Failed to write temp post-processed spec: {}",
1229                        e
1230                    ))
1231                })?;
1232
1233            let full_tmp = scoped_fs.full_path(&tmp_path);
1234            // Prepare transfer object: copy local tmp -> remote final path inside scoped fs
1235            let tf = TransferedFile::new(full_tmp, spec_path.to_path_buf());
1236            scoped_fs.copy_files(vec![&tf]).await.map_err(|e| {
1237                GeneratorError::ChainSpecGeneration(format!(
1238                    "Failed to copy temp spec into final path: {}",
1239                    e
1240                ))
1241            })?;
1242
1243            // Remove temporary file
1244            let _ = tokio_fs::remove_file(scoped_fs.full_path(&tmp_path)).await;
1245
1246            info!(
1247                "Script output applied to spec (bytes: {})",
1248                stdout_trimmed.len()
1249            );
1250        } else {
1251            info!("Script produced no output; spec left unchanged");
1252        }
1253
1254        Ok(())
1255    }
1256}
1257
1258type GenesisNodeKey = (String, String, HashMap<String, String>);
1259
1260async fn build_locally<'a, T>(
1261    generate_command: GenerateFileCommand,
1262    scoped_fs: &ScopedFilesystem<'a, T>,
1263    maybe_output: Option<&Path>,
1264) -> Result<(), GeneratorError>
1265where
1266    T: FileSystem,
1267{
1268    // generate_command.
1269
1270    let result = Command::new(generate_command.program.clone())
1271        .args(generate_command.args.clone())
1272        .output()
1273        .await
1274        .map_err(|err| {
1275            GeneratorError::ChainSpecGeneration(format!(
1276                "Error running cmd: {} args: {}, err: {}",
1277                &generate_command.program,
1278                &generate_command.args.join(" "),
1279                err
1280            ))
1281        })?;
1282
1283    if result.status.success() {
1284        let raw_output = if let Some(output_path) = maybe_output {
1285            tokio::fs::read(output_path).await.map_err(|err| {
1286                GeneratorError::ChainSpecGeneration(format!(
1287                    "Error reading output file at {}: {}",
1288                    output_path.display(),
1289                    err
1290                ))
1291            })?
1292        } else {
1293            result.stdout
1294        };
1295        scoped_fs
1296            .write(
1297                generate_command.local_output_path,
1298                String::from_utf8_lossy(&raw_output).to_string(),
1299            )
1300            .await?;
1301        Ok(())
1302    } else {
1303        Err(GeneratorError::ChainSpecGeneration(format!(
1304            "Error running cmd: {} args: {}, err: {}",
1305            &generate_command.program,
1306            &generate_command.args.join(" "),
1307            String::from_utf8_lossy(&result.stderr)
1308        )))
1309    }
1310}
1311
1312async fn is_raw<'a, T>(
1313    file: PathBuf,
1314    scoped_fs: &ScopedFilesystem<'a, T>,
1315) -> Result<bool, ProviderError>
1316where
1317    T: FileSystem,
1318{
1319    let content = scoped_fs.read_to_string(file).await?;
1320    let chain_spec_json: serde_json::Value = serde_json::from_str(&content).unwrap();
1321
1322    Ok(chain_spec_json.pointer("/genesis/raw/top").is_some())
1323}
1324
1325// Internal Chain-spec customizations
1326
1327async fn add_parachain_to_genesis<'a, T, U>(
1328    runtime_config_ptr: &str,
1329    chain_spec_json: &mut serde_json::Value,
1330    para_genesis_config: &ParaGenesisConfig<U>,
1331    scoped_fs: &ScopedFilesystem<'a, T>,
1332) -> Result<(), anyhow::Error>
1333where
1334    T: FileSystem,
1335    U: AsRef<Path>,
1336{
1337    if let Some(val) = chain_spec_json.pointer_mut(runtime_config_ptr) {
1338        let paras_pointer = if val.get("paras").is_some() {
1339            "/paras/paras"
1340        } else if val.get("parachainsParas").is_some() {
1341            // For retro-compatibility with substrate pre Polkadot 0.9.5
1342            "/parachainsParas/paras"
1343        } else {
1344            // The config may not contain paras. Since chainspec allows to contain the RuntimeGenesisConfig patch we can inject it.
1345            val["paras"] = json!({ "paras": [] });
1346            "/paras/paras"
1347        };
1348
1349        let paras = val
1350            .pointer_mut(paras_pointer)
1351            .ok_or(anyhow!("paras pointer should be valid {paras_pointer:?} "))?;
1352        let paras_vec = paras
1353            .as_array_mut()
1354            .ok_or(anyhow!("paras should be an array"))?;
1355
1356        let head = scoped_fs
1357            .read_to_string(para_genesis_config.state_path.as_ref())
1358            .await?;
1359        let wasm = scoped_fs
1360            .read_to_string(para_genesis_config.wasm_path.as_ref())
1361            .await?;
1362
1363        paras_vec.push(json!([
1364            para_genesis_config.id,
1365            [head.trim(), wasm.trim(), para_genesis_config.as_parachain]
1366        ]));
1367
1368        Ok(())
1369    } else {
1370        unreachable!("pointer to runtime config should be valid!")
1371    }
1372}
1373
1374fn get_runtime_config_pointer(chain_spec_json: &serde_json::Value) -> Result<String, String> {
1375    // runtime_genesis_config is no longer in ChainSpec after rococo runtime rework (refer to: https://github.com/paritytech/polkadot-sdk/pull/1256)
1376    // ChainSpec may contain a RuntimeGenesisConfigPatch
1377    let pointers = [
1378        "/genesis/runtimeGenesis/config",
1379        "/genesis/runtimeGenesis/patch",
1380        "/genesis/runtimeGenesisConfigPatch",
1381        "/genesis/runtime/runtime_genesis_config",
1382        "/genesis/runtime",
1383    ];
1384
1385    for pointer in pointers {
1386        if chain_spec_json.pointer(pointer).is_some() {
1387            return Ok(pointer.to_string());
1388        }
1389    }
1390
1391    Err("Can not find the runtime pointer".into())
1392}
1393
1394fn percolate_overrides<'a>(
1395    pointer: &str,
1396    overrides: &'a serde_json::Value,
1397) -> Result<&'a serde_json::Value, anyhow::Error> {
1398    let pointer_parts = pointer.split('/').collect::<Vec<&str>>();
1399    trace!("pointer_parts: {pointer_parts:?}");
1400
1401    let top_level = overrides
1402        .as_object()
1403        .ok_or_else(|| anyhow!("Overrides must be an object"))?;
1404    let top_level_key = top_level
1405        .keys()
1406        .next()
1407        .ok_or_else(|| anyhow!("Invalid override value: {overrides:?}"))?;
1408    trace!("top_level_key: {top_level_key}");
1409    let index = pointer_parts.iter().position(|x| *x == top_level_key);
1410    let Some(i) = index else {
1411        info!("Top level key '{top_level_key}' isn't part of the pointer ({pointer}), returning without percolating");
1412        return Ok(overrides);
1413    };
1414
1415    let p = if i == pointer_parts.len() - 1 {
1416        // top level key is at end of the pointer
1417        let p = format!("/{}", pointer_parts[i]);
1418        trace!("overrides pointer {p}");
1419        p
1420    } else {
1421        // example: pointer is `/genesis/runtimeGenesis/patch` and the overrides start at  `runtimeGenesis`
1422        let p = format!("/{}", pointer_parts[i..].join("/"));
1423        trace!("overrides pointer {p}");
1424        p
1425    };
1426    let overrides_to_use = overrides
1427        .pointer(&p)
1428        .ok_or_else(|| anyhow!("Invalid override value: {overrides:?}"))?;
1429    Ok(overrides_to_use)
1430}
1431
1432#[allow(dead_code)]
1433fn construct_runtime_pointer_from_overrides(
1434    overrides: &serde_json::Value,
1435) -> Result<String, anyhow::Error> {
1436    if overrides.get("genesis").is_some() {
1437        // overrides already start with /genesis
1438        return Ok("/genesis".into());
1439    } else {
1440        // check if we are one level inner
1441        if let Some(top_level) = overrides.as_object() {
1442            let k = top_level
1443                .keys()
1444                .next()
1445                .ok_or_else(|| anyhow!("Invalid override value: {overrides:?}"))?;
1446            match k.as_str() {
1447                "runtimeGenesisConfigPatch" | "runtime" | "runtimeGenesis" => {
1448                    return Ok(("/genesis").into())
1449                },
1450                "config" | "path" => {
1451                    return Ok(("/genesis/runtimeGenesis").into());
1452                },
1453                "runtime_genesis_config" => {
1454                    return Ok(("/genesis/runtime").into());
1455                },
1456                _ => {},
1457            }
1458        }
1459    }
1460
1461    Err(anyhow!("Can not find the runtime pointer"))
1462}
1463
1464// Merge `patch_section` with `overrides`.
1465fn merge(patch_section: &mut serde_json::Value, overrides: &serde_json::Value) {
1466    trace!("patch: {:?}", patch_section);
1467    trace!("overrides: {:?}", overrides);
1468    if let (Some(genesis_obj), Some(overrides_obj)) =
1469        (patch_section.as_object_mut(), overrides.as_object())
1470    {
1471        for overrides_key in overrides_obj.keys() {
1472            trace!("overrides_key: {:?}", overrides_key);
1473            // we only want to override keys present in the genesis object
1474            if let Some(genesis_value) = genesis_obj.get_mut(overrides_key) {
1475                match (&genesis_value, overrides_obj.get(overrides_key)) {
1476                    // recurse if genesis value is an object
1477                    (serde_json::Value::Object(_), Some(overrides_value))
1478                        if overrides_value.is_object() =>
1479                    {
1480                        merge(genesis_value, overrides_value);
1481                    },
1482                    // override if genesis value not an object
1483                    (_, Some(overrides_value)) => {
1484                        trace!("overriding: {:?} / {:?}", genesis_value, overrides_value);
1485                        *genesis_value = overrides_value.clone();
1486                    },
1487                    _ => {
1488                        trace!("not match!");
1489                    },
1490                }
1491            } else {
1492                // Allow to add keys, see (https://github.com/paritytech/zombienet/issues/1614)
1493                info!("key: {overrides_key} not present in genesis_obj (adding key)");
1494                trace!(
1495                    "key: {overrides_key} not present in genesis_obj: {:?} (adding key)",
1496                    genesis_obj
1497                );
1498                let overrides_value = overrides_obj.get(overrides_key).expect(&format!(
1499                    "overrides_key {overrides_key} should be present in the overrides obj. qed"
1500                ));
1501                genesis_obj.insert(overrides_key.clone(), overrides_value.clone());
1502            }
1503        }
1504    }
1505}
1506
1507fn clear_authorities(
1508    runtime_config_ptr: &str,
1509    chain_spec_json: &mut serde_json::Value,
1510    ctx: &Context,
1511) {
1512    if let Some(val) = chain_spec_json.pointer_mut(runtime_config_ptr) {
1513        // clear keys (session, aura, grandpa)
1514        if val.get("session").is_some() {
1515            val["session"]["keys"] = json!([]);
1516        }
1517
1518        if val.get("aura").is_some() {
1519            val["aura"]["authorities"] = json!([]);
1520        }
1521
1522        if val.get("grandpa").is_some() {
1523            val["grandpa"]["authorities"] = json!([]);
1524        }
1525
1526        // clear collatorSelector
1527        if val.get("collatorSelection").is_some() {
1528            val["collatorSelection"]["invulnerables"] = json!([]);
1529        }
1530
1531        // clear staking but not `validatorCount` if `devStakers` is set
1532        if val.get("staking").is_some() && ctx == &Context::Relay {
1533            val["staking"]["invulnerables"] = json!([]);
1534            val["staking"]["stakers"] = json!([]);
1535
1536            if val["staking"]["devStakers"] == json!(null) {
1537                val["staking"]["validatorCount"] = json!(0);
1538            }
1539        }
1540    } else {
1541        unreachable!("pointer to runtime config should be valid!")
1542    }
1543}
1544
1545fn get_staking_min(runtime_config_ptr: &str, chain_spec_json: &mut serde_json::Value) -> u128 {
1546    // get min staking
1547    let staking_ptr = format!("{runtime_config_ptr}/staking/stakers");
1548    if let Some(stakers) = chain_spec_json.pointer(&staking_ptr) {
1549        // stakers should be an array
1550        let min = stakers[0][2].clone();
1551        min.as_u64().unwrap_or(0).into()
1552    } else {
1553        0
1554    }
1555}
1556
1557fn add_balances(
1558    runtime_config_ptr: &str,
1559    chain_spec_json: &mut serde_json::Value,
1560    balances_to_add: Vec<(String, u128)>,
1561    // token_decimals: u8,
1562) {
1563    if let Some(val) = chain_spec_json.pointer_mut(runtime_config_ptr) {
1564        let Some(balances) = val.pointer("/balances/balances") else {
1565            // should be a info log
1566            warn!("NO 'balances' key in runtime config, skipping...");
1567            return;
1568        };
1569
1570        // create a balance map
1571        let mut balances_map = generate_balance_map(balances);
1572        for balance in balances_to_add {
1573            balances_map.insert(balance.0, balance.1);
1574        }
1575
1576        // convert the map and store again
1577        let new_balances: Vec<(&String, &u128)> =
1578            balances_map.iter().collect::<Vec<(&String, &u128)>>();
1579
1580        val["balances"]["balances"] = json!(new_balances);
1581    } else {
1582        unreachable!("pointer to runtime config should be valid!")
1583    }
1584}
1585
1586/// Gets the address for a given key scheme from the node's accounts.
1587fn get_address_for_scheme(node: &NodeSpec, scheme: KeyScheme) -> String {
1588    let account_key = scheme.account_key();
1589    node.accounts
1590        .accounts
1591        .get(account_key)
1592        .expect(&format!(
1593            "'{}' account should be set at spec computation {THIS_IS_A_BUG}",
1594            account_key
1595        ))
1596        .address
1597        .clone()
1598}
1599
1600fn get_node_keys(
1601    node: &NodeSpec,
1602    session_key: SessionKeyType,
1603    asset_hub_polkadot: bool,
1604) -> GenesisNodeKey {
1605    let sr_account = node.accounts.accounts.get("sr").unwrap();
1606    let sr_stash = node.accounts.accounts.get("sr_stash").unwrap();
1607    let ed_account = node.accounts.accounts.get("ed").unwrap();
1608    let ec_account = node.accounts.accounts.get("ec").unwrap();
1609    let eth_account = node.accounts.accounts.get("eth").unwrap();
1610    let mut keys = HashMap::new();
1611    for k in [
1612        "babe",
1613        "im_online",
1614        "parachain_validator",
1615        "authority_discovery",
1616        "para_validator",
1617        "para_assignment",
1618        "aura",
1619        "nimbus",
1620        "vrf",
1621    ] {
1622        if k == "aura" && asset_hub_polkadot {
1623            keys.insert(k.to_string(), ed_account.address.clone());
1624            continue;
1625        }
1626        keys.insert(k.to_string(), sr_account.address.clone());
1627    }
1628
1629    keys.insert("grandpa".to_string(), ed_account.address.clone());
1630    keys.insert("beefy".to_string(), ec_account.address.clone());
1631    keys.insert("eth".to_string(), eth_account.public_key.clone());
1632
1633    let account_to_use = match session_key {
1634        SessionKeyType::Default => sr_account.address.clone(),
1635        SessionKeyType::Stash => sr_stash.address.clone(),
1636        SessionKeyType::Evm => format!("0x{}", eth_account.public_key),
1637    };
1638
1639    (account_to_use.clone(), account_to_use, keys)
1640}
1641
1642/// Generates session keys for a node with custom key types.
1643/// Returns (account, account, keys_map) tuple.
1644fn get_node_keys_with_custom_types(
1645    node: &NodeSpec,
1646    session_key: SessionKeyType,
1647    custom_key_types: &[ChainSpecKeyType],
1648) -> GenesisNodeKey {
1649    let sr_account = node.accounts.accounts.get("sr").unwrap();
1650    let sr_stash = node.accounts.accounts.get("sr_stash").unwrap();
1651    let eth_account = node.accounts.accounts.get("eth").unwrap();
1652
1653    // key_name -> address
1654    let mut keys = HashMap::new();
1655    for key_type in custom_key_types {
1656        let scheme = key_type.scheme;
1657        let account_key = scheme.account_key();
1658        let address = node
1659            .accounts
1660            .accounts
1661            .get(account_key)
1662            .expect(&format!(
1663                "'{}' account should be set at spec computation {THIS_IS_A_BUG}",
1664                account_key
1665            ))
1666            .address
1667            .clone();
1668        keys.insert(key_type.key_name.clone(), address);
1669    }
1670
1671    let account_to_use = match session_key {
1672        SessionKeyType::Default => sr_account.address.clone(),
1673        SessionKeyType::Stash => sr_stash.address.clone(),
1674        SessionKeyType::Evm => format!("0x{}", eth_account.public_key),
1675    };
1676
1677    (account_to_use.clone(), account_to_use, keys)
1678}
1679
1680fn add_authorities(
1681    runtime_config_ptr: &str,
1682    chain_spec_json: &mut serde_json::Value,
1683    nodes: &[&NodeSpec],
1684    session_key: SessionKeyType,
1685) {
1686    let asset_hub_polkadot = chain_spec_json
1687        .get("id")
1688        .and_then(|v| v.as_str())
1689        .map(|id| id.starts_with("asset-hub-polkadot"))
1690        .unwrap_or_default();
1691    if let Some(val) = chain_spec_json.pointer_mut(runtime_config_ptr) {
1692        if let Some(session_keys) = val.pointer_mut("/session/keys") {
1693            let keys: Vec<GenesisNodeKey> = nodes
1694                .iter()
1695                .map(|node| {
1696                    if let Some(custom_key_types) =
1697                        parse_chain_spec_key_types(&node.chain_spec_key_types, asset_hub_polkadot)
1698                    {
1699                        get_node_keys_with_custom_types(node, session_key, &custom_key_types)
1700                    } else {
1701                        get_node_keys(node, session_key, asset_hub_polkadot)
1702                    }
1703                })
1704                .collect();
1705            *session_keys = json!(keys);
1706        } else {
1707            warn!("⚠️  'session/keys' key not present in runtime config.");
1708        }
1709    } else {
1710        unreachable!("pointer to runtime config should be valid!")
1711    }
1712}
1713fn add_hrmp_channels(
1714    runtime_config_ptr: &str,
1715    chain_spec_json: &mut serde_json::Value,
1716    hrmp_channels: &[HrmpChannelConfig],
1717) {
1718    if let Some(val) = chain_spec_json.pointer_mut(runtime_config_ptr) {
1719        if let Some(preopen_hrmp_channels) = val.pointer_mut("/hrmp/preopenHrmpChannels") {
1720            let hrmp_channels = hrmp_channels
1721                .iter()
1722                .map(|c| {
1723                    (
1724                        c.sender(),
1725                        c.recipient(),
1726                        c.max_capacity(),
1727                        c.max_message_size(),
1728                    )
1729                })
1730                .collect::<Vec<_>>();
1731            *preopen_hrmp_channels = json!(hrmp_channels);
1732        } else {
1733            warn!("⚠️  'hrmp/preopenHrmpChannels' key not present in runtime config.");
1734        }
1735    } else {
1736        unreachable!("pointer to runtime config should be valid!")
1737    }
1738}
1739
1740fn add_aura_authorities(
1741    runtime_config_ptr: &str,
1742    chain_spec_json: &mut serde_json::Value,
1743    nodes: &[&NodeSpec],
1744    _key_type: KeyType,
1745) {
1746    if let Some(val) = chain_spec_json.pointer_mut(runtime_config_ptr) {
1747        if let Some(aura_authorities) = val.pointer_mut("/aura/authorities") {
1748            let keys: Vec<String> = nodes
1749                .iter()
1750                .map(|node| {
1751                    node.accounts
1752                        .accounts
1753                        .get("sr")
1754                        .expect(&format!(
1755                            "'sr' account should be set at spec computation {THIS_IS_A_BUG}"
1756                        ))
1757                        .address
1758                        .clone()
1759                })
1760                .collect();
1761            *aura_authorities = json!(keys);
1762        } else {
1763            warn!("⚠️  'aura/authorities' key not present in runtime config.");
1764        }
1765    } else {
1766        unreachable!("pointer to runtime config should be valid!")
1767    }
1768}
1769
1770fn add_grandpa_authorities(
1771    runtime_config_ptr: &str,
1772    chain_spec_json: &mut serde_json::Value,
1773    nodes: &[&NodeSpec],
1774    _key_type: KeyType,
1775) {
1776    if let Some(val) = chain_spec_json.pointer_mut(runtime_config_ptr) {
1777        if let Some(grandpa_authorities) = val.pointer_mut("/grandpa/authorities") {
1778            let keys: Vec<(String, usize)> = nodes
1779                .iter()
1780                .map(|node| {
1781                    (
1782                        node.accounts
1783                            .accounts
1784                            .get("ed")
1785                            .expect(&format!(
1786                                "'ed' account should be set at spec computation {THIS_IS_A_BUG}"
1787                            ))
1788                            .address
1789                            .clone(),
1790                        1,
1791                    )
1792                })
1793                .collect();
1794            *grandpa_authorities = json!(keys);
1795        } else {
1796            warn!("⚠️  'grandpa/authorities' key not present in runtime config.");
1797        }
1798    } else {
1799        unreachable!("pointer to runtime config should be valid!")
1800    }
1801}
1802
1803fn add_staking(
1804    runtime_config_ptr: &str,
1805    chain_spec_json: &mut serde_json::Value,
1806    nodes: &Vec<NodeSpec>,
1807    staking_min: u128,
1808) {
1809    if let Some(val) = chain_spec_json.pointer_mut(runtime_config_ptr) {
1810        let Some(_) = val.pointer("/staking") else {
1811            // should be a info log
1812            warn!("NO 'staking' key in runtime config, skipping...");
1813            return;
1814        };
1815
1816        let mut stakers = vec![];
1817        let mut invulnerables = vec![];
1818        for node in nodes {
1819            let sr_stash_addr = &node
1820                .accounts
1821                .accounts
1822                .get("sr_stash")
1823                .expect("'sr_stash account should be defined for the node. qed")
1824                .address;
1825            stakers.push(json!([
1826                sr_stash_addr,
1827                sr_stash_addr,
1828                staking_min,
1829                "Validator"
1830            ]));
1831
1832            if node.is_invulnerable {
1833                invulnerables.push(sr_stash_addr);
1834            }
1835        }
1836
1837        val["staking"]["validatorCount"] = json!(stakers.len());
1838        val["staking"]["stakers"] = json!(stakers);
1839        val["staking"]["invulnerables"] = json!(invulnerables);
1840    } else {
1841        unreachable!("pointer to runtime config should be valid!")
1842    }
1843}
1844
1845// TODO: (team)
1846// fn add_nominators() {}
1847
1848// // TODO: (team) we should think a better way to use the decorators from
1849// // current version (ts).
1850// fn para_custom() { todo!() }
1851fn override_parachain_info(
1852    runtime_config_ptr: &str,
1853    chain_spec_json: &mut serde_json::Value,
1854    para_id: u32,
1855) {
1856    if let Some(val) = chain_spec_json.pointer_mut(runtime_config_ptr) {
1857        if let Some(parachain_id) = val.pointer_mut("/parachainInfo/parachainId") {
1858            *parachain_id = json!(para_id)
1859        } else {
1860            // Add warning here!
1861        }
1862    } else {
1863        unreachable!("pointer to runtime config should be valid!")
1864    }
1865}
1866
1867fn generate_balance_to_add_from_assets_pallet(
1868    runtime_config_ptr: &str,
1869    chain_spec_json: &serde_json::Value,
1870) -> Vec<(String, u128)> {
1871    if let Some(val) = chain_spec_json.pointer(runtime_config_ptr) {
1872        // generate the current balance map, to only add missing accounts
1873        let balances_map = if let Some(balances) = val.pointer("/balances/balances") {
1874            generate_balance_map(balances)
1875        } else {
1876            Default::default()
1877        };
1878
1879        if let Some(assets_accounts) = val.pointer("/assets/accounts") {
1880            let assets_accounts = assets_accounts
1881                .as_array()
1882                .expect("assets_accounts config should be an array, qed");
1883            let accounts_to_add: Vec<(String, u128)> = assets_accounts
1884                .iter()
1885                .filter_map(|account| {
1886                    let account = account
1887                        .as_array()
1888                        .expect("assets_accounts config should be an array, qed");
1889                    // map account / balance
1890                    let account_balance = (
1891                        account[1]
1892                            .as_str()
1893                            .expect("account should be a valid string. qed")
1894                            .to_string(),
1895                        account[2]
1896                            .as_number()
1897                            .expect("balance should be a valid str")
1898                            .to_string()
1899                            .parse::<u128>()
1900                            .expect("balance should be a valid u128"),
1901                    );
1902
1903                    if balances_map.contains_key(&account_balance.0) {
1904                        None
1905                    } else {
1906                        Some(account_balance)
1907                    }
1908                })
1909                .collect();
1910            accounts_to_add
1911        } else {
1912            vec![]
1913        }
1914    } else {
1915        unreachable!("pointer to runtime config should be valid!")
1916    }
1917}
1918
1919fn add_collator_selection(
1920    runtime_config_ptr: &str,
1921    chain_spec_json: &mut serde_json::Value,
1922    nodes: &[&NodeSpec],
1923    session_key: SessionKeyType,
1924) {
1925    if let Some(val) = chain_spec_json.pointer_mut(runtime_config_ptr) {
1926        let key_type = if let SessionKeyType::Evm = session_key {
1927            "eth"
1928        } else {
1929            "sr"
1930        };
1931        let keys: Vec<String> = nodes
1932            .iter()
1933            .map(|node| {
1934                node.accounts
1935                    .accounts
1936                    .get(key_type)
1937                    .expect(&format!(
1938                        "'sr' account should be set at spec computation {THIS_IS_A_BUG}"
1939                    ))
1940                    .address
1941                    .clone()
1942            })
1943            .collect();
1944
1945        // collatorSelection.invulnerables
1946        if let Some(invulnerables) = val.pointer_mut("/collatorSelection/invulnerables") {
1947            *invulnerables = json!(keys);
1948        } else {
1949            // TODO: add a nice warning here.
1950            debug!("⚠️  'invulnerables' not present in spec, will not be customized");
1951        }
1952    } else {
1953        unreachable!("pointer to runtime config should be valid!")
1954    }
1955}
1956
1957// Helpers
1958fn generate_balance_map(balances: &serde_json::Value) -> HashMap<String, u128> {
1959    // SAFETY: balances is always an array in chain-spec with items [k,v]
1960    let balances_map: HashMap<String, u128> =
1961        serde_json::from_value::<Vec<(String, u128)>>(balances.to_owned())
1962            .unwrap()
1963            .iter()
1964            .fold(HashMap::new(), |mut memo, balance| {
1965                memo.insert(balance.0.clone(), balance.1);
1966                memo
1967            });
1968    balances_map
1969}
1970
1971fn generate_balance_to_add_from_nodes(
1972    nodes: &[NodeSpec],
1973    staking_min: u128,
1974) -> Vec<(String, u128)> {
1975    // generate balances to add
1976    let mut balances_to_add = vec![];
1977
1978    for node in nodes {
1979        if node.initial_balance.eq(&0) {
1980            continue;
1981        };
1982
1983        // Double down the minimal stake defined
1984        let balance = std::cmp::max(node.initial_balance, staking_min * 2);
1985        for k in ["sr", "sr_stash"] {
1986            let account = node.accounts.accounts.get(k).unwrap();
1987            balances_to_add.push((account.address.clone(), balance));
1988        }
1989    }
1990    balances_to_add
1991}
1992
1993#[cfg(test)]
1994mod tests {
1995    use std::fs;
1996
1997    use configuration::HrmpChannelConfigBuilder;
1998
1999    use super::*;
2000    use crate::{generators, shared::types::NodeAccounts};
2001
2002    const ROCOCO_LOCAL_PLAIN_TESTING: &str = "./testing/rococo-local-plain.json";
2003    const ROCOCO_PENPAL_LOCAL_PLAIN_TESTING: &str = "./testing/rococo-penpal-local-plain.json";
2004
2005    fn chain_spec_test(file: &str) -> serde_json::Value {
2006        let content = fs::read_to_string(file).unwrap();
2007        serde_json::from_str(&content).unwrap()
2008    }
2009
2010    fn chain_spec_with_stake() -> serde_json::Value {
2011        json!({"genesis": {
2012            "runtimeGenesis" : {
2013                "patch": {
2014                    "staking": {
2015                        "forceEra": "NotForcing",
2016                        "invulnerables": [
2017                          "5GNJqTPyNqANBkUVMN1LPPrxXnFouWXoe2wNSmmEoLctxiZY",
2018                          "5HpG9w8EBLe5XCrbczpwq5TSXvedjrBGCwqxK1iQ7qUsSWFc"
2019                        ],
2020                        "minimumValidatorCount": 1,
2021                        "slashRewardFraction": 100000000,
2022                        "stakers": [
2023                          [
2024                            "5GNJqTPyNqANBkUVMN1LPPrxXnFouWXoe2wNSmmEoLctxiZY",
2025                            "5GNJqTPyNqANBkUVMN1LPPrxXnFouWXoe2wNSmmEoLctxiZY",
2026                            100000000000001_u128,
2027                            "Validator"
2028                          ],
2029                          [
2030                            "5HpG9w8EBLe5XCrbczpwq5TSXvedjrBGCwqxK1iQ7qUsSWFc",
2031                            "5HpG9w8EBLe5XCrbczpwq5TSXvedjrBGCwqxK1iQ7qUsSWFc",
2032                            100000000000000_u128,
2033                            "Validator"
2034                          ]
2035                        ],
2036                        "validatorCount": 2
2037                    },
2038                }
2039            }
2040        }})
2041    }
2042
2043    fn chain_spec_with_dev_stakers() -> serde_json::Value {
2044        json!({"genesis": {
2045            "runtimeGenesis" : {
2046                "patch": {
2047                    "staking": {
2048                        "activeEra": [
2049                            0,
2050                            0,
2051                            0
2052                        ],
2053                        "canceledPayout": 0,
2054                        "devStakers": [
2055                            2000,
2056                            25000
2057                        ],
2058                        "forceEra": "NotForcing",
2059                        "invulnerables": [],
2060                        "maxNominatorCount": null,
2061                        "maxValidatorCount": null,
2062                        "minNominatorBond": 0,
2063                        "minValidatorBond": 0,
2064                        "slashRewardFraction": 0,
2065                        "stakers": [],
2066                        "validatorCount": 500
2067                    },
2068                }
2069            }
2070        }})
2071    }
2072
2073    #[test]
2074    fn get_min_stake_works() {
2075        let mut chain_spec_json = chain_spec_with_stake();
2076
2077        let pointer = get_runtime_config_pointer(&chain_spec_json).unwrap();
2078        let min = get_staking_min(&pointer, &mut chain_spec_json);
2079
2080        assert_eq!(100000000000001, min);
2081    }
2082
2083    #[test]
2084    fn dev_stakers_not_override_count_works() {
2085        let mut chain_spec_json = chain_spec_with_dev_stakers();
2086
2087        let pointer = get_runtime_config_pointer(&chain_spec_json).unwrap();
2088        clear_authorities(&pointer, &mut chain_spec_json, &Context::Relay);
2089
2090        let validator_count = chain_spec_json
2091            .pointer(&format!("{pointer}/staking/validatorCount"))
2092            .unwrap();
2093        assert_eq!(validator_count, &json!(500));
2094    }
2095
2096    #[test]
2097    fn dev_stakers_override_count_works() {
2098        let mut chain_spec_json = chain_spec_with_stake();
2099
2100        let pointer = get_runtime_config_pointer(&chain_spec_json).unwrap();
2101        clear_authorities(&pointer, &mut chain_spec_json, &Context::Relay);
2102
2103        let validator_count = chain_spec_json
2104            .pointer(&format!("{pointer}/staking/validatorCount"))
2105            .unwrap();
2106        assert_eq!(validator_count, &json!(0));
2107    }
2108
2109    #[test]
2110    fn overrides_from_toml_works() {
2111        use serde::{Deserialize, Serialize};
2112
2113        #[derive(Debug, Serialize, Deserialize)]
2114        struct MockConfig {
2115            #[serde(rename = "genesis", skip_serializing_if = "Option::is_none")]
2116            genesis_overrides: Option<serde_json::Value>,
2117        }
2118
2119        let mut chain_spec_json = chain_spec_test(ROCOCO_LOCAL_PLAIN_TESTING);
2120        // Could also be  something like [genesis.runtimeGenesis.patch.balances]
2121        const TOML: &str = "[genesis.runtime.balances]
2122            devAccounts = [
2123            20000,
2124            1000000000000000000,
2125            \"//Sender//{}\"
2126        ]";
2127        let override_toml: MockConfig = toml::from_str(TOML).unwrap();
2128        let overrides = override_toml.genesis_overrides.unwrap();
2129        let pointer = get_runtime_config_pointer(&chain_spec_json).unwrap();
2130
2131        let percolated_overrides = percolate_overrides(&pointer, &overrides)
2132            .map_err(|e| GeneratorError::ChainSpecGeneration(e.to_string()))
2133            .unwrap();
2134        trace!("percolated_overrides: {:#?}", percolated_overrides);
2135        if let Some(genesis) = chain_spec_json.pointer_mut(&pointer) {
2136            merge(genesis, percolated_overrides);
2137        }
2138
2139        trace!("chain spec: {chain_spec_json:#?}");
2140        assert!(chain_spec_json
2141            .pointer("/genesis/runtime/balances/devAccounts")
2142            .is_some());
2143    }
2144
2145    #[test]
2146    fn add_balances_works() {
2147        let mut spec_plain = chain_spec_test(ROCOCO_LOCAL_PLAIN_TESTING);
2148        let mut name = String::from("luca");
2149        let initial_balance = 1_000_000_000_000_u128;
2150        let seed = format!("//{}{name}", name.remove(0).to_uppercase());
2151        let accounts = NodeAccounts {
2152            accounts: generators::generate_node_keys(&seed).unwrap(),
2153            seed,
2154        };
2155        let node = NodeSpec {
2156            name,
2157            accounts,
2158            initial_balance,
2159            ..Default::default()
2160        };
2161
2162        let nodes = vec![node];
2163        let balances_to_add = generate_balance_to_add_from_nodes(&nodes, 0);
2164        add_balances("/genesis/runtime", &mut spec_plain, balances_to_add);
2165
2166        let new_balances = spec_plain
2167            .pointer("/genesis/runtime/balances/balances")
2168            .unwrap();
2169
2170        let balances_map = generate_balance_map(new_balances);
2171
2172        // sr and sr_stash keys exists
2173        let sr = nodes[0].accounts.accounts.get("sr").unwrap();
2174        let sr_stash = nodes[0].accounts.accounts.get("sr_stash").unwrap();
2175        assert_eq!(balances_map.get(&sr.address).unwrap(), &initial_balance);
2176        assert_eq!(
2177            balances_map.get(&sr_stash.address).unwrap(),
2178            &initial_balance
2179        );
2180    }
2181
2182    #[test]
2183    fn add_balances_ensure_zombie_account() {
2184        let mut spec_plain = chain_spec_test(ROCOCO_LOCAL_PLAIN_TESTING);
2185
2186        let balances = spec_plain
2187            .pointer("/genesis/runtime/balances/balances")
2188            .unwrap();
2189        let balances_map = generate_balance_map(balances);
2190
2191        let nodes: Vec<NodeSpec> = vec![];
2192        let mut balances_to_add = generate_balance_to_add_from_nodes(&nodes, 0);
2193        balances_to_add.push((ZOMBIE_KEY.to_string(), 1000 * 10_u128.pow(12)));
2194        add_balances("/genesis/runtime", &mut spec_plain, balances_to_add);
2195
2196        let new_balances = spec_plain
2197            .pointer("/genesis/runtime/balances/balances")
2198            .unwrap();
2199
2200        let new_balances_map = generate_balance_map(new_balances);
2201
2202        // sr and sr_stash keys exists
2203        assert!(new_balances_map.contains_key(ZOMBIE_KEY));
2204        assert_eq!(
2205            new_balances_map.len(),
2206            balances_map.len() + 1,
2207            "Number of balances should includes one more key (zombie key)."
2208        );
2209    }
2210
2211    #[test]
2212    fn add_balances_spec_without_balances() {
2213        let mut spec_plain = chain_spec_test(ROCOCO_LOCAL_PLAIN_TESTING);
2214
2215        {
2216            let balances = spec_plain.pointer_mut("/genesis/runtime/balances").unwrap();
2217            *balances = json!(serde_json::Value::Null);
2218        }
2219
2220        let mut name = String::from("luca");
2221        let initial_balance = 1_000_000_000_000_u128;
2222        let seed = format!("//{}{name}", name.remove(0).to_uppercase());
2223        let accounts = NodeAccounts {
2224            accounts: generators::generate_node_keys(&seed).unwrap(),
2225            seed,
2226        };
2227        let node = NodeSpec {
2228            name,
2229            accounts,
2230            initial_balance,
2231            ..Default::default()
2232        };
2233
2234        let nodes = vec![node];
2235        let balances_to_add = generate_balance_to_add_from_nodes(&nodes, 0);
2236        add_balances("/genesis/runtime", &mut spec_plain, balances_to_add);
2237
2238        let new_balances = spec_plain.pointer("/genesis/runtime/balances/balances");
2239
2240        // assert 'balances' is not created
2241        assert_eq!(new_balances, None);
2242    }
2243
2244    #[test]
2245    fn add_staking_works() {
2246        let mut chain_spec_json = chain_spec_with_stake();
2247        let mut name = String::from("luca");
2248        let initial_balance = 1_000_000_000_000_u128;
2249        let seed = format!("//{}{name}", name.remove(0).to_uppercase());
2250        let accounts = NodeAccounts {
2251            accounts: generators::generate_node_keys(&seed).unwrap(),
2252            seed,
2253        };
2254        let node = NodeSpec {
2255            name,
2256            accounts,
2257            initial_balance,
2258            ..Default::default()
2259        };
2260
2261        let pointer = get_runtime_config_pointer(&chain_spec_json).unwrap();
2262        let min = get_staking_min(&pointer, &mut chain_spec_json);
2263
2264        let nodes = vec![node];
2265        add_staking(&pointer, &mut chain_spec_json, &nodes, min);
2266
2267        let new_staking = chain_spec_json
2268            .pointer("/genesis/runtimeGenesis/patch/staking")
2269            .unwrap();
2270
2271        // stakers should be one (with the luca sr_stash accounts)
2272        let sr_stash = nodes[0].accounts.accounts.get("sr_stash").unwrap();
2273        assert_eq!(new_staking["stakers"][0][0], json!(sr_stash.address));
2274        // with the calculated minimal bound
2275        assert_eq!(new_staking["stakers"][0][2], json!(min));
2276        // and only one
2277        assert_eq!(new_staking["stakers"].as_array().unwrap().len(), 1);
2278    }
2279
2280    #[test]
2281    fn adding_hrmp_channels_works() {
2282        let mut spec_plain = chain_spec_test(ROCOCO_LOCAL_PLAIN_TESTING);
2283
2284        {
2285            let current_hrmp_channels = spec_plain
2286                .pointer("/genesis/runtime/hrmp/preopenHrmpChannels")
2287                .unwrap();
2288            // assert should be empty
2289            assert_eq!(current_hrmp_channels, &json!([]));
2290        }
2291
2292        let para_100_101 = HrmpChannelConfigBuilder::new()
2293            .with_sender(100)
2294            .with_recipient(101)
2295            .build();
2296        let para_101_100 = HrmpChannelConfigBuilder::new()
2297            .with_sender(101)
2298            .with_recipient(100)
2299            .build();
2300        let channels = vec![para_100_101, para_101_100];
2301
2302        add_hrmp_channels("/genesis/runtime", &mut spec_plain, &channels);
2303        let new_hrmp_channels = spec_plain
2304            .pointer("/genesis/runtime/hrmp/preopenHrmpChannels")
2305            .unwrap()
2306            .as_array()
2307            .unwrap();
2308
2309        assert_eq!(new_hrmp_channels.len(), 2);
2310        assert_eq!(new_hrmp_channels.first().unwrap()[0], 100);
2311        assert_eq!(new_hrmp_channels.first().unwrap()[1], 101);
2312        assert_eq!(new_hrmp_channels.last().unwrap()[0], 101);
2313        assert_eq!(new_hrmp_channels.last().unwrap()[1], 100);
2314    }
2315
2316    #[test]
2317    fn adding_hrmp_channels_to_an_spec_without_channels() {
2318        let mut spec_plain = chain_spec_test("./testing/rococo-local-plain.json");
2319
2320        {
2321            let hrmp = spec_plain.pointer_mut("/genesis/runtime/hrmp").unwrap();
2322            *hrmp = json!(serde_json::Value::Null);
2323        }
2324
2325        let para_100_101 = HrmpChannelConfigBuilder::new()
2326            .with_sender(100)
2327            .with_recipient(101)
2328            .build();
2329        let para_101_100 = HrmpChannelConfigBuilder::new()
2330            .with_sender(101)
2331            .with_recipient(100)
2332            .build();
2333        let channels = vec![para_100_101, para_101_100];
2334
2335        add_hrmp_channels("/genesis/runtime", &mut spec_plain, &channels);
2336        let new_hrmp_channels = spec_plain.pointer("/genesis/runtime/hrmp/preopenHrmpChannels");
2337
2338        // assert 'preopenHrmpChannels' is not created
2339        assert_eq!(new_hrmp_channels, None);
2340    }
2341
2342    #[test]
2343    fn get_node_keys_works() {
2344        let mut name = String::from("luca");
2345        let seed = format!("//{}{name}", name.remove(0).to_uppercase());
2346        let accounts = NodeAccounts {
2347            accounts: generators::generate_node_keys(&seed).unwrap(),
2348            seed,
2349        };
2350        let node = NodeSpec {
2351            name,
2352            accounts,
2353            ..Default::default()
2354        };
2355
2356        let sr = &node.accounts.accounts["sr"];
2357        let keys = [
2358            ("babe".into(), sr.address.clone()),
2359            ("im_online".into(), sr.address.clone()),
2360            ("parachain_validator".into(), sr.address.clone()),
2361            ("authority_discovery".into(), sr.address.clone()),
2362            ("para_validator".into(), sr.address.clone()),
2363            ("para_assignment".into(), sr.address.clone()),
2364            ("aura".into(), sr.address.clone()),
2365            ("nimbus".into(), sr.address.clone()),
2366            ("vrf".into(), sr.address.clone()),
2367            (
2368                "grandpa".into(),
2369                node.accounts.accounts["ed"].address.clone(),
2370            ),
2371            ("beefy".into(), node.accounts.accounts["ec"].address.clone()),
2372            ("eth".into(), node.accounts.accounts["eth"].address.clone()),
2373        ]
2374        .into();
2375
2376        // Stash
2377        let sr_stash = &node.accounts.accounts["sr_stash"];
2378        let node_key = get_node_keys(&node, SessionKeyType::Stash, false);
2379        assert_eq!(node_key.0, sr_stash.address);
2380        assert_eq!(node_key.1, sr_stash.address);
2381        assert_eq!(node_key.2, keys);
2382        // Non-stash
2383        let node_key = get_node_keys(&node, SessionKeyType::Default, false);
2384        assert_eq!(node_key.0, sr.address);
2385        assert_eq!(node_key.1, sr.address);
2386        assert_eq!(node_key.2, keys);
2387    }
2388
2389    #[test]
2390    fn get_node_keys_supports_asset_hub_polkadot() {
2391        let mut name = String::from("luca");
2392        let seed = format!("//{}{name}", name.remove(0).to_uppercase());
2393        let accounts = NodeAccounts {
2394            accounts: generators::generate_node_keys(&seed).unwrap(),
2395            seed,
2396        };
2397        let node = NodeSpec {
2398            name,
2399            accounts,
2400            ..Default::default()
2401        };
2402
2403        let node_key = get_node_keys(&node, SessionKeyType::default(), false);
2404        assert_eq!(node_key.2["aura"], node.accounts.accounts["sr"].address);
2405
2406        let node_key = get_node_keys(&node, SessionKeyType::default(), true);
2407        assert_eq!(node_key.2["aura"], node.accounts.accounts["ed"].address);
2408    }
2409
2410    #[test]
2411    fn ensure_penpal_assets_works() {
2412        let mut spec_plain = chain_spec_test(ROCOCO_PENPAL_LOCAL_PLAIN_TESTING);
2413
2414        let balances = spec_plain
2415            .pointer("/genesis/runtimeGenesis/patch/balances/balances")
2416            .unwrap();
2417        let balances_map = generate_balance_map(balances);
2418        println!("balance {balances_map:?}");
2419
2420        let nodes: Vec<NodeSpec> = vec![];
2421        let balances_to_add = generate_balance_to_add_from_nodes(&nodes, 0);
2422        add_balances(
2423            "/genesis/runtimeGenesis/patch",
2424            &mut spec_plain,
2425            balances_to_add,
2426        );
2427
2428        //
2429        let balances_to_add_from_assets = generate_balance_to_add_from_assets_pallet(
2430            "/genesis/runtimeGenesis/patch",
2431            &spec_plain,
2432        );
2433        println!("to add : {balances_to_add_from_assets:?}");
2434        add_balances(
2435            "/genesis/runtimeGenesis/patch",
2436            &mut spec_plain,
2437            balances_to_add_from_assets,
2438        );
2439
2440        let new_balances = spec_plain
2441            .pointer("/genesis/runtimeGenesis/patch/balances/balances")
2442            .unwrap();
2443
2444        let new_balances_map = generate_balance_map(new_balances);
2445        println!("balance {new_balances_map:?}");
2446
2447        assert_eq!(new_balances_map.len(), balances_map.len() + 1);
2448    }
2449
2450    #[test]
2451    fn get_node_keys_with_custom_types_works() {
2452        use super::super::{chain_spec_key_types::ChainSpecKeyType, keystore_key_types::KeyScheme};
2453
2454        let mut name = String::from("alice");
2455        let seed = format!("//{}{name}", name.remove(0).to_uppercase());
2456        let accounts = NodeAccounts {
2457            accounts: generators::generate_node_keys(&seed).unwrap(),
2458            seed,
2459        };
2460        let node = NodeSpec {
2461            name,
2462            accounts,
2463            ..Default::default()
2464        };
2465
2466        let custom_key_types = vec![
2467            ChainSpecKeyType::new("aura", KeyScheme::Ed),
2468            ChainSpecKeyType::new("grandpa", KeyScheme::Sr),
2469        ];
2470
2471        let node_key =
2472            get_node_keys_with_custom_types(&node, SessionKeyType::Default, &custom_key_types);
2473
2474        // Account should be sr (default)
2475        assert_eq!(node_key.0, node.accounts.accounts["sr"].address);
2476        assert_eq!(node_key.1, node.accounts.accounts["sr"].address);
2477
2478        // Keys should use custom schemes
2479        assert_eq!(node_key.2["aura"], node.accounts.accounts["ed"].address);
2480        assert_eq!(node_key.2["grandpa"], node.accounts.accounts["sr"].address);
2481    }
2482
2483    #[test]
2484    fn get_node_keys_with_custom_types_stash_works() {
2485        use super::super::{chain_spec_key_types::ChainSpecKeyType, keystore_key_types::KeyScheme};
2486
2487        let mut name = String::from("alice");
2488        let seed = format!("//{}{name}", name.remove(0).to_uppercase());
2489        let accounts = NodeAccounts {
2490            accounts: generators::generate_node_keys(&seed).unwrap(),
2491            seed,
2492        };
2493        let node = NodeSpec {
2494            name,
2495            accounts,
2496            ..Default::default()
2497        };
2498
2499        let custom_key_types = vec![ChainSpecKeyType::new("aura", KeyScheme::Sr)];
2500
2501        let node_key =
2502            get_node_keys_with_custom_types(&node, SessionKeyType::Stash, &custom_key_types);
2503
2504        // Account should be sr_stash (stash derivation)
2505        assert_eq!(node_key.0, node.accounts.accounts["sr_stash"].address);
2506        assert_eq!(node_key.1, node.accounts.accounts["sr_stash"].address);
2507        assert_eq!(node_key.2["aura"], node.accounts.accounts["sr"].address);
2508    }
2509}