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