From 7f0847107c636ce91b0217374ac03344dd115ea5 Mon Sep 17 00:00:00 2001 From: David Craven Date: Thu, 25 Jun 2020 08:05:00 +0200 Subject: [PATCH] Events sub (#126) * Make event subscription logic more generic. * Fix build. * Add test-node. * Update deps. * Address review comments. --- Cargo.toml | 10 +- client/Cargo.toml | 3 +- client/gen-chain-spec.sh | 2 +- client/purge-chain.sh | 2 +- client/run.sh | 2 +- client/src/lib.rs | 21 +- proc-macro/Cargo.toml | 2 +- src/frame/balances.rs | 26 ++ src/lib.rs | 8 +- src/rpc.rs | 104 +++----- src/subscription.rs | 121 +++++++++ test-node/Cargo.toml | 42 ++++ test-node/build.rs | 26 ++ test-node/runtime/Cargo.toml | 70 ++++++ test-node/runtime/build.rs | 26 ++ test-node/runtime/src/lib.rs | 460 +++++++++++++++++++++++++++++++++++ test-node/src/chain_spec.rs | 161 ++++++++++++ test-node/src/cli.rs | 30 +++ test-node/src/command.rs | 84 +++++++ test-node/src/lib.rs | 18 ++ test-node/src/main.rs | 28 +++ test-node/src/service.rs | 303 +++++++++++++++++++++++ 22 files changed, 1451 insertions(+), 98 deletions(-) create mode 100644 src/subscription.rs create mode 100644 test-node/Cargo.toml create mode 100644 test-node/build.rs create mode 100644 test-node/runtime/Cargo.toml create mode 100644 test-node/runtime/build.rs create mode 100644 test-node/runtime/src/lib.rs create mode 100644 test-node/src/chain_spec.rs create mode 100644 test-node/src/cli.rs create mode 100644 test-node/src/command.rs create mode 100644 test-node/src/lib.rs create mode 100644 test-node/src/main.rs create mode 100644 test-node/src/service.rs diff --git a/Cargo.toml b/Cargo.toml index 0e167a12c391a..43aba9b02c971 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = [".", "client", "proc-macro"] +members = [".", "client", "proc-macro", "test-node"] [package] name = "substrate-subxt" @@ -25,7 +25,7 @@ thiserror = "1.0.20" futures = "0.3.5" jsonrpsee = { version = "0.1.0", features = ["ws"] } num-traits = { version = "0.2.12", default-features = false } -serde = { version = "1.0.113", features = ["derive"] } +serde = { version = "1.0.114", features = ["derive"] } serde_json = "1.0.55" url = "2.1.1" codec = { package = "parity-scale-codec", version = "1.3", default-features = false, features = ["derive", "full"] } @@ -48,12 +48,8 @@ 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/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/paritytech/substrate" } -sc-service = { git = "https://github.com/paritytech/substrate" } +test-node = { path = "test-node" } diff --git a/client/Cargo.toml b/client/Cargo.toml index 5264a4bb7d530..821cced7a771e 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -26,5 +26,6 @@ thiserror = "1.0.20" [dev-dependencies] async-std = { version = "=1.5.0", features = ["attributes"] } env_logger = "0.7.1" -node-template = { git = "https://github.com/paritytech/substrate" } substrate-subxt = { path = ".." } +tempdir = "0.3.7" +test-node = { path = "../test-node" } diff --git a/client/gen-chain-spec.sh b/client/gen-chain-spec.sh index ef05a3cada51a..14c19000fe0ef 100755 --- a/client/gen-chain-spec.sh +++ b/client/gen-chain-spec.sh @@ -1,5 +1,5 @@ #!/bin/sh -NODE_TEMPLATE=../../substrate/target/release/node-template +NODE_TEMPLATE=../target/release/test-node $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 99400bfad5905..4202e349ef914 100755 --- a/client/purge-chain.sh +++ b/client/purge-chain.sh @@ -1,4 +1,4 @@ #!/bin/sh -NODE_TEMPLATE=../../substrate/target/release/node-template +NODE_TEMPLATE=../target/release/test-node $NODE_TEMPLATE purge-chain --chain=dev-chain.json rm -rf /tmp/subxt-light-client diff --git a/client/run.sh b/client/run.sh index 327a07a0bbb58..6f67c6875ca35 100755 --- a/client/run.sh +++ b/client/run.sh @@ -1,3 +1,3 @@ #!/bin/sh -NODE_TEMPLATE=../../substrate/target/release/node-template +NODE_TEMPLATE=../target/release/test-node $NODE_TEMPLATE --chain=dev-chain.json --alice diff --git a/client/src/lib.rs b/client/src/lib.rs index f0a88e2e88cc3..a5176b0607153 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -60,7 +60,6 @@ use sc_service::{ use std::{ future::Future, pin::Pin, - sync::Arc, task::Poll, }; use thiserror::Error; @@ -209,7 +208,7 @@ fn start_subxt_client( impl_version: config.impl_version, chain_spec: Box::new(config.chain_spec), role: config.role.into(), - task_executor: Arc::new(move |fut, ty| { + task_executor: std::sync::Arc::new(move |fut, ty| { match ty { TaskType::Async => task::spawn(fut), TaskType::Blocking => task::spawn_blocking(|| task::block_on(fut)), @@ -220,7 +219,6 @@ 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(), @@ -233,7 +231,6 @@ fn start_subxt_client( pruning: Default::default(), rpc_cors: Default::default(), rpc_http: Default::default(), - rpc_ipc: Default::default(), rpc_ws: Default::default(), rpc_ws_max_connections: Default::default(), rpc_methods: Default::default(), @@ -303,6 +300,7 @@ mod tests { KusamaRuntime as NodeTemplateRuntime, PairSigner, }; + use tempdir::TempDir; #[async_std::test] #[ignore] @@ -328,17 +326,18 @@ mod tests { 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(); + test_node::chain_spec::ChainSpec::from_json_bytes(bytes).unwrap(); + let tmp = TempDir::new("subxt-").expect("failed to create tempdir"); 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(), + path: tmp.path().into(), cache_size: 64, }, - builder: node_template::service::new_light, + builder: test_node::service::new_light, chain_spec, role: Role::Light, }; @@ -358,18 +357,18 @@ mod tests { #[async_std::test] async fn test_full_client() { env_logger::try_init().ok(); - let chain_spec = node_template::chain_spec::development_config(); + 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: "David Craven", copyright_start_year: 2020, db: DatabaseConfig::RocksDb { - path: "/tmp/subxt-full-client".into(), + path: tmp.path().into(), cache_size: 128, }, - builder: node_template::service::new_full, - chain_spec, + builder: test_node::service::new_full, + chain_spec: test_node::chain_spec::development_config(), role: Role::Authority(AccountKeyring::Alice), }; let client = ClientBuilder::::new() diff --git a/proc-macro/Cargo.toml b/proc-macro/Cargo.toml index 84274c64eeff0..fb3c0b09d7914 100644 --- a/proc-macro/Cargo.toml +++ b/proc-macro/Cargo.toml @@ -20,7 +20,7 @@ proc-macro2 = "1.0.18" proc-macro-crate = "0.1.4" proc-macro-error = "1.0.2" quote = "1.0.7" -syn = "1.0.31" +syn = "1.0.33" synstructure = "0.12.4" [dev-dependencies] diff --git a/src/frame/balances.rs b/src/frame/balances.rs index 3d1a6111ba106..bb1bdad498a2b 100644 --- a/src/frame/balances.rs +++ b/src/frame/balances.rs @@ -114,10 +114,12 @@ mod tests { Error, RuntimeError, }, + events::EventsDecoder, signer::{ PairSigner, Signer, }, + subscription::EventSubscription, system::AccountStoreExt, tests::{ test_client, @@ -201,4 +203,28 @@ mod tests { panic!("expected an error"); } } + + #[async_std::test] + async fn test_transfer_subscription() { + env_logger::try_init().ok(); + let alice = PairSigner::new(AccountKeyring::Alice.pair()); + let bob = AccountKeyring::Bob.to_account_id(); + let (client, _) = test_client().await; + let sub = client.subscribe_events().await.unwrap(); + let mut decoder = EventsDecoder::::new(client.metadata().clone()); + decoder.with_balances(); + let mut sub = EventSubscription::::new(sub, decoder); + sub.filter_event::>(); + client.transfer(&alice, &bob, 10_000).await.unwrap(); + let raw = sub.next().await.unwrap().unwrap(); + let event = TransferEvent::::decode(&mut &raw.data[..]).unwrap(); + assert_eq!( + event, + TransferEvent { + from: alice.account_id().clone(), + to: bob.clone(), + amount: 10_000, + } + ); + } } diff --git a/src/lib.rs b/src/lib.rs index b374780ac26bc..689851488d5ef 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -68,6 +68,7 @@ mod metadata; mod rpc; mod runtimes; mod signer; +mod subscription; pub use crate::{ error::Error, @@ -87,6 +88,7 @@ pub use crate::{ }, runtimes::*, signer::*, + subscription::*, substrate_subxt_proc_macro::*, }; use crate::{ @@ -446,6 +448,7 @@ mod tests { pub(crate) type TestRuntime = crate::NodeTemplateRuntime; pub(crate) async fn test_client() -> (Client, TempDir) { + env_logger::try_init().ok(); let tmp = TempDir::new("subxt-").expect("failed to create tempdir"); let config = SubxtClientConfig { impl_name: "substrate-subxt-full-client", @@ -456,8 +459,8 @@ mod tests { path: tmp.path().into(), cache_size: 128, }, - builder: node_template::service::new_full, - chain_spec: node_template::chain_spec::development_config(), + builder: test_node::service::new_full, + chain_spec: test_node::chain_spec::development_config(), role: Role::Authority(AccountKeyring::Alice), }; let client = ClientBuilder::new() @@ -470,7 +473,6 @@ mod tests { #[async_std::test] 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(); diff --git a/src/rpc.rs b/src/rpc.rs index 3e85570761a4a..3663b07177983 100644 --- a/src/rpc.rs +++ b/src/rpc.rs @@ -70,13 +70,12 @@ use crate::{ RawEvent, }, frame::{ - system::{ - Phase, - System, - }, + system::System, Event, }, metadata::Metadata, + runtimes::Runtime, + subscription::EventSubscription, }; pub type ChainBlock = @@ -107,12 +106,12 @@ where } /// Client for substrate rpc interfaces -pub struct Rpc { +pub struct Rpc { client: Client, marker: PhantomData, } -impl Clone for Rpc { +impl Clone for Rpc { fn clone(&self) -> Self { Self { client: self.client.clone(), @@ -121,7 +120,7 @@ impl Clone for Rpc { } } -impl Rpc { +impl Rpc { pub fn new(client: Client) -> Self { Self { client, @@ -256,7 +255,7 @@ impl Rpc { /// Subscribe to substrate System Events pub async fn subscribe_events( &self, - ) -> Result::Hash>>, Error> { + ) -> Result>, Error> { let mut storage_key = twox_128(b"System").to_vec(); storage_key.extend(twox_128(b"Events").to_vec()); log::debug!("Events storage key {:?}", hex::encode(&storage_key)); @@ -360,14 +359,31 @@ impl Rpc { block_hash, signed_block.block.extrinsics.len() ); - wait_for_block_events( - decoder, - ext_hash, - signed_block, - block_hash, - events_sub, - ) - .await + let ext_index = signed_block + .block + .extrinsics + .iter() + .position(|ext| { + let hash = T::Hashing::hash_of(ext); + hash == ext_hash + }) + .ok_or_else(|| { + Error::Other(format!( + "Failed to find Extrinsic with hash {:?}", + ext_hash, + )) + })?; + let mut sub = EventSubscription::new(events_sub, decoder); + sub.filter_extrinsic(block_hash, ext_index); + let mut events = vec![]; + while let Some(event) = sub.next().await { + events.push(event?); + } + Ok(ExtrinsicSuccess { + block: block_hash, + extrinsic: ext_hash, + events, + }) } None => { Err(format!("Failed to find block {:?}", block_hash).into()) @@ -424,59 +440,3 @@ impl ExtrinsicSuccess { } } } - -/// Waits for events for the block triggered by the extrinsic -pub async fn wait_for_block_events( - decoder: EventsDecoder, - ext_hash: T::Hash, - signed_block: ChainBlock, - block_hash: T::Hash, - events_subscription: Subscription>, -) -> Result, Error> { - let ext_index = signed_block - .block - .extrinsics - .iter() - .position(|ext| { - let hash = T::Hashing::hash_of(ext); - hash == ext_hash - }) - .ok_or_else(|| { - Error::Other(format!("Failed to find Extrinsic with hash {:?}", ext_hash)) - })?; - - let mut subscription = events_subscription; - while let change_set = subscription.next().await { - // only interested in events for the given block - if change_set.block != block_hash { - continue - } - let mut events = Vec::new(); - for (_key, data) in change_set.changes { - if let Some(data) = data { - match decoder.decode_events(&mut &data.0[..]) { - Ok(raw_events) => { - for (phase, event) in raw_events { - if let Phase::ApplyExtrinsic(i) = phase { - if i as usize == ext_index { - events.push(event) - } - } - } - } - Err(err) => return Err(err), - } - } - } - return if !events.is_empty() { - Ok(ExtrinsicSuccess { - block: block_hash, - extrinsic: ext_hash, - events, - }) - } else { - Err(format!("No events found for block {}", block_hash).into()) - } - } - unreachable!() -} diff --git a/src/subscription.rs b/src/subscription.rs new file mode 100644 index 0000000000000..6208ce6f7b138 --- /dev/null +++ b/src/subscription.rs @@ -0,0 +1,121 @@ +// 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 jsonrpsee::client::Subscription; +use sp_core::storage::StorageChangeSet; +use std::collections::VecDeque; + +use crate::{ + error::Error, + events::{ + EventsDecoder, + RawEvent, + }, + frame::{ + system::Phase, + Event, + }, + runtimes::Runtime, +}; + +/// Event subscription simplifies filtering a storage change set stream for +/// events of interest. +pub struct EventSubscription { + subscription: Subscription>, + decoder: EventsDecoder, + block: Option, + extrinsic: Option, + event: Option<(&'static str, &'static str)>, + events: VecDeque, + finished: bool, +} + +impl EventSubscription { + /// Creates a new event subscription. + pub fn new( + subscription: Subscription>, + decoder: EventsDecoder, + ) -> Self { + Self { + subscription, + decoder, + block: None, + extrinsic: None, + event: None, + events: Default::default(), + finished: false, + } + } + + /// Only returns events contained in the block with the given hash. + pub fn filter_block(&mut self, block: T::Hash) { + self.block = Some(block); + } + + /// Only returns events from block emitted by extrinsic with index. + pub fn filter_extrinsic(&mut self, block: T::Hash, ext_index: usize) { + self.block = Some(block); + self.extrinsic = Some(ext_index); + } + + /// Filters events by type. + pub fn filter_event>(&mut self) { + self.event = Some((E::MODULE, E::EVENT)); + } + + /// Gets the next event. + pub async fn next(&mut self) -> Option> { + loop { + if let Some(event) = self.events.pop_front() { + return Some(Ok(event)) + } + if self.finished { + return None + } + let change_set = self.subscription.next().await; + if let Some(hash) = self.block.as_ref() { + if &change_set.block == hash { + self.finished = true; + } else { + continue + } + } + for (_key, data) in change_set.changes { + if let Some(data) = data { + let raw_events = match self.decoder.decode_events(&mut &data.0[..]) { + Ok(events) => events, + Err(error) => return Some(Err(error)), + }; + for (phase, event) in raw_events { + if let Phase::ApplyExtrinsic(i) = phase { + if let Some(ext_index) = self.extrinsic { + if i as usize != ext_index { + continue + } + } + if let Some((module, variant)) = self.event { + if event.module != module || event.variant != variant { + continue + } + } + self.events.push_back(event); + } + } + } + } + } + } +} diff --git a/test-node/Cargo.toml b/test-node/Cargo.toml new file mode 100644 index 0000000000000..9691a2053dab6 --- /dev/null +++ b/test-node/Cargo.toml @@ -0,0 +1,42 @@ +[package] +name = "test-node" +version = "2.0.0-rc3" +authors = ["Anonymous"] +description = "Substrate Node template" +edition = "2018" +license = "Unlicense" +build = "build.rs" +homepage = "https://substrate.dev" +repository = "https://github.com/paritytech/substrate/" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +futures = "0.3.5" +log = "0.4.8" +structopt = "0.3.15" +parking_lot = "0.11.0" + +sc-cli = { version = "0.8.0-rc3", features = ["wasmtime"] } +sp-core = "2.0.0-rc3" +sc-executor = { version = "0.8.0-rc3", features = ["wasmtime"] } +sc-service = { version = "0.8.0-rc3", features = ["wasmtime"] } +sp-inherents = "2.0.0-rc3" +sc-transaction-pool = "2.0.0-rc3" +sp-transaction-pool = "2.0.0-rc3" +sc-network = "0.8.0-rc3" +sc-consensus-aura = "0.8.0-rc3" +sp-consensus-aura = "0.8.0-rc3" +sp-consensus = "0.8.0-rc3" +sc-consensus = "0.8.0-rc3" +sc-finality-grandpa = "0.8.0-rc3" +sp-finality-grandpa = "2.0.0-rc3" +sc-client-api = "2.0.0-rc3" +sp-runtime = "2.0.0-rc3" +sc-basic-authorship = "0.8.0-rc3" + +test-node-runtime = { version = "2.0.0-rc3", path = "runtime" } + +[build-dependencies] +substrate-build-script-utils = "2.0.0-rc3" diff --git a/test-node/build.rs b/test-node/build.rs new file mode 100644 index 0000000000000..ca699c6c738b5 --- /dev/null +++ b/test-node/build.rs @@ -0,0 +1,26 @@ +// 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 substrate_build_script_utils::{ + generate_cargo_keys, + rerun_if_git_head_changed, +}; + +fn main() { + generate_cargo_keys(); + + rerun_if_git_head_changed(); +} diff --git a/test-node/runtime/Cargo.toml b/test-node/runtime/Cargo.toml new file mode 100644 index 0000000000000..f5c3a9e6fcf7e --- /dev/null +++ b/test-node/runtime/Cargo.toml @@ -0,0 +1,70 @@ +[package] +name = "test-node-runtime" +version = "2.0.0-rc3" +authors = ["Anonymous"] +edition = "2018" +license = "Unlicense" +homepage = "https://substrate.dev" +repository = "https://github.com/paritytech/substrate/" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { package = "parity-scale-codec", version = "1.3.1", default-features = false, features = ["derive"] } + +aura = { version = "2.0.0-rc3", default-features = false, package = "pallet-aura" } +balances = { version = "2.0.0-rc3", default-features = false, package = "pallet-balances" } +frame-support = { version = "2.0.0-rc3", default-features = false } +grandpa = { version = "2.0.0-rc3", default-features = false, package = "pallet-grandpa" } +randomness-collective-flip = { version = "2.0.0-rc3", default-features = false, package = "pallet-randomness-collective-flip" } +sudo = { version = "2.0.0-rc3", default-features = false, package = "pallet-sudo" } +system = { version = "2.0.0-rc3", default-features = false, package = "frame-system" } +timestamp = { version = "2.0.0-rc3", default-features = false, package = "pallet-timestamp" } +transaction-payment = { version = "2.0.0-rc3", default-features = false, package = "pallet-transaction-payment" } +frame-executive = { version = "2.0.0-rc3", default-features = false } +serde = { version = "1.0.114", optional = true, features = ["derive"] } +sp-api = { version = "2.0.0-rc3", default-features = false } +sp-block-builder = { default-features = false, version = "2.0.0-rc3" } +sp-consensus-aura = { version = "0.8.0-rc3", default-features = false } +sp-core = { version = "2.0.0-rc3", default-features = false } +sp-inherents = { default-features = false, version = "2.0.0-rc3" } +sp-io = { version = "2.0.0-rc3", default-features = false } +sp-offchain = { version = "2.0.0-rc3", default-features = false } +sp-runtime = { version = "2.0.0-rc3", default-features = false } +sp-session = { version = "2.0.0-rc3", default-features = false } +sp-std = { version = "2.0.0-rc3", default-features = false } +sp-transaction-pool = { version = "2.0.0-rc3", default-features = false } +sp-version = { version = "2.0.0-rc3", default-features = false } + +[build-dependencies] +wasm-builder-runner = { version = "1.0.5", package = "substrate-wasm-builder-runner" } + +[features] +default = ["std"] +std = [ + "aura/std", + "balances/std", + "codec/std", + "frame-executive/std", + "frame-support/std", + "grandpa/std", + "randomness-collective-flip/std", + "serde", + "sp-api/std", + "sp-block-builder/std", + "sp-consensus-aura/std", + "sp-core/std", + "sp-inherents/std", + "sp-io/std", + "sp-offchain/std", + "sp-runtime/std", + "sp-session/std", + "sp-std/std", + "sp-transaction-pool/std", + "sp-version/std", + "sudo/std", + "system/std", + "timestamp/std", + "transaction-payment/std", +] diff --git a/test-node/runtime/build.rs b/test-node/runtime/build.rs new file mode 100644 index 0000000000000..8dfb883c04095 --- /dev/null +++ b/test-node/runtime/build.rs @@ -0,0 +1,26 @@ +// 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 wasm_builder_runner::WasmBuilder; + +fn main() { + WasmBuilder::new() + .with_current_project() + .with_wasm_builder_from_crates("1.0.11") + .export_heap_base() + .import_memory() + .build() +} diff --git a/test-node/runtime/src/lib.rs b/test-node/runtime/src/lib.rs new file mode 100644 index 0000000000000..140aabdbdfc0e --- /dev/null +++ b/test-node/runtime/src/lib.rs @@ -0,0 +1,460 @@ +// 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 . + +//! The Substrate Node Template runtime. This can be compiled with `#[no_std]`, ready for Wasm. + +#![cfg_attr(not(feature = "std"), no_std)] +// `construct_runtime!` does a lot of recursion and requires us to increase the limit to 256. +#![recursion_limit = "256"] + +// Make the WASM binary available. +#[cfg(feature = "std")] +include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); + +use grandpa::{ + fg_primitives, + AuthorityId as GrandpaId, + AuthorityList as GrandpaAuthorityList, +}; +use sp_api::impl_runtime_apis; +use sp_consensus_aura::sr25519::AuthorityId as AuraId; +use sp_core::{ + crypto::KeyTypeId, + OpaqueMetadata, +}; +use sp_runtime::{ + create_runtime_str, + generic, + impl_opaque_keys, + traits::{ + BlakeTwo256, + Block as BlockT, + IdentifyAccount, + IdentityLookup, + NumberFor, + Saturating, + Verify, + }, + transaction_validity::{ + TransactionSource, + TransactionValidity, + }, + ApplyExtrinsicResult, + MultiSignature, +}; +use sp_std::prelude::*; +#[cfg(feature = "std")] +use sp_version::NativeVersion; +use sp_version::RuntimeVersion; + +// A few exports that help ease life for downstream crates. +pub use balances::Call as BalancesCall; +pub use frame_support::{ + construct_runtime, + parameter_types, + traits::{ + KeyOwnerProofSystem, + Randomness, + }, + weights::{ + constants::{ + BlockExecutionWeight, + ExtrinsicBaseWeight, + RocksDbWeight, + WEIGHT_PER_SECOND, + }, + IdentityFee, + Weight, + }, + StorageValue, +}; +#[cfg(any(feature = "std", test))] +pub use sp_runtime::BuildStorage; +pub use sp_runtime::{ + Perbill, + Permill, +}; +pub use timestamp::Call as TimestampCall; + +/// An index to a block. +pub type BlockNumber = u32; + +/// Alias to 512-bit hash when used in the context of a transaction signature on the chain. +pub type Signature = MultiSignature; + +/// Some way of identifying an account on the chain. We intentionally make it equivalent +/// to the public key of our transaction signing scheme. +pub type AccountId = <::Signer as IdentifyAccount>::AccountId; + +/// The type for looking up accounts. We don't expect more than 4 billion of them, but you +/// never know... +pub type AccountIndex = u32; + +/// Balance of an account. +pub type Balance = u128; + +/// Index of a transaction in the chain. +pub type Index = u32; + +/// A hash of some data used by the chain. +pub type Hash = sp_core::H256; + +/// Digest item type. +pub type DigestItem = generic::DigestItem; + +/// Opaque types. These are used by the CLI to instantiate machinery that don't need to know +/// the specifics of the runtime. They can then be made to be agnostic over specific formats +/// of data like extrinsics, allowing for them to continue syncing the network through upgrades +/// to even the core data structures. +pub mod opaque { + use super::*; + + pub use sp_runtime::OpaqueExtrinsic as UncheckedExtrinsic; + + /// Opaque block header type. + pub type Header = generic::Header; + /// Opaque block type. + pub type Block = generic::Block; + /// Opaque block identifier type. + pub type BlockId = generic::BlockId; + + impl_opaque_keys! { + pub struct SessionKeys { + pub aura: Aura, + pub grandpa: Grandpa, + } + } +} + +/// This runtime version. +pub const VERSION: RuntimeVersion = RuntimeVersion { + spec_name: create_runtime_str!("subxt-test-node"), + impl_name: create_runtime_str!("subxt-test-node"), + authoring_version: 1, + spec_version: 1, + impl_version: 1, + apis: RUNTIME_API_VERSIONS, + transaction_version: 1, +}; + +pub const MILLISECS_PER_BLOCK: u64 = 6000; + +pub const SLOT_DURATION: u64 = MILLISECS_PER_BLOCK; + +// These time units are defined in number of blocks. +pub const MINUTES: BlockNumber = 60_000 / (MILLISECS_PER_BLOCK as BlockNumber); +pub const HOURS: BlockNumber = MINUTES * 60; +pub const DAYS: BlockNumber = HOURS * 24; + +/// The version information used to identify this runtime when compiled natively. +#[cfg(feature = "std")] +pub fn native_version() -> NativeVersion { + NativeVersion { + runtime_version: VERSION, + can_author_with: Default::default(), + } +} + +parameter_types! { + pub const BlockHashCount: BlockNumber = 2400; + /// We allow for 2 seconds of compute with a 6 second average block time. + pub const MaximumBlockWeight: Weight = 2 * WEIGHT_PER_SECOND; + pub const AvailableBlockRatio: Perbill = Perbill::from_percent(75); + /// Assume 10% of weight for average on_initialize calls. + pub MaximumExtrinsicWeight: Weight = AvailableBlockRatio::get() + .saturating_sub(Perbill::from_percent(10)) * MaximumBlockWeight::get(); + pub const MaximumBlockLength: u32 = 5 * 1024 * 1024; + pub const Version: RuntimeVersion = VERSION; +} + +impl system::Trait for Runtime { + /// The identifier used to distinguish between accounts. + type AccountId = AccountId; + /// The aggregated dispatch type that is available for extrinsics. + type Call = Call; + /// The lookup mechanism to get account ID from whatever is passed in dispatchers. + type Lookup = IdentityLookup; + /// The index type for storing how many extrinsics an account has signed. + type Index = Index; + /// The index type for blocks. + type BlockNumber = BlockNumber; + /// The type for hashing blocks and tries. + type Hash = Hash; + /// The hashing algorithm used. + type Hashing = BlakeTwo256; + /// The header type. + type Header = generic::Header; + /// The ubiquitous event type. + type Event = Event; + /// The ubiquitous origin type. + type Origin = Origin; + /// Maximum number of block number to block hash mappings to keep (oldest pruned first). + type BlockHashCount = BlockHashCount; + /// Maximum weight of each block. + type MaximumBlockWeight = MaximumBlockWeight; + /// The weight of database operations that the runtime can invoke. + type DbWeight = RocksDbWeight; + /// The weight of the overhead invoked on the block import process, independent of the + /// extrinsics included in that block. + type BlockExecutionWeight = BlockExecutionWeight; + /// The base weight of any extrinsic processed by the runtime, independent of the + /// logic of that extrinsic. (Signature verification, nonce increment, fee, etc...) + type ExtrinsicBaseWeight = ExtrinsicBaseWeight; + /// The maximum weight that a single extrinsic of `Normal` dispatch class can have, + /// idependent of the logic of that extrinsics. (Roughly max block weight - average on + /// initialize cost). + type MaximumExtrinsicWeight = MaximumExtrinsicWeight; + /// Maximum size of all encoded transactions (in bytes) that are allowed in one block. + type MaximumBlockLength = MaximumBlockLength; + /// Portion of the block weight that is available to all normal transactions. + type AvailableBlockRatio = AvailableBlockRatio; + /// Version of the runtime. + type Version = Version; + /// Converts a module to the index of the module in `construct_runtime!`. + /// + /// This type is being generated by `construct_runtime!`. + type ModuleToIndex = ModuleToIndex; + /// What to do if a new account is created. + type OnNewAccount = (); + /// What to do if an account is fully reaped from the system. + type OnKilledAccount = (); + /// The data to be stored in an account. + type AccountData = balances::AccountData; +} + +impl aura::Trait for Runtime { + type AuthorityId = AuraId; +} + +impl grandpa::Trait for Runtime { + type Event = Event; + type Call = Call; + + type KeyOwnerProofSystem = (); + + type KeyOwnerProof = + >::Proof; + + type KeyOwnerIdentification = >::IdentificationTuple; + + type HandleEquivocation = (); +} + +parameter_types! { + pub const MinimumPeriod: u64 = SLOT_DURATION / 2; +} + +impl timestamp::Trait for Runtime { + /// A timestamp: milliseconds since the unix epoch. + type Moment = u64; + type OnTimestampSet = Aura; + type MinimumPeriod = MinimumPeriod; +} + +parameter_types! { + pub const ExistentialDeposit: u128 = 500; +} + +impl balances::Trait for Runtime { + /// The type for recording an account's balance. + type Balance = Balance; + /// The ubiquitous event type. + type Event = Event; + type DustRemoval = (); + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; +} + +parameter_types! { + pub const TransactionByteFee: Balance = 1; +} + +impl transaction_payment::Trait for Runtime { + type Currency = balances::Module; + type OnTransactionPayment = (); + type TransactionByteFee = TransactionByteFee; + type WeightToFee = IdentityFee; + type FeeMultiplierUpdate = (); +} + +impl sudo::Trait for Runtime { + type Event = Event; + type Call = Call; +} + +construct_runtime!( + pub enum Runtime where + Block = Block, + NodeBlock = opaque::Block, + UncheckedExtrinsic = UncheckedExtrinsic + { + System: system::{Module, Call, Config, Storage, Event}, + RandomnessCollectiveFlip: randomness_collective_flip::{Module, Call, Storage}, + Timestamp: timestamp::{Module, Call, Storage, Inherent}, + Aura: aura::{Module, Config, Inherent(Timestamp)}, + Grandpa: grandpa::{Module, Call, Storage, Config, Event}, + Balances: balances::{Module, Call, Storage, Config, Event}, + TransactionPayment: transaction_payment::{Module, Storage}, + Sudo: sudo::{Module, Call, Config, Storage, Event}, + } +); + +/// The address format for describing accounts. +pub type Address = AccountId; +/// Block header type as expected by this runtime. +pub type Header = generic::Header; +/// Block type as expected by this runtime. +pub type Block = generic::Block; +/// A Block signed with a Justification +pub type SignedBlock = generic::SignedBlock; +/// BlockId type as expected by this runtime. +pub type BlockId = generic::BlockId; +/// The SignedExtension to the basic transaction logic. +pub type SignedExtra = ( + system::CheckSpecVersion, + system::CheckTxVersion, + system::CheckGenesis, + system::CheckEra, + system::CheckNonce, + system::CheckWeight, + transaction_payment::ChargeTransactionPayment, +); +/// Unchecked extrinsic type as expected by this runtime. +pub type UncheckedExtrinsic = + generic::UncheckedExtrinsic; +/// Extrinsic type that has already been checked. +pub type CheckedExtrinsic = generic::CheckedExtrinsic; +/// Executive: handles dispatch to the various modules. +pub type Executive = frame_executive::Executive< + Runtime, + Block, + system::ChainContext, + Runtime, + AllModules, +>; + +impl_runtime_apis! { + impl sp_api::Core for Runtime { + fn version() -> RuntimeVersion { + VERSION + } + + fn execute_block(block: Block) { + Executive::execute_block(block) + } + + fn initialize_block(header: &::Header) { + Executive::initialize_block(header) + } + } + + impl sp_api::Metadata for Runtime { + fn metadata() -> OpaqueMetadata { + Runtime::metadata().into() + } + } + + impl sp_block_builder::BlockBuilder for Runtime { + fn apply_extrinsic(extrinsic: ::Extrinsic) -> ApplyExtrinsicResult { + Executive::apply_extrinsic(extrinsic) + } + + fn finalize_block() -> ::Header { + Executive::finalize_block() + } + + fn inherent_extrinsics(data: sp_inherents::InherentData) -> Vec<::Extrinsic> { + data.create_extrinsics() + } + + fn check_inherents( + block: Block, + data: sp_inherents::InherentData, + ) -> sp_inherents::CheckInherentsResult { + data.check_extrinsics(&block) + } + + fn random_seed() -> ::Hash { + RandomnessCollectiveFlip::random_seed() + } + } + + impl sp_transaction_pool::runtime_api::TaggedTransactionQueue for Runtime { + fn validate_transaction( + source: TransactionSource, + tx: ::Extrinsic, + ) -> TransactionValidity { + Executive::validate_transaction(source, tx) + } + } + + impl sp_offchain::OffchainWorkerApi for Runtime { + fn offchain_worker(header: &::Header) { + Executive::offchain_worker(header) + } + } + + impl sp_consensus_aura::AuraApi for Runtime { + fn slot_duration() -> u64 { + Aura::slot_duration() + } + + fn authorities() -> Vec { + Aura::authorities() + } + } + + impl sp_session::SessionKeys for Runtime { + fn generate_session_keys(seed: Option>) -> Vec { + opaque::SessionKeys::generate(seed) + } + + fn decode_session_keys( + encoded: Vec, + ) -> Option, KeyTypeId)>> { + opaque::SessionKeys::decode_into_raw_public_keys(&encoded) + } + } + + impl fg_primitives::GrandpaApi for Runtime { + fn grandpa_authorities() -> GrandpaAuthorityList { + Grandpa::grandpa_authorities() + } + + fn submit_report_equivocation_extrinsic( + _equivocation_proof: fg_primitives::EquivocationProof< + ::Hash, + NumberFor, + >, + _key_owner_proof: fg_primitives::OpaqueKeyOwnershipProof, + ) -> Option<()> { + None + } + + fn generate_key_ownership_proof( + _set_id: fg_primitives::SetId, + _authority_id: GrandpaId, + ) -> Option { + // NOTE: this is the only implementation possible since we've + // defined our key owner proof type as a bottom type (i.e. a type + // with no values). + None + } + } +} diff --git a/test-node/src/chain_spec.rs b/test-node/src/chain_spec.rs new file mode 100644 index 0000000000000..9b751ee0bbe44 --- /dev/null +++ b/test-node/src/chain_spec.rs @@ -0,0 +1,161 @@ +// 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 sc_service::ChainType; +use sp_consensus_aura::sr25519::AuthorityId as AuraId; +use sp_core::{ + sr25519, + Pair, + Public, +}; +use sp_finality_grandpa::AuthorityId as GrandpaId; +use sp_runtime::traits::{ + IdentifyAccount, + Verify, +}; +use test_node_runtime::{ + AccountId, + AuraConfig, + BalancesConfig, + GenesisConfig, + GrandpaConfig, + Signature, + SudoConfig, + SystemConfig, + WASM_BINARY, +}; + +// Note this is the URL for the telemetry server +// const STAGING_TELEMETRY_URL: &str = "wss://telemetry.polkadot.io/submit/"; + +/// Specialized `ChainSpec`. This is a specialization of the general Substrate ChainSpec type. +pub type ChainSpec = sc_service::GenericChainSpec; + +/// Helper function to generate a crypto pair from seed +pub fn get_from_seed(seed: &str) -> ::Public { + TPublic::Pair::from_string(&format!("//{}", seed), None) + .expect("static values are valid; qed") + .public() +} + +type AccountPublic = ::Signer; + +/// Helper function to generate an account ID from seed +pub fn get_account_id_from_seed(seed: &str) -> AccountId +where + AccountPublic: From<::Public>, +{ + AccountPublic::from(get_from_seed::(seed)).into_account() +} + +/// Helper function to generate an authority key for Aura +pub fn authority_keys_from_seed(s: &str) -> (AuraId, GrandpaId) { + (get_from_seed::(s), get_from_seed::(s)) +} + +pub fn development_config() -> ChainSpec { + ChainSpec::from_genesis( + "Development", + "dev", + ChainType::Development, + || { + testnet_genesis( + vec![authority_keys_from_seed("Alice")], + get_account_id_from_seed::("Alice"), + vec![ + get_account_id_from_seed::("Alice"), + get_account_id_from_seed::("Bob"), + get_account_id_from_seed::("Alice//stash"), + get_account_id_from_seed::("Bob//stash"), + ], + true, + ) + }, + vec![], + None, + None, + None, + None, + ) +} + +pub fn local_testnet_config() -> ChainSpec { + ChainSpec::from_genesis( + "Local Testnet", + "local_testnet", + ChainType::Local, + || { + testnet_genesis( + vec![ + authority_keys_from_seed("Alice"), + authority_keys_from_seed("Bob"), + ], + get_account_id_from_seed::("Alice"), + vec![ + get_account_id_from_seed::("Alice"), + get_account_id_from_seed::("Bob"), + get_account_id_from_seed::("Charlie"), + get_account_id_from_seed::("Dave"), + get_account_id_from_seed::("Eve"), + get_account_id_from_seed::("Ferdie"), + get_account_id_from_seed::("Alice//stash"), + get_account_id_from_seed::("Bob//stash"), + get_account_id_from_seed::("Charlie//stash"), + get_account_id_from_seed::("Dave//stash"), + get_account_id_from_seed::("Eve//stash"), + get_account_id_from_seed::("Ferdie//stash"), + ], + true, + ) + }, + vec![], + None, + None, + None, + None, + ) +} + +fn testnet_genesis( + initial_authorities: Vec<(AuraId, GrandpaId)>, + root_key: AccountId, + endowed_accounts: Vec, + _enable_println: bool, +) -> GenesisConfig { + GenesisConfig { + system: Some(SystemConfig { + code: WASM_BINARY.to_vec(), + changes_trie_config: Default::default(), + }), + balances: Some(BalancesConfig { + balances: endowed_accounts + .iter() + .cloned() + .map(|k| (k, 1 << 60)) + .collect(), + }), + aura: Some(AuraConfig { + authorities: initial_authorities.iter().map(|x| (x.0.clone())).collect(), + }), + grandpa: Some(GrandpaConfig { + authorities: initial_authorities + .iter() + .map(|x| (x.1.clone(), 1)) + .collect(), + }), + sudo: Some(SudoConfig { key: root_key }), + } +} diff --git a/test-node/src/cli.rs b/test-node/src/cli.rs new file mode 100644 index 0000000000000..b94a06ad74216 --- /dev/null +++ b/test-node/src/cli.rs @@ -0,0 +1,30 @@ +// 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 sc_cli::{ + RunCmd, + Subcommand, +}; +use structopt::StructOpt; + +#[derive(Debug, StructOpt)] +pub struct Cli { + #[structopt(subcommand)] + pub subcommand: Option, + + #[structopt(flatten)] + pub run: RunCmd, +} diff --git a/test-node/src/command.rs b/test-node/src/command.rs new file mode 100644 index 0000000000000..ec0b0fed28ecf --- /dev/null +++ b/test-node/src/command.rs @@ -0,0 +1,84 @@ +// 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 crate::{ + chain_spec, + cli::Cli, + service, +}; +use sc_cli::SubstrateCli; + +impl SubstrateCli for Cli { + fn impl_name() -> &'static str { + "Substrate Node" + } + + fn impl_version() -> &'static str { + env!("SUBSTRATE_CLI_IMPL_VERSION") + } + + fn description() -> &'static str { + env!("CARGO_PKG_DESCRIPTION") + } + + fn author() -> &'static str { + env!("CARGO_PKG_AUTHORS") + } + + fn support_url() -> &'static str { + "support.anonymous.an" + } + + fn copyright_start_year() -> i32 { + 2017 + } + + fn executable_name() -> &'static str { + env!("CARGO_PKG_NAME") + } + + fn load_spec(&self, id: &str) -> Result, String> { + Ok(match id { + "dev" => Box::new(chain_spec::development_config()), + "" | "local" => Box::new(chain_spec::local_testnet_config()), + path => { + Box::new(chain_spec::ChainSpec::from_json_file( + std::path::PathBuf::from(path), + )?) + } + }) + } +} + +/// Parse and run command line arguments +pub fn run() -> sc_cli::Result<()> { + let cli = Cli::from_args(); + + match &cli.subcommand { + Some(subcommand) => { + let runner = cli.create_runner(subcommand)?; + runner.run_subcommand(subcommand, |config| Ok(new_full_start!(config).0)) + } + None => { + let runner = cli.create_runner(&cli.run)?; + runner.run_node( + service::new_light, + service::new_full, + test_node_runtime::VERSION, + ) + } + } +} diff --git a/test-node/src/lib.rs b/test-node/src/lib.rs new file mode 100644 index 0000000000000..4ea802838c482 --- /dev/null +++ b/test-node/src/lib.rs @@ -0,0 +1,18 @@ +// 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 . + +pub mod chain_spec; +pub mod service; diff --git a/test-node/src/main.rs b/test-node/src/main.rs new file mode 100644 index 0000000000000..caeb7bf244754 --- /dev/null +++ b/test-node/src/main.rs @@ -0,0 +1,28 @@ +// 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 . + +//! Substrate Node Template CLI library. +#![warn(missing_docs)] + +mod chain_spec; +#[macro_use] +mod service; +mod cli; +mod command; + +fn main() -> sc_cli::Result<()> { + command::run() +} diff --git a/test-node/src/service.rs b/test-node/src/service.rs new file mode 100644 index 0000000000000..599f149f66010 --- /dev/null +++ b/test-node/src/service.rs @@ -0,0 +1,303 @@ +// 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 . + +//! Service and ServiceFactory implementation. Specialized wrapper over substrate service. + +use sc_client_api::ExecutorProvider; +use sc_consensus::LongestChain; +use sc_executor::native_executor_instance; +pub use sc_executor::NativeExecutor; +use sc_finality_grandpa::{ + FinalityProofProvider as GrandpaFinalityProofProvider, + SharedVoterState, + StorageAndProofProvider, +}; +use sc_service::{ + error::Error as ServiceError, + AbstractService, + Configuration, + ServiceBuilder, +}; +use sp_consensus_aura::sr25519::AuthorityPair as AuraPair; +use sp_inherents::InherentDataProviders; +use std::{ + sync::Arc, + time::Duration, +}; +use test_node_runtime::{ + self, + opaque::Block, + RuntimeApi, +}; + +// Our native executor instance. +native_executor_instance!( + pub Executor, + test_node_runtime::api::dispatch, + test_node_runtime::native_version, +); + +/// Starts a `ServiceBuilder` for a full service. +/// +/// Use this macro if you don't actually need the full service, but just the builder in order to +/// be able to perform chain operations. +macro_rules! new_full_start { + ($config:expr) => {{ + use sp_consensus_aura::sr25519::AuthorityPair as AuraPair; + use std::sync::Arc; + + let mut import_setup = None; + let inherent_data_providers = sp_inherents::InherentDataProviders::new(); + + let builder = + sc_service::ServiceBuilder::new_full::< + test_node_runtime::opaque::Block, + test_node_runtime::RuntimeApi, + crate::service::Executor, + >($config)? + .with_select_chain(|_config, backend| { + Ok(sc_consensus::LongestChain::new(backend.clone())) + })? + .with_transaction_pool(|builder| { + let pool_api = + sc_transaction_pool::FullChainApi::new(builder.client().clone()); + Ok(sc_transaction_pool::BasicPool::new( + builder.config().transaction_pool.clone(), + std::sync::Arc::new(pool_api), + builder.prometheus_registry(), + )) + })? + .with_import_queue( + |_config, + client, + mut select_chain, + _transaction_pool, + spawn_task_handle, + registry| { + let select_chain = select_chain + .take() + .ok_or_else(|| sc_service::Error::SelectChainRequired)?; + + let (grandpa_block_import, grandpa_link) = + sc_finality_grandpa::block_import( + client.clone(), + &(client.clone() as Arc<_>), + select_chain, + )?; + + let aura_block_import = + sc_consensus_aura::AuraBlockImport::<_, _, _, AuraPair>::new( + grandpa_block_import.clone(), + client.clone(), + ); + + let import_queue = + sc_consensus_aura::import_queue::<_, _, _, AuraPair, _>( + sc_consensus_aura::slot_duration(&*client)?, + aura_block_import, + Some(Box::new(grandpa_block_import.clone())), + None, + client, + inherent_data_providers.clone(), + spawn_task_handle, + registry, + )?; + + import_setup = Some((grandpa_block_import, grandpa_link)); + + Ok(import_queue) + }, + )?; + + (builder, import_setup, inherent_data_providers) + }}; +} + +/// Builds a new service for a full client. +pub fn new_full(config: Configuration) -> Result { + let role = config.role.clone(); + let force_authoring = config.force_authoring; + let name = config.network.node_name.clone(); + let disable_grandpa = config.disable_grandpa; + + let (builder, mut import_setup, inherent_data_providers) = new_full_start!(config); + + let (block_import, grandpa_link) = + import_setup.take() + .expect("Link Half and Block Import are present for Full Services or setup failed before. qed"); + + let service = builder + .with_finality_proof_provider(|client, backend| { + // GenesisAuthoritySetProvider is implemented for StorageAndProofProvider + let provider = client as Arc>; + Ok(Arc::new(GrandpaFinalityProofProvider::new(backend, provider)) as _) + })? + .build()?; + + if role.is_authority() { + let proposer = sc_basic_authorship::ProposerFactory::new( + service.client(), + service.transaction_pool(), + service.prometheus_registry().as_ref(), + ); + + let client = service.client(); + let select_chain = service + .select_chain() + .ok_or(ServiceError::SelectChainRequired)?; + + let can_author_with = + sp_consensus::CanAuthorWithNativeVersion::new(client.executor().clone()); + + let aura = sc_consensus_aura::start_aura::<_, _, _, _, _, AuraPair, _, _, _>( + sc_consensus_aura::slot_duration(&*client)?, + client, + select_chain, + block_import, + proposer, + service.network(), + inherent_data_providers.clone(), + force_authoring, + service.keystore(), + can_author_with, + )?; + + // the AURA authoring task is considered essential, i.e. if it + // fails we take down the service with it. + service.spawn_essential_task("aura", aura); + } + + // if the node isn't actively participating in consensus then it doesn't + // need a keystore, regardless of which protocol we use below. + let keystore = if role.is_authority() { + Some(service.keystore()) + } else { + None + }; + + let grandpa_config = sc_finality_grandpa::Config { + // #1578 make this available through chainspec + gossip_duration: Duration::from_millis(333), + justification_period: 512, + name: Some(name), + observer_enabled: false, + keystore, + is_authority: role.is_network_authority(), + }; + + let enable_grandpa = !disable_grandpa; + if enable_grandpa { + // start the full GRANDPA voter + // NOTE: non-authorities could run the GRANDPA observer protocol, but at + // this point the full voter should provide better guarantees of block + // and vote data availability than the observer. The observer has not + // been tested extensively yet and having most nodes in a network run it + // could lead to finality stalls. + let grandpa_config = sc_finality_grandpa::GrandpaParams { + config: grandpa_config, + link: grandpa_link, + network: service.network(), + inherent_data_providers: inherent_data_providers.clone(), + telemetry_on_connect: Some(service.telemetry_on_connect_stream()), + voting_rule: sc_finality_grandpa::VotingRulesBuilder::default().build(), + prometheus_registry: service.prometheus_registry(), + shared_voter_state: SharedVoterState::empty(), + }; + + // the GRANDPA voter task is considered infallible, i.e. + // if it fails we take down the service with it. + service.spawn_essential_task( + "grandpa-voter", + sc_finality_grandpa::run_grandpa_voter(grandpa_config)?, + ); + } else { + sc_finality_grandpa::setup_disabled_grandpa( + service.client(), + &inherent_data_providers, + service.network(), + )?; + } + + Ok(service) +} + +/// Builds a new service for a light client. +pub fn new_light(config: Configuration) -> Result { + let inherent_data_providers = InherentDataProviders::new(); + + ServiceBuilder::new_light::(config)? + .with_select_chain(|_config, backend| { + Ok(LongestChain::new(backend.clone())) + })? + .with_transaction_pool(|builder| { + let fetcher = builder.fetcher() + .ok_or_else(|| "Trying to start light transaction pool without active fetcher")?; + + let pool_api = sc_transaction_pool::LightChainApi::new( + builder.client().clone(), + fetcher.clone(), + ); + let pool = sc_transaction_pool::BasicPool::with_revalidation_type( + builder.config().transaction_pool.clone(), + Arc::new(pool_api), + builder.prometheus_registry(), + sc_transaction_pool::RevalidationType::Light, + ); + Ok(pool) + })? + .with_import_queue_and_fprb(| + _config, + client, + backend, + fetcher, + _select_chain, + _tx_pool, + spawn_task_handle, + prometheus_registry, + | { + let fetch_checker = fetcher + .map(|fetcher| fetcher.checker().clone()) + .ok_or_else(|| "Trying to start light import queue without active fetch checker")?; + let grandpa_block_import = sc_finality_grandpa::light_block_import( + client.clone(), + backend, + &(client.clone() as Arc<_>), + Arc::new(fetch_checker), + )?; + let finality_proof_import = grandpa_block_import.clone(); + let finality_proof_request_builder = + finality_proof_import.create_finality_proof_request_builder(); + + let import_queue = sc_consensus_aura::import_queue::<_, _, _, AuraPair, _>( + sc_consensus_aura::slot_duration(&*client)?, + grandpa_block_import, + None, + Some(Box::new(finality_proof_import)), + client, + inherent_data_providers.clone(), + spawn_task_handle, + prometheus_registry, + )?; + + Ok((import_queue, finality_proof_request_builder)) + })? + .with_finality_proof_provider(|client, backend| { + // GenesisAuthoritySetProvider is implemented for StorageAndProofProvider + let provider = client as Arc>; + Ok(Arc::new(GrandpaFinalityProofProvider::new(backend, provider)) as _) + })? + .build() +}