1use crate::{
4 errors::Error, omni_node::PolkadotOmniNodeCli::PolkadotOmniNode, registry::traits::Rollup,
5 up::chain_specs::Runtime,
6};
7pub use chain_specs::Runtime as Relay;
8use glob::glob;
9use indexmap::IndexMap;
10use pop_common::sourcing::traits::{Source as _, enums::Source as _};
11pub use pop_common::{
12 Profile,
13 git::{GitHub, Repository},
14 sourcing::{Binary, GitHub::*, Source, Source::*},
15};
16use std::{
17 collections::BTreeSet,
18 fmt::Debug,
19 iter::once,
20 path::{Path, PathBuf},
21};
22use strum::VariantArray;
23use symlink::{remove_symlink_file, symlink_file};
24use toml_edit::DocumentMut;
25use zombienet_configuration::{
26 NodeConfig,
27 shared::node::{Buildable, Initial, NodeConfigBuilder},
28};
29pub use zombienet_sdk::NetworkConfigBuilder;
30use zombienet_sdk::{LocalFileSystem, Network, NetworkConfig, NetworkConfigExt};
31
32mod chain_specs;
33pub mod chains;
35mod relay;
36
37const VALIDATORS: [&str; 6] = ["alice", "bob", "charlie", "dave", "eve", "ferdie"];
38
39pub struct Zombienet {
41 network_config: NetworkConfiguration,
43 relay_chain: RelayChain,
45 parachains: IndexMap<u32, Chain>,
47 hrmp_channels: bool,
49}
50
51impl Zombienet {
52 pub async fn new(
67 cache: &Path,
68 network_config: NetworkConfiguration,
69 relay_chain_version: Option<&str>,
70 relay_chain_runtime_version: Option<&str>,
71 system_parachain_version: Option<&str>,
72 system_parachain_runtime_version: Option<&str>,
73 parachains: Option<&Vec<String>>,
74 ) -> Result<Self, Error> {
75 let relay_chain = Self::init_relay_chain(
77 relay_chain_version,
78 relay_chain_runtime_version,
79 &network_config,
80 cache,
81 )
82 .await?;
83 let parachains = match parachains {
84 Some(parachains) => Some(
85 parachains
86 .iter()
87 .map(|url| Repository::parse(url))
88 .collect::<Result<Vec<_>, _>>()?,
89 ),
90 None => None,
91 };
92 let parachains = Self::parachains(
93 &relay_chain,
94 system_parachain_version,
95 system_parachain_runtime_version.or(relay_chain_runtime_version),
96 parachains,
97 &network_config,
98 cache,
99 )
100 .await?;
101 let hrmp_channels = !network_config.0.hrmp_channels().is_empty();
102 Ok(Self { network_config, relay_chain, parachains, hrmp_channels })
103 }
104
105 pub fn binaries(&mut self) -> impl Iterator<Item = &mut Binary> {
107 once([Some(&mut self.relay_chain.binary), self.relay_chain.chain_spec_generator.as_mut()])
108 .chain(
109 self.parachains
110 .values_mut()
111 .map(|p| [Some(&mut p.binary), p.chain_spec_generator.as_mut()]),
112 )
113 .flatten()
114 .flatten()
115 }
116
117 async fn parachains(
129 relay_chain: &RelayChain,
130 system_parachain_version: Option<&str>,
131 system_parachain_runtime_version: Option<&str>,
132 parachains: Option<Vec<Repository>>,
133 network_config: &NetworkConfiguration,
134 cache: &Path,
135 ) -> Result<IndexMap<u32, Chain>, Error> {
136 let mut paras: IndexMap<u32, Chain> = IndexMap::new();
137 'outer: for parachain in network_config.0.parachains() {
138 let id = parachain.id();
139 let chain = parachain.chain().map(|c| c.as_str());
140
141 let command = parachain
142 .default_command()
143 .map(|c| c.as_str())
144 .or_else(|| {
145 for collator in parachain.collators() {
147 if let Some(command) = collator.command().map(|i| i.as_str()) {
148 return Some(command);
149 }
150 }
151
152 Some("polkadot-parachain")
154 })
155 .expect("missing default_command set above")
156 .to_lowercase();
157
158 if let Some(parachain) = chains::system(
160 id,
161 &command,
162 system_parachain_version,
163 system_parachain_runtime_version,
164 relay_chain.binary.version().expect("expected relay chain to have version"),
165 chain,
166 cache,
167 )
168 .await?
169 {
170 paras.insert(id, parachain);
171 continue;
172 }
173
174 let version = parachains.as_ref().and_then(|r| {
176 r.iter()
177 .filter_map(|r| {
178 (r.package == command).then_some(r.reference.as_ref()).flatten()
179 })
180 .nth(0)
181 .map(|v| v.as_str())
182 });
183 if let Some(parachain) =
184 chains::from(&relay_chain.runtime, id, &command, version, chain, cache).await?
185 {
186 paras.insert(id, parachain);
187 continue;
188 }
189
190 if let Some(parachains) = parachains.as_ref() &&
192 let Some(repo) = parachains.iter().find(|r| command == r.package)
193 {
194 paras.insert(id, Chain::from_repository(id, repo, chain, cache)?);
195 continue 'outer;
196 }
197
198 if ["./", "../", "/"].iter().any(|p| command.starts_with(p)) {
200 paras.insert(id, Chain::from_local(id, command.into(), chain)?);
201 continue;
202 }
203
204 if ["parachain-template-node", "substrate-contracts-node"].contains(&command.as_str()) {
207 for profile in Profile::VARIANTS {
208 let binary_path = profile.target_directory(Path::new("./")).join(&command);
209 if binary_path.exists() {
210 paras.insert(id, Chain::from_local(id, binary_path, chain)?);
211 continue 'outer;
212 }
213 }
214 return Err(Error::MissingBinary(command));
215 }
216
217 if command.starts_with(PolkadotOmniNode.binary()) {
218 paras.insert(id, Chain::from_omni_node(id, cache)?);
219 continue 'outer;
220 }
221
222 return Err(Error::MissingBinary(command));
223 }
224 Ok(paras)
225 }
226
227 async fn init_relay_chain(
237 version: Option<&str>,
238 runtime_version: Option<&str>,
239 network_config: &NetworkConfiguration,
240 cache: &Path,
241 ) -> Result<RelayChain, Error> {
242 let relay_chain = network_config.0.relaychain();
244 let chain = relay_chain.chain().as_str();
245 if let Some(default_command) = relay_chain.default_command().map(|c| c.as_str()) {
246 let relay =
247 relay::from(default_command, version, runtime_version, chain, cache).await?;
248 for node in relay_chain.nodes() {
250 if let Some(command) = node.command().map(|c| c.as_str()) &&
251 command.to_lowercase() != relay.binary.name()
252 {
253 return Err(Error::UnsupportedCommand(format!(
254 "the relay chain command is unsupported: {command}",
255 )));
256 }
257 }
258 return Ok(relay);
259 }
260 let mut relay: Option<RelayChain> = None;
262 for node in relay_chain.nodes() {
263 if let Some(command) = node.command().map(|c| c.as_str()) {
264 match &relay {
265 Some(relay) =>
266 if command.to_lowercase() != relay.binary.name() {
267 return Err(Error::UnsupportedCommand(format!(
268 "the relay chain command is unsupported: {command}",
269 )));
270 },
271 None => {
272 relay = Some(
273 relay::from(command, version, runtime_version, chain, cache).await?,
274 );
275 },
276 }
277 }
278 }
279 if let Some(relay) = relay {
280 return Ok(relay);
281 }
282 relay::default(version, runtime_version, chain, cache).await
284 }
285
286 pub fn relay_chain(&self) -> &str {
288 &self.relay_chain.chain
289 }
290
291 pub fn hrmp_channels(&self) -> bool {
293 self.hrmp_channels
294 }
295
296 pub async fn spawn(&mut self) -> Result<Network<LocalFileSystem>, Error> {
298 let relay_chain_binary_path = self.relay_chain.binary.path();
300 if !relay_chain_binary_path.exists() {
301 return Err(Error::MissingBinary(self.relay_chain.binary.name().to_string()));
302 }
303 let cache = relay_chain_binary_path
304 .parent()
305 .expect("expected relay chain binary path to exist");
306 let version = self.relay_chain.binary.version().ok_or_else(|| {
307 Error::MissingBinary(format!(
308 "Could not determine version for `{}` binary",
309 self.relay_chain.binary.name()
310 ))
311 })?;
312 for worker in &self.relay_chain.workers {
313 let dest = cache.join(worker);
314 if dest.exists() {
315 remove_symlink_file(&dest)?;
316 }
317 symlink_file(cache.join(format!("{worker}-{version}")), dest)?;
318 }
319
320 let network_config = self.network_config.adapt(&self.relay_chain, &self.parachains)?;
322 Ok(network_config.spawn_native().await?)
323 }
324}
325
326#[derive(Debug, PartialEq)]
330pub struct NetworkConfiguration(NetworkConfig, BTreeSet<u32>);
331
332impl NetworkConfiguration {
333 pub fn build(
340 relay_chain: Relay,
341 port: Option<u16>,
342 rollups: Option<&[Box<dyn Rollup>]>,
343 ) -> Result<Self, Error> {
344 let validators: Vec<_> = VALIDATORS
345 .into_iter()
346 .take(rollups.as_ref().map(|v| v.len()).unwrap_or_default().max(2))
347 .map(String::from)
348 .collect();
349
350 let mut builder = NetworkConfigBuilder::new().with_relaychain(|builder| {
351 let mut builder = builder.with_chain(relay_chain.chain()).with_validator(|builder| {
352 let mut builder = builder
353 .with_name(validators.first().expect("at least two validators defined above"));
354 if let Some(port) = port {
355 builder = builder.with_rpc_port(port)
356 }
357 builder
358 });
359
360 for validator in validators.iter().skip(1) {
361 builder = builder.with_validator(|builder| builder.with_name(validator))
362 }
363 builder
364 });
365
366 if let Some(rollups) = rollups {
367 let mut dependencies =
368 rollups.iter().filter_map(|p| p.requires()).flatten().collect::<Vec<_>>();
369
370 for rollup in rollups {
371 builder = builder.with_parachain(|builder| {
372 let mut builder = builder
373 .with_id(rollup.id())
374 .with_chain(rollup.chain())
375 .with_default_command(rollup.binary());
376
377 let mut genesis_overrides = serde_json::Map::new();
379 if let Some(mut r#override) = rollup.genesis_overrides() {
380 r#override(&mut genesis_overrides);
381 }
382 for (_, r#override) in
383 dependencies.iter_mut().filter(|(t, _)| t == &rollup.as_any().type_id())
384 {
385 r#override(&mut genesis_overrides);
386 }
387 if !genesis_overrides.is_empty() {
388 builder = builder.with_genesis_overrides(genesis_overrides);
389 }
390
391 builder.with_collator(|builder| {
392 let mut builder =
393 builder.with_name(&format!("{}-collator", rollup.name())).with_args(
394 rollup
395 .args()
396 .map(|args| args.into_iter().map(|arg| arg.into()).collect())
397 .unwrap_or_default(),
398 );
399 if let Some(port) = rollup.port() {
400 builder = builder.with_rpc_port(*port)
401 }
402 builder
403 })
404 })
405 }
406
407 let rollups = || rollups.iter().map(|p| p.id());
409 for (sender, recipient) in
410 rollups().flat_map(|s| rollups().filter(move |r| s != *r).map(move |r| (s, r)))
411 {
412 builder = builder.with_hrmp_channel(|channel| {
413 channel
414 .with_sender(sender)
415 .with_recipient(recipient)
416 .with_max_capacity(1_000)
417 .with_max_message_size(8_000)
418 })
419 }
420 }
421
422 Ok(NetworkConfiguration(
423 builder.build().map_err(Error::NetworkConfigurationError)?,
424 Default::default(),
425 ))
426 }
427
428 fn adapt(
435 &self,
436 relay_chain: &RelayChain,
437 parachains: &IndexMap<u32, Chain>,
438 ) -> Result<NetworkConfig, Error> {
439 let binary_path = NetworkConfiguration::resolve_path(&relay_chain.binary.path())?;
441 let chain_spec_generator = match &relay_chain.chain_spec_generator {
442 None => None,
443 Some(path) => Some(format!(
444 "{} {}",
445 NetworkConfiguration::resolve_path(&path.path())?,
446 "{{chainName}}"
447 )),
448 };
449
450 let mut builder = NetworkConfigBuilder::new()
452 .with_relaychain(|relay| {
453 let source = self.0.relaychain();
454 let nodes = source.nodes();
455
456 let mut builder = relay
457 .with_chain(source.chain().as_str())
458 .with_default_args(source.default_args().into_iter().cloned().collect())
459 .with_default_command(binary_path.as_str());
461
462 if let Some(command) = source.chain_spec_command() {
464 builder = builder.with_chain_spec_command(command);
465 }
466 if source.chain_spec_command_is_local() {
467 builder = builder.chain_spec_command_is_local(true);
468 }
469 if let Some(location) = source.chain_spec_path() {
470 builder = builder.with_chain_spec_path(location.clone());
471 }
472 if let Some(chain_spec_command_output_path) =
473 source.chain_spec_command_output_path()
474 {
475 builder =
476 builder.with_chain_spec_command_output_path(chain_spec_command_output_path);
477 }
478 if let Some(command) = chain_spec_generator {
480 builder = builder.with_chain_spec_command(command);
481 }
482 if let Some(genesis) = source.runtime_genesis_patch() {
484 builder = builder.with_genesis_overrides(genesis.clone());
485 }
486 if let Some(location) = source.wasm_override() {
487 builder = builder.with_wasm_override(location.clone());
488 }
489
490 let mut builder = builder.with_validator(|builder| {
492 let source = nodes.first().expect("expected at least one node");
493 Self::build_node_from_source(builder, source, binary_path.as_str())
494 });
495 for source in nodes.iter().skip(1) {
496 builder = builder.with_validator(|builder| {
497 Self::build_node_from_source(builder, source, binary_path.as_str())
498 });
499 }
500
501 builder
502 })
503 .with_global_settings(|settings| {
505 settings.with_network_spawn_timeout(1_000).with_node_spawn_timeout(300)
506 });
507
508 let parachains = ¶chains;
510 for source in self.0.parachains() {
511 let id = source.id();
512 let collators = source.collators();
513 let para =
514 parachains.get(&id).expect("expected parachain existence due to preprocessing");
515
516 let binary_path = NetworkConfiguration::resolve_path(¶.binary.path())?;
518 let mut chain_spec_generator = match ¶.chain_spec_generator {
519 None => None,
520 Some(path) => Some(format!(
521 "{} {}",
522 NetworkConfiguration::resolve_path(&path.path())?,
523 "{{chainName}}"
524 )),
525 };
526
527 builder = builder.with_parachain(|builder| {
528 let mut builder = builder
529 .with_id(id)
530 .with_default_args(source.default_args().into_iter().cloned().collect())
531 .with_default_command(binary_path.as_str());
533
534 if let Some(chain) = source.chain() {
536 builder = builder.with_chain(chain.as_str());
537 if chain.as_str().contains("passet-hub") {
540 let chain_spec = crate::get_passet_hub_spec_content();
541 let temp_dir = std::env::temp_dir();
542 let spec_path = temp_dir.join("passet-hub-spec.json");
543 std::fs::write(&spec_path, chain_spec)
544 .expect("Failed to write passet-hub chain spec");
545 builder = builder.with_chain_spec_path(spec_path);
546 chain_spec_generator = None;
547 }
548 }
549 if let Some(command) = source.chain_spec_command() {
550 builder = builder.with_chain_spec_command(command);
551 }
552 if source.chain_spec_command_is_local() {
553 builder = builder.chain_spec_command_is_local(true);
554 }
555 if let Some(chain_spec_command_output_path) =
556 source.chain_spec_command_output_path()
557 {
558 builder =
559 builder.with_chain_spec_command_output_path(chain_spec_command_output_path)
560 }
561 if let Some(location) = source.chain_spec_path() {
562 builder = builder.with_chain_spec_path(location.clone());
563 }
564 if let Some(command) = chain_spec_generator {
566 builder = builder.with_chain_spec_command(command);
567 }
568 if let Some(genesis) = source.genesis_overrides() {
570 builder = builder.with_genesis_overrides(genesis.clone());
571 }
572 if let Some(location) = source.wasm_override() {
573 builder = builder.with_wasm_override(location.clone());
574 }
575 builder = builder.evm_based(self.1.contains(&id) || source.is_evm_based());
577
578 let mut builder = builder.with_collator(|builder| {
580 let source = collators.first().expect("expected at least one collator");
581 Self::build_node_from_source(builder, source, binary_path.as_str())
582 });
583 for source in collators.iter().skip(1) {
584 builder = builder.with_collator(|builder| {
585 Self::build_node_from_source(builder, source, binary_path.as_str())
586 });
587 }
588
589 builder
590 });
591 }
592
593 for source in self.0.hrmp_channels() {
595 builder = builder.with_hrmp_channel(|channel| {
596 channel
597 .with_sender(source.sender())
598 .with_recipient(source.recipient())
599 .with_max_capacity(source.max_capacity())
600 .with_max_message_size(source.max_message_size())
601 })
602 }
603
604 builder
605 .build()
606 .map_err(|e| Error::Config(format!("could not configure network {:?}", e)))
607 }
608
609 fn build_node_from_source(
611 builder: NodeConfigBuilder<Initial>,
612 source: &NodeConfig,
613 binary_path: &str,
614 ) -> NodeConfigBuilder<Buildable> {
615 let mut builder = builder
616 .with_name(source.name())
617 .bootnode(source.is_bootnode())
618 .invulnerable(source.is_invulnerable())
619 .validator(source.is_validator())
620 .with_args(source.args().into_iter().cloned().collect())
621 .with_command(binary_path)
622 .with_env(source.env().into_iter().cloned().collect());
623 if let Some(command) = source.subcommand() {
624 builder = builder.with_subcommand(command.clone())
625 }
626 if let Some(port) = source.rpc_port() {
627 builder = builder.with_rpc_port(port)
628 }
629 if let Some(port) = source.ws_port() {
630 builder = builder.with_ws_port(port)
631 }
632 builder
633 }
634
635 fn resolve_path(path: &Path) -> Result<String, Error> {
640 path.canonicalize()
641 .map_err(|_| {
642 Error::Config(format!("the canonical path of {:?} could not be resolved", path))
643 })
644 .map(|p| p.to_str().map(|p| p.to_string()))?
645 .ok_or_else(|| Error::Config("the path is invalid".into()))
646 }
647}
648
649impl TryFrom<&Path> for NetworkConfiguration {
650 type Error = Error;
651
652 fn try_from(file: &Path) -> Result<Self, Self::Error> {
653 if !file.exists() {
654 return Err(Error::Config(format!("The {file:?} configuration file was not found")));
655 }
656
657 let contents = std::fs::read_to_string(file)?;
659 let config = contents.parse::<DocumentMut>().map_err(|err| Error::TomlError(err.into()))?;
660 let evm_based = config
661 .get("parachains")
662 .and_then(|p| p.as_array_of_tables())
663 .map(|tables| {
664 tables
665 .iter()
666 .filter_map(|table| {
667 table
668 .get("force_decorator")
669 .and_then(|i| i.as_str())
670 .filter(|v| *v == "generic-evm")
671 .and_then(|_| table.get("id"))
672 .and_then(|i| i.as_integer())
673 .map(|id| id as u32)
674 })
675 .collect()
676 })
677 .unwrap_or_default();
678
679 Ok(NetworkConfiguration(
680 NetworkConfig::load_from_toml(
681 file.to_str().expect("expected file path to be convertible to string"),
682 )
683 .map_err(|e| Error::Config(e.to_string()))?,
684 evm_based,
685 ))
686 }
687}
688
689impl TryFrom<NetworkConfig> for NetworkConfiguration {
690 type Error = ();
691
692 fn try_from(value: NetworkConfig) -> Result<Self, Self::Error> {
693 Ok(NetworkConfiguration(value, Default::default()))
694 }
695}
696
697struct RelayChain {
699 runtime: Runtime,
701 binary: Binary,
703 workers: [&'static str; 2],
705 #[allow(dead_code)]
707 chain: String,
708 chain_spec_generator: Option<Binary>,
710}
711
712#[derive(Debug, PartialEq)]
714struct Chain {
715 id: u32,
717 binary: Binary,
719 chain: Option<String>,
721 chain_spec_generator: Option<Binary>,
723}
724
725impl Chain {
726 fn from_local(id: u32, path: PathBuf, chain: Option<&str>) -> Result<Chain, Error> {
733 let name = path
734 .file_name()
735 .and_then(|f| f.to_str())
736 .ok_or_else(|| Error::Config(format!("unable to determine file name for {path:?}")))?
737 .to_string();
738 let manifest = resolve_manifest(&name, &path)?;
740 Ok(Chain {
741 id,
742 binary: Binary::Local { name, path, manifest },
743 chain: chain.map(|c| c.to_string()),
744 chain_spec_generator: None,
745 })
746 }
747
748 fn from_repository(
757 id: u32,
758 repo: &Repository,
759 chain: Option<&str>,
760 cache: &Path,
761 ) -> Result<Chain, Error> {
762 if repo.url.host_str().is_some_and(|h| h.to_lowercase() == "github.com") {
764 let github = GitHub::parse(repo.url.as_str())?;
765 let source = Source::GitHub(SourceCodeArchive {
766 owner: github.org,
767 repository: github.name,
768 reference: repo.reference.clone(),
769 manifest: None,
770 package: repo.package.clone(),
771 artifacts: vec![repo.package.clone()],
772 })
773 .into();
774 Ok(Chain {
775 id,
776 binary: Binary::Source {
777 name: repo.package.clone(),
778 source,
779 cache: cache.to_path_buf(),
780 },
781 chain: chain.map(|c| c.to_string()),
782 chain_spec_generator: None,
783 })
784 } else {
785 Ok(Chain {
786 id,
787 binary: Binary::Source {
788 name: repo.package.clone(),
789 source: Git {
790 url: repo.url.clone(),
791 reference: repo.reference.clone(),
792 manifest: None,
793 package: repo.package.clone(),
794 artifacts: vec![repo.package.clone()],
795 }
796 .into(),
797 cache: cache.to_path_buf(),
798 },
799 chain: chain.map(|c| c.to_string()),
800 chain_spec_generator: None,
801 })
802 }
803 }
804
805 fn from_omni_node(id: u32, cache: &Path) -> Result<Chain, Error> {
806 Ok(Chain {
807 id,
808 binary: Binary::Source {
809 name: PolkadotOmniNode.binary().to_string(),
810 source: Box::new(PolkadotOmniNode.source()?),
811 cache: cache.to_path_buf(),
812 },
813 chain: None,
814 chain_spec_generator: None,
815 })
816 }
817}
818
819fn resolve_manifest(package: &str, path: &Path) -> Result<Option<PathBuf>, Error> {
825 let matches_package = |config: &DocumentMut| {
826 config
827 .get("package")
828 .and_then(|i| i.as_table())
829 .and_then(|t| t.get("name"))
830 .and_then(|i| i.as_str()) ==
831 Some(package)
832 };
833
834 let mut manifest = Some(path);
835 'outer: while let Some(path) = manifest {
836 let manifest_path = path.join("Cargo.toml");
837 if !manifest_path.exists() {
838 manifest = path.parent();
839 continue;
840 }
841 let contents = std::fs::read_to_string(&manifest_path)?;
842 let config = contents.parse::<DocumentMut>().map_err(|err| Error::TomlError(err.into()))?;
843 if matches_package(&config) {
845 break 'outer;
846 }
847 if let Some(members) = config
849 .get("workspace")
850 .and_then(|i| i.as_table())
851 .and_then(|t| t.get("members"))
852 .and_then(|m| m.as_array())
853 .map(|a| a.iter().filter_map(|v| v.as_str()))
854 {
855 for member in members {
857 let member_path = path.join(member);
858 for entry in glob(member_path.to_string_lossy().as_ref())
859 .expect("expected valid glob for workspace member")
860 .filter_map(Result::ok)
861 {
862 let manifest_path = entry.join("Cargo.toml");
863 if manifest_path.exists() {
864 let contents = std::fs::read_to_string(&manifest_path)?;
865 let config = contents
866 .parse::<DocumentMut>()
867 .map_err(|err| Error::TomlError(err.into()))?;
868 if matches_package(&config) {
869 break 'outer;
870 }
871 }
872 }
873 }
874 };
875 manifest = path.parent();
876 }
877 Ok(manifest.map(|p| p.join("Cargo.toml")))
878}
879
880#[cfg(test)]
881mod tests {
882 use super::*;
883 use anyhow::Result;
884 use std::{
885 env::current_dir,
886 fs::{File, create_dir_all, remove_dir, remove_file},
887 io::Write,
888 };
889 use tempfile::{Builder, tempdir};
890
891 pub(crate) const FALLBACK: &str = "stable2412";
892 pub(crate) const RELAY_BINARY_VERSION: &str = "stable2412-4";
893 pub(crate) const SYSTEM_PARA_BINARY_VERSION: &str = "stable2503";
894 const SYSTEM_PARA_RUNTIME_VERSION: &str = "v1.4.1";
895
896 mod zombienet {
897 use super::*;
898 use pop_common::{Status, helpers::with_current_dir_async};
899
900 pub(crate) struct Output;
901 impl Status for Output {
902 fn update(&self, status: &str) {
903 println!("{status}")
904 }
905 }
906
907 #[tokio::test]
908 async fn new_with_relay_only_works() -> Result<()> {
909 let temp_dir = tempdir()?;
910 let cache = PathBuf::from(temp_dir.path());
911 let config = Builder::new().suffix(".toml").tempfile()?;
912 writeln!(
913 config.as_file(),
914 r#"
915[relaychain]
916chain = "paseo-local"
917"#
918 )?;
919
920 let zombienet = Zombienet::new(
921 &cache,
922 config.path().try_into()?,
923 Some(RELAY_BINARY_VERSION),
924 None,
925 None,
926 None,
927 None,
928 )
929 .await?;
930
931 let relay_chain = &zombienet.relay_chain.binary;
932 assert_eq!(relay_chain.name(), "polkadot");
933 assert_eq!(
934 relay_chain.path(),
935 temp_dir.path().join(format!("polkadot-{RELAY_BINARY_VERSION}"))
936 );
937 assert_eq!(relay_chain.version().unwrap(), RELAY_BINARY_VERSION);
938 assert!(matches!(
939 relay_chain,
940 Binary::Source { source, .. }
941 if matches!(source.as_ref(), Source::GitHub(ReleaseArchive { tag, .. })
942 if *tag == Some(format!("polkadot-{RELAY_BINARY_VERSION}"))
943 )
944 ));
945 assert!(zombienet.parachains.is_empty());
946 assert_eq!(zombienet.relay_chain(), "paseo-local");
947 assert!(!zombienet.hrmp_channels());
948 Ok(())
949 }
950
951 #[tokio::test]
952 async fn new_with_relay_only_from_network_config_works() -> Result<()> {
953 let temp_dir = tempdir()?;
954 let cache = PathBuf::from(temp_dir.path());
955 let config = NetworkConfigBuilder::new()
956 .with_relaychain(|b| {
957 b.with_chain("paseo-local").with_validator(|b| b.with_name("alice"))
958 })
959 .build()
960 .unwrap();
961
962 let zombienet = Zombienet::new(
963 &cache,
964 config.try_into().unwrap(),
965 Some(RELAY_BINARY_VERSION),
966 None,
967 None,
968 None,
969 None,
970 )
971 .await?;
972
973 let relay_chain = &zombienet.relay_chain.binary;
974 assert_eq!(relay_chain.name(), "polkadot");
975 assert_eq!(
976 relay_chain.path(),
977 temp_dir.path().join(format!("polkadot-{RELAY_BINARY_VERSION}"))
978 );
979 assert_eq!(relay_chain.version().unwrap(), RELAY_BINARY_VERSION);
980 assert!(matches!(
981 relay_chain,
982 Binary::Source { source, .. }
983 if matches!(source.as_ref(), Source::GitHub(ReleaseArchive { tag, .. })
984 if *tag == Some(format!("polkadot-{RELAY_BINARY_VERSION}"))
985 )
986 ));
987 assert!(zombienet.parachains.is_empty());
988 assert_eq!(zombienet.relay_chain(), "paseo-local");
989 assert!(!zombienet.hrmp_channels());
990 Ok(())
991 }
992
993 #[tokio::test]
994 async fn new_with_relay_chain_spec_generator_works() -> Result<()> {
995 let temp_dir = tempdir()?;
996 let cache = PathBuf::from(temp_dir.path());
997 let config = Builder::new().suffix(".toml").tempfile()?;
998 writeln!(
999 config.as_file(),
1000 r#"
1001[relaychain]
1002chain = "paseo-local"
1003"#
1004 )?;
1005 let version = "v1.3.3";
1006
1007 let zombienet = Zombienet::new(
1008 &cache,
1009 config.path().try_into()?,
1010 None,
1011 Some(version),
1012 None,
1013 None,
1014 None,
1015 )
1016 .await?;
1017
1018 assert_eq!(zombienet.relay_chain.chain, "paseo-local");
1019 let chain_spec_generator = &zombienet.relay_chain.chain_spec_generator.unwrap();
1020 assert_eq!(chain_spec_generator.name(), "paseo-chain-spec-generator");
1021 assert_eq!(
1022 chain_spec_generator.path(),
1023 temp_dir.path().join(format!("paseo-chain-spec-generator-{version}"))
1024 );
1025 assert_eq!(chain_spec_generator.version().unwrap(), version);
1026 assert!(matches!(
1027 chain_spec_generator,
1028 Binary::Source { source, .. }
1029 if matches!(source.as_ref(), Source::GitHub(ReleaseArchive { tag, .. })
1030 if *tag == Some(version.to_string())
1031 )
1032 ));
1033 assert!(zombienet.parachains.is_empty());
1034 Ok(())
1035 }
1036
1037 #[tokio::test]
1038 async fn new_with_default_command_works() -> Result<()> {
1039 let temp_dir = tempdir()?;
1040 let cache = PathBuf::from(temp_dir.path());
1041 let config = Builder::new().suffix(".toml").tempfile()?;
1042 writeln!(
1043 config.as_file(),
1044 r#"
1045[relaychain]
1046chain = "paseo-local"
1047default_command = "./bin-stable2503/polkadot"
1048"#
1049 )?;
1050
1051 let zombienet = Zombienet::new(
1052 &cache,
1053 config.path().try_into()?,
1054 Some(RELAY_BINARY_VERSION),
1055 None,
1056 None,
1057 None,
1058 None,
1059 )
1060 .await?;
1061
1062 let relay_chain = &zombienet.relay_chain.binary;
1063 assert_eq!(relay_chain.name(), "polkadot");
1064 assert_eq!(
1065 relay_chain.path(),
1066 temp_dir.path().join(format!("polkadot-{RELAY_BINARY_VERSION}"))
1067 );
1068 assert_eq!(relay_chain.version().unwrap(), RELAY_BINARY_VERSION);
1069 assert!(matches!(
1070 relay_chain,
1071 Binary::Source { source, ..}
1072 if matches!(source.as_ref(), Source::GitHub(ReleaseArchive { tag, .. })
1073 if *tag == Some(format!("polkadot-{RELAY_BINARY_VERSION}"))
1074 )
1075 ));
1076 assert!(zombienet.parachains.is_empty());
1077 Ok(())
1078 }
1079
1080 #[tokio::test]
1081 async fn new_with_node_command_works() -> Result<()> {
1082 let temp_dir = tempdir()?;
1083 let cache = PathBuf::from(temp_dir.path());
1084 let config = Builder::new().suffix(".toml").tempfile()?;
1085 writeln!(
1086 config.as_file(),
1087 r#"
1088[relaychain]
1089chain = "paseo-local"
1090
1091[[relaychain.nodes]]
1092name = "alice"
1093validator = true
1094command = "polkadot"
1095"#
1096 )?;
1097
1098 let zombienet = Zombienet::new(
1099 &cache,
1100 config.path().try_into()?,
1101 Some(RELAY_BINARY_VERSION),
1102 None,
1103 None,
1104 None,
1105 None,
1106 )
1107 .await?;
1108
1109 let relay_chain = &zombienet.relay_chain.binary;
1110 assert_eq!(relay_chain.name(), "polkadot");
1111 assert_eq!(
1112 relay_chain.path(),
1113 temp_dir.path().join(format!("polkadot-{RELAY_BINARY_VERSION}"))
1114 );
1115 assert_eq!(relay_chain.version().unwrap(), RELAY_BINARY_VERSION);
1116 assert!(matches!(
1117 relay_chain,
1118 Binary::Source { source, .. }
1119 if matches!(source.as_ref(), Source::GitHub(ReleaseArchive { tag, .. })
1120 if *tag == Some(format!("polkadot-{RELAY_BINARY_VERSION}"))
1121 )
1122 ));
1123 assert!(zombienet.parachains.is_empty());
1124 Ok(())
1125 }
1126
1127 #[tokio::test]
1128 async fn new_with_node_command_from_network_config_works() -> Result<()> {
1129 let temp_dir = tempdir()?;
1130 let cache = PathBuf::from(temp_dir.path());
1131 let config = NetworkConfigBuilder::new()
1132 .with_relaychain(|b| {
1133 b.with_chain("paseo-local")
1134 .with_validator(|b| b.with_name("alice").with_command("polkadot"))
1135 })
1136 .build()
1137 .unwrap();
1138
1139 let zombienet = Zombienet::new(
1140 &cache,
1141 config.try_into().unwrap(),
1142 Some(RELAY_BINARY_VERSION),
1143 None,
1144 None,
1145 None,
1146 None,
1147 )
1148 .await?;
1149
1150 let relay_chain = &zombienet.relay_chain.binary;
1151 assert_eq!(relay_chain.name(), "polkadot");
1152 assert_eq!(
1153 relay_chain.path(),
1154 temp_dir.path().join(format!("polkadot-{RELAY_BINARY_VERSION}"))
1155 );
1156 assert_eq!(relay_chain.version().unwrap(), RELAY_BINARY_VERSION);
1157 assert!(matches!(
1158 relay_chain,
1159 Binary::Source { source, .. }
1160 if matches!(source.as_ref(), Source::GitHub(ReleaseArchive { tag, .. })
1161 if *tag == Some(format!("polkadot-{RELAY_BINARY_VERSION}"))
1162 )
1163 ));
1164 assert!(zombienet.parachains.is_empty());
1165 Ok(())
1166 }
1167
1168 #[tokio::test]
1169 async fn new_ensures_node_commands_valid() -> Result<()> {
1170 let temp_dir = tempdir()?;
1171 let cache = PathBuf::from(temp_dir.path());
1172 let config = Builder::new().suffix(".toml").tempfile()?;
1173 writeln!(
1174 config.as_file(),
1175 r#"
1176[relaychain]
1177chain = "paseo-local"
1178
1179[[relaychain.nodes]]
1180name = "alice"
1181validator = true
1182command = "polkadot"
1183
1184[[relaychain.nodes]]
1185name = "bob"
1186validator = true
1187command = "polkadot-stable2503"
1188"#
1189 )?;
1190
1191 assert!(matches!(
1192 Zombienet::new(&cache, config.path().try_into()?, None, None, None, None, None).await,
1193 Err(Error::UnsupportedCommand(error))
1194 if error == "the relay chain command is unsupported: polkadot-stable2503"
1195 ));
1196 Ok(())
1197 }
1198
1199 #[tokio::test]
1200 async fn new_ensures_node_command_valid() -> Result<()> {
1201 let temp_dir = tempdir()?;
1202 let cache = PathBuf::from(temp_dir.path());
1203 let config = Builder::new().suffix(".toml").tempfile()?;
1204 writeln!(
1205 config.as_file(),
1206 r#"
1207[relaychain]
1208chain = "paseo-local"
1209default_command = "polkadot"
1210
1211[[relaychain.nodes]]
1212name = "alice"
1213validator = true
1214command = "polkadot-stable2503"
1215"#
1216 )?;
1217
1218 assert!(matches!(
1219 Zombienet::new(&cache, config.path().try_into()?, None, None, None, None, None).await,
1220 Err(Error::UnsupportedCommand(error))
1221 if error == "the relay chain command is unsupported: polkadot-stable2503"
1222 ));
1223 Ok(())
1224 }
1225
1226 #[tokio::test]
1227 async fn new_ensures_node_command_from_network_config_valid() -> Result<()> {
1228 let temp_dir = tempdir()?;
1229 let cache = PathBuf::from(temp_dir.path());
1230 let config = NetworkConfigBuilder::new()
1231 .with_relaychain(|b| {
1232 b.with_chain("paseo-local")
1233 .with_validator(|b| b.with_name("alice").with_command("polkadot"))
1234 .with_validator(|b| b.with_name("bob").with_command("polkadot"))
1235 .with_validator(|b| b.with_name("charlie").with_command("p0lk4d0t"))
1236 })
1237 .build()
1238 .unwrap();
1239
1240 assert!(matches!(
1241 Zombienet::new(&cache, config.try_into().unwrap(), None, None, None, None,None).await,
1242 Err(Error::UnsupportedCommand(error))
1243 if error == "the relay chain command is unsupported: p0lk4d0t"
1244 ));
1245 Ok(())
1246 }
1247
1248 #[tokio::test]
1249 async fn new_with_system_chain_works() -> Result<()> {
1250 let temp_dir = tempdir()?;
1251 let cache = PathBuf::from(temp_dir.path());
1252 let config = Builder::new().suffix(".toml").tempfile()?;
1253 writeln!(
1254 config.as_file(),
1255 r#"
1256[relaychain]
1257chain = "paseo-local"
1258
1259[[parachains]]
1260id = 1000
1261chain = "asset-hub-paseo-local"
1262"#
1263 )?;
1264
1265 let zombienet = Zombienet::new(
1266 &cache,
1267 config.path().try_into()?,
1268 Some(RELAY_BINARY_VERSION),
1269 None,
1270 Some(SYSTEM_PARA_BINARY_VERSION),
1271 None,
1272 None,
1273 )
1274 .await?;
1275
1276 assert_eq!(zombienet.parachains.len(), 1);
1277 let system_parachain = &zombienet.parachains.get(&1000).unwrap().binary;
1278 assert_eq!(system_parachain.name(), "polkadot-parachain");
1279 assert_eq!(
1280 system_parachain.path(),
1281 temp_dir.path().join(format!("polkadot-parachain-{SYSTEM_PARA_BINARY_VERSION}"))
1282 );
1283 assert_eq!(system_parachain.version().unwrap(), SYSTEM_PARA_BINARY_VERSION);
1284 assert!(matches!(
1285 system_parachain,
1286 Binary::Source { source, .. }
1287 if matches!(source.as_ref(), Source::GitHub(ReleaseArchive { tag, .. })
1288 if *tag == Some(format!("polkadot-{SYSTEM_PARA_BINARY_VERSION}"))
1289 )
1290 ));
1291 Ok(())
1292 }
1293
1294 #[tokio::test]
1295 async fn new_with_system_chain_spec_generator_works() -> Result<()> {
1296 let temp_dir = tempdir()?;
1297 let cache = PathBuf::from(temp_dir.path());
1298 let config = Builder::new().suffix(".toml").tempfile()?;
1299 writeln!(
1300 config.as_file(),
1301 r#"
1302[relaychain]
1303chain = "paseo-local"
1304
1305[[parachains]]
1306id = 1000
1307chain = "asset-hub-paseo-local"
1308"#
1309 )?;
1310
1311 let zombienet = Zombienet::new(
1312 &cache,
1313 config.path().try_into()?,
1314 None,
1315 None,
1316 None,
1317 Some(SYSTEM_PARA_RUNTIME_VERSION),
1318 None,
1319 )
1320 .await?;
1321
1322 assert_eq!(zombienet.parachains.len(), 1);
1323 let system_parachain = &zombienet.parachains.get(&1000).unwrap();
1324 assert_eq!(system_parachain.chain.as_ref().unwrap(), "asset-hub-paseo-local");
1325 let chain_spec_generator = system_parachain.chain_spec_generator.as_ref().unwrap();
1326 assert_eq!(chain_spec_generator.name(), "paseo-chain-spec-generator");
1327 assert_eq!(
1328 chain_spec_generator.path(),
1329 temp_dir
1330 .path()
1331 .join(format!("paseo-chain-spec-generator-{SYSTEM_PARA_RUNTIME_VERSION}"))
1332 );
1333 assert_eq!(chain_spec_generator.version().unwrap(), SYSTEM_PARA_RUNTIME_VERSION);
1334 assert!(matches!(
1335 chain_spec_generator,
1336 Binary::Source { source, .. }
1337 if matches!(source.as_ref(), Source::GitHub(ReleaseArchive { tag, .. })
1338 if *tag == Some(SYSTEM_PARA_RUNTIME_VERSION.to_string())
1339 )
1340 ));
1341 Ok(())
1342 }
1343
1344 #[tokio::test]
1345 async fn new_with_pop_works() -> Result<()> {
1346 let temp_dir = tempdir()?;
1347 let cache = PathBuf::from(temp_dir.path());
1348 let config = Builder::new().suffix(".toml").tempfile()?;
1349 writeln!(
1350 config.as_file(),
1351 r#"
1352[relaychain]
1353chain = "paseo-local"
1354
1355[[parachains]]
1356id = 4385
1357default_command = "pop-node"
1358"#
1359 )?;
1360
1361 let zombienet =
1362 Zombienet::new(&cache, config.path().try_into()?, None, None, None, None, None)
1363 .await?;
1364
1365 assert_eq!(zombienet.parachains.len(), 1);
1366 let pop = &zombienet.parachains.get(&4385).unwrap().binary;
1367 let version = pop.latest().unwrap();
1368 assert_eq!(pop.name(), "pop-node");
1369 assert_eq!(pop.path(), temp_dir.path().join(format!("pop-node-{version}")));
1370 assert_eq!(pop.version().unwrap(), version);
1371 assert!(matches!(
1372 pop,
1373 Binary::Source { source, .. }
1374 if matches!(source.as_ref(), Source::GitHub(ReleaseArchive { tag, .. })
1375 if *tag == Some(format!("node-{version}"))
1376 )
1377 ));
1378 Ok(())
1379 }
1380
1381 #[tokio::test]
1382 async fn new_with_pop_version_works() -> Result<()> {
1383 let temp_dir = tempdir()?;
1384 let cache = PathBuf::from(temp_dir.path());
1385 let config = Builder::new().suffix(".toml").tempfile()?;
1386 writeln!(
1387 config.as_file(),
1388 r#"
1389[relaychain]
1390chain = "paseo-local"
1391
1392[[parachains]]
1393id = 4385
1394default_command = "pop-node"
1395"#
1396 )?;
1397 let version = "v1.0";
1398
1399 let zombienet = Zombienet::new(
1400 &cache,
1401 config.path().try_into()?,
1402 None,
1403 None,
1404 None,
1405 None,
1406 Some(&vec![format!("https://github.com/r0gue-io/pop-node#{version}")]),
1407 )
1408 .await?;
1409
1410 assert_eq!(zombienet.parachains.len(), 1);
1411 let pop = &zombienet.parachains.get(&4385).unwrap().binary;
1412 assert_eq!(pop.name(), "pop-node");
1413 assert_eq!(pop.path(), temp_dir.path().join(format!("pop-node-{version}")));
1414 assert_eq!(pop.version().unwrap(), version);
1415 assert!(matches!(
1416 pop,
1417 Binary::Source { source, .. }
1418 if matches!(source.as_ref(), Source::GitHub(ReleaseArchive { tag, .. })
1419 if *tag == Some(format!("node-{version}"))
1420 )
1421 ));
1422 Ok(())
1423 }
1424
1425 #[tokio::test]
1426 async fn new_with_local_parachain_works() -> Result<()> {
1427 let temp_dir = tempdir()?;
1428 let cache = PathBuf::from(temp_dir.path());
1429 let config = Builder::new().suffix(".toml").tempfile()?;
1430 writeln!(
1431 config.as_file(),
1432 r#"
1433[relaychain]
1434chain = "paseo-local"
1435
1436[[parachains]]
1437id = 2000
1438default_command = "./target/release/parachain-template-node"
1439"#
1440 )?;
1441
1442 let zombienet =
1443 Zombienet::new(&cache, config.path().try_into()?, None, None, None, None, None)
1444 .await?;
1445
1446 assert_eq!(zombienet.parachains.len(), 1);
1447 let pop = &zombienet.parachains.get(&2000).unwrap().binary;
1448 assert_eq!(pop.name(), "parachain-template-node");
1449 assert_eq!(pop.path(), Path::new("./target/release/parachain-template-node"));
1450 assert_eq!(pop.version(), None);
1451 assert!(matches!(pop, Binary::Local { .. }));
1452 Ok(())
1453 }
1454
1455 #[tokio::test]
1456 async fn new_with_local_parachain_without_path_works() -> Result<()> {
1457 let temp_dir = tempdir()?;
1458 let cache = PathBuf::from(temp_dir.path());
1459 let config = Builder::new().suffix(".toml").tempfile()?;
1460 writeln!(
1461 config.as_file(),
1462 r#"
1463[relaychain]
1464chain = "paseo-local"
1465
1466[[parachains]]
1467id = 1000
1468
1469[parachains.collator]
1470name = "collator"
1471command = "parachain-template-node"
1472
1473[[parachains]]
1474id = 2000
1475
1476[parachains.collator]
1477name = "collator"
1478command = "substrate-contracts-node"
1479"#
1480 )?;
1481 let temp_workspace = tempdir()?;
1482 with_current_dir_async(temp_workspace.path(), async || {
1483 assert!(matches!(
1486 Zombienet::new(&cache, config.path().try_into()?, None, None, None, None, None).await,
1487 Err(Error::MissingBinary(command))
1488 if command == "parachain-template-node"
1489 ));
1490 let parachain_template = PathBuf::from("target/release/parachain-template-node");
1492 create_dir_all(parachain_template.parent().unwrap())?;
1493 File::create(¶chain_template)?;
1494 let parachain_contracts_template =
1496 PathBuf::from("target/debug/substrate-contracts-node");
1497 create_dir_all(parachain_contracts_template.parent().unwrap())?;
1498 File::create(¶chain_contracts_template)?;
1499
1500 let zombienet =
1501 Zombienet::new(&cache, config.path().try_into()?, None, None, None, None, None)
1502 .await?;
1503 remove_file(¶chain_template)?;
1506 remove_file(¶chain_contracts_template)?;
1507 remove_dir(parachain_template.parent().unwrap())?;
1508 remove_dir(parachain_contracts_template.parent().unwrap())?;
1509
1510 assert_eq!(zombienet.parachains.len(), 2);
1511 let parachain = &zombienet.parachains.get(&1000).unwrap().binary;
1512 assert_eq!(parachain.name(), "parachain-template-node");
1513 assert_eq!(parachain.path(), Path::new("./target/release/parachain-template-node"));
1514 assert_eq!(parachain.version(), None);
1515 assert!(matches!(parachain, Binary::Local { .. }));
1516 let contract_parachain = &zombienet.parachains.get(&2000).unwrap().binary;
1517 assert_eq!(contract_parachain.name(), "substrate-contracts-node");
1518 assert_eq!(
1519 contract_parachain.path(),
1520 Path::new("./target/debug/substrate-contracts-node")
1521 );
1522 assert_eq!(contract_parachain.version(), None);
1523 assert!(matches!(contract_parachain, Binary::Local { .. }));
1524 Ok(())
1525 })
1526 .await
1527 }
1528
1529 #[tokio::test]
1530 async fn new_with_collator_command_works() -> Result<()> {
1531 let temp_dir = tempdir()?;
1532 let cache = PathBuf::from(temp_dir.path());
1533 let config = Builder::new().suffix(".toml").tempfile()?;
1534 writeln!(
1535 config.as_file(),
1536 r#"
1537[relaychain]
1538chain = "paseo-local"
1539
1540[[parachains]]
1541id = 2000
1542
1543[[parachains.collators]]
1544name = "collator-01"
1545command = "./target/release/parachain-template-node"
1546"#
1547 )?;
1548
1549 let zombienet =
1550 Zombienet::new(&cache, config.path().try_into()?, None, None, None, None, None)
1551 .await?;
1552
1553 assert_eq!(zombienet.parachains.len(), 1);
1554 let pop = &zombienet.parachains.get(&2000).unwrap().binary;
1555 assert_eq!(pop.name(), "parachain-template-node");
1556 assert_eq!(pop.path(), Path::new("./target/release/parachain-template-node"));
1557 assert_eq!(pop.version(), None);
1558 assert!(matches!(pop, Binary::Local { .. }));
1559 Ok(())
1560 }
1561
1562 #[tokio::test]
1563 async fn new_with_moonbeam_works() -> Result<()> {
1564 let temp_dir = tempdir()?;
1565 let cache = PathBuf::from(temp_dir.path());
1566 let config = Builder::new().suffix(".toml").tempfile()?;
1567 writeln!(
1568 config.as_file(),
1569 r#"
1570[relaychain]
1571chain = "paseo-local"
1572
1573[[parachains]]
1574id = 2000
1575default_command = "moonbeam"
1576"#
1577 )?;
1578 let version = "v0.38.0";
1579
1580 let zombienet = Zombienet::new(
1581 &cache,
1582 config.path().try_into()?,
1583 None,
1584 None,
1585 None,
1586 None,
1587 Some(&vec![format!("https://github.com/moonbeam-foundation/moonbeam#{version}")]),
1588 )
1589 .await?;
1590
1591 assert_eq!(zombienet.parachains.len(), 1);
1592 let pop = &zombienet.parachains.get(&2000).unwrap().binary;
1593 assert_eq!(pop.name(), "moonbeam");
1594 assert_eq!(pop.path(), temp_dir.path().join(format!("moonbeam-{version}")));
1595 assert_eq!(pop.version().unwrap(), version);
1596 assert!(matches!(
1597 pop,
1598 Binary::Source { source, .. }
1599 if matches!(source.as_ref(), Source::GitHub(SourceCodeArchive { reference, .. })
1600 if *reference == Some(version.to_string())
1601 )
1602 ));
1603 Ok(())
1604 }
1605
1606 #[tokio::test]
1607 async fn new_with_hrmp_channels_works() -> Result<()> {
1608 let temp_dir = tempdir()?;
1609 let cache = PathBuf::from(temp_dir.path());
1610 let config = Builder::new().suffix(".toml").tempfile()?;
1611 writeln!(
1612 config.as_file(),
1613 r#"
1614[relaychain]
1615chain = "paseo-local"
1616
1617[[parachains]]
1618id = 1000
1619chain = "asset-hub-paseo-local"
1620
1621[[parachains]]
1622id = 4385
1623default_command = "pop-node"
1624
1625[[hrmp_channels]]
1626sender = 4385
1627recipient = 1000
1628max_capacity = 1000
1629max_message_size = 8000
1630"#
1631 )?;
1632
1633 let zombienet =
1634 Zombienet::new(&cache, config.path().try_into()?, None, None, None, None, None)
1635 .await?;
1636
1637 assert!(zombienet.hrmp_channels());
1638 Ok(())
1639 }
1640
1641 #[tokio::test]
1642 async fn new_handles_missing_binary() -> Result<()> {
1643 let temp_dir = tempdir()?;
1644 let cache = PathBuf::from(temp_dir.path());
1645 let config = Builder::new().suffix(".toml").tempfile()?;
1646 writeln!(
1647 config.as_file(),
1648 r#"
1649[relaychain]
1650chain = "paseo-local"
1651
1652[[parachains]]
1653id = 404
1654default_command = "missing-binary"
1655"#
1656 )?;
1657
1658 assert!(matches!(
1659 Zombienet::new(&cache, config.path().try_into()?, None, None, None, None, None).await,
1660 Err(Error::MissingBinary(command))
1661 if command == "missing-binary"
1662 ));
1663 Ok(())
1664 }
1665
1666 #[tokio::test]
1667 async fn binaries_works() -> Result<()> {
1668 let temp_dir = tempdir()?;
1669 let cache = PathBuf::from(temp_dir.path());
1670 let config = Builder::new().suffix(".toml").tempfile()?;
1671 writeln!(
1672 config.as_file(),
1673 r#"
1674[relaychain]
1675chain = "paseo-local"
1676
1677[[parachains]]
1678id = 1000
1679chain = "asset-hub-paseo-local"
1680
1681[[parachains]]
1682id = 2000
1683default_command = "./target/release/parachain-template-node"
1684
1685[[parachains]]
1686id = 4385
1687default_command = "pop-node"
1688"#
1689 )?;
1690
1691 let mut zombienet =
1692 Zombienet::new(&cache, config.path().try_into()?, None, None, None, None, None)
1693 .await?;
1694 assert_eq!(zombienet.binaries().count(), 6);
1695 Ok(())
1696 }
1697
1698 #[tokio::test]
1699 async fn binaries_includes_chain_spec_generators() -> Result<()> {
1700 let temp_dir = tempdir()?;
1701 let cache = PathBuf::from(temp_dir.path());
1702 let config = Builder::new().suffix(".toml").tempfile()?;
1703 writeln!(
1704 config.as_file(),
1705 r#"
1706[relaychain]
1707chain = "paseo-local"
1708
1709[[parachains]]
1710id = 1000
1711chain = "asset-hub-paseo-local"
1712"#
1713 )?;
1714
1715 let mut zombienet =
1716 Zombienet::new(&cache, config.path().try_into()?, None, None, None, None, None)
1717 .await?;
1718 assert_eq!(zombienet.binaries().count(), 4);
1719 Ok(())
1720 }
1721
1722 #[tokio::test]
1723 async fn spawn_ensures_relay_chain_binary_exists() -> Result<()> {
1724 let temp_dir = tempdir()?;
1725 let cache = PathBuf::from(temp_dir.path());
1726 let config = Builder::new().suffix(".toml").tempfile()?;
1727 writeln!(
1728 config.as_file(),
1729 r#"
1730[relaychain]
1731chain = "paseo-local"
1732"#
1733 )?;
1734
1735 let mut zombienet =
1736 Zombienet::new(&cache, config.path().try_into()?, None, None, None, None, None)
1737 .await?;
1738 assert!(matches!(
1739 zombienet.spawn().await,
1740 Err(Error::MissingBinary(error))
1741 if error == "polkadot"
1742 ));
1743 Ok(())
1744 }
1745
1746 #[tokio::test]
1747 async fn spawn_ensures_relay_chain_version_set() -> Result<()> {
1748 let temp_dir = tempdir()?;
1749 let cache = PathBuf::from(temp_dir.path());
1750 let config = Builder::new().suffix(".toml").tempfile()?;
1751 writeln!(
1752 config.as_file(),
1753 r#"
1754[relaychain]
1755chain = "paseo-local"
1756"#
1757 )?;
1758 File::create(cache.join("polkadot"))?;
1759
1760 let mut zombienet =
1761 Zombienet::new(&cache, config.path().try_into()?, None, None, None, None, None)
1762 .await?;
1763 let Binary::Source { source, .. } = &mut zombienet.relay_chain.binary else {
1764 panic!("expected binary which needs to be sourced")
1765 };
1766 if let Source::GitHub(ReleaseArchive { tag, .. }) = source.as_mut() {
1767 *tag = None
1768 }
1769 assert!(matches!(
1770 zombienet.spawn().await,
1771 Err(Error::MissingBinary(error))
1772 if error == "Could not determine version for `polkadot` binary",
1773 ));
1774 Ok(())
1775 }
1776
1777 #[tokio::test]
1778 async fn spawn_symlinks_workers() -> Result<()> {
1779 let temp_dir = tempdir()?;
1780 let cache = PathBuf::from(temp_dir.path());
1781 let config = Builder::new().suffix(".toml").tempfile()?;
1782 writeln!(
1783 config.as_file(),
1784 r#"
1785[relaychain]
1786chain = "paseo-local"
1787"#
1788 )?;
1789 File::create(cache.join(format!("polkadot-{RELAY_BINARY_VERSION}")))?;
1790 File::create(cache.join(format!("polkadot-execute-worker-{RELAY_BINARY_VERSION}")))?;
1791 File::create(cache.join(format!("polkadot-prepare-worker-{RELAY_BINARY_VERSION}")))?;
1792
1793 let mut zombienet = Zombienet::new(
1794 &cache,
1795 config.path().try_into()?,
1796 Some(RELAY_BINARY_VERSION),
1797 None,
1798 None,
1799 None,
1800 None,
1801 )
1802 .await?;
1803 assert!(!cache.join("polkadot-execute-worker").exists());
1804 assert!(!cache.join("polkadot-prepare-worker").exists());
1805 let _ = zombienet.spawn().await;
1806 assert!(cache.join("polkadot-execute-worker").exists());
1807 assert!(cache.join("polkadot-prepare-worker").exists());
1808 let _ = zombienet.spawn().await;
1809 Ok(())
1810 }
1811
1812 #[tokio::test]
1813 async fn spawn_works() -> Result<()> {
1814 let temp_dir = tempdir()?;
1815 let cache = PathBuf::from(temp_dir.path());
1816 let config = Builder::new().suffix(".toml").tempfile()?;
1817 writeln!(
1818 config.as_file(),
1819 r#"
1820[relaychain]
1821chain = "paseo-local"
1822
1823[[relaychain.nodes]]
1824name = "alice"
1825validator = true
1826"#
1827 )?;
1828
1829 let mut zombienet =
1830 Zombienet::new(&cache, config.path().try_into()?, None, None, None, None, None)
1831 .await?;
1832 for b in zombienet.binaries() {
1833 b.source(true, &Output, true).await?;
1834 }
1835
1836 zombienet.spawn().await?;
1837 Ok(())
1838 }
1839 }
1840
1841 mod network_config {
1842 use super::{Relay::*, *};
1843 use crate::registry::rollups;
1844 use std::{
1845 fs::{File, create_dir_all},
1846 io::Write,
1847 path::PathBuf,
1848 };
1849 use tempfile::{Builder, tempdir};
1850
1851 #[test]
1852 fn initializing_from_file_fails_when_missing() {
1853 assert!(NetworkConfiguration::try_from(PathBuf::new().as_path()).is_err());
1854 }
1855
1856 #[test]
1857 fn initializing_from_file_fails_when_malformed() -> Result<(), Error> {
1858 let config = Builder::new().suffix(".toml").tempfile()?;
1859 writeln!(config.as_file(), "[")?;
1860 assert!(matches!(
1861 NetworkConfiguration::try_from(config.path()),
1862 Err(Error::TomlError(..))
1863 ));
1864 Ok(())
1865 }
1866
1867 #[test]
1868 fn initializing_from_file_fails_when_relaychain_missing() -> Result<(), Error> {
1869 let config = Builder::new().suffix(".toml").tempfile()?;
1870 assert!(matches!(
1871 NetworkConfiguration::try_from(config.path()),
1872 Err(Error::Config(error)) if error == "Relay chain does not exist."
1873 ));
1874 Ok(())
1875 }
1876
1877 #[tokio::test]
1878 async fn initializing_from_file_fails_when_parachain_id_missing() -> Result<()> {
1879 let config = Builder::new().suffix(".toml").tempfile()?;
1880 writeln!(
1881 config.as_file(),
1882 r#"
1883[relaychain]
1884chain = "paseo-local"
1885
1886[[parachains]]
1887"#
1888 )?;
1889
1890 assert!(matches!(
1891 <&Path as TryInto<NetworkConfiguration>>::try_into(config.path()),
1892 Err(Error::Config(error))
1893 if error == "TOML parse error at line 5, column 1\n |\n5 | [[parachains]]\n | ^^^^^^^^^^^^^^\nmissing field `id`\n"
1894 ));
1895 Ok(())
1896 }
1897
1898 #[test]
1899 fn initializes_relay_from_file() -> Result<(), Error> {
1900 let config = Builder::new().suffix(".toml").tempfile()?;
1901 writeln!(
1902 config.as_file(),
1903 r#"
1904 [relaychain]
1905 chain = "paseo-local"
1906 default_command = "polkadot"
1907 [[relaychain.nodes]]
1908 name = "alice"
1909 "#
1910 )?;
1911 let network_config = NetworkConfiguration::try_from(config.path())?;
1912 let relay_chain = network_config.0.relaychain();
1913 assert_eq!("paseo-local", relay_chain.chain().as_str());
1914 assert_eq!(Some("polkadot"), relay_chain.default_command().map(|c| c.as_str()));
1915 let nodes = relay_chain.nodes();
1916 assert_eq!("alice", nodes.first().unwrap().name());
1917 assert!(network_config.0.parachains().is_empty());
1918 Ok(())
1919 }
1920
1921 #[test]
1922 fn initializes_parachains_from_file() -> Result<(), Error> {
1923 let config = Builder::new().suffix(".toml").tempfile()?;
1924 writeln!(
1925 config.as_file(),
1926 r#"
1927 [relaychain]
1928 chain = "paseo-local"
1929 [[parachains]]
1930 id = 2000
1931 default_command = "node"
1932 "#
1933 )?;
1934 let network_config = NetworkConfiguration::try_from(config.path())?;
1935 let parachains = network_config.0.parachains();
1936 let para_2000 = parachains.first().unwrap();
1937 assert_eq!(2000, para_2000.id());
1938 assert_eq!(Some("node"), para_2000.default_command().map(|c| c.as_str()));
1939 Ok(())
1940 }
1941
1942 #[test]
1943 fn initializing_from_network_config_works() -> Result<(), Error> {
1944 let network_config = NetworkConfigBuilder::new()
1945 .with_relaychain(|b| {
1946 b.with_chain("paseo-local").with_validator(|b| b.with_name("alice"))
1947 })
1948 .build()
1949 .unwrap();
1950 let config = NetworkConfiguration::try_from(network_config.clone()).unwrap();
1951 assert_eq!(config, NetworkConfiguration(network_config, Default::default()));
1952 Ok(())
1953 }
1954
1955 #[test]
1956 fn build_works() -> Result<(), Error> {
1957 let port = 9944;
1958 for relay in [Paseo, Kusama, Polkadot, Westend] {
1959 let mut rollups: Vec<_> = rollups(&relay).to_vec();
1960 rollups
1961 .iter_mut()
1962 .enumerate()
1963 .for_each(|(i, rollup)| rollup.set_port(port + i as u16 + 1));
1964 let relay_chain = relay.chain();
1965
1966 let config =
1967 NetworkConfiguration::build(relay, Some(port), Some(rollups.as_slice()))?;
1968
1969 let relay_config = config.0.relaychain();
1970 assert_eq!(relay_config.chain().as_str(), relay_chain);
1971 assert_eq!(
1975 relay_config.nodes().iter().map(|n| n.name()).collect::<Vec<_>>(),
1976 VALIDATORS.into_iter().take(relay_config.nodes().len()).collect::<Vec<_>>()
1977 );
1978 assert_eq!(relay_config.nodes().first().unwrap().rpc_port().unwrap(), port);
1979
1980 let parachains = config.0.parachains();
1981 assert_eq!(parachains.len(), rollups.len());
1982 for (i, rollup) in rollups.iter().enumerate() {
1983 let parachain = parachains.iter().find(|p| p.id() == rollup.id()).unwrap();
1984 assert_eq!(parachain.chain().unwrap().as_str(), rollup.chain());
1985 assert_eq!(parachain.default_command().unwrap().as_str(), rollup.binary());
1986 println!("{} {}", relay_chain, rollup.name());
1987 assert_eq!(
1988 parachain.genesis_overrides().is_some(),
1989 rollup.genesis_overrides().is_some() ||
1990 rollups.iter().any(|r| r
1991 .requires()
1992 .map(|r| r.contains_key(&rollup.as_any().type_id()))
1993 .unwrap_or_default())
1994 );
1995 let collators = parachain.collators();
1996 assert_eq!(collators.len(), 1);
1997 let collator = collators.first().unwrap();
1998 assert_eq!(collator.name(), &format!("{}-collator", rollup.name()));
1999 assert_eq!(
2000 collator.args().len(),
2001 rollup.args().map(|a| a.len()).unwrap_or_default()
2002 );
2003 assert_eq!(collator.rpc_port(), Some(port + i as u16 + 1));
2004 }
2005
2006 let channels = config.0.hrmp_channels();
2008 assert_eq!(channels.len(), rollups.len() * (rollups.len() - 1));
2009 for rollup in rollups.iter() {
2010 for other in rollups.iter().filter(|r| r.id() != rollup.id()) {
2011 assert!(
2012 channels
2013 .iter()
2014 .any(|c| c.sender() == rollup.id() && c.recipient() == other.id())
2015 );
2016 assert!(
2017 channels
2018 .iter()
2019 .any(|c| c.sender() == other.id() && c.recipient() == rollup.id())
2020 );
2021 }
2022 }
2023 assert!(
2024 channels
2025 .iter()
2026 .all(|c| c.max_capacity() == 1000 && c.max_message_size() == 8000)
2027 );
2028 }
2029 Ok(())
2030 }
2031
2032 #[test]
2033 fn adapt_works() -> Result<(), Error> {
2034 let config = Builder::new().suffix(".toml").tempfile()?;
2035 writeln!(
2036 config.as_file(),
2037 r#"
2038[relaychain]
2039chain = "paseo-local"
2040
2041[[relaychain.nodes]]
2042name = "alice"
2043command = "polkadot"
2044
2045[[relaychain.nodes]]
2046name = "bob"
2047
2048[[parachains]]
2049id = 1000
2050chain = "asset-hub-paseo-local"
2051
2052[[parachains.collators]]
2053name = "asset-hub-1"
2054command = "polkadot-parachain"
2055
2056[[parachains.collators]]
2057name = "asset-hub-2"
2058
2059[[parachains]]
2060id = 2000
2061default_command = "pop-node"
2062
2063[[parachains.collators]]
2064name = "pop"
2065command = "pop-node"
2066
2067[[parachains]]
2068id = 2001
2069default_command = "./target/release/parachain-template-node"
2070
2071[[parachains.collators]]
2072name = "collator-2001"
2073command = "./target/release/parachain-template-node"
2074
2075[[parachains]]
2076id = 2002
2077default_command = "./target/release/parachain-template-node"
2078
2079[parachains.collator]
2080name = "collator-2002"
2081command = "./target/release/parachain-template-node"
2082subcommand = "test"
2083ws_port = 9945
2084rpc_port = 9944
2085"#
2086 )?;
2087 let network_config = NetworkConfiguration::try_from(config.path())?;
2088
2089 let relay_chain_binary = Builder::new().tempfile()?;
2090 let relay_chain = relay_chain_binary.path();
2091 File::create(relay_chain)?;
2092 let system_chain_binary = Builder::new().tempfile()?;
2093 let system_chain = system_chain_binary.path();
2094 File::create(system_chain)?;
2095 let pop_binary = Builder::new().tempfile()?;
2096 let pop = pop_binary.path();
2097 File::create(pop)?;
2098 let parachain_template_node = Builder::new().tempfile()?;
2099 let parachain_template = parachain_template_node.path();
2100 create_dir_all(parachain_template.parent().unwrap())?;
2101 File::create(parachain_template)?;
2102
2103 let adapted = network_config.adapt(
2104 &RelayChain {
2105 runtime: Paseo,
2106 binary: Binary::Local {
2107 name: "polkadot".to_string(),
2108 path: relay_chain.to_path_buf(),
2109 manifest: None,
2110 },
2111 workers: ["polkadot-execute-worker", ""],
2112 chain: "paseo-local".to_string(),
2113 chain_spec_generator: None,
2114 },
2115 &[
2116 (
2117 1000,
2118 Chain {
2119 id: 1000,
2120 binary: Binary::Local {
2121 name: "polkadot-parachain".to_string(),
2122 path: system_chain.to_path_buf(),
2123 manifest: None,
2124 },
2125 chain: None,
2126 chain_spec_generator: None,
2127 },
2128 ),
2129 (
2130 2000,
2131 Chain {
2132 id: 2000,
2133 binary: Binary::Local {
2134 name: "pop-node".to_string(),
2135 path: pop.to_path_buf(),
2136 manifest: None,
2137 },
2138 chain: None,
2139 chain_spec_generator: None,
2140 },
2141 ),
2142 (
2143 2001,
2144 Chain {
2145 id: 2001,
2146 binary: Binary::Local {
2147 name: "parachain-template-node".to_string(),
2148 path: parachain_template.to_path_buf(),
2149 manifest: None,
2150 },
2151 chain: None,
2152 chain_spec_generator: None,
2153 },
2154 ),
2155 (
2156 2002,
2157 Chain {
2158 id: 2002,
2159 binary: Binary::Local {
2160 name: "parachain-template-node".to_string(),
2161 path: parachain_template.to_path_buf(),
2162 manifest: None,
2163 },
2164 chain: None,
2165 chain_spec_generator: None,
2166 },
2167 ),
2168 ]
2169 .into(),
2170 )?;
2171
2172 let contents = adapted.dump_to_toml().unwrap();
2173 assert_eq!(
2174 contents,
2175 format!(
2176 r#"[settings]
2177timeout = 1000
2178node_spawn_timeout = 300
2179tear_down_on_failure = true
2180
2181[relaychain]
2182chain = "paseo-local"
2183default_command = "{0}"
2184
2185[[relaychain.nodes]]
2186name = "alice"
2187validator = true
2188invulnerable = true
2189bootnode = false
2190balance = 2000000000000
2191
2192[[relaychain.nodes]]
2193name = "bob"
2194validator = true
2195invulnerable = true
2196bootnode = false
2197balance = 2000000000000
2198
2199[[parachains]]
2200id = 1000
2201chain = "asset-hub-paseo-local"
2202add_to_genesis = true
2203balance = 2000000000000
2204default_command = "{1}"
2205cumulus_based = true
2206evm_based = false
2207
2208[[parachains.collators]]
2209name = "asset-hub-1"
2210validator = true
2211invulnerable = true
2212bootnode = false
2213balance = 2000000000000
2214
2215[[parachains.collators]]
2216name = "asset-hub-2"
2217validator = true
2218invulnerable = true
2219bootnode = false
2220balance = 2000000000000
2221
2222[[parachains]]
2223id = 2000
2224add_to_genesis = true
2225balance = 2000000000000
2226default_command = "{2}"
2227cumulus_based = true
2228evm_based = false
2229
2230[[parachains.collators]]
2231name = "pop"
2232validator = true
2233invulnerable = true
2234bootnode = false
2235balance = 2000000000000
2236
2237[[parachains]]
2238id = 2001
2239add_to_genesis = true
2240balance = 2000000000000
2241default_command = "{3}"
2242cumulus_based = true
2243evm_based = false
2244
2245[[parachains.collators]]
2246name = "collator-2001"
2247validator = true
2248invulnerable = true
2249bootnode = false
2250balance = 2000000000000
2251
2252[[parachains]]
2253id = 2002
2254add_to_genesis = true
2255balance = 2000000000000
2256default_command = "{3}"
2257cumulus_based = true
2258evm_based = false
2259
2260[[parachains.collators]]
2261name = "collator-2002"
2262subcommand = "test"
2263validator = true
2264invulnerable = true
2265bootnode = false
2266balance = 2000000000000
2267ws_port = 9945
2268rpc_port = 9944
2269"#,
2270 relay_chain.canonicalize()?.to_str().unwrap(),
2271 system_chain.canonicalize()?.to_str().unwrap(),
2272 pop.canonicalize()?.to_str().unwrap(),
2273 parachain_template.canonicalize()?.to_str().unwrap()
2274 )
2275 );
2276 Ok(())
2277 }
2278
2279 #[test]
2280 fn adapt_with_chain_spec_generator_works() -> Result<(), Error> {
2281 let config = Builder::new().suffix(".toml").tempfile()?;
2282 writeln!(
2283 config.as_file(),
2284 r#"
2285[relaychain]
2286chain = "paseo-local"
2287
2288[[relaychain.nodes]]
2289name = "alice"
2290command = "polkadot"
2291
2292[[parachains]]
2293id = 1000
2294chain = "asset-hub-paseo-local"
2295
2296[[parachains.collators]]
2297name = "asset-hub"
2298command = "polkadot-parachain"
2299
2300"#
2301 )?;
2302 let network_config = NetworkConfiguration::try_from(config.path())?;
2303
2304 let relay_chain_binary = Builder::new().tempfile()?;
2305 let relay_chain = relay_chain_binary.path();
2306 File::create(relay_chain)?;
2307 let relay_chain_spec_generator = Builder::new().tempfile()?;
2308 let relay_chain_spec_generator = relay_chain_spec_generator.path();
2309 File::create(relay_chain_spec_generator)?;
2310 let system_chain_binary = Builder::new().tempfile()?;
2311 let system_chain = system_chain_binary.path();
2312 File::create(system_chain)?;
2313 let system_chain_spec_generator = Builder::new().tempfile()?;
2314 let system_chain_spec_generator = system_chain_spec_generator.path();
2315 File::create(system_chain_spec_generator)?;
2316
2317 let adapted = network_config.adapt(
2318 &RelayChain {
2319 runtime: Paseo,
2320 binary: Binary::Local {
2321 name: "polkadot".to_string(),
2322 path: relay_chain.to_path_buf(),
2323 manifest: None,
2324 },
2325 workers: ["polkadot-execute-worker", ""],
2326 chain: "paseo-local".to_string(),
2327 chain_spec_generator: Some(Binary::Local {
2328 name: "paseo-chain-spec-generator".to_string(),
2329 path: relay_chain_spec_generator.to_path_buf(),
2330 manifest: None,
2331 }),
2332 },
2333 &[(
2334 1000,
2335 Chain {
2336 id: 1000,
2337 binary: Binary::Local {
2338 name: "polkadot-parachain".to_string(),
2339 path: system_chain.to_path_buf(),
2340 manifest: None,
2341 },
2342 chain: Some("asset-hub-paseo-local".to_string()),
2343 chain_spec_generator: Some(Binary::Local {
2344 name: "paseo-chain-spec-generator".to_string(),
2345 path: system_chain_spec_generator.to_path_buf(),
2346 manifest: None,
2347 }),
2348 },
2349 )]
2350 .into(),
2351 )?;
2352
2353 let contents = adapted.dump_to_toml().unwrap();
2354 assert_eq!(
2355 contents,
2356 format!(
2357 r#"[settings]
2358timeout = 1000
2359node_spawn_timeout = 300
2360tear_down_on_failure = true
2361
2362[relaychain]
2363chain = "paseo-local"
2364default_command = "{0}"
2365chain_spec_command = "{1} {2}"
2366
2367[[relaychain.nodes]]
2368name = "alice"
2369validator = true
2370invulnerable = true
2371bootnode = false
2372balance = 2000000000000
2373
2374[[parachains]]
2375id = 1000
2376chain = "asset-hub-paseo-local"
2377add_to_genesis = true
2378balance = 2000000000000
2379default_command = "{3}"
2380chain_spec_command = "{4} {2}"
2381cumulus_based = true
2382evm_based = false
2383
2384[[parachains.collators]]
2385name = "asset-hub"
2386validator = true
2387invulnerable = true
2388bootnode = false
2389balance = 2000000000000
2390"#,
2391 relay_chain.canonicalize()?.to_str().unwrap(),
2392 relay_chain_spec_generator.canonicalize()?.to_str().unwrap(),
2393 "{{chainName}}",
2394 system_chain.canonicalize()?.to_str().unwrap(),
2395 system_chain_spec_generator.canonicalize()?.to_str().unwrap(),
2396 )
2397 );
2398 Ok(())
2399 }
2400
2401 #[test]
2402 fn adapt_with_hrmp_channels_works() -> Result<(), Error> {
2403 let config = Builder::new().suffix(".toml").tempfile()?;
2404 writeln!(
2405 config.as_file(),
2406 r#"
2407[relaychain]
2408chain = "paseo-local"
2409
2410[[relaychain.nodes]]
2411name = "alice"
2412
2413[[parachains]]
2414id = 1000
2415chain = "asset-hub-paseo-local"
2416
2417[[parachains.collators]]
2418name = "asset-hub"
2419
2420[[parachains]]
2421id = 2000
2422default_command = "pop-node"
2423
2424[[parachains.collators]]
2425name = "pop"
2426
2427[[hrmp_channels]]
2428sender = 1000
2429recipient = 2000
2430max_capacity = 1000
2431max_message_size = 5000
2432
2433[[hrmp_channels]]
2434sender = 2000
2435recipient = 1000
2436max_capacity = 1000
2437max_message_size = 8000
2438
2439"#
2440 )?;
2441 let network_config = NetworkConfiguration::try_from(config.path())?;
2442
2443 let relay_chain_binary = Builder::new().tempfile()?;
2444 let relay_chain = relay_chain_binary.path();
2445 File::create(relay_chain)?;
2446 let system_chain_binary = Builder::new().tempfile()?;
2447 let system_chain = system_chain_binary.path();
2448 File::create(system_chain)?;
2449 let pop_binary = Builder::new().tempfile()?;
2450 let pop = pop_binary.path();
2451 File::create(pop)?;
2452
2453 let adapted = network_config.adapt(
2454 &RelayChain {
2455 runtime: Paseo,
2456 binary: Binary::Local {
2457 name: "polkadot".to_string(),
2458 path: relay_chain.to_path_buf(),
2459 manifest: None,
2460 },
2461 workers: ["polkadot-execute-worker", ""],
2462 chain: "paseo-local".to_string(),
2463 chain_spec_generator: None,
2464 },
2465 &[
2466 (
2467 1000,
2468 Chain {
2469 id: 1000,
2470 binary: Binary::Local {
2471 name: "polkadot-parachain".to_string(),
2472 path: system_chain.to_path_buf(),
2473 manifest: None,
2474 },
2475 chain: Some("asset-hub-paseo-local".to_string()),
2476 chain_spec_generator: None,
2477 },
2478 ),
2479 (
2480 2000,
2481 Chain {
2482 id: 2000,
2483 binary: Binary::Local {
2484 name: "pop-node".to_string(),
2485 path: pop.to_path_buf(),
2486 manifest: None,
2487 },
2488 chain: None,
2489 chain_spec_generator: None,
2490 },
2491 ),
2492 ]
2493 .into(),
2494 )?;
2495
2496 let contents = adapted.dump_to_toml().unwrap();
2497 assert_eq!(
2498 contents,
2499 format!(
2500 r#"[settings]
2501timeout = 1000
2502node_spawn_timeout = 300
2503tear_down_on_failure = true
2504
2505[relaychain]
2506chain = "paseo-local"
2507default_command = "{0}"
2508
2509[[relaychain.nodes]]
2510name = "alice"
2511validator = true
2512invulnerable = true
2513bootnode = false
2514balance = 2000000000000
2515
2516[[parachains]]
2517id = 1000
2518chain = "asset-hub-paseo-local"
2519add_to_genesis = true
2520balance = 2000000000000
2521default_command = "{1}"
2522cumulus_based = true
2523evm_based = false
2524
2525[[parachains.collators]]
2526name = "asset-hub"
2527validator = true
2528invulnerable = true
2529bootnode = false
2530balance = 2000000000000
2531
2532[[parachains]]
2533id = 2000
2534add_to_genesis = true
2535balance = 2000000000000
2536default_command = "{2}"
2537cumulus_based = true
2538evm_based = false
2539
2540[[parachains.collators]]
2541name = "pop"
2542validator = true
2543invulnerable = true
2544bootnode = false
2545balance = 2000000000000
2546
2547[[hrmp_channels]]
2548sender = 1000
2549recipient = 2000
2550max_capacity = 1000
2551max_message_size = 5000
2552
2553[[hrmp_channels]]
2554sender = 2000
2555recipient = 1000
2556max_capacity = 1000
2557max_message_size = 8000
2558"#,
2559 relay_chain.canonicalize()?.to_str().unwrap(),
2560 system_chain.canonicalize()?.to_str().unwrap(),
2561 pop.canonicalize()?.to_str().unwrap(),
2562 )
2563 );
2564 Ok(())
2565 }
2566
2567 #[test]
2568 fn adapt_with_chain_spec_works() -> Result<(), Error> {
2569 let config = Builder::new().suffix(".toml").tempfile()?;
2570 writeln!(
2571 config.as_file(),
2572 r#"
2573[relaychain]
2574chain = "paseo-local"
2575chain_spec_command = "cmd_template"
2576chain_spec_command_is_local = true
2577chain_spec_path = "./path/to/paseo-local.spec.json"
2578
2579[[relaychain.nodes]]
2580name = "alice"
2581
2582[[parachains]]
2583id = 1000
2584chain = "asset-hub-paseo-local"
2585chain_spec_command = "cmd_template"
2586chain_spec_command_is_local = true
2587chain_spec_path = "./path/to/asset-hub-paseo-local.spec.json"
2588
2589[[parachains.collators]]
2590name = "asset-hub"
2591"#
2592 )?;
2593 let network_config = NetworkConfiguration::try_from(config.path())?;
2594
2595 let relay_chain_binary = Builder::new().tempfile()?;
2596 let relay_chain = relay_chain_binary.path();
2597 File::create(relay_chain)?;
2598 let system_chain_binary = Builder::new().tempfile()?;
2599 let system_chain = system_chain_binary.path();
2600 File::create(system_chain)?;
2601 let pop_binary = Builder::new().tempfile()?;
2602 let pop = pop_binary.path();
2603 File::create(pop)?;
2604
2605 let adapted = network_config.adapt(
2606 &RelayChain {
2607 runtime: Paseo,
2608 binary: Binary::Local {
2609 name: "polkadot".to_string(),
2610 path: relay_chain.to_path_buf(),
2611 manifest: None,
2612 },
2613 workers: ["polkadot-execute-worker", ""],
2614 chain: "paseo-local".to_string(),
2615 chain_spec_generator: None,
2616 },
2617 &[(
2618 1000,
2619 Chain {
2620 id: 1000,
2621 binary: Binary::Local {
2622 name: "polkadot-parachain".to_string(),
2623 path: system_chain.to_path_buf(),
2624 manifest: None,
2625 },
2626 chain: Some("asset-hub-paseo-local".to_string()),
2627 chain_spec_generator: None,
2628 },
2629 )]
2630 .into(),
2631 )?;
2632
2633 let contents = adapted.dump_to_toml().unwrap();
2634 assert_eq!(
2635 contents,
2636 format!(
2637 r#"[settings]
2638timeout = 1000
2639node_spawn_timeout = 300
2640tear_down_on_failure = true
2641
2642[relaychain]
2643chain = "paseo-local"
2644default_command = "{0}"
2645chain_spec_path = "./path/to/paseo-local.spec.json"
2646chain_spec_command = "cmd_template"
2647chain_spec_command_is_local = true
2648
2649[[relaychain.nodes]]
2650name = "alice"
2651validator = true
2652invulnerable = true
2653bootnode = false
2654balance = 2000000000000
2655
2656[[parachains]]
2657id = 1000
2658chain = "asset-hub-paseo-local"
2659add_to_genesis = true
2660balance = 2000000000000
2661default_command = "{1}"
2662chain_spec_path = "./path/to/asset-hub-paseo-local.spec.json"
2663chain_spec_command = "cmd_template"
2664chain_spec_command_is_local = true
2665cumulus_based = true
2666evm_based = false
2667
2668[[parachains.collators]]
2669name = "asset-hub"
2670validator = true
2671invulnerable = true
2672bootnode = false
2673balance = 2000000000000
2674"#,
2675 relay_chain.canonicalize()?.to_str().unwrap(),
2676 system_chain.canonicalize()?.to_str().unwrap(),
2677 )
2678 );
2679 Ok(())
2680 }
2681
2682 #[test]
2683 fn adapt_with_overrides_works() -> Result<(), Error> {
2684 let config = Builder::new().suffix(".toml").tempfile()?;
2685 writeln!(
2686 config.as_file(),
2687 r#"
2688[relaychain]
2689chain = "paseo-local"
2690wasm_override = "./path/to/paseo-local.wasm"
2691
2692[[relaychain.nodes]]
2693name = "alice"
2694
2695[relaychain.genesis.balances]
2696balances = [["5Ec4AhPKXY9B4ayGshkz2wFMh7N8gP7XKfAvtt1cigpG9FkJ", 420000000000]]
2697
2698[[parachains]]
2699id = 1000
2700chain = "asset-hub-paseo-local"
2701wasm_override = "./path/to/asset-hub-paseo-local.wasm"
2702
2703[[parachains.collators]]
2704name = "asset-hub"
2705
2706[parachains.genesis.balances]
2707balances = [["5Ec4AhPKXY9B4ayGshkz2wFMh7N8gP7XKfAvtt1cigpG9FkJ", 420000000000]]
2708
2709"#
2710 )?;
2711 let network_config = NetworkConfiguration::try_from(config.path())?;
2712
2713 let relay_chain_binary = Builder::new().tempfile()?;
2714 let relay_chain = relay_chain_binary.path();
2715 File::create(relay_chain)?;
2716 let system_chain_binary = Builder::new().tempfile()?;
2717 let system_chain = system_chain_binary.path();
2718 File::create(system_chain)?;
2719 let pop_binary = Builder::new().tempfile()?;
2720 let pop = pop_binary.path();
2721 File::create(pop)?;
2722
2723 let adapted = network_config.adapt(
2724 &RelayChain {
2725 runtime: Paseo,
2726 binary: Binary::Local {
2727 name: "polkadot".to_string(),
2728 path: relay_chain.to_path_buf(),
2729 manifest: None,
2730 },
2731 workers: ["polkadot-execute-worker", ""],
2732 chain: "paseo-local".to_string(),
2733 chain_spec_generator: None,
2734 },
2735 &[(
2736 1000,
2737 Chain {
2738 id: 1000,
2739 binary: Binary::Local {
2740 name: "polkadot-parachain".to_string(),
2741 path: system_chain.to_path_buf(),
2742 manifest: None,
2743 },
2744 chain: Some("asset-hub-paseo-local".to_string()),
2745 chain_spec_generator: None,
2746 },
2747 )]
2748 .into(),
2749 )?;
2750
2751 let contents = adapted.dump_to_toml().unwrap();
2752 assert_eq!(
2753 contents,
2754 format!(
2755 r#"[settings]
2756timeout = 1000
2757node_spawn_timeout = 300
2758tear_down_on_failure = true
2759
2760[relaychain]
2761chain = "paseo-local"
2762default_command = "{0}"
2763wasm_override = "./path/to/paseo-local.wasm"
2764
2765[[relaychain.nodes]]
2766name = "alice"
2767validator = true
2768invulnerable = true
2769bootnode = false
2770balance = 2000000000000
2771
2772[relaychain.genesis.balances]
2773balances = [[
2774 "5Ec4AhPKXY9B4ayGshkz2wFMh7N8gP7XKfAvtt1cigpG9FkJ",
2775 {{ "$serde_json::private::Number" = "420000000000" }},
2776]]
2777
2778[[parachains]]
2779id = 1000
2780chain = "asset-hub-paseo-local"
2781add_to_genesis = true
2782balance = 2000000000000
2783default_command = "{1}"
2784wasm_override = "./path/to/asset-hub-paseo-local.wasm"
2785cumulus_based = true
2786evm_based = false
2787
2788[parachains.genesis.balances]
2789balances = [[
2790 "5Ec4AhPKXY9B4ayGshkz2wFMh7N8gP7XKfAvtt1cigpG9FkJ",
2791 {{ "$serde_json::private::Number" = "420000000000" }},
2792]]
2793
2794[[parachains.collators]]
2795name = "asset-hub"
2796validator = true
2797invulnerable = true
2798bootnode = false
2799balance = 2000000000000
2800"#,
2801 relay_chain.canonicalize()?.to_str().unwrap(),
2802 system_chain.canonicalize()?.to_str().unwrap(),
2803 )
2804 );
2805 Ok(())
2806 }
2807
2808 #[test]
2809 fn resolves_path() -> Result<(), Error> {
2810 let working_dir = tempdir()?;
2811 let path = working_dir.path().join("./target/release/node");
2812 assert!(
2813 matches!(NetworkConfiguration::resolve_path(&path), Err(Error::Config(message))
2814 if message == format!("the canonical path of {:?} could not be resolved", path)
2815 )
2816 );
2817
2818 create_dir_all(path.parent().unwrap())?;
2819 File::create(&path)?;
2820 assert_eq!(
2821 NetworkConfiguration::resolve_path(&path)?,
2822 path.canonicalize()?.to_str().unwrap().to_string()
2823 );
2824 Ok(())
2825 }
2826 }
2827
2828 mod parachain {
2829 use super::*;
2830 use pop_common::sourcing::GitHub::SourceCodeArchive;
2831 use std::path::PathBuf;
2832
2833 #[test]
2834 fn initializes_from_local_binary() -> Result<(), Error> {
2835 let name = "parachain-template-node";
2836 let command = PathBuf::from("./target/release").join(name);
2837 assert_eq!(
2838 Chain::from_local(2000, command.clone(), Some("dev"))?,
2839 Chain {
2840 id: 2000,
2841 binary: Binary::Local { name: name.to_string(), path: command, manifest: None },
2842 chain: Some("dev".to_string()),
2843 chain_spec_generator: None,
2844 }
2845 );
2846 Ok(())
2847 }
2848
2849 #[test]
2850 fn initializes_from_local_package() -> Result<(), Error> {
2851 let name = "pop-chains";
2852 let command = PathBuf::from("./target/release").join(name);
2853 assert_eq!(
2854 Chain::from_local(2000, command.clone(), Some("dev"))?,
2855 Chain {
2856 id: 2000,
2857 binary: Binary::Local {
2858 name: name.to_string(),
2859 path: command,
2860 manifest: Some(PathBuf::from("./Cargo.toml"))
2861 },
2862 chain: Some("dev".to_string()),
2863 chain_spec_generator: None,
2864 }
2865 );
2866 Ok(())
2867 }
2868
2869 #[test]
2870 fn initializes_from_git() -> Result<(), Error> {
2871 let repo = Repository::parse("https://git.com/r0gue-io/pop-node#v1.0")?;
2872 let cache = tempdir()?;
2873 assert_eq!(
2874 Chain::from_repository(2000, &repo, Some("dev"), cache.path())?,
2875 Chain {
2876 id: 2000,
2877 binary: Binary::Source {
2878 name: "pop-node".to_string(),
2879 source: Git {
2880 url: repo.url,
2881 reference: repo.reference,
2882 manifest: None,
2883 package: "pop-node".to_string(),
2884 artifacts: vec!["pop-node".to_string()],
2885 }
2886 .into(),
2887 cache: cache.path().to_path_buf(),
2888 },
2889 chain: Some("dev".to_string()),
2890 chain_spec_generator: None,
2891 }
2892 );
2893 Ok(())
2894 }
2895
2896 #[test]
2897 fn initializes_from_github() -> Result<(), Error> {
2898 let repo = Repository::parse("https://github.com/r0gue-io/pop-node#v1.0")?;
2899 let cache = tempdir()?;
2900 assert_eq!(
2901 Chain::from_repository(2000, &repo, Some("dev"), cache.path())?,
2902 Chain {
2903 id: 2000,
2904 binary: Binary::Source {
2905 name: "pop-node".to_string(),
2906 source: Source::GitHub(SourceCodeArchive {
2907 owner: "r0gue-io".to_string(),
2908 repository: "pop-node".to_string(),
2909 reference: Some("v1.0".to_string()),
2910 manifest: None,
2911 package: "pop-node".to_string(),
2912 artifacts: vec!["pop-node".to_string()],
2913 })
2914 .into(),
2915 cache: cache.path().to_path_buf(),
2916 },
2917 chain: Some("dev".to_string()),
2918 chain_spec_generator: None,
2919 },
2920 );
2921 Ok(())
2922 }
2923 }
2924
2925 #[test]
2926 fn resolve_manifest_works() -> Result<()> {
2927 let current_dir = current_dir()?;
2928 assert_eq!(
2930 current_dir.join("Cargo.toml"),
2931 resolve_manifest("pop-chains", ¤t_dir)?.unwrap()
2932 );
2933 assert_eq!(
2935 current_dir.join("../../Cargo.toml").canonicalize()?,
2936 resolve_manifest("pop-cli", ¤t_dir)?.unwrap()
2937 );
2938 Ok(())
2939 }
2940}