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#[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 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 command: Option<CommandInContext>,
88 image: Option<String>,
90 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 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 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 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 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 let sanitized_cmd = if replacement_value.is_empty() {
200 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 build_locally(generate_command, scoped_fs).await?;
225 } else {
226 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 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 let chain_spec_path_local = format!(
284 "{}/{}",
285 ns.base_dir().to_string_lossy(),
286 maybe_plain_path.display()
287 );
288 let chain_spec_path_in_pod = format!("{}/{}", NODE_CONFIG_DIR, maybe_plain_path.display());
290 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 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 build_locally(generate_command, scoped_fs).await?;
328 } else {
329 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 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 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 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 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 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 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 (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 (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 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 if let Some(overrides) = ¶.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 let validators: Vec<&NodeSpec> = para
523 .collators
524 .iter()
525 .filter(|node| node.is_validator)
526 .collect();
527
528 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 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_parachain_info(&pointer, &mut chain_spec_json, para.id);
559
560 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 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 let pointer = get_runtime_config_pointer(&chain_spec_json)
603 .map_err(GeneratorError::ChainSpecGeneration)?;
604
605 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 let staking_min = get_staking_min(&pointer, &mut chain_spec_json);
616
617 clear_authorities(&pointer, &mut chain_spec_json);
619
620 add_balances(
622 &pointer,
623 &mut chain_spec_json,
624 &relaychain.nodes,
625 token_decimals,
626 staking_min,
627 );
628
629 add_staking(
631 &pointer,
632 &mut chain_spec_json,
633 &relaychain.nodes,
634 staking_min,
635 );
636
637 let validators: Vec<&NodeSpec> = relaychain
639 .nodes
640 .iter()
641 .filter(|node| node.is_validator)
642 .collect();
643
644 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 if !hrmp_channels.is_empty() {
663 add_hrmp_channels(&pointer, &mut chain_spec_json, hrmp_channels);
664 }
665
666 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 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 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 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 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
813async 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 "/parachainsParas/paras"
831 } else {
832 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 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 let p = format!("/{}", pointer_parts[i]);
907 trace!("overrides pointer {p}");
908 p
909 } else {
910 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 return Ok("/genesis".into());
928 } else {
929 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
953fn 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 if let Some(genesis_value) = genesis_obj.get_mut(overrides_key) {
964 match (&genesis_value, overrides_obj.get(overrides_key)) {
965 (serde_json::Value::Object(_), Some(overrides_value))
967 if overrides_value.is_object() =>
968 {
969 merge(genesis_value, overrides_value);
970 },
971 (_, 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 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 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 if val.get("collatorSelection").is_some() {
1012 val["collatorSelection"]["invulnerables"] = json!([]);
1013 }
1014
1015 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 let staking_ptr = format!("{runtime_config_ptr}/staking/stakers");
1032 if let Some(stakers) = chain_spec_json.pointer(&staking_ptr) {
1033 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 warn!("NO 'balances' key in runtime config, skipping...");
1052 return;
1053 };
1054
1055 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 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 balances_map.insert(
1074 "5FTcLfwFc7ctvqp3RhbEig6UuHLHcHVRujuUm8r21wy4dAR8".to_string(),
1075 1000 * 10_u128.pow(token_decimals as u32),
1076 );
1077
1078 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 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
1286fn 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 }
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 if let Some(invulnerables) = val.pointer_mut("/collatorSelection/invulnerables") {
1335 *invulnerables = json!(keys);
1336 } else {
1337 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
1345fn generate_balance_map(balances: &serde_json::Value) -> HashMap<String, u128> {
1347 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 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 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 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_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 let sr_stash = nodes[0].accounts.accounts.get("sr_stash").unwrap();
1630 assert_eq!(new_staking["stakers"][0][0], json!(sr_stash.address));
1631 assert_eq!(new_staking["stakers"][0][2], json!(min));
1633 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_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_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 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 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}