zombienet_orchestrator/generators/
chain_spec.rs

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