Skip to main content

pop_chains/up/
chain_specs.rs

1// SPDX-License-Identifier: GPL-3.0
2
3use crate::{Error, registry::traits};
4use pop_common::{
5	git::GitHub,
6	polkadot_sdk::sort_by_latest_semantic_version,
7	sourcing::{
8		ArchiveFileSpec, Binary,
9		GitHub::*,
10		Source,
11		filters::prefix,
12		traits::{
13			Source as SourceT,
14			enums::{Source as _, *},
15		},
16	},
17	target,
18};
19use std::path::Path;
20use strum::{EnumProperty as _, VariantArray as _};
21use strum_macros::{AsRefStr, EnumProperty, VariantArray};
22
23/// A supported runtime.
24#[repr(u8)]
25#[derive(AsRefStr, Clone, Debug, EnumProperty, Eq, Hash, PartialEq, VariantArray)]
26pub enum Runtime {
27	/// Kusama.
28	#[strum(props(
29		Repository = "https://github.com/r0gue-io/polkadot-runtimes",
30		Binary = "chain-spec-generator",
31		Chain = "kusama-local",
32		Fallback = "v1.4.1"
33	))]
34	Kusama = 0,
35	/// Paseo.
36	#[strum(props(
37		Repository = "https://github.com/r0gue-io/paseo-runtimes",
38		Binary = "chain-spec-generator",
39		Chain = "paseo-local",
40		Fallback = "v1.4.1"
41	))]
42	Paseo = 1,
43	/// Polkadot.
44	#[strum(props(
45		Repository = "https://github.com/r0gue-io/polkadot-runtimes",
46		Binary = "chain-spec-generator",
47		Chain = "polkadot-local",
48		Fallback = "v1.4.1"
49	))]
50	Polkadot = 2,
51	/// Westend.
52	#[strum(props(Repository = "https://github.com/r0gue-io/polkadot", Chain = "westend-local",))]
53	Westend = 3,
54}
55
56impl SourceT for Runtime {
57	type Error = Error;
58	/// Defines the source of the binary required for generating chain specifications.
59	fn source(&self) -> Result<Source, Error> {
60		// Source from GitHub release asset
61		let repo = GitHub::parse(self.repository())?;
62		let name = self.name().to_lowercase();
63		let binary = self.binary();
64		Ok(Source::GitHub(ReleaseArchive {
65			owner: repo.org,
66			repository: repo.name,
67			tag: None,
68			tag_pattern: self.tag_pattern().map(|t| t.into()),
69			prerelease: false,
70			version_comparator: sort_by_latest_semantic_version,
71			fallback: self.fallback().into(),
72			archive: format!("{binary}-{}.tar.gz", target()?),
73			contents: vec![ArchiveFileSpec::new(
74				binary.into(),
75				Some(format!("{name}-{binary}").into()),
76				true,
77			)],
78			latest: None,
79		}))
80	}
81}
82
83impl Runtime {
84	/// Converts an underlying discriminator value to a relay chain [Runtime].
85	///
86	/// # Arguments
87	/// * `value` - The discriminator value to be converted.
88	pub fn from(value: u8) -> Option<Self> {
89		match value {
90			0 => Some(Self::Kusama),
91			1 => Some(Self::Paseo),
92			2 => Some(Self::Polkadot),
93			3 => Some(Self::Westend),
94			_ => None,
95		}
96	}
97
98	/// Parses a [Runtime] from its chain identifier.
99	///
100	/// # Arguments
101	/// * `chain` - The chain identifier.
102	pub fn from_chain(chain: &str) -> Option<Self> {
103		Runtime::VARIANTS
104			.iter()
105			.find(|r| chain.to_lowercase().ends_with(r.chain()))
106			.cloned()
107	}
108
109	/// The chain spec identifier.
110	pub(crate) fn chain(&self) -> &'static str {
111		self.get_str("Chain").expect("expected specification of `Chain`")
112	}
113
114	/// The name of the runtime.
115	pub fn name(&self) -> &str {
116		self.as_ref()
117	}
118
119	/// Returns the chains registered for the relay chain.
120	pub fn chains(&self) -> &'static [Box<dyn traits::Chain>] {
121		crate::registry::chains(self)
122	}
123}
124
125pub(super) async fn chain_spec_generator(
126	chain: &str,
127	version: Option<&str>,
128	cache: &Path,
129) -> Result<Option<Binary>, Error> {
130	if let Some(runtime) = Runtime::from_chain(chain) {
131		if runtime == Runtime::Westend {
132			// Westend runtimes included with binary.
133			return Ok(None);
134		}
135		let name = format!("{}-{}", runtime.name().to_lowercase(), runtime.binary());
136		let source = runtime
137			.source()?
138			.resolve(&name, version, cache, |f| prefix(f, &name))
139			.await
140			.into();
141		let binary = Binary::Source { name, source, cache: cache.to_path_buf() };
142		return Ok(Some(binary));
143	}
144	Ok(None)
145}
146
147#[cfg(test)]
148mod tests {
149	use super::*;
150	use Runtime::*;
151	use tempfile::tempdir;
152
153	#[tokio::test]
154	async fn kusama_works() -> anyhow::Result<()> {
155		let expected = Runtime::Kusama;
156		let version = "v1.4.1";
157		let temp_dir = tempdir()?;
158		let binary = chain_spec_generator("kusama-local", Some(version), temp_dir.path())
159			.await?
160			.unwrap();
161		assert!(matches!(binary, Binary::Source { name, source, cache }
162			if name == format!("{}-{}", expected.name().to_lowercase(), expected.binary()) &&
163				source == Source::GitHub(ReleaseArchive {
164					owner: "r0gue-io".to_string(),
165					repository: "polkadot-runtimes".to_string(),
166					tag: Some(version.to_string()),
167					tag_pattern: None,
168					prerelease: false,
169					version_comparator: sort_by_latest_semantic_version,
170					fallback: expected.fallback().to_string(),
171					archive: format!("chain-spec-generator-{}.tar.gz", target()?),
172					contents: ["chain-spec-generator"].map(|b| ArchiveFileSpec::new(b.into(), Some(format!("kusama-{b}").into()), true)).to_vec(),
173					latest: binary.latest().map(|l| l.to_string()),
174				}).into() &&
175				cache == temp_dir.path()
176		));
177		Ok(())
178	}
179
180	#[tokio::test]
181	async fn paseo_works() -> anyhow::Result<()> {
182		let expected = Runtime::Paseo;
183		let version = "v1.4.1";
184		let temp_dir = tempdir()?;
185		let binary = chain_spec_generator("paseo-local", Some(version), temp_dir.path())
186			.await?
187			.unwrap();
188		assert!(matches!(binary, Binary::Source { name, source, cache }
189			if name == format!("{}-{}", expected.name().to_lowercase(), expected.binary()) &&
190				source == Source::GitHub(ReleaseArchive {
191					owner: "r0gue-io".to_string(),
192					repository: "paseo-runtimes".to_string(),
193					tag: Some(version.to_string()),
194					tag_pattern: None,
195					prerelease: false,
196					version_comparator: sort_by_latest_semantic_version,
197					fallback: expected.fallback().to_string(),
198					archive: format!("chain-spec-generator-{}.tar.gz", target()?),
199					contents: ["chain-spec-generator"].map(|b| ArchiveFileSpec::new(b.into(), Some(format!("paseo-{b}").into()), true)).to_vec(),
200					latest: binary.latest().map(|l| l.to_string()),
201				}).into() &&
202				cache == temp_dir.path()
203		));
204		Ok(())
205	}
206
207	#[tokio::test]
208	async fn polkadot_works() -> anyhow::Result<()> {
209		let expected = Runtime::Polkadot;
210		let version = "v1.4.1";
211		let temp_dir = tempdir()?;
212		let binary = chain_spec_generator("polkadot-local", Some(version), temp_dir.path())
213			.await?
214			.unwrap();
215		assert!(matches!(binary, Binary::Source { name, source, cache }
216			if name == format!("{}-{}", expected.name().to_lowercase(), expected.binary()) &&
217				source == Source::GitHub(ReleaseArchive {
218					owner: "r0gue-io".to_string(),
219					repository: "polkadot-runtimes".to_string(),
220					tag: Some(version.to_string()),
221					tag_pattern: None,
222					prerelease: false,
223					version_comparator: sort_by_latest_semantic_version,
224					fallback: expected.fallback().to_string(),
225					archive: format!("chain-spec-generator-{}.tar.gz", target()?),
226					contents: ["chain-spec-generator"].map(|b| ArchiveFileSpec::new(b.into(), Some(format!("polkadot-{b}").into()), true)).to_vec(),
227					latest: binary.latest().map(|l| l.to_string()),
228				}).into() &&
229				cache == temp_dir.path()
230		));
231		Ok(())
232	}
233
234	#[tokio::test]
235	async fn chain_spec_generator_returns_none_when_no_match() -> anyhow::Result<()> {
236		let temp_dir = tempdir()?;
237		assert_eq!(chain_spec_generator("rococo-local", None, temp_dir.path()).await?, None);
238		Ok(())
239	}
240
241	#[tokio::test]
242	async fn chain_spec_generator_returns_none_when_westend() -> anyhow::Result<()> {
243		let temp_dir = tempdir()?;
244		assert_eq!(chain_spec_generator("westend-local", None, temp_dir.path()).await?, None);
245		Ok(())
246	}
247
248	#[test]
249	fn from_u8_works() {
250		for i in 0u8..4 {
251			assert_eq!(Runtime::from(i).unwrap() as u8, i);
252		}
253		assert_eq!(Runtime::from(4), None);
254	}
255
256	#[test]
257	fn from_chain_works() {
258		for (chain, expected) in [
259			("kusama-local", Kusama),
260			("paseo-local", Paseo),
261			("polkadot-local", Polkadot),
262			("westend-local", Westend),
263		] {
264			assert_eq!(Runtime::from_chain(chain).unwrap(), expected);
265		}
266		assert_eq!(Runtime::from_chain("pop"), None);
267	}
268
269	#[test]
270	fn chains_works() {
271		let comparator = |chains: &[Box<dyn traits::Chain>]| {
272			chains
273				.iter()
274				.map(|r| (r.id(), r.chain().to_string(), r.name().to_string()))
275				.collect::<Vec<_>>()
276		};
277		{};
278		for runtime in Runtime::VARIANTS {
279			assert_eq!(comparator(runtime.chains()), comparator(crate::registry::chains(runtime)));
280		}
281	}
282}