zombienet_orchestrator/network/
parachain.rs1use std::{
2 path::{Path, PathBuf},
3 str::FromStr,
4};
5
6use anyhow::anyhow;
7use async_trait::async_trait;
8use provider::types::TransferedFile;
9use serde::Serialize;
10use subxt::{dynamic::Value, tx::TxStatus, OnlineClient, SubstrateConfig};
11use subxt_signer::{sr25519::Keypair, SecretUri};
12use support::{constants::THIS_IS_A_BUG, fs::FileSystem, net::wait_ws_ready};
13use tracing::info;
14
15use super::{chain_upgrade::ChainUpgrade, node::NetworkNode};
16use crate::{
17 network_spec::parachain::ParachainSpec,
18 shared::types::{RegisterParachainOptions, RuntimeUpgradeOptions},
19 tx_helper::client::get_client_from_url,
20 ScopedFilesystem,
21};
22
23#[derive(Debug, Serialize)]
24pub struct Parachain {
25 pub(crate) chain: Option<String>,
26 pub(crate) para_id: u32,
27 pub(crate) unique_id: String,
30 pub(crate) chain_id: Option<String>,
31 pub(crate) chain_spec_path: Option<PathBuf>,
32 pub(crate) collators: Vec<NetworkNode>,
33 pub(crate) files_to_inject: Vec<TransferedFile>,
34 pub(crate) bootnodes_addresses: Vec<multiaddr::Multiaddr>,
35}
36
37#[async_trait]
38impl ChainUpgrade for Parachain {
39 async fn runtime_upgrade(&self, options: RuntimeUpgradeOptions) -> Result<(), anyhow::Error> {
40 let node = if let Some(node_name) = &options.node_name {
42 if let Some(node) = self
43 .collators()
44 .into_iter()
45 .find(|node| node.name() == node_name)
46 {
47 node
48 } else {
49 return Err(anyhow!("Node: {node_name} is not part of the set of nodes"));
50 }
51 } else {
52 if let Some(node) = self.collators().first() {
54 node
55 } else {
56 return Err(anyhow!("chain doesn't have any node!"));
57 }
58 };
59
60 self.perform_runtime_upgrade(node, options).await
61 }
62}
63
64impl Parachain {
65 pub(crate) fn new(para_id: u32, unique_id: impl Into<String>) -> Self {
66 Self {
67 chain: None,
68 para_id,
69 unique_id: unique_id.into(),
70 chain_id: None,
71 chain_spec_path: None,
72 collators: Default::default(),
73 files_to_inject: Default::default(),
74 bootnodes_addresses: vec![],
75 }
76 }
77
78 pub(crate) fn with_chain_spec(
79 para_id: u32,
80 unique_id: impl Into<String>,
81 chain_id: impl Into<String>,
82 chain_spec_path: impl AsRef<Path>,
83 ) -> Self {
84 Self {
85 para_id,
86 unique_id: unique_id.into(),
87 chain: None,
88 chain_id: Some(chain_id.into()),
89 chain_spec_path: Some(chain_spec_path.as_ref().into()),
90 collators: Default::default(),
91 files_to_inject: Default::default(),
92 bootnodes_addresses: vec![],
93 }
94 }
95
96 pub(crate) async fn from_spec(
97 para: &ParachainSpec,
98 files_to_inject: &[TransferedFile],
99 scoped_fs: &ScopedFilesystem<'_, impl FileSystem>,
100 ) -> Result<Self, anyhow::Error> {
101 let mut para_files_to_inject = files_to_inject.to_owned();
102
103 let mut parachain = if let Some(chain_spec) = para.chain_spec.as_ref() {
105 let id = chain_spec.read_chain_id(scoped_fs).await?;
106
107 let spec_name = chain_spec.chain_spec_name();
109 let base = PathBuf::from_str(scoped_fs.base_dir)?;
110 para_files_to_inject.push(TransferedFile::new(
111 base.join(format!("{spec_name}.json")),
112 PathBuf::from(format!("/cfg/{}.json", para.id)),
113 ));
114
115 let raw_path = chain_spec
116 .raw_path()
117 .ok_or(anyhow::anyhow!("chain-spec path should be set by now.",))?;
118 let mut running_para =
119 Parachain::with_chain_spec(para.id, ¶.unique_id, id, raw_path);
120 if let Some(chain_name) = chain_spec.chain_name() {
121 running_para.chain = Some(chain_name.to_string());
122 }
123
124 running_para
125 } else {
126 Parachain::new(para.id, ¶.unique_id)
127 };
128
129 parachain.bootnodes_addresses = para.bootnodes_addresses().into_iter().cloned().collect();
130 parachain.files_to_inject = para_files_to_inject;
131
132 Ok(parachain)
133 }
134
135 pub async fn register(
136 options: RegisterParachainOptions,
137 scoped_fs: &ScopedFilesystem<'_, impl FileSystem>,
138 ) -> Result<(), anyhow::Error> {
139 info!("Registering parachain: {:?}", options);
140 let sudo: Keypair;
142 if let Some(possible_seed) = options.seed {
143 sudo = Keypair::from_secret_key(possible_seed)
144 .expect(&format!("seed should return a Keypair {THIS_IS_A_BUG}"));
145 } else {
146 let uri = SecretUri::from_str("//Alice")?;
147 sudo = Keypair::from_uri(&uri)?;
148 }
149
150 let genesis_state = scoped_fs
151 .read_to_string(options.state_path)
152 .await
153 .expect(&format!(
154 "State Path should be ok by this point {THIS_IS_A_BUG}"
155 ));
156 let wasm_data = scoped_fs
157 .read_to_string(options.wasm_path)
158 .await
159 .expect(&format!(
160 "Wasm Path should be ok by this point {THIS_IS_A_BUG}"
161 ));
162
163 wait_ws_ready(options.node_ws_url.as_str())
164 .await
165 .map_err(|_| {
166 anyhow::anyhow!(
167 "Error waiting for ws to be ready, at {}",
168 options.node_ws_url.as_str()
169 )
170 })?;
171
172 let api: OnlineClient<SubstrateConfig> = get_client_from_url(&options.node_ws_url).await?;
173
174 let schedule_para = subxt::dynamic::tx(
175 "ParasSudoWrapper",
176 "sudo_schedule_para_initialize",
177 vec![
178 Value::primitive(options.id.into()),
179 Value::named_composite([
180 (
181 "genesis_head",
182 Value::from_bytes(hex::decode(&genesis_state[2..])?),
183 ),
184 (
185 "validation_code",
186 Value::from_bytes(hex::decode(&wasm_data[2..])?),
187 ),
188 ("para_kind", Value::bool(options.onboard_as_para)),
189 ]),
190 ],
191 );
192
193 let sudo_call = subxt::dynamic::tx("Sudo", "sudo", vec![schedule_para.into_value()]);
194
195 let mut tx = api
198 .tx()
199 .sign_and_submit_then_watch_default(&sudo_call, &sudo)
200 .await?;
201
202 while let Some(status) = tx.next().await {
205 match status? {
206 TxStatus::InBestBlock(tx_in_block) | TxStatus::InFinalizedBlock(tx_in_block) => {
207 let _result = tx_in_block.wait_for_success().await?;
208 info!("In block: {:#?}", tx_in_block.block_hash());
209 },
210 TxStatus::Error { message }
211 | TxStatus::Invalid { message }
212 | TxStatus::Dropped { message } => {
213 return Err(anyhow::format_err!("Error submitting tx: {message}"));
214 },
215 _ => continue,
216 }
217 }
218
219 Ok(())
220 }
221
222 pub fn para_id(&self) -> u32 {
223 self.para_id
224 }
225
226 pub fn unique_id(&self) -> &str {
227 self.unique_id.as_str()
228 }
229
230 pub fn chain_id(&self) -> Option<&str> {
231 self.chain_id.as_deref()
232 }
233
234 pub fn collators(&self) -> Vec<&NetworkNode> {
235 self.collators.iter().collect()
236 }
237
238 pub fn bootnodes_addresses(&self) -> Vec<&multiaddr::Multiaddr> {
239 self.bootnodes_addresses.iter().collect()
240 }
241}
242
243#[cfg(test)]
244mod tests {
245 use std::collections::HashMap;
246
247 use super::*;
248
249 #[test]
250 fn create_with_is_works() {
251 let para = Parachain::new(100, "100");
252 assert_eq!(para.para_id, 100);
254 assert_eq!(para.unique_id, "100");
255 assert_eq!(para.chain_id, None);
256 assert_eq!(para.chain, None);
257 assert_eq!(para.chain_spec_path, None);
258 }
259
260 #[test]
261 fn create_with_chain_spec_works() {
262 let para = Parachain::with_chain_spec(100, "100", "rococo-local", "/tmp/rococo-local.json");
263 assert_eq!(para.para_id, 100);
264 assert_eq!(para.unique_id, "100");
265 assert_eq!(para.chain_id, Some("rococo-local".to_string()));
266 assert_eq!(para.chain, None);
267 assert_eq!(
268 para.chain_spec_path,
269 Some(PathBuf::from("/tmp/rococo-local.json"))
270 );
271 }
272
273 #[tokio::test]
274 async fn create_with_para_spec_works() {
275 use configuration::ParachainConfigBuilder;
276
277 use crate::network_spec::parachain::ParachainSpec;
278
279 let bootnode_addresses = vec!["/ip4/10.41.122.55/tcp/45421"];
280
281 let para_config = ParachainConfigBuilder::new(Default::default())
282 .with_id(100)
283 .cumulus_based(false)
284 .with_default_command("adder-collator")
285 .with_raw_bootnodes_addresses(bootnode_addresses.clone())
286 .with_collator(|c| c.with_name("col"))
287 .build()
288 .unwrap();
289
290 let para_spec =
291 ParachainSpec::from_config(¶_config, "rococo-local".try_into().unwrap()).unwrap();
292 let fs = support::fs::in_memory::InMemoryFileSystem::new(HashMap::default());
293 let scoped_fs = ScopedFilesystem {
294 fs: &fs,
295 base_dir: "/tmp/some",
296 };
297
298 let files = vec![TransferedFile::new(
299 PathBuf::from("/tmp/some"),
300 PathBuf::from("/tmp/some"),
301 )];
302 let para = Parachain::from_spec(¶_spec, &files, &scoped_fs)
303 .await
304 .unwrap();
305 println!("{para:#?}");
306 assert_eq!(para.para_id, 100);
307 assert_eq!(para.unique_id, "100");
308 assert_eq!(para.chain_id, None);
309 assert_eq!(para.chain, None);
310 assert_eq!(para.files_to_inject.len(), 1);
312 assert_eq!(
313 para.bootnodes_addresses()
314 .iter()
315 .map(|addr| addr.to_string())
316 .collect::<Vec<_>>(),
317 bootnode_addresses
318 );
319 }
320}