diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ba4cc4058..da102618d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,16 @@ +# Version 0.9.0 (2020-06-25) + +* Events sub [#126](https://github.com/paritytech/substrate-subxt/pull/126) +* Improve error handling in proc-macros, handle DispatchError etc. [#123](https://github.com/paritytech/substrate-subxt/pull/123) +* Support embedded full/light node clients. [#91](https://github.com/paritytech/substrate-subxt/pull/91) +* Zero sized types [#121](https://github.com/paritytech/substrate-subxt/pull/121) +* Fix optional store items. [#120](https://github.com/paritytech/substrate-subxt/pull/120) +* Make signing fallable and asynchronous [#119](https://github.com/paritytech/substrate-subxt/pull/119) + # Version 0.8.0 (2020-05-26) * Update to Substrate release candidate [#116](https://github.com/paritytech/substrate-subxt/pull/116) -* Update to alpha.8 [#114](https://github.com/paritytech/substrate-subxt/pull/114) +* Update to alpha.8 [#114]c * Refactors the api [#113](https://github.com/paritytech/substrate-subxt/pull/113) # Version 0.7.0 (2020-05-13) diff --git a/Cargo.toml b/Cargo.toml index 76386197c8..05087d53d9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,9 +1,9 @@ [workspace] -members = [".", "client", "proc-macro"] +members = [".", "client", "proc-macro", "test-node"] [package] name = "substrate-subxt" -version = "0.8.0" +version = "0.9.0" authors = ["Parity Technologies "] edition = "2018" @@ -23,12 +23,12 @@ client = ["substrate-subxt-client"] [dependencies] log = "0.4.8" -thiserror = "1.0.19" +thiserror = "1.0.20" futures = "0.3.5" 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" +num-traits = { version = "0.2.12", default-features = false } +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"] } @@ -42,8 +42,8 @@ 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" } +substrate-subxt-client = { version = "0.1.0", path = "client", optional = true } +substrate-subxt-proc-macro = { version = "0.9.0", path = "proc-macro" } sp-std = "2.0.0-rc3" application-crypto = { version = "2.0.0-rc3", package = "sp-application-crypto", default-features = false } sp-finality-grandpa = { version = "2.0.0-rc3" } @@ -57,12 +57,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" } +substrate-subxt-client = { version = "0.1.0", 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 c7ad49ef1e..821cced7a7 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -19,12 +19,13 @@ 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" +serde_json = "1.0.55" sp-keyring = "2.0.0-rc3" -thiserror = "1.0.19" +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 ef05a3cada..14c19000fe 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 99400bfad5..4202e349ef 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 327a07a0bb..6f67c6875c 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 f0a88e2e88..3fed1f10ad 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(), @@ -303,6 +301,7 @@ mod tests { KusamaRuntime as NodeTemplateRuntime, PairSigner, }; + use tempdir::TempDir; #[async_std::test] #[ignore] @@ -328,17 +327,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 +358,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 c88b64d32e..0a6d6d983a 100644 --- a/proc-macro/Cargo.toml +++ b/proc-macro/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "substrate-subxt-proc-macro" -version = "0.8.0" +version = "0.9.0" authors = ["David Craven ", "Parity Technologies "] edition = "2018" autotests = false @@ -18,8 +18,9 @@ proc-macro = true heck = "0.3.1" proc-macro2 = "1.0.18" proc-macro-crate = "0.1.4" +proc-macro-error = "1.0.2" quote = "1.0.7" -syn = "1.0.30" +syn = "1.0.33" synstructure = "0.12.4" [dev-dependencies] @@ -29,7 +30,7 @@ env_logger = "0.7.1" pretty_assertions = "0.6.1" sp-keyring = "2.0.0-rc3" substrate-subxt = { path = ".." } -trybuild = "1.0.28" +trybuild = "1.0.30" [[test]] name = "balances" diff --git a/proc-macro/src/call.rs b/proc-macro/src/call.rs index 095e0952eb..cedf993fe0 100644 --- a/proc-macro/src/call.rs +++ b/proc-macro/src/call.rs @@ -28,7 +28,6 @@ use synstructure::Structure; pub fn call(s: Structure) -> TokenStream { let subxt = utils::use_crate("substrate-subxt"); - let codec = utils::use_crate("parity-scale-codec"); let ident = &s.ast().ident; let generics = &s.ast().generics; let params = utils::type_params(generics); @@ -60,32 +59,29 @@ pub fn call(s: Structure) -> TokenStream { } /// Call extension trait. - pub trait #call_trait> { + pub trait #call_trait { /// Create and submit an extrinsic. fn #call<'a>( &'a self, - signer: &'a (dyn #subxt::Signer + Send + Sync), + signer: &'a (dyn #subxt::Signer + Send + Sync), #args ) -> core::pin::Pin> + Send + 'a>>; /// Create, submit and watch an extrinsic. fn #call_and_watch<'a>( &'a self, - signer: &'a (dyn #subxt::Signer + Send + Sync), + signer: &'a (dyn #subxt::Signer + Send + Sync), #args ) -> core::pin::Pin, #subxt::Error>> + Send + 'a>>; } - impl #call_trait for #subxt::Client + impl #call_trait for #subxt::Client where - T: #module + #subxt::system::System + Send + Sync + 'static, - S: #codec::Encode + Send + Sync + 'static, - E: #subxt::SignedExtra + #subxt::sp_runtime::traits::SignedExtension + Send + Sync + 'static, - <>::Extra as #subxt::sp_runtime::traits::SignedExtension>::AdditionalSigned: Send + Sync, + <>::Extra as #subxt::SignedExtension>::AdditionalSigned: Send + Sync, { fn #call<'a>( &'a self, - signer: &'a (dyn #subxt::Signer + Send + Sync), + signer: &'a (dyn #subxt::Signer + Send + Sync), #args ) -> core::pin::Pin> + Send + 'a>> { let #marker = core::marker::PhantomData::; @@ -94,7 +90,7 @@ pub fn call(s: Structure) -> TokenStream { fn #call_and_watch<'a>( &'a self, - signer: &'a (dyn #subxt::Signer + Send + Sync), + signer: &'a (dyn #subxt::Signer + Send + Sync), #args ) -> core::pin::Pin, #subxt::Error>> + Send + 'a>> { let #marker = core::marker::PhantomData::; @@ -130,11 +126,11 @@ mod tests { } /// Call extension trait. - pub trait TransferCallExt> { + pub trait TransferCallExt { /// Create and submit an extrinsic. fn transfer<'a>( &'a self, - signer: &'a (dyn substrate_subxt::Signer + Send + Sync), + signer: &'a (dyn substrate_subxt::Signer + Send + Sync), to: &'a ::Address, amount: T::Balance, ) -> core::pin::Pin> + Send + 'a>>; @@ -142,22 +138,19 @@ mod tests { /// Create, submit and watch an extrinsic. fn transfer_and_watch<'a>( &'a self, - signer: &'a (dyn substrate_subxt::Signer + Send + Sync), + signer: &'a (dyn substrate_subxt::Signer + Send + Sync), to: &'a ::Address, amount: T::Balance, ) -> core::pin::Pin, substrate_subxt::Error>> + Send + 'a>>; } - impl TransferCallExt for substrate_subxt::Client + impl TransferCallExt for substrate_subxt::Client where - T: Balances + substrate_subxt::system::System + Send + Sync + 'static, - S: codec::Encode + Send + Sync + 'static, - E: substrate_subxt::SignedExtra + substrate_subxt::sp_runtime::traits::SignedExtension + Send + Sync + 'static, - <>::Extra as substrate_subxt::sp_runtime::traits::SignedExtension>::AdditionalSigned: Send + Sync, + <>::Extra as substrate_subxt::SignedExtension>::AdditionalSigned: Send + Sync, { fn transfer<'a>( &'a self, - signer: &'a (dyn substrate_subxt::Signer + Send + Sync), + signer: &'a (dyn substrate_subxt::Signer + Send + Sync), to: &'a ::Address, amount: T::Balance, ) -> core::pin::Pin> + Send + 'a>> { @@ -167,7 +160,7 @@ mod tests { fn transfer_and_watch<'a>( &'a self, - signer: &'a (dyn substrate_subxt::Signer + Send + Sync), + signer: &'a (dyn substrate_subxt::Signer + Send + Sync), to: &'a ::Address, amount: T::Balance, ) -> core::pin::Pin, substrate_subxt::Error>> + Send + 'a>> { diff --git a/proc-macro/src/event.rs b/proc-macro/src/event.rs index a57df0d15f..6c03551fd0 100644 --- a/proc-macro/src/event.rs +++ b/proc-macro/src/event.rs @@ -36,7 +36,7 @@ pub fn event(s: Structure) -> TokenStream { let event = format_ident!("{}", event_name.to_snake_case()); let event_trait = format_ident!("{}EventExt", event_name); - let expanded = quote! { + quote! { impl #subxt::Event for #ident { const MODULE: &'static str = MODULE; const EVENT: &'static str = #event_name; @@ -53,9 +53,7 @@ pub fn event(s: Structure) -> TokenStream { self.find_event() } } - }; - - TokenStream::from(expanded) + } } #[cfg(test)] diff --git a/proc-macro/src/lib.rs b/proc-macro/src/lib.rs index c42006f935..ccbea7e2b4 100644 --- a/proc-macro/src/lib.rs +++ b/proc-macro/src/lib.rs @@ -24,32 +24,35 @@ mod test; mod utils; use proc_macro::TokenStream; +use proc_macro_error::proc_macro_error; use synstructure::{ decl_derive, Structure, }; #[proc_macro_attribute] +#[proc_macro_error] pub fn module(args: TokenStream, input: TokenStream) -> TokenStream { module::module(args.into(), input.into()).into() } -decl_derive!([Call] => call); +decl_derive!([Call] => #[proc_macro_error] call); fn call(s: Structure) -> TokenStream { call::call(s).into() } -decl_derive!([Event] => event); +decl_derive!([Event] => #[proc_macro_error] event); fn event(s: Structure) -> TokenStream { event::event(s).into() } -decl_derive!([Store, attributes(store)] => store); +decl_derive!([Store, attributes(store)] => #[proc_macro_error] store); fn store(s: Structure) -> TokenStream { store::store(s).into() } #[proc_macro] +#[proc_macro_error] pub fn subxt_test(input: TokenStream) -> TokenStream { test::test(input.into()).into() } diff --git a/proc-macro/src/module.rs b/proc-macro/src/module.rs index 162bb584a7..5dddfd5899 100644 --- a/proc-macro/src/module.rs +++ b/proc-macro/src/module.rs @@ -17,6 +17,7 @@ use crate::utils; use heck::SnakeCase; use proc_macro2::TokenStream; +use proc_macro_error::abort; use quote::{ format_ident, quote, @@ -48,8 +49,10 @@ type ModuleAttrs = utils::Attrs; fn ignore(attrs: &[syn::Attribute]) -> bool { for attr in attrs { if let Some(ident) = attr.path.get_ident() { - if ident.to_string() == "module" { - let attrs: ModuleAttrs = syn::parse2(attr.tokens.clone()).unwrap(); + if ident == "module" { + let attrs: ModuleAttrs = syn::parse2(attr.tokens.clone()) + .map_err(|err| abort!("{}", err)) + .unwrap(); if !attrs.attrs.is_empty() { return true } @@ -69,10 +72,12 @@ fn with_module_ident(module: &syn::Ident) -> syn::Ident { pub fn module(_args: TokenStream, tokens: TokenStream) -> TokenStream { let input: Result = syn::parse2(tokens.clone()); - if input.is_err() { + let input = if let Ok(input) = input { + input + } else { + // handle #[module(ignore)] by just returning the tokens return tokens - } - let input = input.unwrap(); + }; let subxt = utils::use_crate("substrate-subxt"); let module = &input.ident; diff --git a/proc-macro/src/store.rs b/proc-macro/src/store.rs index db528c61ac..47294589a1 100644 --- a/proc-macro/src/store.rs +++ b/proc-macro/src/store.rs @@ -20,6 +20,7 @@ use heck::{ SnakeCase, }; use proc_macro2::TokenStream; +use proc_macro_error::abort; use quote::{ format_ident, quote, @@ -50,7 +51,9 @@ impl Parse for StoreAttr { type StoreAttrs = utils::Attrs; fn parse_returns_attr(attr: &syn::Attribute) -> Option<(syn::Type, syn::Type, bool)> { - let attrs: StoreAttrs = syn::parse2(attr.tokens.clone()).unwrap(); + let attrs: StoreAttrs = syn::parse2(attr.tokens.clone()) + .map_err(|err| abort!("{}", err)) + .unwrap(); attrs.attrs.into_iter().next().map(|attr| { let StoreAttr::Returns(attr) = attr; let ty = attr.value; @@ -81,7 +84,9 @@ pub fn store(s: Structure) -> TokenStream { .iter() .filter_map(|bi| bi.ast().attrs.iter().filter_map(parse_returns_attr).next()) .next() - .expect("#[store(returns = ..)] needs to be specified."); + .unwrap_or_else(|| { + abort!(ident, "#[store(returns = ..)] needs to be specified.") + }); let fetch = if uses_default { quote!(fetch_or_default) } else { @@ -93,7 +98,13 @@ pub fn store(s: Structure) -> TokenStream { 0 => "plain", 1 => "map", 2 => "double_map", - _ => panic!("invalid number of arguments"), + _ => { + abort!( + ident, + "Expected 0-2 fields but found {}", + filtered_fields.len() + ); + } } ); let keys = filtered_fields @@ -127,12 +138,7 @@ pub fn store(s: Structure) -> TokenStream { ) -> core::pin::Pin> + Send + 'a>>; } - impl #store_trait for #subxt::Client - where - T: #module + Send + Sync, - S: 'static, - E: Send + Sync + 'static, - { + impl #store_trait for #subxt::Client { fn #store<'a>( &'a self, #args @@ -185,12 +191,7 @@ mod tests { ) -> core::pin::Pin, substrate_subxt::Error>> + Send + 'a>>; } - impl AccountStoreExt for substrate_subxt::Client - where - T: Balances + Send + Sync, - S: 'static, - E: Send + Sync + 'static, - { + impl AccountStoreExt for substrate_subxt::Client { fn account<'a>( &'a self, account_id: &'a ::AccountId, diff --git a/proc-macro/src/test.rs b/proc-macro/src/test.rs index bef72e0eb3..fc5ebfeb52 100644 --- a/proc-macro/src/test.rs +++ b/proc-macro/src/test.rs @@ -16,6 +16,7 @@ use crate::utils; use proc_macro2::TokenStream; +use proc_macro_error::abort; use quote::{ format_ident, quote, @@ -34,8 +35,6 @@ mod kw { custom_keyword!(name); custom_keyword!(runtime); custom_keyword!(account); - custom_keyword!(signature); - custom_keyword!(extra); custom_keyword!(prelude); custom_keyword!(step); custom_keyword!(state); @@ -70,10 +69,9 @@ struct Items { impl Parse for Items { fn parse(input: ParseStream) -> syn::Result { let content; - Ok(Self { - brace: syn::braced!(content in input), - items: content.parse_terminated(I::parse)?, - }) + let brace = syn::braced!(content in input); + let items = content.parse_terminated(I::parse)?; + Ok(Self { brace, items }) } } @@ -82,10 +80,8 @@ type ItemTest = Items; #[derive(Debug)] enum TestItem { Name(Item), - Runtime(Item), + Runtime(Item>), Account(Item), - Signature(Item), - Extra(Item), State(Item), Prelude(Item), Step(Item), @@ -99,10 +95,6 @@ impl Parse for TestItem { Ok(TestItem::Runtime(input.parse()?)) } else if input.peek(kw::account) { Ok(TestItem::Account(input.parse()?)) - } else if input.peek(kw::signature) { - Ok(TestItem::Signature(input.parse()?)) - } else if input.peek(kw::extra) { - Ok(TestItem::Extra(input.parse()?)) } else if input.peek(kw::state) { Ok(TestItem::State(input.parse()?)) } else if input.peek(kw::prelude) { @@ -142,10 +134,8 @@ type StateItem = Item; struct Test { name: syn::Ident, - runtime: syn::Type, + runtime: Box, account: syn::Ident, - signature: syn::Type, - extra: syn::Type, state: Option, prelude: Option, steps: Vec, @@ -156,11 +146,11 @@ impl From for Test { let mut name = None; let mut runtime = None; let mut account = None; - let mut signature = None; - let mut extra = None; let mut state = None; let mut prelude = None; let mut steps = vec![]; + + let span = test.brace.span; for test_item in test.items { match test_item { TestItem::Name(item) => { @@ -172,12 +162,6 @@ impl From for Test { TestItem::Account(item) => { account = Some(item.value); } - TestItem::Signature(item) => { - signature = Some(item.value); - } - TestItem::Extra(item) => { - extra = Some(item.value); - } TestItem::State(item) => { state = Some(item.value.into()); } @@ -193,14 +177,8 @@ impl From for Test { let runtime = runtime .unwrap_or_else(|| syn::parse2(quote!(#subxt::DefaultNodeRuntime)).unwrap()); Self { - name: name.expect("No name specified"), + name: name.unwrap_or_else(|| abort!(span, "No name specified")), account: account.unwrap_or_else(|| format_ident!("Alice")), - signature: signature.unwrap_or_else(|| { - syn::parse2(quote!(#subxt::sp_runtime::MultiSignature)).unwrap() - }), - extra: extra.unwrap_or_else(|| { - syn::parse2(quote!(#subxt::DefaultExtra<#runtime>)).unwrap() - }), runtime, state, prelude, @@ -219,8 +197,6 @@ impl Test { name, runtime, account, - signature, - extra, state, prelude, steps, @@ -234,7 +210,7 @@ impl Test { #[ignore] async fn #name() { #env_logger - let client = #subxt::ClientBuilder::<#runtime, #signature, #extra>::new() + let client = #subxt::ClientBuilder::<#runtime>::new() .build().await.unwrap(); let signer = #subxt::PairSigner::new(#sp_keyring::AccountKeyring::#account.pair()); @@ -277,6 +253,7 @@ impl From for Step { let mut event = vec![]; let mut assert = None; + let span = step.brace.span; for step_item in step.items { match step_item { StepItem::State(item) => { @@ -297,7 +274,7 @@ impl From for Step { Self { state, - call: call.expect("Step requires a call."), + call: call.unwrap_or_else(|| abort!(span, "Step requires a call.")), event_name, event, assert, @@ -404,14 +381,15 @@ impl From for State { fn struct_name(expr: &syn::Expr) -> syn::Path { if let syn::Expr::Struct(syn::ExprStruct { path, .. }) = expr { - return path.clone() + path.clone() } else { - panic!("not a struct"); + abort!(expr, "Expected a struct"); } } pub fn test(input: TokenStream) -> TokenStream { - let item_test: ItemTest = syn::parse2(input).unwrap(); + let item_test: ItemTest = + syn::parse2(input).map_err(|err| abort!("{}", err)).unwrap(); Test::from(item_test).into_tokens() } @@ -450,11 +428,7 @@ mod tests { #[ignore] async fn test_transfer_balance() { env_logger::try_init().ok(); - let client = substrate_subxt::ClientBuilder::< - KusamaRuntime, - substrate_subxt::sp_runtime::MultiSignature, - substrate_subxt::DefaultExtra - >::new().build().await.unwrap(); + let client = substrate_subxt::ClientBuilder::::new().build().await.unwrap(); let signer = substrate_subxt::PairSigner::new(sp_keyring::AccountKeyring::Alice.pair()); #[allow(unused)] let alice = sp_keyring::AccountKeyring::Alice.to_account_id(); diff --git a/proc-macro/src/utils.rs b/proc-macro/src/utils.rs index 25a9198eae..a595d42fe6 100644 --- a/proc-macro/src/utils.rs +++ b/proc-macro/src/utils.rs @@ -56,7 +56,7 @@ pub fn bindings<'a>(s: &'a Structure) -> Vec<&'a BindingInfo<'a>> { type Field = (syn::Ident, syn::Type); -pub fn fields<'a>(bindings: &'a [&'a BindingInfo<'a>]) -> Vec { +pub fn fields(bindings: &[&BindingInfo<'_>]) -> Vec { bindings .iter() .enumerate() @@ -72,7 +72,7 @@ pub fn fields<'a>(bindings: &'a [&'a BindingInfo<'a>]) -> Vec { .collect() } -pub fn marker_field<'a>(fields: &'a [Field]) -> Option { +pub fn marker_field(fields: &[Field]) -> Option { fields .iter() .filter_map(|(field, ty)| { @@ -86,7 +86,7 @@ pub fn marker_field<'a>(fields: &'a [Field]) -> Option { .cloned() } -pub fn filter_fields<'a>(fields: &'a [Field], field: &'a syn::Ident) -> Vec { +pub fn filter_fields(fields: &[Field], field: &syn::Ident) -> Vec { fields .iter() .filter_map(|(field2, ty)| { @@ -99,12 +99,12 @@ pub fn filter_fields<'a>(fields: &'a [Field], field: &'a syn::Ident) -> Vec(fields: &'a [Field]) -> TokenStream { +pub fn fields_to_args(fields: &[Field]) -> TokenStream { let args = fields.iter().map(|(field, ty)| quote!(#field: #ty,)); quote!(#(#args)*) } -pub fn build_struct<'a>(ident: &'a syn::Ident, fields: &'a [Field]) -> TokenStream { +pub fn build_struct(ident: &syn::Ident, fields: &[Field]) -> TokenStream { let fields = fields.iter().map(|(field, _)| field); quote!(#ident { #(#fields,)* }) } @@ -170,7 +170,7 @@ pub fn type_params(generics: &syn::Generics) -> Vec { pub fn parse_option(ty: &syn::Type) -> Option { if let syn::Type::Path(ty_path) = ty { if let Some(seg) = ty_path.path.segments.first() { - if seg.ident.to_string() == "Option" { + if &seg.ident == "Option" { if let syn::PathArguments::AngleBracketed(args) = &seg.arguments { if let Some(syn::GenericArgument::Type(ty)) = args.args.first() { return Some(ty.clone()) @@ -191,10 +191,9 @@ pub struct Attrs { impl Parse for Attrs { fn parse(input: ParseStream) -> syn::Result { let content; - Ok(Self { - paren: syn::parenthesized!(content in input), - attrs: content.parse_terminated(A::parse)?, - }) + let paren = syn::parenthesized!(content in input); + let attrs = content.parse_terminated(A::parse)?; + Ok(Self { paren, attrs }) } } diff --git a/src/error.rs b/src/error.rs index bda8fbb527..cbadeb35f5 100644 --- a/src/error.rs +++ b/src/error.rs @@ -19,15 +19,19 @@ use jsonrpsee::{ transport::ws::WsNewDnsError, }; use sp_core::crypto::SecretStringError; -use sp_runtime::transaction_validity::TransactionValidityError; +use sp_runtime::{ + transaction_validity::TransactionValidityError, + DispatchError, +}; +use thiserror::Error; -use crate::{ - events::EventsError, - metadata::MetadataError, +use crate::metadata::{ + Metadata, + MetadataError, }; /// Error enum. -#[derive(Debug, thiserror::Error)] +#[derive(Debug, Error)] pub enum Error { /// Io error. #[error("Io error: {0}")] @@ -50,12 +54,21 @@ pub enum Error { /// Extrinsic validity error #[error("Transaction Validity Error: {0:?}")] Invalid(TransactionValidityError), - /// Events error. - #[error("Event error: {0}")] - Events(#[from] EventsError), /// Metadata error. #[error("Metadata error: {0}")] Metadata(#[from] MetadataError), + /// Type size unavailable. + #[error("Type size unavailable while decoding event: {0:?}")] + TypeSizeUnavailable(String), + /// Runtime error. + #[error("Runtime error: {0}")] + Runtime(RuntimeError), + /// Bad origin. + #[error("Bad origin: throw by ensure_signed, ensure_root or ensure_none.")] + BadOrigin, + /// Cannot lookup. + #[error("Cannot lookup some information required to validate the transaction.")] + CannotLookup, /// Other error. #[error("Other error: {0}")] Other(String), @@ -84,3 +97,34 @@ impl From for Error { Error::Other(error) } } + +impl Error { + /// Converts a `DispatchError` into a subxt error. + pub fn from_dispatch(metadata: &Metadata, error: DispatchError) -> Result<(), Self> { + match error { + DispatchError::Module { + index, + error, + message: _, + } => { + let module = metadata.module_with_errors(index)?; + let error = module.error(error)?; + Err(Error::Runtime(RuntimeError { + module: module.name().to_string(), + error: error.to_string(), + })) + } + DispatchError::BadOrigin => Err(Error::BadOrigin), + DispatchError::CannotLookup => Err(Error::CannotLookup), + DispatchError::Other(msg) => Err(Error::Other(msg.into())), + } + } +} + +/// Runtime errors. +#[derive(Clone, Debug, Eq, Error, PartialEq)] +#[error("{error} from {module}")] +pub struct RuntimeError { + pub module: String, + pub error: String, +} diff --git a/src/events.rs b/src/events.rs index 81b3376d22..b226947e4a 100644 --- a/src/events.rs +++ b/src/events.rs @@ -19,10 +19,14 @@ use codec::{ Compact, Decode, Encode, - Error as CodecError, Input, Output, }; +use frame_support::dispatch::DispatchInfo; +use sp_runtime::{ + DispatchError, + DispatchResult, +}; use std::{ collections::{ HashMap, @@ -33,26 +37,17 @@ use std::{ Send, }, }; -use thiserror::Error; use crate::{ + error::Error, metadata::{ EventArg, Metadata, - MetadataError, }, Phase, System, - SystemEvent, }; -/// Top level Event that can be produced by a substrate runtime -#[derive(Debug)] -pub enum RuntimeEvent { - System(SystemEvent), - Raw(RawEvent), -} - /// Raw bytes for an Event #[derive(Debug)] pub struct RawEvent { @@ -64,20 +59,6 @@ pub struct RawEvent { pub data: Vec, } -/// Events error. -#[derive(Debug, Error)] -pub enum EventsError { - /// Codec error. - #[error("Scale codec error: {0:?}")] - CodecError(#[from] CodecError), - /// Metadata error. - #[error("Metadata error: {0:?}")] - Metadata(#[from] MetadataError), - /// Type size unavailable. - #[error("Type Sizes Unavailable: {0:?}")] - TypeSizeUnavailable(String), -} - /// Event decoder. #[derive(Debug)] pub struct EventsDecoder { @@ -95,6 +76,8 @@ impl EventsDecoder { marker: PhantomData, }; // register default event arg type sizes for dynamic decoding of events + decoder.register_type_size::<()>("PhantomData"); + decoder.register_type_size::("DispatchInfo"); decoder.register_type_size::("bool"); decoder.register_type_size::("ReferendumIndex"); decoder.register_type_size::<[u8; 16]>("Kind"); @@ -132,10 +115,7 @@ impl EventsDecoder { for event in module.events() { for arg in event.arguments() { for primitive in arg.primitives() { - if module.name() != "System" - && !self.type_sizes.contains_key(&primitive) - && !primitive.contains("PhantomData") - { + if !self.type_sizes.contains_key(&primitive) { missing.insert(format!( "{}::{}::{}", module.name(), @@ -161,7 +141,7 @@ impl EventsDecoder { args: &[EventArg], input: &mut I, output: &mut W, - ) -> Result<(), EventsError> { + ) -> Result<(), Error> { for arg in args { match arg { EventArg::Vec(arg) => { @@ -173,16 +153,22 @@ impl EventsDecoder { } EventArg::Tuple(args) => self.decode_raw_bytes(args, input, output)?, EventArg::Primitive(name) => { - if name.contains("PhantomData") { - // PhantomData is size 0 - return Ok(()) - } - if let Some(size) = self.type_sizes.get(name) { - let mut buf = vec![0; *size]; - input.read(&mut buf)?; - output.write(&buf); - } else { - return Err(EventsError::TypeSizeUnavailable(name.to_owned())) + let result = match name.as_str() { + "DispatchResult" => DispatchResult::decode(input)?, + "DispatchError" => Err(DispatchError::decode(input)?), + _ => { + if let Some(size) = self.type_sizes.get(name) { + let mut buf = vec![0; *size]; + input.read(&mut buf)?; + output.write(&buf); + Ok(()) + } else { + return Err(Error::TypeSizeUnavailable(name.to_owned())) + } + } + }; + if let Err(error) = result { + Error::from_dispatch(&self.metadata, error)?; } } } @@ -194,7 +180,7 @@ impl EventsDecoder { pub fn decode_events( &self, input: &mut &[u8], - ) -> Result)>, EventsError> { + ) -> Result, Error> { let compact_len = >::decode(input)?; let len = compact_len.0 as usize; @@ -205,33 +191,24 @@ impl EventsDecoder { let module_variant = input.read_byte()?; let module = self.metadata.module_with_events(module_variant)?; - let event = if module.name() == "System" { - let system_event = SystemEvent::decode(input)?; - RuntimeEvent::System(system_event) - } else { - let event_variant = input.read_byte()?; - let event_metadata = module.event(event_variant)?; + let event_variant = input.read_byte()?; + let event_metadata = module.event(event_variant)?; - log::debug!( - "received event '{}::{}'", - module.name(), - event_metadata.name - ); + log::debug!( + "received event '{}::{}'", + module.name(), + event_metadata.name + ); - let mut event_data = Vec::::new(); - self.decode_raw_bytes( - &event_metadata.arguments(), - input, - &mut event_data, - )?; + let mut event_data = Vec::::new(); + self.decode_raw_bytes(&event_metadata.arguments(), input, &mut event_data)?; - log::debug!("raw bytes: {}", hex::encode(&event_data),); + log::debug!("raw bytes: {}", hex::encode(&event_data),); - RuntimeEvent::Raw(RawEvent { - module: module.name().to_string(), - variant: event_metadata.name.clone(), - data: event_data, - }) + let event = RawEvent { + module: module.name().to_string(), + variant: event_metadata.name.clone(), + data: event_data, }; // topics come after the event data in EventRecord diff --git a/src/extra.rs b/src/extra.rs index b7f4cc6ce3..fa7ff54a55 100644 --- a/src/extra.rs +++ b/src/extra.rs @@ -222,9 +222,9 @@ where } /// Trait for implementing transaction extras for a runtime. -pub trait SignedExtra { +pub trait SignedExtra: SignedExtension { /// The type the extras. - type Extra: SignedExtension; + type Extra: SignedExtension + Send + Sync; /// Creates a new `SignedExtra`. fn new( diff --git a/src/frame/balances.rs b/src/frame/balances.rs index 6f6eb4ebc3..bb1bdad498 100644 --- a/src/frame/balances.rs +++ b/src/frame/balances.rs @@ -110,36 +110,58 @@ pub struct TransferEvent { mod tests { use super::*; use crate::{ - system::{ - AccountStore, - AccountStoreExt, + error::{ + Error, + RuntimeError, }, - tests::test_client, + events::EventsDecoder, + signer::{ + PairSigner, + Signer, + }, + subscription::EventSubscription, + system::AccountStoreExt, + tests::{ + test_client, + TestRuntime, + }, + }; + use sp_core::{ + sr25519::Pair, + Pair as _, }; use sp_keyring::AccountKeyring; - subxt_test!({ - name: test_transfer, - step: { - state: { - alice: AccountStore { account_id: &alice }, - bob: AccountStore { account_id: &bob }, - }, - call: TransferCall { - to: &bob.clone().into(), - amount: 10_000, - }, - event: TransferEvent { - from: alice.clone(), - to: bob.clone(), - amount: 10_000, - }, - assert: { - assert!(pre.alice.data.free - 10_000 >= post.alice.data.free); - assert_eq!(pre.bob.data.free + 10_000, post.bob.data.free); - }, - }, - }); + #[async_std::test] + async fn test_transfer() { + env_logger::try_init().ok(); + let alice = PairSigner::::new(AccountKeyring::Alice.pair()); + let bob = PairSigner::::new(AccountKeyring::Bob.pair()); + let (client, _) = test_client().await; + + let alice_pre = client.account(alice.account_id(), None).await.unwrap(); + let bob_pre = client.account(bob.account_id(), None).await.unwrap(); + + let event = client + .transfer_and_watch(&alice, &bob.account_id(), 10_000) + .await + .unwrap() + .transfer() + .unwrap() + .unwrap(); + let expected_event = TransferEvent { + from: alice.account_id().clone(), + to: bob.account_id().clone(), + amount: 10_000, + }; + assert_eq!(event, expected_event); + + let alice_post = client.account(alice.account_id(), None).await.unwrap(); + let bob_post = client.account(bob.account_id(), None).await.unwrap(); + + assert!(alice_pre.data.free - 10_000 >= alice_post.data.free); + assert_eq!(bob_pre.data.free + 10_000, bob_post.data.free); + } #[async_std::test] async fn test_state_total_issuance() { @@ -157,4 +179,52 @@ mod tests { let info = client.account(&account, None).await.unwrap(); assert_ne!(info.data.free, 0); } + + #[async_std::test] + async fn test_transfer_error() { + env_logger::try_init().ok(); + let alice = PairSigner::new(AccountKeyring::Alice.pair()); + let hans = PairSigner::new(Pair::generate().0); + let (client, _) = test_client().await; + client + .transfer_and_watch(&alice, hans.account_id(), 100_000_000_000) + .await + .unwrap(); + let res = client + .transfer_and_watch(&hans, alice.account_id(), 100_000_000_000) + .await; + if let Err(Error::Runtime(error)) = res { + let error2 = RuntimeError { + module: "Balances".into(), + error: "InsufficientBalance".into(), + }; + assert_eq!(error, error2); + } else { + 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/frame/mod.rs b/src/frame/mod.rs index ad3571b20e..f9b552d5ff 100644 --- a/src/frame/mod.rs +++ b/src/frame/mod.rs @@ -33,6 +33,7 @@ pub mod balances; pub mod contracts; pub mod session; pub mod staking; +pub mod sudo; pub mod system; /// Store trait. diff --git a/src/frame/sudo.rs b/src/frame/sudo.rs new file mode 100644 index 0000000000..541a923bfc --- /dev/null +++ b/src/frame/sudo.rs @@ -0,0 +1,78 @@ +// 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 . + +//! Implements support for the frame_sudo module. + +use crate::{ + frame::system::{ + System, + SystemEventsDecoder, + }, + Encoded, +}; +use codec::Encode; +use core::marker::PhantomData; + +/// The subset of the `frame_sudo::Trait` that a client must implement. +#[module] +pub trait Sudo: System {} + +/// Execute a transaction with sudo permissions. +#[derive(Clone, Debug, Eq, PartialEq, Call, Encode)] +pub struct SudoCall<'a, T: Sudo> { + /// Runtime marker. + pub _runtime: PhantomData, + /// Encoded transaction. + pub call: &'a Encoded, +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + error::Error, + frame::balances::TransferCall, + signer::PairSigner, + tests::{ + test_client, + TestRuntime, + }, + }; + use sp_keyring::AccountKeyring; + + #[async_std::test] + async fn test_sudo() { + env_logger::try_init().ok(); + let alice = PairSigner::::new(AccountKeyring::Alice.pair()); + let (client, _) = test_client().await; + + let call = client + .encode(TransferCall { + to: &AccountKeyring::Bob.to_account_id(), + amount: 10_000, + }) + .unwrap(); + + let res = client.sudo_and_watch(&alice, &call).await; + assert!( + if let Err(Error::BadOrigin) = res { + true + } else { + false + } + ); + } +} diff --git a/src/frame/system.rs b/src/frame/system.rs index 410e9a5299..f9beaebc46 100644 --- a/src/frame/system.rs +++ b/src/frame/system.rs @@ -157,21 +157,6 @@ pub struct SetCodeCall<'a, T: System> { pub code: &'a [u8], } -/// Event for the System module. -#[derive(Clone, Debug, Eq, PartialEq, Decode)] -pub enum SystemEvent { - /// An extrinsic completed successfully. - ExtrinsicSuccess(DispatchInfo), - /// An extrinsic failed. - ExtrinsicFailed(DispatchError, DispatchInfo), - /// `:code` was updated. - CodeUpdated, - /// A new account was created. - NewAccount(T::AccountId), - /// An account was reaped. - KilledAccount(T::AccountId), -} - /// A phase of a block's execution. #[derive(Clone, Debug, Eq, PartialEq, Decode)] pub enum Phase { @@ -180,3 +165,44 @@ pub enum Phase { /// The end. Finalization, } + +/// An extrinsic completed successfully. +#[derive(Clone, Debug, Eq, PartialEq, Event, Decode)] +pub struct ExtrinsicSuccessEvent { + /// Runtime marker. + pub _runtime: PhantomData, + /// The dispatch info. + pub info: DispatchInfo, +} + +/// An extrinsic failed. +#[derive(Clone, Debug, Eq, PartialEq, Event, Decode)] +pub struct ExtrinsicFailedEvent { + /// Runtime marker. + pub _runtime: PhantomData, + /// The dispatch error. + pub error: DispatchError, + /// The dispatch info. + pub info: DispatchInfo, +} + +/// `:code` was updated. +#[derive(Clone, Debug, Eq, PartialEq, Event, Decode)] +pub struct CodeUpdatedEvent { + /// Runtime marker. + pub _runtime: PhantomData, +} + +/// A new account was created. +#[derive(Clone, Debug, Eq, PartialEq, Event, Decode)] +pub struct NewAccountEvent { + /// Created account id. + pub account: T::AccountId, +} + +/// An account was reaped. +#[derive(Clone, Debug, Eq, PartialEq, Event, Decode)] +pub struct KilledAccountEvent { + /// Killed account id. + pub account: T::AccountId, +} diff --git a/src/lib.rs b/src/lib.rs index 5dc9a5fbea..5c1264461d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,6 +21,7 @@ bad_style, const_err, improper_ctypes, + missing_docs, non_shorthand_field_patterns, no_mangle_generic_items, overflowing_literals, @@ -49,10 +50,7 @@ pub use substrate_subxt_client as client; pub use sp_core; pub use sp_runtime; -use codec::{ - Decode, - Encode, -}; +use codec::Decode; use futures::future; use jsonrpsee::client::Subscription; use sc_rpc_api::state::ReadProof; @@ -60,14 +58,7 @@ use sp_core::storage::{ StorageChangeSet, StorageKey, }; -use sp_runtime::{ - generic::{ - SignedPayload, - UncheckedExtrinsic, - }, - traits::SignedExtension, - MultiSignature, -}; +pub use sp_runtime::traits::SignedExtension; use sp_version::RuntimeVersion; use std::marker::PhantomData; @@ -79,12 +70,12 @@ mod metadata; mod rpc; mod runtimes; mod signer; +mod subscription; pub use crate::{ error::Error, events::{ EventsDecoder, - EventsError, RawEvent, }, extra::*, @@ -99,6 +90,7 @@ pub use crate::{ }, runtimes::*, signer::*, + subscription::*, substrate_subxt_proc_macro::*, }; use crate::{ @@ -106,7 +98,6 @@ use crate::{ AccountStoreExt, Phase, System, - SystemEvent, }, rpc::{ ChainBlock, @@ -117,13 +108,13 @@ use crate::{ /// ClientBuilder for constructing a Client. #[derive(Default)] #[allow(missing_debug_implementations)] -pub struct ClientBuilder> { - _marker: std::marker::PhantomData<(T, S, E)>, +pub struct ClientBuilder { + _marker: std::marker::PhantomData, url: Option, client: Option, } -impl ClientBuilder { +impl ClientBuilder { /// Creates a new ClientBuilder. pub fn new() -> Self { Self { @@ -146,7 +137,7 @@ impl ClientBuilder { } /// Creates a new Client. - pub async fn build(self) -> Result, Error> { + pub async fn build(self) -> Result, Error> { let client = if let Some(client) = self.client { client } else { @@ -176,15 +167,15 @@ impl ClientBuilder { /// Client to interface with a substrate node. #[allow(missing_debug_implementations)] -pub struct Client> { +pub struct Client { rpc: Rpc, genesis_hash: T::Hash, metadata: Metadata, runtime_version: RuntimeVersion, - _marker: PhantomData<(fn() -> S, E)>, + _marker: PhantomData<(fn() -> T::Signature, T::Extra)>, } -impl Clone for Client { +impl Clone for Client { fn clone(&self) -> Self { Self { rpc: self.rpc.clone(), @@ -196,7 +187,7 @@ impl Clone for Client { } } -impl Client { +impl Client { /// Returns the genesis hash. pub fn genesis(&self) -> &T::Hash { &self.genesis_hash @@ -312,14 +303,15 @@ impl Client { let headers = self.rpc.subscribe_finalized_blocks().await?; Ok(headers) } -} -impl Client -where - T: System + Send + Sync + 'static, - S: Encode + Send + Sync + 'static, - E: SignedExtra + SignedExtension + Send + Sync + 'static, -{ + /// Encodes a call. + pub fn encode>(&self, call: C) -> Result { + Ok(self + .metadata() + .module_with_calls(C::MODULE) + .and_then(|module| module.call(C::FUNCTION, call))?) + } + /// Creates an unsigned extrinsic. /// /// If `nonce` is `None` the nonce will be fetched from the chain. @@ -328,7 +320,11 @@ where call: C, account_id: &::AccountId, nonce: Option, - ) -> Result>::Extra>, Error> { + ) -> Result, Error> + where + <>::Extra as SignedExtension>::AdditionalSigned: + Send + Sync, + { let account_nonce = if let Some(nonce) = nonce { nonce } else { @@ -337,12 +333,10 @@ where let spec_version = self.runtime_version.spec_version; let tx_version = self.runtime_version.transaction_version; let genesis_hash = self.genesis_hash; - let call = self - .metadata() - .module_with_calls(C::MODULE) - .and_then(|module| module.call(C::FUNCTION, call))?; - let extra: E = E::new(spec_version, tx_version, account_nonce, genesis_hash); - let raw_payload = SignedPayload::new(call, extra.extra())?; + let call = self.encode(call)?; + let extra: T::Extra = + T::Extra::new(spec_version, tx_version, account_nonce, genesis_hash); + let raw_payload = SignedPayload::::new(call, extra.extra())?; Ok(raw_payload) } @@ -350,13 +344,11 @@ where pub async fn create_signed + Send + Sync>( &self, call: C, - signer: &(dyn Signer + Send + Sync), - ) -> Result< - UncheckedExtrinsic>::Extra>, - Error, - > + signer: &(dyn Signer + Send + Sync), + ) -> Result, Error> where - <>::Extra as SignedExtension>::AdditionalSigned: Send + Sync, + <>::Extra as SignedExtension>::AdditionalSigned: + Send + Sync, { let unsigned = self .create_unsigned(call, signer.account_id(), signer.nonce()) @@ -376,12 +368,7 @@ where /// Create and submit an extrinsic and return corresponding Hash if successful pub async fn submit_extrinsic( &self, - extrinsic: UncheckedExtrinsic< - T::Address, - Encoded, - S, - >::Extra, - >, + extrinsic: UncheckedExtrinsic, ) -> Result { self.rpc.submit_extrinsic(extrinsic).await } @@ -389,12 +376,7 @@ where /// Create and submit an extrinsic and return corresponding Event if successful pub async fn submit_and_watch_extrinsic( &self, - extrinsic: UncheckedExtrinsic< - T::Address, - Encoded, - S, - >::Extra, - >, + extrinsic: UncheckedExtrinsic, decoder: EventsDecoder, ) -> Result, Error> { self.rpc @@ -406,10 +388,11 @@ where pub async fn submit + Send + Sync>( &self, call: C, - signer: &(dyn Signer + Send + Sync), + signer: &(dyn Signer + Send + Sync), ) -> Result where - <>::Extra as SignedExtension>::AdditionalSigned: Send + Sync, + <>::Extra as SignedExtension>::AdditionalSigned: + Send + Sync, { let extrinsic = self.create_signed(call, signer).await?; self.submit_extrinsic(extrinsic).await @@ -419,10 +402,11 @@ where pub async fn watch + Send + Sync>( &self, call: C, - signer: &(dyn Signer + Send + Sync), + signer: &(dyn Signer + Send + Sync), ) -> Result, Error> where - <>::Extra as SignedExtension>::AdditionalSigned: Send + Sync, + <>::Extra as SignedExtension>::AdditionalSigned: + Send + Sync, { let extrinsic = self.create_signed(call, signer).await?; let decoder = self.events_decoder::(); @@ -432,7 +416,7 @@ where /// Wraps an already encoded byte vector, prevents being encoded as a raw byte vector as part of /// the transaction payload -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Eq, PartialEq)] pub struct Encoded(pub Vec); impl codec::Encode for Encoded { @@ -444,6 +428,7 @@ impl codec::Encode for Encoded { #[cfg(test)] mod tests { use super::*; + use codec::Encode; use sp_core::{ storage::{ well_known_keys, @@ -455,6 +440,7 @@ mod tests { AccountKeyring, Ed25519Keyring, }; + use sp_runtime::MultiSignature; use substrate_subxt_client::{ DatabaseConfig, Role, @@ -463,7 +449,10 @@ mod tests { }; use tempdir::TempDir; - pub(crate) async fn test_client() -> (Client, TempDir) { + 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", @@ -474,8 +463,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() @@ -488,7 +477,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/metadata.rs b/src/metadata.rs index cf5f9c9852..899aed4c23 100644 --- a/src/metadata.rs +++ b/src/metadata.rs @@ -58,6 +58,9 @@ pub enum MetadataError { /// Event is not in metadata. #[error("Event {0} not found")] EventNotFound(u8), + /// Event is not in metadata. + #[error("Error {0} not found")] + ErrorNotFound(u8), /// Storage is not in metadata. #[error("Storage {0} not found")] StorageNotFound(&'static str), @@ -75,6 +78,7 @@ pub struct Metadata { modules: HashMap, modules_with_calls: HashMap, modules_with_events: HashMap, + modules_with_errors: HashMap, } impl Metadata { @@ -116,6 +120,16 @@ impl Metadata { .ok_or(MetadataError::ModuleIndexNotFound(module_index)) } + pub(crate) fn module_with_errors( + &self, + module_index: u8, + ) -> Result<&ModuleWithErrors, MetadataError> { + self.modules_with_errors + .values() + .find(|&module| module.index == module_index) + .ok_or(MetadataError::ModuleIndexNotFound(module_index)) + } + /// Pretty print metadata. pub fn pretty(&self) -> String { let mut string = String::new(); @@ -206,6 +220,25 @@ impl ModuleWithEvents { } } +#[derive(Clone, Debug)] +pub struct ModuleWithErrors { + index: u8, + name: String, + errors: HashMap, +} + +impl ModuleWithErrors { + pub fn name(&self) -> &str { + &self.name + } + + pub fn error(&self, index: u8) -> Result<&String, MetadataError> { + self.errors + .get(&index) + .ok_or(MetadataError::ErrorNotFound(index)) + } +} + #[derive(Clone, Debug)] pub struct StorageMetadata { module_prefix: String, @@ -437,6 +470,7 @@ impl TryFrom for Metadata { let mut modules = HashMap::new(); let mut modules_with_calls = HashMap::new(); let mut modules_with_events = HashMap::new(); + let mut modules_with_errors = HashMap::new(); for module in convert(meta.modules)?.into_iter() { let module_name = convert(module.name.clone())?; @@ -490,11 +524,24 @@ impl TryFrom for Metadata { }, ); } + let mut error_map = HashMap::new(); + for (index, error) in convert(module.errors)?.into_iter().enumerate() { + error_map.insert(index as u8, convert_error(error)?); + } + modules_with_errors.insert( + module_name.clone(), + ModuleWithErrors { + index: modules_with_errors.len() as u8, + name: module_name.clone(), + errors: error_map, + }, + ); } Ok(Metadata { modules, modules_with_calls, modules_with_events, + modules_with_errors, }) } } @@ -534,3 +581,9 @@ fn convert_entry( default, }) } + +fn convert_error( + error: frame_metadata::ErrorMetadata, +) -> Result { + convert(error.name) +} diff --git a/src/rpc.rs b/src/rpc.rs index 047ddf05fc..81e79a4134 100644 --- a/src/rpc.rs +++ b/src/rpc.rs @@ -68,17 +68,14 @@ use crate::{ events::{ EventsDecoder, RawEvent, - RuntimeEvent, }, frame::{ - system::{ - Phase, - System, - SystemEvent, - }, + system::System, Event, }, metadata::Metadata, + runtimes::Runtime, + subscription::EventSubscription, }; pub type ChainBlock = @@ -109,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(), @@ -123,7 +120,7 @@ impl Clone for Rpc { } } -impl Rpc { +impl Rpc { pub fn new(client: Client) -> Self { Self { client, @@ -258,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)); @@ -362,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()) @@ -403,36 +417,16 @@ pub struct ExtrinsicSuccess { /// Extrinsic hash. pub extrinsic: T::Hash, /// Raw runtime events, can be decoded by the caller. - pub events: Vec>, + pub events: Vec, } impl ExtrinsicSuccess { /// Find the Event for the given module/variant, with raw encoded event data. /// Returns `None` if the Event is not found. pub fn find_event_raw(&self, module: &str, variant: &str) -> Option<&RawEvent> { - self.events.iter().find_map(|evt| { - match evt { - RuntimeEvent::Raw(ref raw) - if raw.module == module && raw.variant == variant => - { - Some(raw) - } - _ => None, - } - }) - } - - /// Returns all System Events - pub fn system_events(&self) -> Vec<&SystemEvent> { self.events .iter() - .filter_map(|evt| { - match evt { - RuntimeEvent::System(evt) => Some(evt), - _ => None, - } - }) - .collect() + .find(|raw| raw.module == module && raw.variant == variant) } /// Find the Event for the given module/variant, attempting to decode the event data. @@ -446,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.into()), - } - } - } - 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/runtimes.rs b/src/runtimes.rs index 7143fda0c2..8714b0dd59 100644 --- a/src/runtimes.rs +++ b/src/runtimes.rs @@ -15,6 +15,7 @@ // along with substrate-subxt. If not, see . #![allow(missing_docs)] +use codec::Encode; use pallet_im_online::sr25519::AuthorityId as ImOnlineId; use sp_runtime::{ generic::Header, @@ -112,17 +113,49 @@ impl_opaque_keys! { } } -use crate::frame::{ - balances::{ - AccountData, - Balances, - }, +use crate::{ contracts::Contracts, + extra::{ + DefaultExtra, + SignedExtra, + }, + frame::{ + balances::{ + AccountData, + Balances, + }, + contracts::Contracts, + sudo::Sudo, + system::System, + }, session::Session, staking::Staking, system::System, + Encoded, }; +/// Runtime trait. +pub trait Runtime: System + Sized + Send + Sync + 'static { + /// Signature type. + type Signature: Verify + Encode + Send + Sync + 'static; + /// Transaction extras. + type Extra: SignedExtra + Send + Sync + 'static; +} + +/// Extra type. +pub type Extra = <::Extra as SignedExtra>::Extra; + +/// UncheckedExtrinsic type. +pub type UncheckedExtrinsic = sp_runtime::generic::UncheckedExtrinsic< + ::Address, + Encoded, + ::Signature, + Extra, +>; + +/// SignedPayload type. +pub type SignedPayload = sp_runtime::generic::SignedPayload>; + /// Concrete type definitions compatible with those in the default substrate `node_runtime` /// /// # Note @@ -141,6 +174,11 @@ impl Staking for DefaultNodeRuntime { type RewardPoint = u32; } +impl Runtime for DefaultNodeRuntime { + type Signature = MultiSignature; + type Extra = DefaultExtra; +} + impl System for DefaultNodeRuntime { type Index = u32; type BlockNumber = u32; @@ -165,6 +203,8 @@ impl Session for DefaultNodeRuntime { impl Contracts for DefaultNodeRuntime {} +impl Sudo for DefaultNodeRuntime {} + /// Concrete type definitions compatible with the node template. /// /// # Note @@ -174,6 +214,11 @@ impl Contracts for DefaultNodeRuntime {} #[derive(Debug, Clone, Eq, PartialEq)] pub struct NodeTemplateRuntime; +impl Runtime for NodeTemplateRuntime { + type Signature = MultiSignature; + type Extra = DefaultExtra; +} + impl System for NodeTemplateRuntime { type Index = u32; type BlockNumber = u32; @@ -190,6 +235,8 @@ impl Balances for NodeTemplateRuntime { type Balance = u128; } +impl Sudo for NodeTemplateRuntime {} + /// Concrete type definitions compatible with those for kusama, v0.7 /// /// # Note @@ -200,6 +247,12 @@ impl Balances for NodeTemplateRuntime { #[derive(Debug, Clone, Eq, PartialEq)] pub struct KusamaRuntime; +#[cfg(feature = "kusama")] +impl Runtime for KusamaRuntime { + type Signature = MultiSignature; + type Extra = DefaultExtra; +} + #[cfg(feature = "kusama")] impl System for KusamaRuntime { type Index = u32; diff --git a/src/signer.rs b/src/signer.rs index fecd5b3423..ebbcd41654 100644 --- a/src/signer.rs +++ b/src/signer.rs @@ -19,29 +19,26 @@ use crate::{ extra::SignedExtra, - frame::system::System, - Encoded, -}; -use codec::Encode; -use sp_core::Pair; -use sp_runtime::{ - generic::{ + runtimes::{ + Runtime, SignedPayload, UncheckedExtrinsic, }, - traits::{ - IdentifyAccount, - Verify, - }, +}; +use codec::Encode; +use sp_core::Pair; +use sp_runtime::traits::{ + IdentifyAccount, + SignedExtension, + Verify, }; use std::{ future::Future, - marker::PhantomData, pin::Pin, }; /// Extrinsic signer. -pub trait Signer> { +pub trait Signer { /// Returns the account id. fn account_id(&self) -> &T::AccountId; @@ -54,42 +51,30 @@ pub trait Signer> { /// refused the operation. fn sign( &self, - extrinsic: SignedPayload, - ) -> Pin< - Box< - dyn Future< - Output = Result< - UncheckedExtrinsic, - String, - >, - > + Send - + Sync, - >, - >; + extrinsic: SignedPayload, + ) -> Pin, String>> + Send + Sync>>; } /// Extrinsic signer using a private key. -#[derive(Debug)] -pub struct PairSigner, P: Pair> { - _marker: PhantomData<(S, E)>, +pub struct PairSigner { account_id: T::AccountId, nonce: Option, signer: P, } -impl PairSigner +impl PairSigner where - T: System, - S: Encode + Verify + From, - S::Signer: From + IdentifyAccount, - E: SignedExtra, + T: Runtime, + T::Signature: From, + ::Signer: + From + IdentifyAccount, P: Pair, { /// Creates a new `Signer` from a `Pair`. pub fn new(signer: P) -> Self { - let account_id = S::Signer::from(signer.public()).into_account(); + let account_id = + ::Signer::from(signer.public()).into_account(); Self { - _marker: PhantomData, account_id, nonce: None, signer, @@ -112,14 +97,14 @@ where } } -impl Signer for PairSigner +impl Signer for PairSigner where - T: System + 'static, + T: Runtime, T::AccountId: Into + 'static, - S: Encode + 'static + Send + Sync, - E: SignedExtra + 'static, + <>::Extra as SignedExtension>::AdditionalSigned: + Send + Sync, P: Pair + 'static, - P::Signature: Into + 'static, + P::Signature: Into + 'static, { fn account_id(&self) -> &T::AccountId { &self.account_id @@ -131,25 +116,17 @@ where fn sign( &self, - extrinsic: SignedPayload, - ) -> Pin< - Box< - dyn Future< - Output = Result< - UncheckedExtrinsic, - String, - >, - > + Send - + Sync, - >, - > { + extrinsic: SignedPayload, + ) -> Pin, String>> + Send + Sync>> + { let signature = extrinsic.using_encoded(|payload| self.signer.sign(payload)); let (call, extra, _) = extrinsic.deconstruct(); - Box::pin(futures::future::ok(UncheckedExtrinsic::new_signed( + let extrinsic = UncheckedExtrinsic::::new_signed( call, self.account_id.clone().into(), signature.into(), extra, - ))) + ); + Box::pin(async move { Ok(extrinsic) }) } } diff --git a/src/subscription.rs b/src/subscription.rs new file mode 100644 index 0000000000..6208ce6f7b --- /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 0000000000..9691a2053d --- /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 0000000000..ca699c6c73 --- /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 0000000000..f5c3a9e6fc --- /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 0000000000..8dfb883c04 --- /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 0000000000..978a648842 --- /dev/null +++ b/test-node/runtime/src/lib.rs @@ -0,0 +1,462 @@ +// 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; + /// The base call filter. + type BaseCallFilter = (); +} + +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 0000000000..9b751ee0bb --- /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 0000000000..b94a06ad74 --- /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 0000000000..ec0b0fed28 --- /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 0000000000..4ea802838c --- /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 0000000000..caeb7bf244 --- /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 0000000000..599f149f66 --- /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() +}