zombienet_orchestrator/generators/
chain_spec.rs

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