From c9b942d0c90e017d4559b67654bea194c810676a Mon Sep 17 00:00:00 2001 From: David Craven Date: Tue, 9 Jun 2020 18:26:54 +0200 Subject: [PATCH 1/7] Add support for light clients. --- .github/workflows/rust.yml | 4 +- Cargo.toml | 59 +++--- client/.gitignore | 1 + client/Cargo.toml | 23 +++ client/gen-chain-spec.sh | 4 + client/purge-chain.sh | 3 + client/run.sh | 3 + client/src/lib.rs | 361 +++++++++++++++++++++++++++++++++++++ proc-macro/Cargo.toml | 14 +- src/frame/balances.rs | 6 +- src/lib.rs | 58 ++++-- src/runtimes.rs | 25 +++ 12 files changed, 504 insertions(+), 57 deletions(-) create mode 100644 client/.gitignore create mode 100644 client/Cargo.toml create mode 100755 client/gen-chain-spec.sh create mode 100755 client/purge-chain.sh create mode 100755 client/run.sh create mode 100644 client/src/lib.rs diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 48d6fd2823..7f84d67bbc 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -22,7 +22,7 @@ jobs: run: cargo +nightly fmt --all -- --check - name: build - run: cargo build --verbose + run: cargo build --workspace --verbose - name: test - run: cargo test --verbose + run: cargo test --workspace --verbose diff --git a/Cargo.toml b/Cargo.toml index 254d303767..c248afa4c8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = [".", "proc-macro"] +members = [".", "client", "proc-macro"] [package] name = "substrate-subxt" @@ -16,33 +16,44 @@ description = "Submit extrinsics (transactions) to a substrate node via RPC" keywords = ["parity", "substrate", "blockchain"] include = ["Cargo.toml", "src/**/*.rs", "README.md", "LICENSE"] +[features] +client = ["substrate-subxt-client"] + [dependencies] -log = "0.4" -thiserror = "1.0" +log = "0.4.8" +thiserror = "1.0.19" futures = "0.3.5" -jsonrpsee = { version = "0.1", features = ["ws"] } -num-traits = { version = "0.2", default-features = false } -serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" -url = "2.1" +jsonrpsee = { version = "0.1.0", features = ["ws"] } +num-traits = { version = "0.2.11", default-features = false } +serde = { version = "1.0.111", features = ["derive"] } +serde_json = "1.0.53" +url = "2.1.1" codec = { package = "parity-scale-codec", version = "1.3", default-features = false, features = ["derive", "full"] } -frame-metadata = { version = "11.0.0-rc2", package = "frame-metadata" } -frame-support = { version = "2.0.0-rc2", package = "frame-support" } -sp-runtime = { version = "2.0.0-rc2", package = "sp-runtime" } -sp-version = { version = "2.0.0-rc2", package = "sp-version" } -pallet-indices = { version = "2.0.0-rc2", package = "pallet-indices" } -hex = "0.4.0" -sp-rpc = { version = "2.0.0-rc2", package = "sp-rpc" } -sp-core = { version = "2.0.0-rc2", package = "sp-core" } -sc-rpc-api = { version = "0.8.0-rc2", package = "sc-rpc-api" } -sp-transaction-pool = { version = "2.0.0-rc2", package = "sp-transaction-pool" } +frame-metadata = { version = "11.0.0-rc3", package = "frame-metadata" } +frame-support = { version = "2.0.0-rc3", package = "frame-support" } +sp-runtime = { version = "2.0.0-rc3", package = "sp-runtime" } +sp-version = { version = "2.0.0-rc3", package = "sp-version" } +pallet-indices = { version = "2.0.0-rc3", package = "pallet-indices" } +hex = "0.4.2" +sp-rpc = { version = "2.0.0-rc3", package = "sp-rpc" } +sp-core = { version = "2.0.0-rc3", package = "sp-core" } +sc-rpc-api = { version = "0.8.0-rc3", package = "sc-rpc-api" } +sp-transaction-pool = { version = "2.0.0-rc3", package = "sp-transaction-pool" } +substrate-subxt-client = { path = "client", optional = true } substrate-subxt-proc-macro = { version = "0.8.0", path = "proc-macro" } [dev-dependencies] -async-std = { version = "1.5.0", features = ["attributes"] } -env_logger = "0.7" -wabt = "0.9" -frame-system = { version = "2.0.0-rc2", package = "frame-system" } -pallet-balances = { version = "2.0.0-rc2", package = "pallet-balances" } -sp-keyring = { version = "2.0.0-rc2", package = "sp-keyring" } +async-std = { version = "=1.5.0", features = ["attributes"] } +env_logger = "0.7.1" +wabt = "0.9.2" +frame-system = { version = "2.0.0-rc3", package = "frame-system" } +node-template = { git = "https://github.com/dvc94ch/substrate", branch = "node-template" } +pallet-balances = { version = "2.0.0-rc3", package = "pallet-balances" } +sp-keyring = { version = "2.0.0-rc3", package = "sp-keyring" } +substrate-subxt-client = { path = "client" } +tempdir = "0.3.7" + +[patch.crates-io] +sc-network = { git = "https://github.com/dvc94ch/substrate", branch = "node-template" } +sc-service = { git = "https://github.com/dvc94ch/substrate", branch = "node-template" } diff --git a/client/.gitignore b/client/.gitignore new file mode 100644 index 0000000000..3005d75f6c --- /dev/null +++ b/client/.gitignore @@ -0,0 +1 @@ +dev-chain.json diff --git a/client/Cargo.toml b/client/Cargo.toml new file mode 100644 index 0000000000..4c81131a8e --- /dev/null +++ b/client/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "substrate-subxt-client" +version = "0.1.0" +authors = ["David Craven "] +edition = "2018" + +[dependencies] +async-std = "=1.5.0" +futures = { version = "0.3.5", features = ["compat"] } +futures01 = { package = "futures", version = "0.1.29" } +jsonrpsee = "0.1.0" +log = "0.4.8" +sc-network = { version = "0.8.0-rc3", default-features = false } +sc-service = { version = "0.8.0-rc3", default-features = false } +serde_json = "1.0.53" +sp-keyring = "2.0.0-rc3" +thiserror = "1.0.19" + +[dev-dependencies] +async-std = { version = "=1.5.0", features = ["attributes"] } +env_logger = "0.7.1" +node-template = { git = "https://github.com/dvc94ch/substrate", branch = "node-template" } +substrate-subxt = { path = ".." } diff --git a/client/gen-chain-spec.sh b/client/gen-chain-spec.sh new file mode 100755 index 0000000000..c4bcc3bf3a --- /dev/null +++ b/client/gen-chain-spec.sh @@ -0,0 +1,4 @@ +#!/bin/sh +NODE_TEMPLATE=../../substrate/target/release/node-template +$NODE_TEMPLATE purge-chain --dev +$NODE_TEMPLATE build-spec --dev > dev-chain.json diff --git a/client/purge-chain.sh b/client/purge-chain.sh new file mode 100755 index 0000000000..0327376495 --- /dev/null +++ b/client/purge-chain.sh @@ -0,0 +1,3 @@ +#!/bin/sh +NODE_TEMPLATE=../../substrate/target/release/node-template +$NODE_TEMPLATE purge-chain --chain=dev-chain.json diff --git a/client/run.sh b/client/run.sh new file mode 100755 index 0000000000..327a07a0bb --- /dev/null +++ b/client/run.sh @@ -0,0 +1,3 @@ +#!/bin/sh +NODE_TEMPLATE=../../substrate/target/release/node-template +$NODE_TEMPLATE --chain=dev-chain.json --alice diff --git a/client/src/lib.rs b/client/src/lib.rs new file mode 100644 index 0000000000..58178a81da --- /dev/null +++ b/client/src/lib.rs @@ -0,0 +1,361 @@ +// Copyright 2019-2020 Parity Technologies (UK) Ltd. +// This file is part of substrate-subxt. +// +// subxt is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// subxt is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with substrate-subxt. If not, see . + +use async_std::task; +use futures::{ + compat::{ + Compat01As03, + Compat01As03Sink, + Sink01CompatExt, + Stream01CompatExt, + }, + future::poll_fn, + sink::SinkExt, + stream::{ + Stream, + StreamExt, + }, +}; +use futures01::sync::mpsc; +use jsonrpsee::{ + common::{ + Request, + Response, + }, + transport::TransportClient, +}; +use sc_network::config::TransportConfig; +pub use sc_service::{ + config::DatabaseConfig, + Error as ServiceError, +}; +use sc_service::{ + config::{ + KeystoreConfig, + NetworkConfiguration, + TaskType, + }, + AbstractService, + ChainSpec, + Configuration, + RpcSession, +}; +use std::{ + future::Future, + pin::Pin, + sync::Arc, + task::Poll, +}; +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum SubxtClientError { + #[error("{0}")] + Json(#[from] serde_json::Error), + #[error("{0}")] + Mpsc(#[from] mpsc::SendError), +} + +#[derive(Clone, Copy, Debug)] +pub enum Role { + Light, + Authority(sp_keyring::AccountKeyring), +} + +impl From for sc_service::Role { + fn from(role: Role) -> Self { + match role { + Role::Light => Self::Light, + Role::Authority(_) => { + Self::Authority { + sentry_nodes: Default::default(), + } + } + } + } +} + +impl From for Option { + fn from(role: Role) -> Self { + match role { + Role::Light => None, + Role::Authority(key) => Some(key.to_seed()), + } + } +} + +#[derive(Clone)] +pub struct SubxtClientConfig { + pub impl_name: &'static str, + pub impl_version: &'static str, + pub author: &'static str, + pub copyright_start_year: i32, + pub db: DatabaseConfig, + pub builder: fn(Configuration) -> Result, + pub chain_spec: C, + pub role: Role, +} + +pub struct SubxtClient { + to_back: Compat01As03Sink, String>, + from_back: Compat01As03>, +} + +impl SubxtClient { + pub fn new( + config: SubxtClientConfig, + ) -> Result { + let (to_back, from_front) = mpsc::channel(4); + let (to_front, from_back) = mpsc::channel(4); + start_subxt_client(config, from_front, to_front)?; + Ok(Self { + to_back: to_back.sink_compat(), + from_back: from_back.compat(), + }) + } +} + +impl TransportClient for SubxtClient { + type Error = SubxtClientError; + + fn send_request<'a>( + &'a mut self, + request: Request, + ) -> Pin> + Send + 'a>> { + Box::pin(async move { + let request = serde_json::to_string(&request)?; + self.to_back.send(request).await?; + Ok(()) + }) + } + + fn next_response<'a>( + &'a mut self, + ) -> Pin> + Send + 'a>> { + Box::pin(async move { + let response = self + .from_back + .next() + .await + .expect("channel shouldn't close") + .unwrap(); + Ok(serde_json::from_str(&response)?) + }) + } +} + +impl From for jsonrpsee::Client { + fn from(client: SubxtClient) -> Self { + let client = jsonrpsee::raw::RawClient::new(client); + jsonrpsee::Client::new(client) + } +} + +fn start_subxt_client( + config: SubxtClientConfig, + from_front: mpsc::Receiver, + to_front: mpsc::Sender, +) -> Result<(), ServiceError> { + let mut network = NetworkConfiguration::new( + format!("{} (subxt client)", config.chain_spec.name()), + "unknown", + Default::default(), + None, + ); + network.boot_nodes = config.chain_spec.boot_nodes().to_vec(); + network.transport = TransportConfig::Normal { + enable_mdns: true, + allow_private_ipv4: true, + wasm_external_transport: None, + use_yamux_flow_control: true, + }; + let service_config = Configuration { + network, + impl_name: config.impl_name, + impl_version: config.impl_version, + chain_spec: Box::new(config.chain_spec), + role: config.role.into(), + task_executor: Arc::new(move |fut, ty| { + match ty { + TaskType::Async => task::spawn(fut), + TaskType::Blocking => task::spawn_blocking(|| task::block_on(fut)), + }; + }), + database: config.db, + keystore: KeystoreConfig::InMemory, + max_runtime_instances: 8, + announce_block: true, + dev_key_seed: config.role.into(), + + telemetry_endpoints: Default::default(), + telemetry_external_transport: Default::default(), + default_heap_pages: Default::default(), + disable_grandpa: Default::default(), + execution_strategies: Default::default(), + force_authoring: Default::default(), + offchain_worker: Default::default(), + prometheus_config: Default::default(), + pruning: Default::default(), + rpc_cors: Default::default(), + rpc_http: Default::default(), + rpc_ws: Default::default(), + rpc_ws_max_connections: Default::default(), + rpc_methods: Default::default(), + state_cache_child_ratio: Default::default(), + state_cache_size: Default::default(), + tracing_receiver: Default::default(), + tracing_targets: Default::default(), + transaction_pool: Default::default(), + wasm_method: Default::default(), + }; + + log::info!("{}", service_config.impl_name); + log::info!("✌️ version {}", service_config.impl_version); + log::info!("❤️ by {}, {}", config.author, config.copyright_start_year); + log::info!( + "📋 Chain specification: {}", + service_config.chain_spec.name() + ); + log::info!("🏷 Node name: {}", service_config.network.node_name); + log::info!("👤 Role: {:?}", service_config.role); + + // Create the service. This is the most heavy initialization step. + let mut service = (config.builder)(service_config)?; + + // Spawn background task. + let session = RpcSession::new(to_front.clone()); + let mut from_front = from_front.compat(); + task::spawn(poll_fn(move |cx| { + loop { + match Pin::new(&mut from_front).poll_next(cx) { + Poll::Ready(Some(message)) => { + let mut to_front = to_front.clone().sink_compat(); + let message = message.unwrap(); + let fut = service.rpc_query(&session, &message); + task::spawn(async move { + if let Some(response) = fut.await { + to_front.send(response).await.ok(); + } + }); + } + Poll::Pending => break, + Poll::Ready(None) => return Poll::Ready(()), + } + } + + loop { + match Pin::new(&mut service).poll(cx) { + Poll::Ready(Ok(())) => return Poll::Ready(()), + Poll::Pending => return Poll::Pending, + Poll::Ready(Err(e)) => log::error!("{}", e), + } + } + })); + + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use sp_keyring::AccountKeyring; + use substrate_subxt::{ + balances::TransferCallExt, + ClientBuilder, + KusamaRuntime as NodeTemplateRuntime, + PairSigner, + }; + + #[async_std::test] + #[ignore] + async fn test_client() { + env_logger::try_init().ok(); + let client = ClientBuilder::::new() + .build() + .await + .unwrap(); + let signer = PairSigner::new(AccountKeyring::Alice.pair()); + let to = AccountKeyring::Bob.to_account_id().into(); + client + .transfer_and_watch(&signer, &to, 10_000) + .await + .unwrap(); + } + + #[async_std::test] + #[ignore] + async fn test_light_client() { + env_logger::try_init().ok(); + let chain_spec = node_template::chain_spec::ChainSpec::from_json_bytes( + &include_bytes!("../dev-chain.json")[..], + ) + .unwrap(); + let config = SubxtClientConfig { + impl_name: "substrate-subxt-light-client", + impl_version: "0.0.1", + author: "David Craven", + copyright_start_year: 2020, + db: DatabaseConfig::RocksDb { + path: "/tmp/subxt-light-client".into(), + cache_size: 64, + }, + builder: node_template::service::new_light, + chain_spec, + role: Role::Light, + }; + let client = ClientBuilder::::new() + .set_client(SubxtClient::new(config).unwrap()) + .build() + .await + .unwrap(); + let signer = PairSigner::new(AccountKeyring::Alice.pair()); + let to = AccountKeyring::Bob.to_account_id().into(); + client + .transfer_and_watch(&signer, &to, 10_000) + .await + .unwrap(); + } + + #[async_std::test] + async fn test_full_client() { + env_logger::try_init().ok(); + let chain_spec = node_template::chain_spec::development_config(); + let config = SubxtClientConfig { + impl_name: "substrate-subxt-full-client", + impl_version: "0.0.1", + author: "David Craven", + copyright_start_year: 2020, + db: DatabaseConfig::RocksDb { + path: "/tmp/subxt-full-client".into(), + cache_size: 128, + }, + builder: node_template::service::new_full, + chain_spec, + role: Role::Authority(AccountKeyring::Alice), + }; + let client = ClientBuilder::::new() + .set_client(SubxtClient::new(config).unwrap()) + .build() + .await + .unwrap(); + let signer = PairSigner::new(AccountKeyring::Alice.pair()); + let to = AccountKeyring::Bob.to_account_id().into(); + client + .transfer_and_watch(&signer, &to, 10_000) + .await + .unwrap(); + } +} diff --git a/proc-macro/Cargo.toml b/proc-macro/Cargo.toml index 44639ea14a..c88b64d32e 100644 --- a/proc-macro/Cargo.toml +++ b/proc-macro/Cargo.toml @@ -16,20 +16,20 @@ proc-macro = true [dependencies] heck = "0.3.1" -proc-macro2 = "1.0.10" +proc-macro2 = "1.0.18" proc-macro-crate = "0.1.4" -quote = "1.0.3" -syn = "1.0.17" -synstructure = "0.12.3" +quote = "1.0.7" +syn = "1.0.30" +synstructure = "0.12.4" [dev-dependencies] -async-std = { version = "1.5.0", features = ["attributes"] } +async-std = { version = "=1.5.0", features = ["attributes"] } codec = { package = "parity-scale-codec", version = "1.3.0", features = ["derive"] } env_logger = "0.7.1" pretty_assertions = "0.6.1" -sp-keyring = "2.0.0-rc2" +sp-keyring = "2.0.0-rc3" substrate-subxt = { path = ".." } -trybuild = "1.0.25" +trybuild = "1.0.28" [[test]] name = "balances" diff --git a/src/frame/balances.rs b/src/frame/balances.rs index d3b0e45aff..6f6eb4ebc3 100644 --- a/src/frame/balances.rs +++ b/src/frame/balances.rs @@ -142,19 +142,17 @@ mod tests { }); #[async_std::test] - #[ignore] // requires locally running substrate node async fn test_state_total_issuance() { env_logger::try_init().ok(); - let client = test_client().await; + let (client, _) = test_client().await; let total_issuance = client.total_issuance(None).await.unwrap(); assert_ne!(total_issuance, 0); } #[async_std::test] - #[ignore] // requires locally running substrate node async fn test_state_read_free_balance() { env_logger::try_init().ok(); - let client = test_client().await; + let (client, _) = test_client().await; let account = AccountKeyring::Alice.to_account_id(); let info = client.account(&account, None).await.unwrap(); assert_ne!(info.data.free, 0); diff --git a/src/lib.rs b/src/lib.rs index a972861a4a..bc8239f5f9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -42,6 +42,9 @@ #[macro_use] extern crate substrate_subxt_proc_macro; +#[cfg(feature = "client")] +pub use substrate_subxt_client as client; + pub use sp_core; pub use sp_runtime; @@ -437,6 +440,7 @@ impl codec::Encode for Encoded { #[cfg(test)] mod tests { + use super::*; use sp_core::{ storage::{ well_known_keys, @@ -448,24 +452,44 @@ mod tests { AccountKeyring, Ed25519Keyring, }; - - use super::*; - - pub(crate) async fn test_client() -> Client { - ClientBuilder::new() + use substrate_subxt_client::{ + DatabaseConfig, + Role, + SubxtClient, + SubxtClientConfig, + }; + use tempdir::TempDir; + + pub(crate) async fn test_client() -> (Client, TempDir) { + let tmp = TempDir::new("subxt-").expect("failed to create tempdir"); + let config = SubxtClientConfig { + impl_name: "substrate-subxt-full-client", + impl_version: "0.0.1", + author: "substrate subxt", + copyright_start_year: 2020, + db: DatabaseConfig::RocksDb { + path: tmp.path().into(), + cache_size: 128, + }, + builder: node_template::service::new_full, + chain_spec: node_template::chain_spec::development_config(), + role: Role::Authority(AccountKeyring::Alice), + }; + let client = ClientBuilder::new() + .set_client(SubxtClient::new(config).expect("Error creating subxt client")) .build() .await - .expect("Error creating client") + .expect("Error creating client"); + (client, tmp) } #[async_std::test] - #[ignore] // requires locally running substrate node async fn test_tx_transfer_balance() { env_logger::try_init().ok(); let mut signer = PairSigner::new(AccountKeyring::Alice.pair()); let dest = AccountKeyring::Bob.to_account_id().into(); - let client = test_client().await; + let (client, _) = test_client().await; let nonce = client .account(&AccountKeyring::Alice.to_account_id(), None) .await @@ -498,24 +522,21 @@ mod tests { } #[async_std::test] - #[ignore] // requires locally running substrate node async fn test_getting_hash() { - let client = test_client().await; + let (client, _) = test_client().await; client.block_hash(None).await.unwrap(); } #[async_std::test] - #[ignore] // requires locally running substrate node async fn test_getting_block() { - let client = test_client().await; + let (client, _) = test_client().await; let block_hash = client.block_hash(None).await.unwrap(); client.block(block_hash).await.unwrap(); } #[async_std::test] - #[ignore] // requires locally running substrate node async fn test_getting_read_proof() { - let client = test_client().await; + let (client, _) = test_client().await; let block_hash = client.block_hash(None).await.unwrap(); client .read_proof( @@ -530,29 +551,26 @@ mod tests { } #[async_std::test] - #[ignore] // requires locally running substrate node async fn test_chain_subscribe_blocks() { - let client = test_client().await; + let (client, _) = test_client().await; let mut blocks = client.subscribe_blocks().await.unwrap(); blocks.next().await; } #[async_std::test] - #[ignore] // requires locally running substrate node async fn test_chain_subscribe_finalized_blocks() { - let client = test_client().await; + let (client, _) = test_client().await; let mut blocks = client.subscribe_finalized_blocks().await.unwrap(); blocks.next().await; } #[async_std::test] - #[ignore] // requires locally running substrate node async fn test_create_raw_payload() { let signer_pair = Ed25519Keyring::Alice.pair(); let signer_account_id = Ed25519Keyring::Alice.to_account_id(); let dest = AccountKeyring::Bob.to_account_id().into(); - let client = test_client().await; + let (client, _) = test_client().await; // create raw payload with AccoundId and sign it let raw_payload = client diff --git a/src/runtimes.rs b/src/runtimes.rs index 3ced7ce0b5..4e247d5048 100644 --- a/src/runtimes.rs +++ b/src/runtimes.rs @@ -61,6 +61,31 @@ impl Balances for DefaultNodeRuntime { impl Contracts for DefaultNodeRuntime {} +/// Concrete type definitions compatible with the node template. +/// +/// # Note +/// +/// Main difference is `type Address = AccountId`. +/// Also the contracts module is not part of the node template runtime. +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct NodeTemplateRuntime; + +impl System for NodeTemplateRuntime { + type Index = u32; + type BlockNumber = u32; + type Hash = sp_core::H256; + type Hashing = BlakeTwo256; + type AccountId = <::Signer as IdentifyAccount>::AccountId; + type Address = Self::AccountId; + type Header = Header; + type Extrinsic = OpaqueExtrinsic; + type AccountData = AccountData<::Balance>; +} + +impl Balances for NodeTemplateRuntime { + type Balance = u128; +} + /// Concrete type definitions compatible with those for kusama, v0.7 /// /// # Note From e8b115cd6c91c1269fc5d217dbbfbaf1ef0bde9b Mon Sep 17 00:00:00 2001 From: David Craven Date: Tue, 9 Jun 2020 18:51:27 +0200 Subject: [PATCH 2/7] Add wasm toolchain to ci. --- .github/workflows/rust.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 7f84d67bbc..b4941647da 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -16,7 +16,8 @@ jobs: - name: setup run: | - rustup install nightly --profile default + rustup install nightly --profile default + rustup +nightly target add wasm32-unknown-unknown - name: fmt run: cargo +nightly fmt --all -- --check From a265ee28c5553ebee48ba349d750ce52dbe63c2c Mon Sep 17 00:00:00 2001 From: David Craven Date: Tue, 9 Jun 2020 19:35:35 +0200 Subject: [PATCH 3/7] Fix ci tests. --- client/src/lib.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/client/src/lib.rs b/client/src/lib.rs index 58178a81da..7efb817e1c 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -271,6 +271,7 @@ fn start_subxt_client( #[cfg(test)] mod tests { use super::*; + use async_std::path::Path; use sp_keyring::AccountKeyring; use substrate_subxt::{ balances::TransferCallExt, @@ -299,10 +300,11 @@ mod tests { #[ignore] async fn test_light_client() { env_logger::try_init().ok(); - let chain_spec = node_template::chain_spec::ChainSpec::from_json_bytes( - &include_bytes!("../dev-chain.json")[..], - ) - .unwrap(); + let chain_spec_path = + Path::new(env!("CARGO_MANIFEST_DIR")).join("dev-chain.json"); + let bytes = async_std::fs::read(chain_spec_path).await.unwrap(); + let chain_spec = + node_template::chain_spec::ChainSpec::from_json_bytes(bytes).unwrap(); let config = SubxtClientConfig { impl_name: "substrate-subxt-light-client", impl_version: "0.0.1", From 116097c20dd6ce8d86b5dd53bcf41c726f404e91 Mon Sep 17 00:00:00 2001 From: David Craven Date: Wed, 10 Jun 2020 14:04:26 +0200 Subject: [PATCH 4/7] Address review comments. --- Cargo.toml | 6 +++--- client/Cargo.toml | 11 +++++++++-- client/src/lib.rs | 22 ++++++++++++++++++++++ 3 files changed, 34 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c248afa4c8..f1e867bcea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,12 +48,12 @@ async-std = { version = "=1.5.0", features = ["attributes"] } env_logger = "0.7.1" wabt = "0.9.2" frame-system = { version = "2.0.0-rc3", package = "frame-system" } -node-template = { git = "https://github.com/dvc94ch/substrate", branch = "node-template" } +node-template = { git = "https://github.com/paritytech/substrate" } pallet-balances = { version = "2.0.0-rc3", package = "pallet-balances" } sp-keyring = { version = "2.0.0-rc3", package = "sp-keyring" } substrate-subxt-client = { path = "client" } tempdir = "0.3.7" [patch.crates-io] -sc-network = { git = "https://github.com/dvc94ch/substrate", branch = "node-template" } -sc-service = { git = "https://github.com/dvc94ch/substrate", branch = "node-template" } +sc-network = { git = "https://github.com/paritytech/substrate" } +sc-service = { git = "https://github.com/paritytech/substrate" } diff --git a/client/Cargo.toml b/client/Cargo.toml index 4c81131a8e..c7ad49ef1e 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -1,9 +1,16 @@ [package] name = "substrate-subxt-client" version = "0.1.0" -authors = ["David Craven "] +authors = ["David Craven ", "Parity Technologies "] edition = "2018" +license = "GPL-3.0" +repository = "https://github.com/paritytech/substrate-subxt" +documentation = "https://docs.rs/substrate-subxt-client" +homepage = "https://www.parity.io/" +description = "Embed a substrate node into your subxt application." +keywords = ["parity", "substrate", "blockchain"] + [dependencies] async-std = "=1.5.0" futures = { version = "0.3.5", features = ["compat"] } @@ -19,5 +26,5 @@ thiserror = "1.0.19" [dev-dependencies] async-std = { version = "=1.5.0", features = ["attributes"] } env_logger = "0.7.1" -node-template = { git = "https://github.com/dvc94ch/substrate", branch = "node-template" } +node-template = { git = "https://github.com/paritytech/substrate" } substrate-subxt = { path = ".." } diff --git a/client/src/lib.rs b/client/src/lib.rs index 7efb817e1c..2f21847fb8 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -14,6 +14,10 @@ // You should have received a copy of the GNU General Public License // along with substrate-subxt. If not, see . +//! Client for embedding substrate nodes. + +#![deny(missing_docs)] + use async_std::task; use futures::{ compat::{ @@ -61,17 +65,23 @@ use std::{ }; use thiserror::Error; +/// Error thrown by the client. #[derive(Debug, Error)] pub enum SubxtClientError { + /// Failed to parse json rpc message. #[error("{0}")] Json(#[from] serde_json::Error), + /// Channel closed. #[error("{0}")] Mpsc(#[from] mpsc::SendError), } +/// Role of the node. #[derive(Clone, Copy, Debug)] pub enum Role { + /// Light client. Light, + /// A full node (maninly used for testing purposes). Authority(sp_keyring::AccountKeyring), } @@ -97,24 +107,35 @@ impl From for Option { } } +/// Client configuration. #[derive(Clone)] pub struct SubxtClientConfig { + /// Name of the implementation. pub impl_name: &'static str, + /// Version of the implementation. pub impl_version: &'static str, + /// Author of the implementation. pub author: &'static str, + /// Copyright start year. pub copyright_start_year: i32, + /// Database configuration. pub db: DatabaseConfig, + /// Service builder. pub builder: fn(Configuration) -> Result, + /// Chain specification. pub chain_spec: C, + /// Role of the node. pub role: Role, } +/// Client for an embedded substrate node. pub struct SubxtClient { to_back: Compat01As03Sink, String>, from_back: Compat01As03>, } impl SubxtClient { + /// Create a new client from a config. pub fn new( config: SubxtClientConfig, ) -> Result { @@ -199,6 +220,7 @@ fn start_subxt_client( max_runtime_instances: 8, announce_block: true, dev_key_seed: config.role.into(), + base_path: None, telemetry_endpoints: Default::default(), telemetry_external_transport: Default::default(), From ff96861da4992d20a6112bc86417775e82e349cd Mon Sep 17 00:00:00 2001 From: David Craven Date: Wed, 10 Jun 2020 14:51:02 +0200 Subject: [PATCH 5/7] Use expect instead of unwrap. --- client/src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/src/lib.rs b/client/src/lib.rs index 2f21847fb8..e500721e3a 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -265,7 +265,8 @@ fn start_subxt_client( match Pin::new(&mut from_front).poll_next(cx) { Poll::Ready(Some(message)) => { let mut to_front = to_front.clone().sink_compat(); - let message = message.unwrap(); + let message = message + .expect("v1 streams require an error type; Stream of String can't fail; qed"); let fut = service.rpc_query(&session, &message); task::spawn(async move { if let Some(response) = fut.await { From 58f00de9475a36e5ff870611c0640dbc5e6a26df Mon Sep 17 00:00:00 2001 From: David Craven Date: Wed, 10 Jun 2020 18:32:03 +0200 Subject: [PATCH 6/7] Purge light client chain too. --- client/gen-chain-spec.sh | 1 + client/purge-chain.sh | 1 + 2 files changed, 2 insertions(+) diff --git a/client/gen-chain-spec.sh b/client/gen-chain-spec.sh index c4bcc3bf3a..ef05a3cada 100755 --- a/client/gen-chain-spec.sh +++ b/client/gen-chain-spec.sh @@ -2,3 +2,4 @@ NODE_TEMPLATE=../../substrate/target/release/node-template $NODE_TEMPLATE purge-chain --dev $NODE_TEMPLATE build-spec --dev > dev-chain.json +rm -rf /tmp/subxt-light-client diff --git a/client/purge-chain.sh b/client/purge-chain.sh index 0327376495..99400bfad5 100755 --- a/client/purge-chain.sh +++ b/client/purge-chain.sh @@ -1,3 +1,4 @@ #!/bin/sh NODE_TEMPLATE=../../substrate/target/release/node-template $NODE_TEMPLATE purge-chain --chain=dev-chain.json +rm -rf /tmp/subxt-light-client From fdb17a5a6f20b2262458b6bd021c06ef64624c67 Mon Sep 17 00:00:00 2001 From: David Craven Date: Thu, 11 Jun 2020 13:41:09 +0200 Subject: [PATCH 7/7] Add README section. --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 2d59d4716e..4a78b1febe 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,11 @@ See [examples](./examples). [substrate-api-client](https://github.com/scs/substrate-api-client) provides similar functionality. +## Subxt Client +By default the client builder will connect to a full node via rpc. The `subxt-client` helps +embedding a light client directly. It can also be used to embed a full node. This is especially +useful for testing and ci. + #### License