diff --git a/crates/configuration/src/lib.rs b/crates/configuration/src/lib.rs index fa434f988100957e65b6b812277b4f152da3926d..8e71fff612723e47b49e6157decc24ea7d7617fc 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 31bc433074af4e3f874b50a74ca2617528b5ca4d..21f2d069d9287384db60b159b186b9f8c586ceb4 100644 --- a/crates/configuration/src/network.rs +++ b/crates/configuration/src/network.rs @@ -373,8 +373,11 @@ impl NetworkConfigBuilder<WithRelaychain> { pub fn with_parachain( self, f: fn( - ParachainConfigBuilder<parachain::Initial>, - ) -> ParachainConfigBuilder<parachain::WithAtLeastOneCollator>, + ParachainConfigBuilder<parachain::states::Initial, parachain::states::Bootstrap>, + ) -> 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 5f3aa083790ff2dc6977e26f4d839dabe5313a9d..e09752da73882979198b25157cc6de99ec67fcb6 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<S> { +pub struct ParachainConfigBuilder<S, C> { config: ParachainConfig, validation_context: Rc<RefCell<ValidationContext>>, errors: Vec<anyhow::Error>, _state: PhantomData<S>, + _context: PhantomData<C>, } -impl Default for ParachainConfigBuilder<Initial> { +impl<C: Context> Default for ParachainConfigBuilder<Initial, C> { fn default() -> Self { Self { config: ParachainConfig { @@ -265,21 +279,23 @@ impl Default for ParachainConfigBuilder<Initial> { validation_context: Default::default(), errors: vec![], _state: PhantomData, + _context: PhantomData, } } } -impl<A> ParachainConfigBuilder<A> { +impl<A, C> ParachainConfigBuilder<A, C> { fn transition<B>( config: ParachainConfig, validation_context: Rc<RefCell<ValidationContext>>, errors: Vec<anyhow::Error>, - ) -> ParachainConfigBuilder<B> { + ) -> ParachainConfigBuilder<B, C> { ParachainConfigBuilder { config, validation_context, errors, _state: PhantomData, + _context: PhantomData, } } @@ -294,19 +310,51 @@ impl<A> ParachainConfigBuilder<A> { } } -impl ParachainConfigBuilder<Initial> { +impl ParachainConfigBuilder<Initial, Bootstrap> { pub fn new( validation_context: Rc<RefCell<ValidationContext>>, - ) -> ParachainConfigBuilder<Initial> { + ) -> ParachainConfigBuilder<Initial, Bootstrap> { Self { validation_context, ..Self::default() } } +} +impl ParachainConfigBuilder<WithId, Bootstrap> { + /// 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<Initial, Running> { + /// Start a new builder in the context of a running network + pub fn new_with_running( + validation_context: Rc<RefCell<ValidationContext>>, + ) -> ParachainConfigBuilder<Initial, Running> { + let mut builder = Self { + validation_context, + ..Self::default() + }; + + // override the registration strategy + builder.config.registration_strategy = Some(RegistrationStrategy::UsingExtrinsic); + builder + } +} + +impl<C: Context> ParachainConfigBuilder<Initial, C> { /// Set the parachain ID (should be unique). // TODO: handle unique validation - pub fn with_id(self, id: u32) -> ParachainConfigBuilder<WithId> { + pub fn with_id(self, id: u32) -> ParachainConfigBuilder<WithId, C> { Self::transition( ParachainConfig { id, ..self.config }, self.validation_context, @@ -315,7 +363,7 @@ impl ParachainConfigBuilder<Initial> { } } -impl ParachainConfigBuilder<WithId> { +impl<C: Context> ParachainConfigBuilder<WithId, C> { /// Set the chain name (e.g. rococo-local). /// Use [`None`], if you are running adder-collator or undying-collator). pub fn with_chain<T>(self, chain: T) -> Self @@ -340,18 +388,6 @@ impl ParachainConfigBuilder<WithId> { } } - /// 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<WithId> { pub fn with_collator( self, f: fn(NodeConfigBuilder<node::Initial>) -> NodeConfigBuilder<node::Buildable>, - ) -> ParachainConfigBuilder<WithAtLeastOneCollator> { + ) -> ParachainConfigBuilder<WithAtLeastOneCollator, C> { match f(NodeConfigBuilder::new( self.default_chain_context(), self.validation_context.clone(), @@ -645,7 +681,7 @@ impl ParachainConfigBuilder<WithId> { } } -impl ParachainConfigBuilder<WithAtLeastOneCollator> { +impl<C: Context> ParachainConfigBuilder<WithAtLeastOneCollator, C> { /// 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 7720b911f41f0e1f3266753ebcddfd671319d0e0..e8b0ff3507851016160569209aaa28327dde6f6f 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<S> { config: NodeConfig, diff --git a/crates/examples/Cargo.toml b/crates/examples/Cargo.toml index 9c4d0c1fd8c69d10cb12a7e1311a3956d5705b13..f067060a678a6e14a59055238bec5c7e45cf44ce 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 0000000000000000000000000000000000000000..ee2e2d38ad9d99f264f0671ed93136b90bbbebad --- /dev/null +++ b/crates/examples/examples/add_para.rs @@ -0,0 +1,51 @@ +use anyhow::anyhow; +use futures::stream::StreamExt; +use zombienet_sdk::{NetworkConfigBuilder, NetworkConfigExt}; + +#[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")) + }) + .build() + .unwrap() + .spawn_native() + .await?; + + println!("🚀🚀🚀🚀 network deployed"); + + let alice = network.get_node("alice")?; + let client = alice.client::<subxt::PolkadotConfig>().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(()) +} diff --git a/crates/orchestrator/src/generators/chain_spec.rs b/crates/orchestrator/src/generators/chain_spec.rs index b363793e3589d5b3b2c1871c60b9e4db5d45a6fd..0d3f9df1c8536244819d34bae0af684f104700f5 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,27 @@ 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<String, GeneratorError> { + 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<String, String>); diff --git a/crates/orchestrator/src/lib.rs b/crates/orchestrator/src/lib.rs index 26f748f271d6cd75c2166e8a5111c92ab2e45d50..01e8c34b526250e6901bcd72c67e56b64d4e311e 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)); } } @@ -360,8 +319,8 @@ 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? - finalization: false, // TODO: Seed is passed by? + seed: None, // 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 0b981e44c20345b377d7f10e6e4e4b196540eaef..73c55c30cc2f967407706da03f68f4a90d60638b 100644 --- a/crates/orchestrator/src/network.rs +++ b/crates/orchestrator/src/network.rs @@ -5,16 +5,22 @@ 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}, + shared::{ + macros, + types::{ChainDefaultContext, RegisterParachainOptions}, + }, spawner::{self, SpawnNodeCtx}, ScopedFilesystem, ZombieRole, }; @@ -290,8 +296,171 @@ impl<T: FileSystem> Network<T> { Ok(()) } - // This should include at least of collator? - // add_parachain() + /// Get a parachain config builder from a 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<Initial, Running> { + // TODO: build the validation context from the running network + ParachainConfigBuilder::new_with_running(Default::default()) + } + + /// 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, + custom_relaychain_spec: Option<PathBuf>, + ) -> 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: 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() + }; + + 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, + }; + + // 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 + .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 +493,7 @@ impl<T: FileSystem> Network<T> { 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 0a3c54d786d6c7f33da502e650507f4fde4fe435..9e43039eadfc7047780b37e99a627a7503e7fc34 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<String>, pub(crate) chain_spec_path: Option<PathBuf>, pub(crate) collators: Vec<NetworkNode>, + pub(crate) files_to_inject: Vec<TransferedFile>, } 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,46 @@ 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<Self, anyhow::Error> { + let mut para_files_to_inject = files_to_inject.to_owned(); + + // parachain id is used for the keystore + 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 +148,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 33e1217f0690f178b5a754d91f24f34768d39705..8597cd468f1efdabec7dc7c7535855b23ef23d81 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,39 @@ 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, + ns: &DynNamespace, + scoped_fs: &ScopedFilesystem<'a, T>, + ) -> Result<Option<PathBuf>, 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) + } }