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