From 9778eff5b3036c3d3094847e5632e825375a7724 Mon Sep 17 00:00:00 2001 From: Javier Viola Date: Tue, 28 Nov 2023 14:56:43 -0300 Subject: [PATCH 1/5] refactor to allow to add parachains to a running network --- crates/configuration/src/lib.rs | 4 +- crates/configuration/src/network.rs | 7 +- crates/configuration/src/parachain.rs | 105 +++++++++++----- crates/configuration/src/shared/node.rs | 10 +- .../orchestrator/src/generators/chain_spec.rs | 37 +++--- crates/orchestrator/src/lib.rs | 77 +++--------- crates/orchestrator/src/network.rs | 103 +++++++++++++++- crates/orchestrator/src/network/parachain.rs | 112 +++++++++++++++++- .../src/network_spec/parachain.rs | 36 ++++++ 9 files changed, 379 insertions(+), 112 deletions(-) diff --git a/crates/configuration/src/lib.rs b/crates/configuration/src/lib.rs index fa434f988..8e71fff61 100644 --- a/crates/configuration/src/lib.rs +++ b/crates/configuration/src/lib.rs @@ -10,7 +10,9 @@ mod utils; pub use global_settings::{GlobalSettings, GlobalSettingsBuilder}; pub use hrmp_channel::{HrmpChannelConfig, HrmpChannelConfigBuilder}; pub use network::{NetworkConfig, NetworkConfigBuilder}; -pub use parachain::{ParachainConfig, ParachainConfigBuilder, RegistrationStrategy}; +pub use parachain::{ + states as para_states, ParachainConfig, ParachainConfigBuilder, RegistrationStrategy, +}; pub use relaychain::{RelaychainConfig, RelaychainConfigBuilder}; // re-export shared pub use shared::{node::NodeConfig, types}; diff --git a/crates/configuration/src/network.rs b/crates/configuration/src/network.rs index 31bc43307..21f2d069d 100644 --- a/crates/configuration/src/network.rs +++ b/crates/configuration/src/network.rs @@ -373,8 +373,11 @@ impl NetworkConfigBuilder { pub fn with_parachain( self, f: fn( - ParachainConfigBuilder, - ) -> ParachainConfigBuilder, + ParachainConfigBuilder, + ) -> ParachainConfigBuilder< + parachain::states::WithAtLeastOneCollator, + parachain::states::Bootstrap, + >, ) -> Self { match f(ParachainConfigBuilder::new(self.validation_context.clone())).build() { Ok(parachain) => Self::transition( diff --git a/crates/configuration/src/parachain.rs b/crates/configuration/src/parachain.rs index 5f3aa0837..e09752da7 100644 --- a/crates/configuration/src/parachain.rs +++ b/crates/configuration/src/parachain.rs @@ -11,7 +11,6 @@ use crate::{ shared::{ errors::{ConfigError, FieldError}, helpers::{merge_errors, merge_errors_vecs}, - macros::states, node::{self, NodeConfig, NodeConfigBuilder}, resources::{Resources, ResourcesBuilder}, types::{ @@ -224,21 +223,36 @@ impl ParachainConfig { } } -states! { - Initial, - WithId, - WithAtLeastOneCollator +pub mod states { + use crate::shared::macros::states; + + states! { + Initial, + WithId, + WithAtLeastOneCollator + } + + states! { + Bootstrap, + Running + } + + pub trait Context {} + impl Context for Bootstrap {} + impl Context for Running {} } +use states::{Bootstrap, Context, Initial, Running, WithAtLeastOneCollator, WithId}; /// A parachain configuration builder, used to build a [`ParachainConfig`] declaratively with fields validation. -pub struct ParachainConfigBuilder { +pub struct ParachainConfigBuilder { config: ParachainConfig, validation_context: Rc>, errors: Vec, _state: PhantomData, + _context: PhantomData, } -impl Default for ParachainConfigBuilder { +impl Default for ParachainConfigBuilder { fn default() -> Self { Self { config: ParachainConfig { @@ -265,21 +279,23 @@ impl Default for ParachainConfigBuilder { validation_context: Default::default(), errors: vec![], _state: PhantomData, + _context: PhantomData, } } } -impl ParachainConfigBuilder { +impl ParachainConfigBuilder { fn transition( config: ParachainConfig, validation_context: Rc>, errors: Vec, - ) -> ParachainConfigBuilder { + ) -> ParachainConfigBuilder { ParachainConfigBuilder { config, validation_context, errors, _state: PhantomData, + _context: PhantomData, } } @@ -294,19 +310,51 @@ impl ParachainConfigBuilder { } } -impl ParachainConfigBuilder { +impl ParachainConfigBuilder { pub fn new( validation_context: Rc>, - ) -> ParachainConfigBuilder { + ) -> ParachainConfigBuilder { Self { validation_context, ..Self::default() } } +} +impl ParachainConfigBuilder { + /// Set the registration strategy for the parachain, could be without registration, using extrinsic or in genesis. + pub fn with_registration_strategy(self, strategy: RegistrationStrategy) -> Self { + Self::transition( + ParachainConfig { + registration_strategy: Some(strategy), + ..self.config + }, + self.validation_context, + self.errors, + ) + } +} + +impl ParachainConfigBuilder { + /// Start a new builder in the context of a running network + pub fn new_with_running( + validation_context: Rc>, + ) -> ParachainConfigBuilder { + let mut builder = Self { + validation_context, + ..Self::default() + }; + + // override the registration strategy + builder.config.registration_strategy = Some(RegistrationStrategy::UsingExtrinsic); + builder + } +} + +impl ParachainConfigBuilder { /// Set the parachain ID (should be unique). // TODO: handle unique validation - pub fn with_id(self, id: u32) -> ParachainConfigBuilder { + pub fn with_id(self, id: u32) -> ParachainConfigBuilder { Self::transition( ParachainConfig { id, ..self.config }, self.validation_context, @@ -315,7 +363,7 @@ impl ParachainConfigBuilder { } } -impl ParachainConfigBuilder { +impl ParachainConfigBuilder { /// Set the chain name (e.g. rococo-local). /// Use [`None`], if you are running adder-collator or undying-collator). pub fn with_chain(self, chain: T) -> Self @@ -340,18 +388,6 @@ impl ParachainConfigBuilder { } } - /// Set the registration strategy for the parachain, could be without registration, using extrinsic or in genesis. - pub fn with_registration_strategy(self, strategy: RegistrationStrategy) -> Self { - Self::transition( - ParachainConfig { - registration_strategy: Some(strategy), - ..self.config - }, - self.validation_context, - self.errors, - ) - } - /// Set whether the parachain should be onboarded or stay a parathread. Default is ```true```. pub fn onboard_as_parachain(self, choice: bool) -> Self { Self::transition( @@ -615,7 +651,7 @@ impl ParachainConfigBuilder { pub fn with_collator( self, f: fn(NodeConfigBuilder) -> NodeConfigBuilder, - ) -> ParachainConfigBuilder { + ) -> ParachainConfigBuilder { match f(NodeConfigBuilder::new( self.default_chain_context(), self.validation_context.clone(), @@ -645,7 +681,7 @@ impl ParachainConfigBuilder { } } -impl ParachainConfigBuilder { +impl ParachainConfigBuilder { /// Add a new collator using a nested [`NodeConfigBuilder`]. pub fn with_collator( self, @@ -1122,4 +1158,19 @@ mod tests { assert!(config.onboard_as_parachain()); } + + #[test] + fn build_config_in_running_context() { + let config = ParachainConfigBuilder::new_with_running(Default::default()) + .with_id(2000) + .with_chain("myparachain") + .with_collator(|collator| collator.with_name("collator")) + .build() + .unwrap(); + + assert_eq!( + config.registration_strategy(), + Some(&RegistrationStrategy::UsingExtrinsic) + ); + } } diff --git a/crates/configuration/src/shared/node.rs b/crates/configuration/src/shared/node.rs index 7720b911f..e8b0ff350 100644 --- a/crates/configuration/src/shared/node.rs +++ b/crates/configuration/src/shared/node.rs @@ -18,6 +18,11 @@ use crate::{ utils::{default_as_true, default_initial_balance}, }; +states! { + Buildable, + Initial +} + /// An environment variable with a name and a value. /// It can be constructed from a `(&str, &str)`. /// @@ -245,11 +250,6 @@ impl NodeConfig { } } -states! { - Initial, - Buildable -} - /// A node configuration builder, used to build a [`NodeConfig`] declaratively with fields validation. pub struct NodeConfigBuilder { config: NodeConfig, diff --git a/crates/orchestrator/src/generators/chain_spec.rs b/crates/orchestrator/src/generators/chain_spec.rs index b363793e3..37f572d07 100644 --- a/crates/orchestrator/src/generators/chain_spec.rs +++ b/crates/orchestrator/src/generators/chain_spec.rs @@ -213,22 +213,7 @@ impl ChainSpec { T: FileSystem, { let (content, _) = self.read_spec(scoped_fs).await?; - let chain_spec_json: serde_json::Value = serde_json::from_str(&content).map_err(|_| { - GeneratorError::ChainSpecGeneration("Can not parse chain-spec as json".into()) - })?; - if let Some(chain_id) = chain_spec_json.get("id") { - if let Some(chain_id) = chain_id.as_str() { - Ok(chain_id.to_string()) - } else { - Err(GeneratorError::ChainSpecGeneration( - "id should be an string in the chain-spec, this is a bug".into(), - )) - } - } else { - Err(GeneratorError::ChainSpecGeneration( - "'id' should be a fields in the chain-spec of the relaychain".into(), - )) - } + ChainSpec::chain_id_from_spec(&content) } async fn read_spec<'a, T>( @@ -511,6 +496,26 @@ impl ChainSpec { Ok(()) } + + pub fn chain_id_from_spec(spec_content: &str) -> Result { + let chain_spec_json: serde_json::Value = + serde_json::from_str(spec_content).map_err(|_| { + GeneratorError::ChainSpecGeneration("Can not parse chain-spec as json".into()) + })?; + if let Some(chain_id) = chain_spec_json.get("id") { + if let Some(chain_id) = chain_id.as_str() { + Ok(chain_id.to_string()) + } else { + Err(GeneratorError::ChainSpecGeneration( + "id should be an string in the chain-spec, this is a bug".into(), + )) + } + } else { + Err(GeneratorError::ChainSpecGeneration( + "'id' should be a fields in the chain-spec of the relaychain".into(), + )) + } + } } type GenesisNodeKey = (String, String, HashMap); diff --git a/crates/orchestrator/src/lib.rs b/crates/orchestrator/src/lib.rs index 26f748f27..96abdae0e 100644 --- a/crates/orchestrator/src/lib.rs +++ b/crates/orchestrator/src/lib.rs @@ -98,33 +98,16 @@ where let relay_chain_name = network_spec.relaychain.chain.as_str(); // TODO: if we don't need to register this para we can skip it for para in network_spec.parachains.iter_mut() { - let para_cloned = para.clone(); - let chain_spec_raw_path = if let Some(chain_spec) = para.chain_spec.as_mut() { - chain_spec.build(&ns, &scoped_fs).await?; - debug!("chain_spec: {:#?}", chain_spec); - - chain_spec - .customize_para(¶_cloned, &relay_chain_id, &scoped_fs) - .await?; - chain_spec.build_raw(&ns).await?; - - let chain_spec_raw_path = - chain_spec - .raw_path() - .ok_or(OrchestratorError::InvariantError( - "chain-spec raw path should be set now", - ))?; - Some(chain_spec_raw_path) - } else { - None - }; + let chain_spec_raw_path = para + .build_chain_spec(&relay_chain_id, &ns, &scoped_fs) + .await?; // TODO: this need to be abstracted in a single call to generate_files. scoped_fs.create_dir(para.id.to_string()).await?; // create wasm/state para.genesis_state .build( - chain_spec_raw_path, + chain_spec_raw_path.clone(), format!("{}/genesis-state", para.id), &ns, &scoped_fs, @@ -279,38 +262,11 @@ where // spawn paras for para in network_spec.parachains.iter() { - // global files to include for this parachain - let mut para_files_to_inject = global_files_to_inject.clone(); - - // parachain id is used for the keystore - let parachain_id = if let Some(chain_spec) = para.chain_spec.as_ref() { - let id = chain_spec.read_chain_id(&scoped_fs).await?; - - // add the spec to global files to inject - let spec_name = chain_spec.chain_spec_name(); - para_files_to_inject.push(TransferedFile { - local_path: ns.base_dir().join(format!("{}.json", spec_name)), - remote_path: PathBuf::from(format!("/cfg/{}.json", para.id)), - }); - - let raw_path = chain_spec - .raw_path() - .ok_or(OrchestratorError::InvariantError( - "chain-spec path should be set by now.", - ))?; - let mut running_para = Parachain::with_chain_spec(para.id, &id, raw_path); - if let Some(chain_name) = chain_spec.chain_name() { - running_para.chain = Some(chain_name.to_string()); - } - network.add_para(running_para); - - Some(id) - } else { - network.add_para(Parachain::new(para.id)); - - None - }; + // Create parachain (in the context of the running network) + let parachain = Parachain::from_spec(para, &global_files_to_inject, &scoped_fs).await?; + let parachain_id = parachain.chain_id.clone(); + // Create `ctx` for spawn the nodes let ctx_para = SpawnNodeCtx { parachain: Some(para), parachain_id: parachain_id.as_deref(), @@ -323,13 +279,16 @@ where ..ctx.clone() }; - let spawning_tasks = para - .collators - .iter() - .map(|node| spawner::spawn_node(node, para_files_to_inject.clone(), &ctx_para)); - // TODO: Add para to Network instance - for node in futures::future::try_join_all(spawning_tasks).await? { - network.add_running_node(node, Some(para.id)); + // Spawn the nodes + let spawning_tasks = para.collators.iter().map(|node| { + spawner::spawn_node(node, parachain.files_to_inject.clone(), &ctx_para) + }); + + let running_nodes = futures::future::try_join_all(spawning_tasks).await?; + let running_para_id = parachain.para_id; + network.add_para(parachain); + for node in running_nodes { + network.add_running_node(node, Some(running_para_id)); } } diff --git a/crates/orchestrator/src/network.rs b/crates/orchestrator/src/network.rs index 0b981e44c..4005538c4 100644 --- a/crates/orchestrator/src/network.rs +++ b/crates/orchestrator/src/network.rs @@ -5,14 +5,17 @@ pub mod relaychain; use std::{collections::HashMap, path::PathBuf}; use configuration::{ + para_states::{Initial, Running}, shared::node::EnvVar, types::{Arg, Command, Image, Port}, + ParachainConfig, ParachainConfigBuilder, }; use provider::{types::TransferedFile, DynNamespace}; use support::fs::FileSystem; use self::{node::NetworkNode, parachain::Parachain, relaychain::Relaychain}; use crate::{ + generators::chain_spec::ChainSpec, network_spec::{self, NetworkSpec}, shared::{macros, types::ChainDefaultContext}, spawner::{self, SpawnNodeCtx}, @@ -290,8 +293,105 @@ impl Network { Ok(()) } + /// Get a parachain config builder from a running network + // TODO: build the validation context from the running network + pub fn para_config_builder(&self) -> ParachainConfigBuilder { + ParachainConfigBuilder::new_with_running(Default::default()) + } + // This should include at least of collator? - // add_parachain() + pub async fn add_parachain( + &mut self, + para_config: &ParachainConfig, + custom_relaychain_spec: Option, + ) -> Result<(), anyhow::Error> { + // build + let mut para_spec = network_spec::parachain::ParachainSpec::from_config(para_config)?; + let base_dir = self.ns.base_dir().to_string_lossy().to_string(); + let scoped_fs = ScopedFilesystem::new(&self.filesystem, &base_dir); + + let mut global_files_to_inject = vec![]; + + // get relaychain id + let relay_chain_id = if let Some(custom_path) = custom_relaychain_spec { + // use this file as relaychain spec + global_files_to_inject.push(TransferedFile { + local_path: custom_path.clone(), + remote_path: PathBuf::from(format!("/cfg/{}.json", self.relaychain().chain)), + }); + let content = std::fs::read_to_string(custom_path)?; + ChainSpec::chain_id_from_spec(&content)? + } else { + global_files_to_inject.push(TransferedFile { + local_path: self.relaychain().chain_spec_path.clone(), + remote_path: PathBuf::from(format!("/cfg/{}.json", self.relaychain().chain)), + }); + self.relay.chain_id.clone() + }; + + let chain_spec_raw_path = para_spec + .build_chain_spec(&relay_chain_id, &self.ns, &scoped_fs) + .await?; + scoped_fs.create_dir(para_spec.id.to_string()).await?; + // create wasm/state + para_spec + .genesis_state + .build( + chain_spec_raw_path.as_ref(), + format!("{}/genesis-state", para_spec.id), + &self.ns, + &scoped_fs, + ) + .await?; + para_spec + .genesis_wasm + .build( + chain_spec_raw_path.as_ref(), + format!("{}/para_spec-wasm", para_spec.id), + &self.ns, + &scoped_fs, + ) + .await?; + + let parachain = + Parachain::from_spec(¶_spec, &global_files_to_inject, &scoped_fs).await?; + let parachain_id = parachain.chain_id.clone(); + + // Create `ctx` for spawn the nodes + let ctx_para = SpawnNodeCtx { + parachain: Some(¶_spec), + parachain_id: parachain_id.as_deref(), + role: if para_spec.is_cumulus_based { + ZombieRole::CumulusCollator + } else { + ZombieRole::Collator + }, + bootnodes_addr: &vec![], + chain_id: &self.relaychain().chain_id, + chain: &self.relaychain().chain, + ns: &self.ns, + scoped_fs: &scoped_fs, + wait_ready: false, + }; + + // Add the parachain to the running network + // self.add_para(parachain); + + // Spawn the nodes + let spawning_tasks = para_spec + .collators + .iter() + .map(|node| spawner::spawn_node(node, parachain.files_to_inject.clone(), &ctx_para)); + + let running_nodes = futures::future::try_join_all(spawning_tasks).await?; + let running_para_id = parachain.para_id; + self.add_para(parachain); + for node in running_nodes { + self.add_running_node(node, Some(running_para_id)); + } + + Ok(()) + } // deregister and stop the collator? // remove_parachain() @@ -324,6 +424,7 @@ impl Network { if let Some(para) = self.parachains.get_mut(¶_id) { para.collators.push(node.clone()); } else { + // is the first node of the para, let create the entry unreachable!() } } else { diff --git a/crates/orchestrator/src/network/parachain.rs b/crates/orchestrator/src/network/parachain.rs index 0a3c54d78..e04c77cfb 100644 --- a/crates/orchestrator/src/network/parachain.rs +++ b/crates/orchestrator/src/network/parachain.rs @@ -3,6 +3,7 @@ use std::{ str::FromStr, }; +use provider::types::TransferedFile; use subxt::{dynamic::Value, OnlineClient, SubstrateConfig}; use subxt_signer::{sr25519::Keypair, SecretUri}; use support::fs::FileSystem; @@ -11,7 +12,10 @@ use tracing::info; // use crate::generators::key::generate_pair; // use sp_core::{sr25519, Pair}; use super::node::NetworkNode; -use crate::{shared::types::RegisterParachainOptions, ScopedFilesystem}; +use crate::{ + network_spec::parachain::ParachainSpec, shared::types::RegisterParachainOptions, + ScopedFilesystem, +}; #[derive(Debug)] pub struct Parachain { @@ -20,6 +24,7 @@ pub struct Parachain { pub(crate) chain_id: Option, pub(crate) chain_spec_path: Option, pub(crate) collators: Vec, + pub(crate) files_to_inject: Vec, } impl Parachain { @@ -30,6 +35,7 @@ impl Parachain { chain_id: None, chain_spec_path: None, collators: Default::default(), + files_to_inject: Default::default(), } } @@ -44,9 +50,47 @@ impl Parachain { chain_id: Some(chain_id.into()), chain_spec_path: Some(chain_spec_path.as_ref().into()), collators: Default::default(), + files_to_inject: Default::default(), } } + pub(crate) async fn from_spec( + para: &ParachainSpec, + files_to_inject: &[TransferedFile], + scoped_fs: &ScopedFilesystem<'_, impl FileSystem>, + ) -> Result { + let mut para_files_to_inject = files_to_inject.to_owned(); + + // parachain id is used for the keystore + // let parachain_id = if let Some(chain_spec) = para.chain_spec.as_ref() { + let mut para = if let Some(chain_spec) = para.chain_spec.as_ref() { + let id = chain_spec.read_chain_id(scoped_fs).await?; + + // add the spec to global files to inject + let spec_name = chain_spec.chain_spec_name(); + let base = PathBuf::from_str(scoped_fs.base_dir)?; + para_files_to_inject.push(TransferedFile { + local_path: base.join(format!("{}.json", spec_name)), + remote_path: PathBuf::from(format!("/cfg/{}.json", para.id)), + }); + + let raw_path = chain_spec + .raw_path() + .ok_or(anyhow::anyhow!("chain-spec path should be set by now.",))?; + let mut running_para = Parachain::with_chain_spec(para.id, id, raw_path); + if let Some(chain_name) = chain_spec.chain_name() { + running_para.chain = Some(chain_name.to_string()); + } + running_para + } else { + Parachain::new(para.id) + }; + + para.files_to_inject = para_files_to_inject; + + Ok(para) + } + pub async fn register( options: RegisterParachainOptions, scoped_fs: &ScopedFilesystem<'_, impl FileSystem>, @@ -105,3 +149,69 @@ impl Parachain { Ok(()) } } + +#[cfg(test)] +mod tests { + use std::collections::HashMap; + + use super::*; + + #[test] + fn create_with_is_works() { + let para = Parachain::new(100); + // only para_id should be set + assert_eq!(para.para_id, 100); + assert_eq!(para.chain_id, None); + assert_eq!(para.chain, None); + assert_eq!(para.chain_spec_path, None); + } + + #[test] + fn create_with_chain_spec_works() { + let para = Parachain::with_chain_spec(100, "rococo-local", "/tmp/rococo-local.json"); + // only para_id should be set + assert_eq!(para.para_id, 100); + assert_eq!(para.chain_id, Some("rococo-local".to_string())); + assert_eq!(para.chain, None); + assert_eq!( + para.chain_spec_path, + Some(PathBuf::from("/tmp/rococo-local.json")) + ); + } + + #[tokio::test] + async fn create_with_para_spec_works() { + use configuration::ParachainConfigBuilder; + + use crate::network_spec::parachain::ParachainSpec; + + let para_config = ParachainConfigBuilder::new(Default::default()) + .with_id(100) + .cumulus_based(false) + .with_default_command("adder-collator") + .with_collator(|c| c.with_name("col")) + .build() + .unwrap(); + + let para_spec = ParachainSpec::from_config(¶_config).unwrap(); + let fs = support::fs::in_memory::InMemoryFileSystem::new(HashMap::default()); + let scoped_fs = ScopedFilesystem { + fs: &fs, + base_dir: "/tmp/some", + }; + + let files = vec![TransferedFile { + local_path: PathBuf::from("/tmp/some"), + remote_path: PathBuf::from("/tmp/some"), + }]; + let para = Parachain::from_spec(¶_spec, &files, &scoped_fs) + .await + .unwrap(); + println!("{:#?}", para); + assert_eq!(para.para_id, 100); + assert_eq!(para.chain_id, None); + assert_eq!(para.chain, None); + // one file should be added. + assert_eq!(para.files_to_inject.len(), 1); + } +} diff --git a/crates/orchestrator/src/network_spec/parachain.rs b/crates/orchestrator/src/network_spec/parachain.rs index 33e1217f0..0648633df 100644 --- a/crates/orchestrator/src/network_spec/parachain.rs +++ b/crates/orchestrator/src/network_spec/parachain.rs @@ -1,8 +1,12 @@ +use std::path::PathBuf; + use configuration::{ shared::resources::Resources, types::{Arg, AssetLocation, Command, Image}, ParachainConfig, RegistrationStrategy, }; +use provider::DynNamespace; +use support::fs::FileSystem; use super::node::NodeSpec; use crate::{ @@ -12,6 +16,7 @@ use crate::{ para_artifact::*, }, shared::types::ChainDefaultContext, + ScopedFilesystem, }; #[derive(Debug, Clone)] @@ -183,4 +188,35 @@ impl ParachainSpec { Ok(para_spec) } + + pub(crate) async fn build_chain_spec<'a, T>( + &mut self, + relay_chain_id: &str, + ns: &DynNamespace, + scoped_fs: &ScopedFilesystem<'a, T>, + ) -> Result, anyhow::Error> + where + T: FileSystem, + { + let cloned = self.clone(); + let chain_spec_raw_path = if let Some(chain_spec) = self.chain_spec.as_mut() { + chain_spec.build(ns, scoped_fs).await?; + + chain_spec + .customize_para(&cloned, relay_chain_id, scoped_fs) + .await?; + chain_spec.build_raw(ns).await?; + + let chain_spec_raw_path = + chain_spec + .raw_path() + .ok_or(OrchestratorError::InvariantError( + "chain-spec raw path should be set now", + ))?; + Some(chain_spec_raw_path.to_path_buf()) + } else { + None + }; + Ok(chain_spec_raw_path) + } } From 1fbe53f47060d8b684f425a75e43843b9c66b5db Mon Sep 17 00:00:00 2001 From: Javier Viola Date: Tue, 28 Nov 2023 15:56:50 -0300 Subject: [PATCH 2/5] add example for adding a para to a running network --- crates/examples/Cargo.toml | 1 + crates/examples/examples/add_para.rs | 59 ++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 crates/examples/examples/add_para.rs diff --git a/crates/examples/Cargo.toml b/crates/examples/Cargo.toml index 9c4d0c1fd..f067060a6 100644 --- a/crates/examples/Cargo.toml +++ b/crates/examples/Cargo.toml @@ -12,3 +12,4 @@ futures = { workspace = true } subxt = { workspace = true } tracing-subscriber = "0.3" serde_json = { workspace = true } +anyhow = { workspace = true } diff --git a/crates/examples/examples/add_para.rs b/crates/examples/examples/add_para.rs new file mode 100644 index 000000000..c08f91c53 --- /dev/null +++ b/crates/examples/examples/add_para.rs @@ -0,0 +1,59 @@ +use futures::stream::StreamExt; +use zombienet_sdk::{NetworkConfigBuilder, NetworkConfigExt}; +use anyhow::anyhow; + +#[tokio::main] +async fn main() -> Result<(), anyhow::Error> { + tracing_subscriber::fmt::init(); + let mut network = NetworkConfigBuilder::new() + .with_relaychain(|r| { + r.with_chain("rococo-local") + .with_default_command("polkadot") + .with_node(|node| node.with_name("alice")) + .with_node(|node| node.with_name("bob")) + }) + // .with_parachain(|p| { + // p.with_id(100) + // .cumulus_based(true) + // .with_collator(|n| n.with_name("collator").with_command("polkadot-parachain")) + // }) + .build() + .unwrap() + .spawn_native() + .await?; + + println!("🚀🚀🚀🚀 network deployed"); + + let alice = network.get_node("alice")?; + let client = alice.client::().await?; + + // wait 3 blocks + let mut blocks = client.blocks().subscribe_finalized().await?.take(3); + + while let Some(block) = blocks.next().await { + println!("Block #{}", block?.header().number); + } + + println!("⚙️ adding parachain to the running network"); + + let para_config = network.para_config_builder() + .with_id(100) + .with_default_command("polkadot-parachain") + .with_collator(|c| { + c.with_name("col-100-1") + }) + .build() + .map_err(|_e| { + anyhow!("Building config") + })?; + + network.add_parachain(¶_config, None).await?; + + // For now let just loop.... + #[allow(clippy::empty_loop)] + loop {} + + #[allow(clippy::unreachable)] + #[allow(unreachable_code)] + Ok(()) +} From 3e415e2b09a635986282cc40e845ed1f091463b7 Mon Sep 17 00:00:00 2001 From: Javier Viola Date: Tue, 28 Nov 2023 15:57:16 -0300 Subject: [PATCH 3/5] register the new para in the running network --- crates/orchestrator/src/lib.rs | 2 +- crates/orchestrator/src/network.rs | 32 ++++++++++++++++++++++++++---- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/crates/orchestrator/src/lib.rs b/crates/orchestrator/src/lib.rs index 96abdae0e..9bb80cd4b 100644 --- a/crates/orchestrator/src/lib.rs +++ b/crates/orchestrator/src/lib.rs @@ -320,7 +320,7 @@ where node_ws_url: node_ws_url.clone(), onboard_as_para: para.onboard_as_parachain, seed: None, // TODO: Seed is passed by? - finalization: false, // TODO: Seed is passed by? + finalization: false, }; Parachain::register(register_para_options, &scoped_fs).await?; diff --git a/crates/orchestrator/src/network.rs b/crates/orchestrator/src/network.rs index 4005538c4..61d78e22e 100644 --- a/crates/orchestrator/src/network.rs +++ b/crates/orchestrator/src/network.rs @@ -17,7 +17,7 @@ use self::{node::NetworkNode, parachain::Parachain, relaychain::Relaychain}; use crate::{ generators::chain_spec::ChainSpec, network_spec::{self, NetworkSpec}, - shared::{macros, types::ChainDefaultContext}, + shared::{macros, types::{ChainDefaultContext, RegisterParachainOptions}}, spawner::{self, SpawnNodeCtx}, ScopedFilesystem, ZombieRole, }; @@ -323,7 +323,7 @@ impl Network { ChainSpec::chain_id_from_spec(&content)? } else { global_files_to_inject.push(TransferedFile { - local_path: self.relaychain().chain_spec_path.clone(), + local_path: PathBuf::from(format!("{}/{}",scoped_fs.base_dir,self.relaychain().chain_spec_path.to_string_lossy())), remote_path: PathBuf::from(format!("/cfg/{}.json", self.relaychain().chain)), }); self.relay.chain_id.clone() @@ -374,8 +374,32 @@ impl Network { wait_ready: false, }; - // Add the parachain to the running network - // self.add_para(parachain); + // Register the parachain to the running network + let first_node_url = self.relaychain().nodes.first().ok_or(anyhow::anyhow!("At least one node of the relaychain should be running"))?.ws_uri(); + let register_para_options = RegisterParachainOptions { + id: parachain.para_id, + // This needs to resolve correctly + wasm_path: para_spec + .genesis_wasm + .artifact_path() + .ok_or(anyhow::anyhow!( + "artifact path for wasm must be set at this point", + ))? + .to_path_buf(), + state_path: para_spec + .genesis_state + .artifact_path() + .ok_or(anyhow::anyhow!( + "artifact path for state must be set at this point", + ))? + .to_path_buf(), + node_ws_url: first_node_url.to_string(), + onboard_as_para: para_spec.onboard_as_parachain, + seed: None, // TODO: Seed is passed by? + finalization: false, + }; + + Parachain::register(register_para_options, &scoped_fs).await?; // Spawn the nodes let spawning_tasks = para_spec From 9a72bba6a21293fc58b55d0c279f3b8da755f244 Mon Sep 17 00:00:00 2001 From: Javier Viola Date: Tue, 28 Nov 2023 16:03:37 -0300 Subject: [PATCH 4/5] fmt --- crates/examples/examples/add_para.rs | 18 +++++------------- crates/orchestrator/src/lib.rs | 2 +- crates/orchestrator/src/network.rs | 22 ++++++++++++++++++---- 3 files changed, 24 insertions(+), 18 deletions(-) diff --git a/crates/examples/examples/add_para.rs b/crates/examples/examples/add_para.rs index c08f91c53..ee2e2d38a 100644 --- a/crates/examples/examples/add_para.rs +++ b/crates/examples/examples/add_para.rs @@ -1,6 +1,6 @@ +use anyhow::anyhow; use futures::stream::StreamExt; use zombienet_sdk::{NetworkConfigBuilder, NetworkConfigExt}; -use anyhow::anyhow; #[tokio::main] async fn main() -> Result<(), anyhow::Error> { @@ -12,11 +12,6 @@ async fn main() -> Result<(), anyhow::Error> { .with_node(|node| node.with_name("alice")) .with_node(|node| node.with_name("bob")) }) - // .with_parachain(|p| { - // p.with_id(100) - // .cumulus_based(true) - // .with_collator(|n| n.with_name("collator").with_command("polkadot-parachain")) - // }) .build() .unwrap() .spawn_native() @@ -36,16 +31,13 @@ async fn main() -> Result<(), anyhow::Error> { println!("⚙️ adding parachain to the running network"); - let para_config = network.para_config_builder() + let para_config = network + .para_config_builder() .with_id(100) .with_default_command("polkadot-parachain") - .with_collator(|c| { - c.with_name("col-100-1") - }) + .with_collator(|c| c.with_name("col-100-1")) .build() - .map_err(|_e| { - anyhow!("Building config") - })?; + .map_err(|_e| anyhow!("Building config"))?; network.add_parachain(¶_config, None).await?; diff --git a/crates/orchestrator/src/lib.rs b/crates/orchestrator/src/lib.rs index 9bb80cd4b..01e8c34b5 100644 --- a/crates/orchestrator/src/lib.rs +++ b/crates/orchestrator/src/lib.rs @@ -319,7 +319,7 @@ where .to_path_buf(), node_ws_url: node_ws_url.clone(), onboard_as_para: para.onboard_as_parachain, - seed: None, // TODO: Seed is passed by? + seed: None, // TODO: Seed is passed by? finalization: false, }; diff --git a/crates/orchestrator/src/network.rs b/crates/orchestrator/src/network.rs index 61d78e22e..d34976b6b 100644 --- a/crates/orchestrator/src/network.rs +++ b/crates/orchestrator/src/network.rs @@ -17,7 +17,10 @@ use self::{node::NetworkNode, parachain::Parachain, relaychain::Relaychain}; use crate::{ generators::chain_spec::ChainSpec, network_spec::{self, NetworkSpec}, - shared::{macros, types::{ChainDefaultContext, RegisterParachainOptions}}, + shared::{ + macros, + types::{ChainDefaultContext, RegisterParachainOptions}, + }, spawner::{self, SpawnNodeCtx}, ScopedFilesystem, ZombieRole, }; @@ -323,7 +326,11 @@ impl Network { ChainSpec::chain_id_from_spec(&content)? } else { global_files_to_inject.push(TransferedFile { - local_path: PathBuf::from(format!("{}/{}",scoped_fs.base_dir,self.relaychain().chain_spec_path.to_string_lossy())), + local_path: PathBuf::from(format!( + "{}/{}", + scoped_fs.base_dir, + self.relaychain().chain_spec_path.to_string_lossy() + )), remote_path: PathBuf::from(format!("/cfg/{}.json", self.relaychain().chain)), }); self.relay.chain_id.clone() @@ -375,7 +382,14 @@ impl Network { }; // Register the parachain to the running network - let first_node_url = self.relaychain().nodes.first().ok_or(anyhow::anyhow!("At least one node of the relaychain should be running"))?.ws_uri(); + let first_node_url = self + .relaychain() + .nodes + .first() + .ok_or(anyhow::anyhow!( + "At least one node of the relaychain should be running" + ))? + .ws_uri(); let register_para_options = RegisterParachainOptions { id: parachain.para_id, // This needs to resolve correctly @@ -395,7 +409,7 @@ impl Network { .to_path_buf(), node_ws_url: first_node_url.to_string(), onboard_as_para: para_spec.onboard_as_parachain, - seed: None, // TODO: Seed is passed by? + seed: None, // TODO: Seed is passed by? finalization: false, }; From 0986c57607e76befb2f55e5100d5fdeed09efcf7 Mon Sep 17 00:00:00 2001 From: Javier Viola Date: Wed, 29 Nov 2023 08:57:45 -0300 Subject: [PATCH 5/5] cleanup and docs --- .../orchestrator/src/generators/chain_spec.rs | 1 + crates/orchestrator/src/network.rs | 35 +++++++++++++++++-- crates/orchestrator/src/network/parachain.rs | 1 - .../src/network_spec/parachain.rs | 4 +++ 4 files changed, 38 insertions(+), 3 deletions(-) diff --git a/crates/orchestrator/src/generators/chain_spec.rs b/crates/orchestrator/src/generators/chain_spec.rs index 37f572d07..0d3f9df1c 100644 --- a/crates/orchestrator/src/generators/chain_spec.rs +++ b/crates/orchestrator/src/generators/chain_spec.rs @@ -497,6 +497,7 @@ impl ChainSpec { Ok(()) } + /// Get the chain_is from the json content of a chain-spec file. pub fn chain_id_from_spec(spec_content: &str) -> Result { let chain_spec_json: serde_json::Value = serde_json::from_str(spec_content).map_err(|_| { diff --git a/crates/orchestrator/src/network.rs b/crates/orchestrator/src/network.rs index d34976b6b..73c55c30c 100644 --- a/crates/orchestrator/src/network.rs +++ b/crates/orchestrator/src/network.rs @@ -297,12 +297,43 @@ impl Network { } /// Get a parachain config builder from a running network - // TODO: build the validation context from the running network + /// + /// This allow you to build a new parachain config to be deployed into + /// the running network. pub fn para_config_builder(&self) -> ParachainConfigBuilder { + // TODO: build the validation context from the running network ParachainConfigBuilder::new_with_running(Default::default()) } - // This should include at least of collator? + /// Add a new parachain to the running network + /// + /// NOTE: para_id must be unique in the whole network. + /// + /// # Example: + /// ```rust + /// # use anyhow::anyhow; + /// # use provider::NativeProvider; + /// # use support::{fs::local::LocalFileSystem, process::os::OsProcessManager}; + /// # use zombienet_orchestrator::{errors, AddCollatorOptions, Orchestrator}; + /// # use configuration::NetworkConfig; + /// # async fn example() -> Result<(), anyhow::Error> { + /// # let provider = NativeProvider::new(LocalFileSystem {}, OsProcessManager {}); + /// # let orchestrator = Orchestrator::new(LocalFileSystem {}, provider); + /// # let config = NetworkConfig::load_from_toml("config.toml")?; + /// let mut network = orchestrator.spawn(config).await?; + /// let para_config = network + /// .para_config_builder() + /// .with_id(100) + /// .with_default_command("polkadot-parachain") + /// .with_collator(|c| c.with_name("col-100-1")) + /// .build() + /// .map_err(|_e| anyhow!("Building config"))?; + /// + /// network.add_parachain(¶_config, None).await?; + /// + /// # Ok(()) + /// # } + /// ``` pub async fn add_parachain( &mut self, para_config: &ParachainConfig, diff --git a/crates/orchestrator/src/network/parachain.rs b/crates/orchestrator/src/network/parachain.rs index e04c77cfb..9e43039ea 100644 --- a/crates/orchestrator/src/network/parachain.rs +++ b/crates/orchestrator/src/network/parachain.rs @@ -62,7 +62,6 @@ impl Parachain { let mut para_files_to_inject = files_to_inject.to_owned(); // parachain id is used for the keystore - // let parachain_id = if let Some(chain_spec) = para.chain_spec.as_ref() { let mut para = if let Some(chain_spec) = para.chain_spec.as_ref() { let id = chain_spec.read_chain_id(scoped_fs).await?; diff --git a/crates/orchestrator/src/network_spec/parachain.rs b/crates/orchestrator/src/network_spec/parachain.rs index 0648633df..8597cd468 100644 --- a/crates/orchestrator/src/network_spec/parachain.rs +++ b/crates/orchestrator/src/network_spec/parachain.rs @@ -189,6 +189,10 @@ impl ParachainSpec { Ok(para_spec) } + /// Build parachain chain-spec + /// + /// This fn customize the chain-spec (if is possible) and build the raw version + /// of the chain-spec. pub(crate) async fn build_chain_spec<'a, T>( &mut self, relay_chain_id: &str,