From f9f69b8c3b387993115afff21322c52a804eb37f Mon Sep 17 00:00:00 2001 From: Demi Obenour Date: Mon, 21 Sep 2020 17:15:46 +0000 Subject: [PATCH] Staking support (#99) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Initial Staking API * Add more staking types * Reformat * Remove dead code * Fix missing documentation * Reformat * Staking: use proc macros * Add partial session support * Reformat * Try to implement nomination This currently fails with compilation errors I do not understand. * Use the #[module] macro This fixes a compile error * Explain undefined method diagnostics * Use ‘#[module]’ and implement session for Kusama * Don’t impl ‘Staking’ for all ‘T: System’ * Add staking payout support * Fix compilation errors and remove useless lifetimes * Respond to code review This fixes most of the issues found during review, with the exception of tests. * Make signing fallable and asynchronous This is needed for hardware wallets, which require human confirmation to sign transactions. Blocking on a human to sign transactions is not a good idea, and the signing might fail for many reasons (device unplugged, authorization not granted, etc). * Reformat * Refactor as suggested by Andrew Jones (@ascjones). * Reformat * Refactor as suggested by Andrew Jones (@ascjones). * Trait cleanups * Make the `Signer` impl require Send + Sync This is what Ledgeracio needs. * Use the correct key for staking maps They use the key type, not ‘PhantomData’. * Implement set_payee call * Switch to associated types for Staking * Implement `set_keys` This is needed for Ledgeracio. * Remove impl of Signer for Box It isn’t needed, since Box implements Deref. * Fix Polkadot and Kusama ‘SessionKey’ structs I had failed to include the ‘Parachains’ component, which the default Substrate runtime doesn’t have. * Include a copy of `ValidatorId` This avoids needing to depend on Polkadot. * Fix syntax error in Cargo.toml * Fix compile errors * Add Debug impls * Fix return type of `BondedStore` * Use some upstream type definitions Also add `Default` impls. * Bump deps and fix build * Remove last reference to Kusama feature * Fix compilation errors * Implement the `concat` in `twox_64_concat` * Expose properties and per-era preferences * Era rewards point support I also did some refactoring. * Expose clipped exposure * Era reward points support * Make `PayoutStakersCall` public * Add in all default features for debugging * Chill support and update to latest Substrate * If property fetch fails, use dummy values * Fix tests * Fix header * Remove some code Ledgeracio does not need * More deletions * Remove more code not needed for Ledgeracio * Remove a pointless change in Cargo.toml w.r.t. upstream. * Remove more junk * Revert contracts put_code test to pure code (not using the macro) * Test contract instantiate * Fmt * WIP * Add some more submission tests * Reformat * More tests * Cleanup * Hopefully fix CI * Remove dead code * Test chill * Add missing docs * Remove unnecessary use * Revert "Remove unnecessary use" This reverts commit bc8bc36bde581f1892ea88a778dfe0fe5bff24d7. * Retry on temporary failures * Ignore the staking tests on CI * Obey the fmt * Run CI with at most one test thread * Implement tests for staking * More tests * Remove unhelpful println! * Revert changes in contract tests * Reformat * Remove spurious diff * More tests Co-authored-by: Demi M. Obenour Co-authored-by: David Palm Co-authored-by: Andrew Jones --- .github/workflows/rust.yml | 2 +- Cargo.toml | 13 +- README.md | 3 + src/extrinsic/signer.rs | 1 + src/frame/mod.rs | 2 + src/frame/session.rs | 75 +++++++ src/frame/staking.rs | 371 +++++++++++++++++++++++++++++++++++ src/frame/system.rs | 2 + src/lib.rs | 15 +- src/rpc.rs | 20 ++ src/runtimes.rs | 105 ++++++++++ test-node/runtime/Cargo.toml | 1 + 12 files changed, 606 insertions(+), 4 deletions(-) create mode 100644 src/frame/session.rs create mode 100644 src/frame/staking.rs diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index b4941647da..46f01d1b84 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -26,4 +26,4 @@ jobs: run: cargo build --workspace --verbose - name: test - run: cargo test --workspace --verbose + run: cargo test --workspace --verbose -- --test-threads=1 diff --git a/Cargo.toml b/Cargo.toml index 210a5c760e..cc653a45ec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,8 @@ keywords = ["parity", "substrate", "blockchain"] include = ["Cargo.toml", "src/**/*.rs", "README.md", "LICENSE"] [features] +kusama = [] +default = ["kusama"] client = ["substrate-subxt-client"] # enable this feature to run tests which require a local dev chain node @@ -31,7 +33,7 @@ num-traits = { version = "0.2.12", default-features = false } serde = { version = "1.0.115", features = ["derive"] } serde_json = "1.0.57" url = "2.1.1" -codec = { package = "parity-scale-codec", version = "1.3.4", default-features = false, features = ["derive", "full"] } +codec = { package = "parity-scale-codec", version = "1.3.5", default-features = false, features = ["derive", "full"] } frame-metadata = { version = "11.0.0-rc6", package = "frame-metadata" } frame-support = { version = "2.0.0-rc6", package = "frame-support" } @@ -39,6 +41,14 @@ sp-runtime = { version = "2.0.0-rc6", package = "sp-runtime" } sp-version = { version = "2.0.0-rc6", package = "sp-version" } pallet-indices = { version = "2.0.0-rc6", package = "pallet-indices" } hex = "0.4.2" +sp-std = "2.0.0-rc6" +application-crypto = { version = "2.0.0-rc6", package = "sp-application-crypto" } +sp-finality-grandpa = "2.0.0-rc6" +sp-consensus-babe = "0.8.0-rc6" +pallet-im-online = "2.0.0-rc6" +sp-authority-discovery = "2.0.0-rc6" +pallet-staking = "2.0.0-rc6" + sp-rpc = { version = "2.0.0-rc6", package = "sp-rpc" } sp-core = { version = "2.0.0-rc6", package = "sp-core" } sc-rpc-api = { version = "0.8.0-rc6", package = "sc-rpc-api" } @@ -56,3 +66,4 @@ substrate-subxt-client = { version = "0.4.0", path = "client" } tempdir = "0.3.7" test-node = { path = "test-node" } wabt = "0.10.0" +assert_matches = "1.3" diff --git a/README.md b/README.md index 4a78b1febe..5da16a331d 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,9 @@ A library to **sub**mit e**xt**rinsics to a [substrate](https://github.com/parit See [examples](./examples). +If you use `#[derive(Call)]` without `#[module]` in the same module, you will get errors +complaining about an undefined method with a name starting with `with_`. + **Alternatives** [substrate-api-client](https://github.com/scs/substrate-api-client) provides similar functionality. diff --git a/src/extrinsic/signer.rs b/src/extrinsic/signer.rs index 13ff50a366..efda6578d6 100644 --- a/src/extrinsic/signer.rs +++ b/src/extrinsic/signer.rs @@ -54,6 +54,7 @@ pub trait Signer { } /// Extrinsic signer using a private key. +#[derive(Debug)] pub struct PairSigner { account_id: T::AccountId, nonce: Option, diff --git a/src/frame/mod.rs b/src/frame/mod.rs index 1d7e262ed7..4a06b510d7 100644 --- a/src/frame/mod.rs +++ b/src/frame/mod.rs @@ -31,6 +31,8 @@ use sp_core::storage::StorageKey; pub mod balances; pub mod contracts; +pub mod session; +pub mod staking; pub mod sudo; pub mod system; diff --git a/src/frame/session.rs b/src/frame/session.rs new file mode 100644 index 0000000000..f5f527b81b --- /dev/null +++ b/src/frame/session.rs @@ -0,0 +1,75 @@ +// 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 . + +//! Session support +use crate::frame::system::{ + System, + SystemEventsDecoder as _, +}; +use codec::Encode; +use frame_support::Parameter; +use sp_runtime::traits::{ + Member, + OpaqueKeys, +}; +use std::{ + fmt::Debug, + marker::PhantomData, +}; +use substrate_subxt_proc_macro::Store; + +/// Impls `Default::default` for some types that have a `_runtime` field of type +/// `PhantomData` as their only field. +macro_rules! default_impl { + ($name:ident) => { + impl Default for $name { + fn default() -> Self { + Self { + _runtime: PhantomData, + } + } + } + }; +} + +/// The trait needed for this module. +#[module] +pub trait Session: System { + /// The validator account identifier type for the runtime. + type ValidatorId: Parameter + Debug + Ord + Default + Send + Sync + 'static; + + /// The keys. + type Keys: OpaqueKeys + Member + Parameter + Default; +} + +/// The current set of validators. +#[derive(Encode, Store, Debug)] +pub struct ValidatorsStore { + #[store(returns = Vec<::ValidatorId>)] + /// Marker for the runtime + pub _runtime: PhantomData, +} + +default_impl!(ValidatorsStore); + +/// Set the session keys for a validator. +#[derive(Encode, Call, Debug)] +pub struct SetKeysCall { + /// The keys + pub keys: T::Keys, + /// The proof. This is not currently used and can be set to an empty vector. + pub proof: Vec, +} diff --git a/src/frame/staking.rs b/src/frame/staking.rs new file mode 100644 index 0000000000..522473accb --- /dev/null +++ b/src/frame/staking.rs @@ -0,0 +1,371 @@ +// 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 pallet_staking module. + +use super::balances::{ + Balances, + BalancesEventsDecoder as _, +}; +use codec::{ + Decode, + Encode, +}; + +use std::{ + collections::BTreeMap, + fmt::Debug, + marker::PhantomData, +}; + +pub use pallet_staking::{ + ActiveEraInfo, + EraIndex, + Exposure, + Nominations, + RewardDestination, + RewardPoint, + StakingLedger, + ValidatorPrefs, +}; + +/// Rewards for the last `HISTORY_DEPTH` eras. +/// If reward hasn't been set or has been removed then 0 reward is returned. +#[derive(Clone, Encode, Decode, Debug, Store)] +pub struct ErasRewardPointsStore { + #[store(returns = EraRewardPoints)] + /// Era index + pub index: EraIndex, + /// Marker for the runtime + pub _phantom: PhantomData, +} + +/// Preference of what happens regarding validation. +#[derive(Clone, Encode, Decode, Debug, Call)] +pub struct SetPayeeCall { + /// The payee + pub payee: RewardDestination, + /// Marker for the runtime + pub _runtime: PhantomData, +} + +/// The subset of the `frame::Trait` that a client must implement. +#[module] +pub trait Staking: Balances {} + +/// Number of eras to keep in history. +/// +/// Information is kept for eras in `[current_era - history_depth; current_era]`. +/// +/// Must be more than the number of eras delayed by session otherwise. +/// I.e. active era must always be in history. +/// I.e. `active_era > current_era - history_depth` must be guaranteed. +#[derive(Encode, Decode, Copy, Clone, Debug, Default, Store)] +pub struct HistoryDepthStore { + #[store(returns = u32)] + /// Marker for the runtime + pub _runtime: PhantomData, +} + +/// Map from all locked "stash" accounts to the controller account. +#[derive(Encode, Copy, Clone, Debug, Hash, PartialEq, Eq, Ord, PartialOrd, Store)] +pub struct BondedStore { + #[store(returns = Option)] + /// Tٗhe stash account + pub stash: T::AccountId, +} + +/// Map from all (unlocked) "controller" accounts to the info regarding the staking. +#[derive(Encode, Copy, Clone, Debug, Hash, PartialEq, Eq, Ord, PartialOrd, Store)] +pub struct LedgerStore { + #[store(returns = Option>)] + /// The controller account + pub controller: T::AccountId, +} + +/// Where the reward payment should be made. Keyed by stash. +#[derive(Encode, Copy, Clone, Debug, Hash, PartialEq, Eq, Ord, PartialOrd, Store)] +pub struct PayeeStore { + #[store(returns = RewardDestination)] + /// Tٗhe stash account + pub stash: T::AccountId, +} + +/// The map from (wannabe) validator stash key to the preferences of that validator. +#[derive(Encode, Copy, Clone, Debug, Hash, PartialEq, Eq, Ord, PartialOrd, Store)] +pub struct ValidatorsStore { + #[store(returns = ValidatorPrefs)] + /// Tٗhe stash account + pub stash: T::AccountId, +} + +/// The map from nominator stash key to the set of stash keys of all validators to nominate. +#[derive(Encode, Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Store)] +pub struct NominatorsStore { + #[store(returns = Option>)] + /// Tٗhe stash account + pub stash: T::AccountId, +} + +/// The current era index. +/// +/// This is the latest planned era, depending on how the Session pallet queues the validator +/// set, it might be active or not. +#[derive(Encode, Copy, Clone, Debug, Store)] +pub struct CurrentEraStore { + #[store(returns = Option)] + /// Marker for the runtime + pub _runtime: PhantomData, +} + +/// Reward points of an era. Used to split era total payout between validators. +/// +/// This points will be used to reward validators and their respective nominators. +#[derive(PartialEq, Encode, Decode, Default, Debug)] +pub struct EraRewardPoints { + /// Total number of points. Equals the sum of reward points for each validator. + pub total: RewardPoint, + /// The reward points earned by a given validator. + pub individual: BTreeMap, +} + +/// Declare no desire to either validate or nominate. +/// +/// Effective at the beginning of the next era. +/// +/// The dispatch origin for this call must be _Signed_ by the controller, not the stash. +/// Can only be called when [`EraElectionStatus`] is `Closed`. +#[derive(Debug, Call, Encode)] +pub struct ChillCall { + /// Runtime marker + pub _runtime: PhantomData, +} + +impl Default for ChillCall { + fn default() -> Self { + Self { + _runtime: PhantomData, + } + } +} +impl Clone for ChillCall { + fn clone(&self) -> Self { + Self { + _runtime: self._runtime, + } + } +} +impl Copy for ChillCall {} + +/// Declare the desire to validate for the origin controller. +/// +/// Effective at the beginning of the next era. +/// +/// The dispatch origin for this call must be _Signed_ by the controller, not the stash. +/// Can only be called when [`EraElectionStatus`] is `Closed`. +#[derive(Clone, Debug, PartialEq, Call, Encode)] +pub struct ValidateCall { + /// Runtime marker + pub _runtime: PhantomData, + /// Validation preferences + pub prefs: ValidatorPrefs, +} + +/// Declare the desire to nominate `targets` for the origin controller. +/// +/// Effective at the beginning of the next era. +/// +/// The dispatch origin for this call must be _Signed_ by the controller, not the stash. +/// Can only be called when [`EraElectionStatus`] is `Closed`. +#[derive(Call, Encode, Debug)] +pub struct NominateCall { + /// The targets that are being nominated + pub targets: Vec, +} + +#[cfg(test)] +#[cfg(feature = "integration-tests")] +mod tests { + use super::*; + use crate::{ + error::RuntimeError, + extrinsic::{ + PairSigner, + Signer, + }, + frame::balances::*, + runtimes::KusamaRuntime as RT, + ClientBuilder, + Error, + ExtrinsicSuccess, + }; + use assert_matches::assert_matches; + use sp_core::{ + sr25519, + Pair, + }; + use sp_keyring::AccountKeyring; + + /// Helper function to generate a crypto pair from seed + fn get_from_seed(seed: &str) -> sr25519::Pair { + sr25519::Pair::from_string(&format!("//{}", seed), None) + .expect("static values are valid; qed") + } + + #[async_std::test] + async fn test_validate_with_controller_account() -> Result<(), Error> { + env_logger::try_init().ok(); + let alice = PairSigner::::new(AccountKeyring::Alice.pair()); + let client = ClientBuilder::::new().build().await?; + let announce_validator = client + .validate_and_watch(&alice, ValidatorPrefs::default()) + .await; + assert_matches!(announce_validator, Ok(ExtrinsicSuccess {block: _, extrinsic: _, events}) => { + // TOOD: this is unsatisfying – can we do better? + assert_eq!(events.len(), 3); + }); + + Ok(()) + } + + #[async_std::test] + async fn test_validate_not_possible_for_stash_account() -> Result<(), Error> { + env_logger::try_init().ok(); + let alice_stash = PairSigner::::new(get_from_seed("Alice//stash")); + let client = ClientBuilder::::new().build().await?; + let announce_validator = client + .validate_and_watch(&alice_stash, ValidatorPrefs::default()) + .await; + assert_matches!(announce_validator, Err(Error::Runtime(RuntimeError::Module(module_err))) => { + assert_eq!(module_err.module, "Staking"); + assert_eq!(module_err.error, "NotController"); + }); + Ok(()) + } + + #[async_std::test] + async fn test_nominate_with_controller_account() -> Result<(), Error> { + env_logger::try_init().ok(); + let alice = PairSigner::::new(AccountKeyring::Alice.pair()); + let bob = PairSigner::::new(AccountKeyring::Bob.pair()); + let client = ClientBuilder::::new().build().await?; + + let nomination = client + .nominate_and_watch(&alice, vec![bob.account_id().clone()]) + .await; + assert_matches!(nomination, Ok(ExtrinsicSuccess {block: _, extrinsic: _, events}) => { + // TOOD: this is unsatisfying – can we do better? + assert_eq!(events.len(), 3); + }); + Ok(()) + } + + #[async_std::test] + async fn test_nominate_not_possible_for_stash_account() -> Result<(), Error> { + env_logger::try_init().ok(); + let alice_stash = + PairSigner::::new(get_from_seed("Alice//stash")); + let bob = PairSigner::::new(AccountKeyring::Bob.pair()); + let client = ClientBuilder::::new().build().await?; + + let nomination = client + .nominate_and_watch(&alice_stash, vec![bob.account_id().clone()]) + .await; + assert_matches!(nomination, Err(Error::Runtime(RuntimeError::Module(module_err))) => { + assert_eq!(module_err.module, "Staking"); + assert_eq!(module_err.error, "NotController"); + }); + Ok(()) + } + + #[async_std::test] + async fn test_chill_works_for_controller_only() -> Result<(), Error> { + env_logger::try_init().ok(); + let alice_stash = + PairSigner::::new(get_from_seed("Alice//stash")); + let bob_stash = PairSigner::::new(get_from_seed("Bob//stash")); + let alice = PairSigner::::new(AccountKeyring::Alice.pair()); + let client = ClientBuilder::::new().build().await?; + + // this will fail the second time, which is why this is one test, not two + client + .nominate_and_watch(&alice, vec![bob_stash.account_id().clone()]) + .await?; + let store = LedgerStore { + controller: alice.account_id().clone(), + }; + let StakingLedger { stash, .. } = client.fetch(&store, None).await?.unwrap(); + assert_eq!(alice_stash.account_id(), &stash); + let chill = client.chill_and_watch(&alice_stash).await; + + assert_matches!(chill, Err(Error::Runtime(RuntimeError::Module(module_err))) => { + assert_eq!(module_err.module, "Staking"); + assert_eq!(module_err.error, "NotController"); + }); + + let chill = client.chill_and_watch(&alice).await; + assert_matches!(chill, Ok(ExtrinsicSuccess {block: _, extrinsic: _, events}) => { + // TOOD: this is unsatisfying – can we do better? + assert_eq!(events.len(), 3); + }); + Ok(()) + } + + #[async_std::test] + async fn test_total_issuance_is_okay() -> Result<(), Error> { + env_logger::try_init().ok(); + let client = ClientBuilder::::new().build().await?; + let total_issuance = client.total_issuance(None).await?; + assert!(total_issuance > 1u128 << 32); + Ok(()) + } + + #[async_std::test] + async fn test_history_depth_is_okay() -> Result<(), Error> { + env_logger::try_init().ok(); + let client = ClientBuilder::::new().build().await?; + let history_depth = client.history_depth(None).await?; + assert_eq!(history_depth, 84); + Ok(()) + } + + #[async_std::test] + async fn test_current_era_is_okay() -> Result<(), Error> { + env_logger::try_init().ok(); + let client = ClientBuilder::::new().build().await?; + let _current_era = client + .current_era(None) + .await? + .expect("current era always exists"); + Ok(()) + } + + #[async_std::test] + async fn test_era_reward_points_is_okay() -> Result<(), Error> { + env_logger::try_init().ok(); + let client = ClientBuilder::::new().build().await?; + let store = ErasRewardPointsStore { + _phantom: PhantomData, + index: 0, + }; + + let _current_era = client + .fetch(&store, None) + .await? + .expect("current era always exists"); + Ok(()) + } +} diff --git a/src/frame/system.rs b/src/frame/system.rs index 1e5526da03..7e9e163e8a 100644 --- a/src/frame/system.rs +++ b/src/frame/system.rs @@ -101,6 +101,8 @@ pub trait System { + MaybeSerialize + Debug + MaybeDisplay + + Encode + + Decode + Ord + Default; diff --git a/src/lib.rs b/src/lib.rs index 260fff9e2b..738420a11d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -35,7 +35,8 @@ while_true, trivial_casts, trivial_numeric_casts, - unused_extern_crates + unused_extern_crates, + clippy::all )] #![allow(clippy::type_complexity)] @@ -93,6 +94,7 @@ pub use crate::{ rpc::{ BlockNumber, ExtrinsicSuccess, + SystemProperties, }, runtimes::*, subscription::*, @@ -161,16 +163,18 @@ impl ClientBuilder { } }; let rpc = Rpc::new(client); - let (metadata, genesis_hash, runtime_version) = future::join3( + let (metadata, genesis_hash, runtime_version, properties) = future::join4( rpc.metadata(), rpc.genesis_hash(), rpc.runtime_version(None), + rpc.system_properties(), ) .await; Ok(Client { rpc, genesis_hash: genesis_hash?, metadata: metadata?, + properties: properties.unwrap_or_else(|_| Default::default()), runtime_version: runtime_version?, _marker: PhantomData, page_size: self.page_size.unwrap_or(10), @@ -183,6 +187,7 @@ pub struct Client { rpc: Rpc, genesis_hash: T::Hash, metadata: Metadata, + properties: SystemProperties, runtime_version: RuntimeVersion, _marker: PhantomData<(fn() -> T::Signature, T::Extra)>, page_size: u32, @@ -194,6 +199,7 @@ impl Clone for Client { rpc: self.rpc.clone(), genesis_hash: self.genesis_hash, metadata: self.metadata.clone(), + properties: self.properties.clone(), runtime_version: self.runtime_version.clone(), _marker: PhantomData, page_size: self.page_size, @@ -258,6 +264,11 @@ impl Client { &self.metadata } + /// Returns the system properties + pub fn properties(&self) -> &SystemProperties { + &self.properties + } + /// Fetch the value under an unhashed storage key pub async fn fetch_unhashed( &self, diff --git a/src/rpc.rs b/src/rpc.rs index dd2aeff40e..25ec9c3e0f 100644 --- a/src/rpc.rs +++ b/src/rpc.rs @@ -96,6 +96,18 @@ impl From for BlockNumber { } } +#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq, Default)] +#[serde(rename_all = "camelCase")] +/// System properties for a Substrate-based runtime +pub struct SystemProperties { + /// The address format + pub ss58_format: u8, + /// The number of digits after the decimal point in the native token + pub token_decimals: u8, + /// The symbol of the native token + pub token_symbol: String, +} + /// Client for substrate rpc interfaces pub struct Rpc { client: Client, @@ -208,6 +220,14 @@ impl Rpc { Ok(metadata) } + /// Fetch system properties + pub async fn system_properties(&self) -> Result { + Ok(self + .client + .request("system_properties", Params::None) + .await?) + } + /// Get a header pub async fn header( &self, diff --git a/src/runtimes.rs b/src/runtimes.rs index 5e1a55ce42..cb8b9133a8 100644 --- a/src/runtimes.rs +++ b/src/runtimes.rs @@ -15,8 +15,10 @@ // along with substrate-subxt. If not, see . use codec::Encode; +use pallet_im_online::sr25519::AuthorityId as ImOnlineId; use sp_runtime::{ generic::Header, + impl_opaque_keys, traits::{ BlakeTwo256, IdentifyAccount, @@ -25,6 +27,88 @@ use sp_runtime::{ MultiSignature, OpaqueExtrinsic, }; +use sp_std::prelude::*; + +/// BABE marker struct +#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Ord, PartialOrd, Hash)] +pub struct Babe; +impl sp_runtime::BoundToRuntimeAppPublic for Babe { + type Public = sp_consensus_babe::AuthorityId; +} + +/// ImOnline marker struct +#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Ord, PartialOrd, Hash)] +pub struct ImOnline; +impl sp_runtime::BoundToRuntimeAppPublic for ImOnline { + type Public = ImOnlineId; +} + +/// GRANDPA marker struct +#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Ord, PartialOrd, Hash)] +pub struct Grandpa; +impl sp_runtime::BoundToRuntimeAppPublic for Grandpa { + type Public = sp_finality_grandpa::AuthorityId; +} + +use sp_authority_discovery::AuthorityId as AuthorityDiscoveryId; + +#[cfg(feature = "kusama")] +mod validator_app { + use application_crypto::{ + app_crypto, + sr25519, + }; + app_crypto!(sr25519, sp_core::crypto::KeyTypeId(*b"para")); +} + +/// Parachain marker struct +#[cfg(feature = "kusama")] +#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Ord, PartialOrd, Hash)] +pub struct Parachains; + +#[cfg(feature = "kusama")] +impl sp_runtime::BoundToRuntimeAppPublic for Parachains { + type Public = validator_app::Public; +} + +/// Authority discovery marker struct +#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Ord, PartialOrd, Hash)] +pub struct AuthorityDiscovery; +impl sp_runtime::BoundToRuntimeAppPublic for AuthorityDiscovery { + type Public = AuthorityDiscoveryId; +} + +impl_opaque_keys! { + /// Substrate base runtime keys + pub struct BasicSessionKeys { + /// GRANDPA session key + pub grandpa: Grandpa, + /// BABE session key + pub babe: Babe, + /// ImOnline session key + pub im_online: ImOnline, + /// Parachain validation session key + pub parachains: Parachains, + /// AuthorityDiscovery session key + pub authority_discovery: AuthorityDiscovery, + } +} + +impl_opaque_keys! { + /// Polkadot/Kusama runtime keys + pub struct SessionKeys { + /// GRANDPA session key + pub grandpa: Grandpa, + /// BABE session key + pub babe: Babe, + /// ImOnline session key + pub im_online: ImOnline, + /// ParachainValidator session key + pub parachain_validator: Parachains, + /// AuthorityDiscovery session key + pub authority_discovery: AuthorityDiscovery, + } +} use crate::{ extrinsic::{ @@ -37,6 +121,8 @@ use crate::{ Balances, }, contracts::Contracts, + session::Session, + staking::Staking, sudo::Sudo, system::System, }, @@ -59,6 +145,8 @@ pub trait Runtime: System + Sized + Send + Sync + 'static { #[derive(Debug, Clone, Eq, PartialEq)] pub struct DefaultNodeRuntime; +impl Staking for DefaultNodeRuntime {} + impl Runtime for DefaultNodeRuntime { type Signature = MultiSignature; type Extra = DefaultExtra; @@ -80,6 +168,11 @@ impl Balances for DefaultNodeRuntime { type Balance = u128; } +impl Session for DefaultNodeRuntime { + type ValidatorId = ::AccountId; + type Keys = BasicSessionKeys; +} + impl Contracts for DefaultNodeRuntime {} impl Sudo for DefaultNodeRuntime {} @@ -114,6 +207,11 @@ impl Balances for NodeTemplateRuntime { type Balance = u128; } +impl Session for NodeTemplateRuntime { + type ValidatorId = ::AccountId; + type Keys = BasicSessionKeys; +} + impl Sudo for NodeTemplateRuntime {} /// Concrete type definitions compatible with the node template, with the @@ -175,6 +273,13 @@ impl System for KusamaRuntime { type AccountData = AccountData<::Balance>; } +impl Session for KusamaRuntime { + type ValidatorId = ::AccountId; + type Keys = SessionKeys; +} + +impl Staking for KusamaRuntime {} + impl Balances for KusamaRuntime { type Balance = u128; } diff --git a/test-node/runtime/Cargo.toml b/test-node/runtime/Cargo.toml index 85463292cc..804f120f16 100644 --- a/test-node/runtime/Cargo.toml +++ b/test-node/runtime/Cargo.toml @@ -20,6 +20,7 @@ pallet-aura = { version = "2.0.0-rc6", default-features = false } pallet-balances = { version = "2.0.0-rc6", default-features = false } pallet-grandpa = { version = "2.0.0-rc6", default-features = false } pallet-randomness-collective-flip = { version = "2.0.0-rc6", default-features = false } +pallet-staking = { version = "2.0.0-rc6", default-features = false } pallet-sudo = { version = "2.0.0-rc6", default-features = false } pallet-timestamp = { version = "2.0.0-rc6", default-features = false } pallet-transaction-payment = { version = "2.0.0-rc6", default-features = false }