From cc66f5cd408ae9f4d8f2c651c75164707ae425f6 Mon Sep 17 00:00:00 2001 From: PG Herveou <pgherveou@gmail.com> Date: Tue, 24 Oct 2023 14:24:19 +0200 Subject: [PATCH] Expose subxt onclineclient / rpc (#125) just wanted to throw a quick implementation that exposes the OnclineClient on the node. This would make the sdk super useful to enable cross chain e2e tests in ink! --------- Co-authored-by: Javier Viola <pepoviola@gmail.com> --- crates/configuration/src/global_settings.rs | 8 +++---- crates/configuration/src/network.rs | 10 ++++---- crates/configuration/src/parachain.rs | 20 ++++++++-------- crates/configuration/src/relaychain.rs | 14 +++++------ crates/configuration/src/shared/node.rs | 24 +++++++++---------- crates/configuration/src/shared/resources.rs | 10 ++++---- crates/examples/Cargo.toml | 2 ++ .../examples/simple_network_example.rs | 18 ++++++++++---- crates/orchestrator/src/network/node.rs | 13 ++++++++++ crates/orchestrator/src/network_spec.rs | 4 ++-- crates/orchestrator/src/spawner.rs | 14 +++++------ 11 files changed, 80 insertions(+), 57 deletions(-) diff --git a/crates/configuration/src/global_settings.rs b/crates/configuration/src/global_settings.rs index f6e7302..36a7600 100644 --- a/crates/configuration/src/global_settings.rs +++ b/crates/configuration/src/global_settings.rs @@ -235,7 +235,7 @@ mod tests { assert_eq!(errors.len(), 1); assert_eq!( - errors.get(0).unwrap().to_string(), + errors.first().unwrap().to_string(), "global_settings.bootnodes_addresses[0]: '/ip4//tcp/45421' failed to parse: invalid IPv4 address syntax" ); } @@ -250,7 +250,7 @@ mod tests { assert_eq!(errors.len(), 2); assert_eq!( - errors.get(0).unwrap().to_string(), + errors.first().unwrap().to_string(), "global_settings.bootnodes_addresses[0]: '/ip4//tcp/45421' failed to parse: invalid IPv4 address syntax" ); assert_eq!( @@ -268,7 +268,7 @@ mod tests { assert_eq!(errors.len(), 1); assert_eq!( - errors.get(0).unwrap().to_string(), + errors.first().unwrap().to_string(), "global_settings.local_ip: invalid IP address syntax" ); } @@ -284,7 +284,7 @@ mod tests { assert_eq!(errors.len(), 3); assert_eq!( - errors.get(0).unwrap().to_string(), + errors.first().unwrap().to_string(), "global_settings.bootnodes_addresses[0]: '/ip4//tcp/45421' failed to parse: invalid IPv4 address syntax" ); assert_eq!( diff --git a/crates/configuration/src/network.rs b/crates/configuration/src/network.rs index ee67b19..cba24ca 100644 --- a/crates/configuration/src/network.rs +++ b/crates/configuration/src/network.rs @@ -582,7 +582,7 @@ mod tests { assert_eq!(errors.len(), 2); assert_eq!( - errors.get(0).unwrap().to_string(), + errors.first().unwrap().to_string(), "relaychain.default_image: 'invalid.image' doesn't match regex '^([ip]|[hostname]/)?[tag_name]:[tag_version]?$'" ); assert_eq!( @@ -622,7 +622,7 @@ mod tests { assert_eq!(errors.len(), 2); assert_eq!( - errors.get(0).unwrap().to_string(), + errors.first().unwrap().to_string(), "parachain[1000].collators['collator1'].command: 'invalid command' shouldn't contains whitespace" ); assert_eq!( @@ -679,7 +679,7 @@ mod tests { assert_eq!(errors.len(), 2); assert_eq!( - errors.get(0).unwrap().to_string(), + errors.first().unwrap().to_string(), "parachain[1000].collators['collator1'].command: 'invalid command' shouldn't contains whitespace" ); assert_eq!( @@ -724,7 +724,7 @@ mod tests { assert_eq!(errors.len(), 2); assert_eq!( - errors.get(0).unwrap().to_string(), + errors.first().unwrap().to_string(), "global_settings.local_ip: invalid IP address syntax" ); assert_eq!( @@ -766,7 +766,7 @@ mod tests { assert_eq!(errors.len(), 3); assert_eq!( - errors.get(0).unwrap().to_string(), + errors.first().unwrap().to_string(), "relaychain.nodes['node'].command: 'invalid command' shouldn't contains whitespace" ); assert_eq!( diff --git a/crates/configuration/src/parachain.rs b/crates/configuration/src/parachain.rs index 58cd23c..5f3aa08 100644 --- a/crates/configuration/src/parachain.rs +++ b/crates/configuration/src/parachain.rs @@ -831,7 +831,7 @@ mod tests { assert_eq!(errors.len(), 1); assert_eq!( - errors.get(0).unwrap().to_string(), + errors.first().unwrap().to_string(), "parachain[1000].chain: 'invalid chain' shouldn't contains whitespace" ); } @@ -853,7 +853,7 @@ mod tests { assert_eq!(errors.len(), 1); assert_eq!( - errors.get(0).unwrap().to_string(), + errors.first().unwrap().to_string(), "parachain[1000].default_command: 'invalid command' shouldn't contains whitespace" ); } @@ -875,7 +875,7 @@ mod tests { assert_eq!(errors.len(), 1); assert_eq!( - errors.get(0).unwrap().to_string(), + errors.first().unwrap().to_string(), r"parachain[1000].default_image: 'invalid image' doesn't match regex '^([ip]|[hostname]/)?[tag_name]:[tag_version]?$'" ); } @@ -902,7 +902,7 @@ mod tests { assert_eq!(errors.len(), 1); assert_eq!( - errors.get(0).unwrap().to_string(), + errors.first().unwrap().to_string(), r"parachain[1000].default_resources.request_cpu: 'invalid' doesn't match regex '^\d+(.\d+)?(m|K|M|G|T|P|E|Ki|Mi|Gi|Ti|Pi|Ei)?$'" ); } @@ -925,7 +925,7 @@ mod tests { assert_eq!(errors.len(), 1); assert_eq!( - errors.get(0).unwrap().to_string(), + errors.first().unwrap().to_string(), "parachain[2000].genesis_wasm_generator: 'invalid command' shouldn't contains whitespace" ); } @@ -948,7 +948,7 @@ mod tests { assert_eq!(errors.len(), 1); assert_eq!( - errors.get(0).unwrap().to_string(), + errors.first().unwrap().to_string(), "parachain[1000].genesis_state_generator: 'invalid command' shouldn't contains whitespace" ); } @@ -971,7 +971,7 @@ mod tests { assert_eq!(errors.len(), 2); assert_eq!( - errors.get(0).unwrap().to_string(), + errors.first().unwrap().to_string(), "parachain[2000].bootnodes_addresses[0]: '/ip4//tcp/45421' failed to parse: invalid IPv4 address syntax" ); assert_eq!( @@ -995,7 +995,7 @@ mod tests { assert_eq!(errors.len(), 1); assert_eq!( - errors.get(0).unwrap().to_string(), + errors.first().unwrap().to_string(), "parachain[1000].collators['collator'].command: 'invalid command' shouldn't contains whitespace" ); } @@ -1024,7 +1024,7 @@ mod tests { assert_eq!(errors.len(), 1); assert_eq!( - errors.get(0).unwrap().to_string(), + errors.first().unwrap().to_string(), "parachain[2000].collators['collator2'].command: 'invalid command' shouldn't contains whitespace" ); } @@ -1060,7 +1060,7 @@ mod tests { assert_eq!(errors.len(), 5); assert_eq!( - errors.get(0).unwrap().to_string(), + errors.first().unwrap().to_string(), "parachain[2000].bootnodes_addresses[0]: '/ip4//tcp/45421' failed to parse: invalid IPv4 address syntax" ); assert_eq!( diff --git a/crates/configuration/src/relaychain.rs b/crates/configuration/src/relaychain.rs index a3782cd..9d33d5f 100644 --- a/crates/configuration/src/relaychain.rs +++ b/crates/configuration/src/relaychain.rs @@ -511,7 +511,7 @@ mod tests { assert_eq!(errors.len(), 1); assert_eq!( - errors.get(0).unwrap().to_string(), + errors.first().unwrap().to_string(), "relaychain.chain: 'invalid chain' shouldn't contains whitespace" ); } @@ -531,7 +531,7 @@ mod tests { assert_eq!(errors.len(), 1); assert_eq!( - errors.get(0).unwrap().to_string(), + errors.first().unwrap().to_string(), "relaychain.default_command: 'invalid command' shouldn't contains whitespace" ); } @@ -551,7 +551,7 @@ mod tests { assert_eq!(errors.len(), 1); assert_eq!( - errors.get(0).unwrap().to_string(), + errors.first().unwrap().to_string(), r"relaychain.default_image: 'invalid image' doesn't match regex '^([ip]|[hostname]/)?[tag_name]:[tag_version]?$'" ); } @@ -576,7 +576,7 @@ mod tests { assert_eq!(errors.len(), 1); assert_eq!( - errors.get(0).unwrap().to_string(), + errors.first().unwrap().to_string(), r"relaychain.default_resources.request_cpu: 'invalid' doesn't match regex '^\d+(.\d+)?(m|K|M|G|T|P|E|Ki|Mi|Gi|Ti|Pi|Ei)?$'" ); } @@ -595,7 +595,7 @@ mod tests { assert_eq!(errors.len(), 1); assert_eq!( - errors.get(0).unwrap().to_string(), + errors.first().unwrap().to_string(), "relaychain.nodes['node'].command: 'invalid command' shouldn't contains whitespace" ); } @@ -620,7 +620,7 @@ mod tests { assert_eq!(errors.len(), 1); assert_eq!( - errors.get(0).unwrap().to_string(), + errors.first().unwrap().to_string(), "relaychain.nodes['node2'].command: 'invalid command' shouldn't contains whitespace" ); } @@ -646,7 +646,7 @@ mod tests { assert_eq!(errors.len(), 2); assert_eq!( - errors.get(0).unwrap().to_string(), + errors.first().unwrap().to_string(), "relaychain.default_resources.limit_cpu: 'invalid' doesn't match regex '^\\d+(.\\d+)?(m|K|M|G|T|P|E|Ki|Mi|Gi|Ti|Pi|Ei)?$'" ); assert_eq!( diff --git a/crates/configuration/src/shared/node.rs b/crates/configuration/src/shared/node.rs index 16b3905..85ff15f 100644 --- a/crates/configuration/src/shared/node.rs +++ b/crates/configuration/src/shared/node.rs @@ -717,7 +717,7 @@ mod tests { assert_eq!(node_name, "node"); assert_eq!(errors.len(), 1); assert_eq!( - errors.get(0).unwrap().to_string(), + errors.first().unwrap().to_string(), "command: 'invalid command' shouldn't contains whitespace" ); } @@ -734,7 +734,7 @@ mod tests { assert_eq!(node_name, "node"); assert_eq!(errors.len(), 1); assert_eq!( - errors.get(0).unwrap().to_string(), + errors.first().unwrap().to_string(), "image: 'myinvalid.image' doesn't match regex '^([ip]|[hostname]/)?[tag_name]:[tag_version]?$'" ); } @@ -752,7 +752,7 @@ mod tests { assert_eq!(node_name, "node"); assert_eq!(errors.len(), 1); assert_eq!( - errors.get(0).unwrap().to_string(), + errors.first().unwrap().to_string(), "bootnodes_addresses[0]: '/ip4//tcp/45421' failed to parse: invalid IPv4 address syntax" ); } @@ -770,7 +770,7 @@ mod tests { assert_eq!(node_name, "node"); assert_eq!(errors.len(), 2); assert_eq!( - errors.get(0).unwrap().to_string(), + errors.first().unwrap().to_string(), "bootnodes_addresses[0]: '/ip4//tcp/45421' failed to parse: invalid IPv4 address syntax" ); assert_eq!( @@ -792,7 +792,7 @@ mod tests { assert_eq!(node_name, "node"); assert_eq!(errors.len(), 1); assert_eq!( - errors.get(0).unwrap().to_string(), + errors.first().unwrap().to_string(), r"resources.limit_cpu: 'invalid' doesn't match regex '^\d+(.\d+)?(m|K|M|G|T|P|E|Ki|Mi|Gi|Ti|Pi|Ei)?$'" ); } @@ -814,7 +814,7 @@ mod tests { assert_eq!(node_name, "node"); assert_eq!(errors.len(), 2); assert_eq!( - errors.get(0).unwrap().to_string(), + errors.first().unwrap().to_string(), r"resources.limit_cpu: 'invalid' doesn't match regex '^\d+(.\d+)?(m|K|M|G|T|P|E|Ki|Mi|Gi|Ti|Pi|Ei)?$'" ); assert_eq!( @@ -842,7 +842,7 @@ mod tests { assert_eq!(node_name, "node"); assert_eq!(errors.len(), 4); assert_eq!( - errors.get(0).unwrap().to_string(), + errors.first().unwrap().to_string(), "command: 'invalid command' shouldn't contains whitespace" ); assert_eq!( @@ -875,7 +875,7 @@ mod tests { assert_eq!(node_name, "mynode"); assert_eq!(errors.len(), 1); assert_eq!( - errors.get(0).unwrap().to_string(), + errors.first().unwrap().to_string(), "name: 'mynode' is already used across config" ); } @@ -897,7 +897,7 @@ mod tests { assert_eq!(node_name, "node"); assert_eq!(errors.len(), 1); assert_eq!( - errors.get(0).unwrap().to_string(), + errors.first().unwrap().to_string(), "ws_port: '30333' is already used across config" ); } @@ -919,7 +919,7 @@ mod tests { assert_eq!(node_name, "node"); assert_eq!(errors.len(), 1); assert_eq!( - errors.get(0).unwrap().to_string(), + errors.first().unwrap().to_string(), "rpc_port: '4444' is already used across config" ); } @@ -941,7 +941,7 @@ mod tests { assert_eq!(node_name, "node"); assert_eq!(errors.len(), 1); assert_eq!( - errors.get(0).unwrap().to_string(), + errors.first().unwrap().to_string(), "prometheus_port: '9089' is already used across config" ); } @@ -963,7 +963,7 @@ mod tests { assert_eq!(node_name, "node"); assert_eq!(errors.len(), 1); assert_eq!( - errors.get(0).unwrap().to_string(), + errors.first().unwrap().to_string(), "p2p_port: '45093' is already used across config" ); } diff --git a/crates/configuration/src/shared/resources.rs b/crates/configuration/src/shared/resources.rs index 8537b18..425831d 100644 --- a/crates/configuration/src/shared/resources.rs +++ b/crates/configuration/src/shared/resources.rs @@ -423,7 +423,7 @@ mod tests { assert_eq!(errors.len(), 1); assert_eq!( - errors.get(0).unwrap().to_string(), + errors.first().unwrap().to_string(), r"request_memory: 'invalid' doesn't match regex '^\d+(.\d+)?(m|K|M|G|T|P|E|Ki|Mi|Gi|Ti|Pi|Ei)?$'" ); } @@ -436,7 +436,7 @@ mod tests { assert_eq!(errors.len(), 1); assert_eq!( - errors.get(0).unwrap().to_string(), + errors.first().unwrap().to_string(), r"request_cpu: 'invalid' doesn't match regex '^\d+(.\d+)?(m|K|M|G|T|P|E|Ki|Mi|Gi|Ti|Pi|Ei)?$'" ); } @@ -449,7 +449,7 @@ mod tests { assert_eq!(errors.len(), 1); assert_eq!( - errors.get(0).unwrap().to_string(), + errors.first().unwrap().to_string(), r"limit_memory: 'invalid' doesn't match regex '^\d+(.\d+)?(m|K|M|G|T|P|E|Ki|Mi|Gi|Ti|Pi|Ei)?$'" ); } @@ -462,7 +462,7 @@ mod tests { assert_eq!(errors.len(), 1); assert_eq!( - errors.get(0).unwrap().to_string(), + errors.first().unwrap().to_string(), r"limit_cpu: 'invalid' doesn't match regex '^\d+(.\d+)?(m|K|M|G|T|P|E|Ki|Mi|Gi|Ti|Pi|Ei)?$'" ); } @@ -478,7 +478,7 @@ mod tests { assert_eq!(errors.len(), 2); assert_eq!( - errors.get(0).unwrap().to_string(), + errors.first().unwrap().to_string(), r"limit_cpu: 'invalid' doesn't match regex '^\d+(.\d+)?(m|K|M|G|T|P|E|Ki|Mi|Gi|Ti|Pi|Ei)?$'" ); assert_eq!( diff --git a/crates/examples/Cargo.toml b/crates/examples/Cargo.toml index dce89cf..a39fd0e 100644 --- a/crates/examples/Cargo.toml +++ b/crates/examples/Cargo.toml @@ -13,3 +13,5 @@ provider = { path = "../provider" } # to review the exports for neeeded types support = { path = "../support" } tokio = { workspace = true } +futures = { workspace = true } +subxt = { workspace = true } diff --git a/crates/examples/examples/simple_network_example.rs b/crates/examples/examples/simple_network_example.rs index fb42ef2..a8731cc 100644 --- a/crates/examples/examples/simple_network_example.rs +++ b/crates/examples/examples/simple_network_example.rs @@ -1,6 +1,7 @@ // use std::time::Duration; use configuration::NetworkConfig; +use futures::stream::StreamExt; use orchestrator::Orchestrator; use provider::NativeProvider; use support::{fs::local::LocalFileSystem, process::os::OsProcessManager}; @@ -14,11 +15,18 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> { let pm = OsProcessManager; let provider = NativeProvider::new(fs.clone(), pm); let orchestrator = Orchestrator::new(fs, provider); - orchestrator.spawn(config).await?; + let network = orchestrator.spawn(config).await?; println!("🚀🚀🚀🚀 network deployed"); - // For now let just loop.... - #[allow(clippy::empty_loop)] - loop {} - // Ok(()) + let client = network + .get_node("alice")? + .client::<subxt::PolkadotConfig>() + .await?; + let mut blocks = client.blocks().subscribe_finalized().await?.take(3); + + while let Some(block) = blocks.next().await { + println!("Block #{}", block?.header().number); + } + + Ok(()) } diff --git a/crates/orchestrator/src/network/node.rs b/crates/orchestrator/src/network/node.rs index 0be0b88..904f96f 100644 --- a/crates/orchestrator/src/network/node.rs +++ b/crates/orchestrator/src/network/node.rs @@ -3,6 +3,7 @@ use std::{sync::Arc, time::Duration}; use anyhow::anyhow; use prom_metrics_parser::MetricMap; use provider::DynNode; +use subxt::{backend::rpc::RpcClient, OnlineClient}; use tokio::sync::RwLock; use crate::network_spec::node::NodeSpec; @@ -45,6 +46,18 @@ impl NetworkNode { Ok(()) } + /// Get the rpc client for the node + pub async fn rpc(&self) -> Result<RpcClient, subxt::Error> { + RpcClient::from_url(&self.ws_uri).await + } + + /// Get the online client for the node + pub async fn client<Config: subxt::Config>( + &self, + ) -> Result<OnlineClient<Config>, subxt::Error> { + OnlineClient::from_url(&self.ws_uri).await + } + /// Resume the node, this is implemented by resuming the /// actual process (e.g polkadot) with sendig `SIGCONT` signal pub async fn resume(&self) -> Result<(), anyhow::Error> { diff --git a/crates/orchestrator/src/network_spec.rs b/crates/orchestrator/src/network_spec.rs index 13f49b8..a38a4f2 100644 --- a/crates/orchestrator/src/network_spec.rs +++ b/crates/orchestrator/src/network_spec.rs @@ -81,7 +81,7 @@ mod tests { .unwrap(); let network_spec = NetworkSpec::from_config(&config).await.unwrap(); - let alice = network_spec.relaychain.nodes.get(0).unwrap(); + let alice = network_spec.relaychain.nodes.first().unwrap(); let bob = network_spec.relaychain.nodes.get(1).unwrap(); assert_eq!(alice.command.as_str(), "polkadot"); assert_eq!(bob.command.as_str(), "polkadot1"); @@ -90,7 +90,7 @@ mod tests { // paras assert_eq!(network_spec.parachains.len(), 1); - let para_100 = network_spec.parachains.get(0).unwrap(); + let para_100 = network_spec.parachains.first().unwrap(); assert_eq!(para_100.id, 100); } } diff --git a/crates/orchestrator/src/spawner.rs b/crates/orchestrator/src/spawner.rs index 0ff0e71..18db51f 100644 --- a/crates/orchestrator/src/spawner.rs +++ b/crates/orchestrator/src/spawner.rs @@ -108,20 +108,20 @@ where let (program, args) = match ctx.role { // Collator should be `non-cumulus` one (e.g adder/undying) ZombieRole::Node | ZombieRole::Collator => { - let maybe_para_id = ctx.parachain.map(|para| para.id); generators::generate_node_command(node, gen_opts, maybe_para_id) }, ZombieRole::CumulusCollator => { - let para = ctx.parachain.expect("parachain must be part of the context, this is a bug"); + let para = ctx + .parachain + .expect("parachain must be part of the context, this is a bug"); let full_p2p = generators::generate_node_port(None)?; generators::generate_node_command_cumulus(node, gen_opts, para.id, full_p2p.0) - } - _ => unreachable!() - // TODO: do we need those? - // ZombieRole::Bootnode => todo!(), - // ZombieRole::Companion => todo!(), + }, + _ => unreachable!(), /* TODO: do we need those? + * ZombieRole::Bootnode => todo!(), + * ZombieRole::Companion => todo!(), */ }; println!("\n"); -- GitLab