From 22b1228b6dc888f4a569cbfd5e72f2f2e3a9c7c8 Mon Sep 17 00:00:00 2001 From: Liu-Cheng Xu Date: Thu, 23 Jul 2020 07:16:12 +0800 Subject: [PATCH] Impl Staking RPC (#152) * Impl API for ValidatorInfos * Cache the treasury account when slashing using struct Slasher * Return Vec * Introduce RpcBalance * Rename to generic U128 * Introduce U128 in xpallet_support * Add doc about U128 * Add RpcValidatorLedger * Fix session keys initialization * Add xstaking_getValidatorByAccount * . --- Cargo.lock | 7 ++ cli/src/chain_spec.rs | 2 +- rpc/src/lib.rs | 2 +- runtime/Cargo.toml | 2 + runtime/src/lib.rs | 7 +- xpallets/mining/staking/rpc/Cargo.toml | 2 + .../mining/staking/rpc/runtime-api/Cargo.toml | 6 ++ .../mining/staking/rpc/runtime-api/src/lib.rs | 16 +++- xpallets/mining/staking/rpc/src/lib.rs | 56 +++++++++---- xpallets/mining/staking/src/lib.rs | 30 ++++++- xpallets/mining/staking/src/types.rs | 57 +++++++++++-- xpallets/support/Cargo.toml | 4 + xpallets/support/src/lib.rs | 2 + xpallets/support/src/u128.rs | 82 +++++++++++++++++++ 14 files changed, 241 insertions(+), 34 deletions(-) create mode 100644 xpallets/support/src/u128.rs diff --git a/Cargo.lock b/Cargo.lock index 545a5594465bc..8c3797f81f581 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -603,6 +603,7 @@ dependencies = [ "xpallet-mining-staking", "xpallet-mining-staking-rpc-runtime-api", "xpallet-protocol", + "xpallet-support", "xpallet-system", "xpallet-transaction-payment", "xpallet-transaction-payment-rpc-runtime-api", @@ -7182,7 +7183,9 @@ dependencies = [ "sp-api", "sp-blockchain", "sp-runtime", + "xpallet-mining-staking", "xpallet-mining-staking-rpc-runtime-api", + "xpallet-support", ] [[package]] @@ -7192,6 +7195,8 @@ dependencies = [ "parity-scale-codec", "sp-api", "sp-std", + "xpallet-mining-staking", + "xpallet-support", ] [[package]] @@ -7208,7 +7213,9 @@ name = "xpallet-support" version = "0.1.0" dependencies = [ "frame-support", + "parity-scale-codec", "rustc-hex", + "serde", "sp-std", ] diff --git a/cli/src/chain_spec.rs b/cli/src/chain_spec.rs index d18a1e88bf8f9..05bd0d0835c26 100644 --- a/cli/src/chain_spec.rs +++ b/cli/src/chain_spec.rs @@ -199,7 +199,7 @@ fn testnet_genesis( .map(|x| { ( x.0.clone(), - x.1.clone(), + x.0.clone(), session_keys(x.2.clone(), x.3.clone(), x.4.clone()), ) }) diff --git a/rpc/src/lib.rs b/rpc/src/lib.rs index 271ac2cc76656..6f14c268a025d 100644 --- a/rpc/src/lib.rs +++ b/rpc/src/lib.rs @@ -73,7 +73,7 @@ where as ProvideRuntimeApi>::Api: xpallet_assets_rpc_runtime_api::AssetsApi, as ProvideRuntimeApi>::Api: - xpallet_mining_staking_rpc_runtime_api::XStakingApi, + xpallet_mining_staking_rpc_runtime_api::XStakingApi, as ProvideRuntimeApi>::Api: xpallet_contracts_rpc::ContractsRuntimeApi, < as ProvideRuntimeApi>::Api as sp_api::ApiErrorExt>::Error: diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index 9312cf88e08ba..6814384d2df48 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -58,6 +58,7 @@ xpallet-mining-staking-rpc-runtime-api = { path = "../xpallets/mining/staking/r xpallet-mining-asset = { path = "../xpallets/mining/asset", default-features = false } xpallet-protocol = { path = "../xpallets/protocol", default-features = false } xpallet-system = { path = "../xpallets/system", default-features = false } +xpallet-support = { path = "../xpallets/support", default-features = false } xpallet-transaction-payment = { path = "../xpallets/transaction-payment", default-features = false } xpallet-transaction-payment-rpc-runtime-api = { path = "../xpallets/transaction-payment/rpc/runtime-api", default-features = false } @@ -113,6 +114,7 @@ std = [ "xpallet-mining-asset/std", "xpallet-protocol/std", "xpallet-system/std", + "xpallet-support/std", "xpallet-transaction-payment/std", "xpallet-transaction-payment-rpc-runtime-api/std", ] diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 8c24e9d20a200..04735bf8c97f6 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -618,10 +618,13 @@ impl_runtime_apis! { } } - impl xpallet_mining_staking_rpc_runtime_api::XStakingApi for Runtime { - fn validators() -> Vec { + impl xpallet_mining_staking_rpc_runtime_api::XStakingApi for Runtime { + fn validators() -> Vec, BlockNumber>> { XStaking::validators_info() } + fn validator_info_of(who: AccountId) -> xpallet_mining_staking::ValidatorInfo, BlockNumber> { + XStaking::validator_info_of(who) + } } impl xpallet_contracts_rpc_runtime_api::ContractsApi diff --git a/xpallets/mining/staking/rpc/Cargo.toml b/xpallets/mining/staking/rpc/Cargo.toml index e23592f3a3f87..50b176e26a1b9 100644 --- a/xpallets/mining/staking/rpc/Cargo.toml +++ b/xpallets/mining/staking/rpc/Cargo.toml @@ -15,4 +15,6 @@ sp-blockchain = { git = "https://github.com/paritytech/substrate.git", tag = "v2 jsonrpc-core = "14.2.0" jsonrpc-derive = "14.2.1" jsonrpc-core-client = "14.2.0" +xpallet-mining-staking = { path = "../" } xpallet-mining-staking-rpc-runtime-api = { path = "./runtime-api" } +xpallet-support = { path = "../../../support" } diff --git a/xpallets/mining/staking/rpc/runtime-api/Cargo.toml b/xpallets/mining/staking/rpc/runtime-api/Cargo.toml index 07483aca53652..3db67523c2a85 100644 --- a/xpallets/mining/staking/rpc/runtime-api/Cargo.toml +++ b/xpallets/mining/staking/rpc/runtime-api/Cargo.toml @@ -12,10 +12,16 @@ codec = { package = "parity-scale-codec", version = "1.3.1", default-features = sp-api = { git = "https://github.com/paritytech/substrate.git", tag = "v2.0.0-rc4", default-features = false } sp-std = { git = "https://github.com/paritytech/substrate.git", tag = "v2.0.0-rc4", default-features = false } +xpallet-mining-staking = { path = "../..", default-features = false } +xpallet-support = { path = "../../../../support", default-features = false } + [features] default = ["std"] std = [ "codec/std", "sp-api/std", "sp-std/std", + + "xpallet-mining-staking/std", + "xpallet-support/std", ] diff --git a/xpallets/mining/staking/rpc/runtime-api/src/lib.rs b/xpallets/mining/staking/rpc/runtime-api/src/lib.rs index f06387e65e056..963f362f78849 100644 --- a/xpallets/mining/staking/rpc/runtime-api/src/lib.rs +++ b/xpallets/mining/staking/rpc/runtime-api/src/lib.rs @@ -2,14 +2,22 @@ #![cfg_attr(not(feature = "std"), no_std)] +use codec::Codec; use sp_std::prelude::*; +use xpallet_mining_staking::ValidatorInfo; +use xpallet_support::RpcBalance; sp_api::decl_runtime_apis! { /// The API to query account nonce (aka transaction index). - pub trait XStakingApi where - AccountId: codec::Codec, + pub trait XStakingApi where + AccountId: Codec, + Balance: Codec, + BlockNumber: Codec, { - /// Get all potential validators. - fn validators() -> Vec; + /// Get overall information about all potential validators. + fn validators() -> Vec, BlockNumber>>; + + /// Get overall information given the validator AccountId. + fn validator_info_of(who: AccountId) -> ValidatorInfo, BlockNumber>; } } diff --git a/xpallets/mining/staking/rpc/src/lib.rs b/xpallets/mining/staking/rpc/src/lib.rs index 016c50a6aea2d..cb78e539abd31 100644 --- a/xpallets/mining/staking/rpc/src/lib.rs +++ b/xpallets/mining/staking/rpc/src/lib.rs @@ -7,19 +7,27 @@ use sp_api::ProvideRuntimeApi; use sp_blockchain::HeaderBackend; use sp_runtime::{generic::BlockId, traits::Block as BlockT}; use std::sync::Arc; +use xpallet_mining_staking::ValidatorInfo; use xpallet_mining_staking_rpc_runtime_api::XStakingApi as XStakingRuntimeApi; +use xpallet_support::RpcBalance; /// XStaking RPC methods. #[rpc] -pub trait XStakingApi { - /// Executes a call to a contract. - /// - /// This call is performed locally without submitting any transactions. Thus executing this - /// won't change any state. Nonetheless, the calling state-changing contracts is still possible. - /// - /// This method is useful for calling getter-like methods on contracts. +pub trait XStakingApi { + /// Get overall information about all potential validators #[rpc(name = "xstaking_getValidators")] - fn validators(&self, at: Option) -> Result>; + fn validators( + &self, + at: Option, + ) -> Result>>; + + /// Get overall information given the validator AccountId. + #[rpc(name = "xstaking_getValidatorByAccount")] + fn validator_info_of( + &self, + who: AccountId, + at: Option, + ) -> Result>; } /// A struct that implements the [`XStakingApi`]. @@ -38,24 +46,42 @@ impl XStaking { } } -impl XStakingApi<::Hash, AccountId> for XStaking +impl + XStakingApi<::Hash, AccountId, RpcBalance, BlockNumber> + for XStaking where Block: BlockT, C: Send + Sync + 'static + ProvideRuntimeApi + HeaderBackend, - C::Api: XStakingRuntimeApi, + C::Api: XStakingRuntimeApi, AccountId: Codec, + Balance: Codec, + BlockNumber: Codec, { - fn validators(&self, at: Option<::Hash>) -> Result> { + fn validators( + &self, + at: Option<::Hash>, + ) -> Result, BlockNumber>>> { let api = self.client.runtime_api(); let at = BlockId::hash(at.unwrap_or_else(|| // If the block hash is not supplied assume the best block. self.client.info().best_hash)); - let result = api - .validators(&at) - .map_err(|e| runtime_error_into_rpc_err(e))?; + Ok(api.validators(&at).map_err(runtime_error_into_rpc_err)?) + } + + fn validator_info_of( + &self, + who: AccountId, + at: Option<::Hash>, + ) -> Result, BlockNumber>> { + let api = self.client.runtime_api(); + let at = BlockId::hash(at.unwrap_or_else(|| + // If the block hash is not supplied assume the best block. + self.client.info().best_hash)); - Ok(result) + Ok(api + .validator_info_of(&at, who) + .map_err(runtime_error_into_rpc_err)?) } } diff --git a/xpallets/mining/staking/src/lib.rs b/xpallets/mining/staking/src/lib.rs index b597353e4b482..0532ff6465043 100644 --- a/xpallets/mining/staking/src/lib.rs +++ b/xpallets/mining/staking/src/lib.rs @@ -27,7 +27,7 @@ use xp_mining_common::{ }; use xp_mining_staking::{AssetMining, SessionIndex, TreasuryAccount, UnbondedIndex}; use xpallet_assets::{AssetErr, AssetType}; -use xpallet_support::debug; +use xpallet_support::{debug, RpcBalance}; pub use impls::{IdentificationTuple, SimpleValidatorRewardPotAccountDeterminer}; pub use types::*; @@ -733,7 +733,31 @@ impl Module { } impl Module { - pub fn validators_info() -> Vec { - Self::validator_set().collect() + pub fn validators_info( + ) -> Vec, T::BlockNumber>> { + Self::validator_set().map(Self::validator_info_of).collect() + } + + pub fn validator_info_of( + who: T::AccountId, + ) -> ValidatorInfo, T::BlockNumber> { + let profile = Validators::::get(&who); + let ledger: RpcValidatorLedger, T::BlockNumber> = + ValidatorLedgers::::get(&who).into(); + let self_bonded: RpcBalance = + Nominations::::get(&who, &who).nomination.into(); + let is_validating = T::SessionInterface::validators().contains(&who); + let reward_pot_account = T::DetermineRewardPotAccount::reward_pot_account_for(&who); + let reward_pot_balance: RpcBalance = + xpallet_assets::Module::::pcx_free_balance(&reward_pot_account).into(); + ValidatorInfo { + account: who, + profile, + ledger, + is_validating, + self_bonded, + reward_pot_account, + reward_pot_balance, + } } } diff --git a/xpallets/mining/staking/src/types.rs b/xpallets/mining/staking/src/types.rs index 7a4548afe9ed5..953719c55ce90 100644 --- a/xpallets/mining/staking/src/types.rs +++ b/xpallets/mining/staking/src/types.rs @@ -6,6 +6,7 @@ use sp_runtime::RuntimeDebug; use sp_runtime::{Deserialize, Serialize}; use xp_mining_common::WeightType; use xp_mining_staking::MiningPower; +use xpallet_support::RpcWeightType; /// Destination for minted fresh PCX on each new session. #[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug)] @@ -51,6 +52,33 @@ pub struct ValidatorLedger { pub last_total_vote_weight_update: BlockNumber, } +/// Vote weight properties of validator. +#[derive(PartialEq, Eq, Clone, Default, Encode, Decode, RuntimeDebug)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "std", serde(rename_all = "camelCase"))] +pub struct RpcValidatorLedger { + /// The total amount of all the nominators' vote balances. + pub total: RpcBalance, + /// Last calculated total vote weight of current validator. + pub last_total_vote_weight: RpcWeightType, + /// Block number at which point `last_total_vote_weight` just updated. + pub last_total_vote_weight_update: BlockNumber, +} + +impl From> + for RpcValidatorLedger, BlockNumber> +{ + fn from(ledger: ValidatorLedger) -> Self { + let last_total_vote_weight: RpcWeightType = ledger.last_total_vote_weight.into(); + let total: RpcBalance = ledger.total.into(); + Self { + total, + last_total_vote_weight, + last_total_vote_weight_update: ledger.last_total_vote_weight_update, + } + } +} + /// Vote weight properties of nominator. #[derive(PartialEq, Eq, Clone, Default, Encode, Decode, RuntimeDebug)] pub struct NominatorLedger { @@ -62,6 +90,17 @@ pub struct NominatorLedger { pub last_vote_weight_update: BlockNumber, } +/// Vote weight properties of nominator. +#[derive(PartialEq, Eq, Clone, Default, Encode, Decode, RuntimeDebug)] +pub struct RpcNominatorLedger { + /// The amount of + pub nomination: Balance, + /// Last calculated total vote weight of current nominator. + pub last_vote_weight: RpcWeightType, + /// Block number at which point `last_vote_weight` just updated. + pub last_vote_weight_update: BlockNumber, +} + /// Profile of staking validator. /// /// These fields are static or updated less frequently. @@ -90,24 +129,25 @@ pub struct NominatorProfile { pub unbonded_chunks: Vec>, } -#[derive(PartialEq, Eq, Clone, Default, Encode, Decode, RuntimeDebug)] +/// Total information about a validator. +#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug)] #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] #[cfg_attr(feature = "std", serde(rename_all = "camelCase"))] -pub struct ValidatorInfo { +pub struct ValidatorInfo { /// AccountId of this (potential) validator. pub account: AccountId, #[cfg_attr(feature = "std", serde(flatten))] pub profile: ValidatorProfile, #[cfg_attr(feature = "std", serde(flatten))] - pub ledger: ValidatorLedger, - /// Being a validator, reponsible for authoring the new blocks. + pub ledger: RpcValidatorLedger, + /// Being a validator, responsible for authoring the new blocks. pub is_validating: bool, /// How much balances the validator has bonded itself. - pub self_bonded: Balance, + pub self_bonded: RpcBalance, /// AccountId of the reward pot of this validator. pub reward_pot_account: AccountId, /// Balance of the reward pot account. - pub reward_pot_balance: Balance, + pub reward_pot_balance: RpcBalance, } /// Information regarding the active era (era in used in session). @@ -142,7 +182,7 @@ impl Default for Forcing { } } -// Top level shares of various reward destinations. +/// Top level shares of various reward destinations. #[derive(Copy, Clone, PartialEq, Eq, Default, Encode, Decode, RuntimeDebug)] #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] pub struct GlobalDistribution { @@ -153,6 +193,7 @@ pub struct GlobalDistribution { impl GlobalDistribution { /// Calculates the rewards for treasury and mining accordingly. pub fn calc_rewards(&self, reward: T::Balance) -> (T::Balance, T::Balance) { + assert!(self.treasury + self.mining > 0); let treasury_reward = reward * self.treasury.saturated_into() / (self.treasury + self.mining).saturated_into(); (treasury_reward, reward - treasury_reward) @@ -212,7 +253,7 @@ impl MiningDistribution { } else { assert!( m2 > 0, - "cross_mining_shares is ensured to be positive in set_distribution_ratio()" + "asset_mining_shares is ensured to be positive in set_distribution_ratio()" ); // There could be some computation loss here, but it's ok. let treasury_extra = (m2 - m1) * asset_mining_reward_cap.saturated_into::() / m2; diff --git a/xpallets/support/Cargo.toml b/xpallets/support/Cargo.toml index 1c3fa76a1d7ab..b89068237b25c 100644 --- a/xpallets/support/Cargo.toml +++ b/xpallets/support/Cargo.toml @@ -5,7 +5,9 @@ authors = ["ChainX community "] edition = "2018" [dependencies] +codec = { package = "parity-scale-codec", version = "1.3.1", features = ["derive"], default-features = false } rustc-hex = { version = "2.0", optional = true } +serde = { version = "1.0", optional = true } # Substrate primitives sp-std = { git = "https://github.com/paritytech/substrate.git", tag = "v2.0.0-rc4", default-features = false } @@ -16,7 +18,9 @@ frame-support = { git = "https://github.com/paritytech/substrate.git", tag = "v2 [features] default = ["std"] std = [ + "codec/std", "rustc-hex", + "serde", "sp-std/std", diff --git a/xpallets/support/src/lib.rs b/xpallets/support/src/lib.rs index 4f84224bd4fd2..f8eaf4fe84800 100644 --- a/xpallets/support/src/lib.rs +++ b/xpallets/support/src/lib.rs @@ -2,11 +2,13 @@ pub mod base58; mod macros; +mod u128; #[cfg(feature = "std")] pub mod x_std; use frame_support::dispatch::{DispatchError, DispatchResult}; +pub use crate::u128::*; pub use frame_support::fail; pub use macros::*; diff --git a/xpallets/support/src/u128.rs b/xpallets/support/src/u128.rs new file mode 100644 index 0000000000000..ea1588b2a7baa --- /dev/null +++ b/xpallets/support/src/u128.rs @@ -0,0 +1,82 @@ +/// Balance type when interacting with RPC. +pub type RpcBalance = U128; + +/// WeightType when interacting with RPC. +pub type RpcWeightType = U128; + +/// This struct provides a wrapper of Balance in runtime due to the u128 serde issue. +/// +/// # Example +/// +/// ```no_run +/// use xpallet_support::RpcBalance; +/// +/// sp_api::decl_runtime_apis! { +/// pub trait PalletApi where +/// Balance: Codec, +/// { +/// /// Get total asset balance. +/// /// +/// /// Ideally: +/// /// fn asset_balance() -> Balance; +/// /// +/// /// Workaround for the u128 serde issue: +/// /// fn asset_balance() -> RpcBalance; +/// /// +/// /// What you need to do is to replace Balance with RpcBalance +/// /// in returned value when interacting with RPC API. +/// /// For the other u128 cases, just U128 directly. +/// fn total_asset_balance() -> RpcBalance; +/// } +/// } +/// ``` +/// +/// TODO: remove this workaround once https://github.com/paritytech/substrate/issues/4641 is resolved. +#[derive(Eq, PartialEq, Clone, codec::Encode, codec::Decode, Default)] +#[cfg_attr(feature = "std", derive(std::fmt::Debug))] +pub struct U128(Balance); + +impl From for U128 { + fn from(inner: Balance) -> Self { + Self(inner) + } +} + +#[cfg(feature = "std")] +impl std::fmt::Display for U128 { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} + +#[cfg(feature = "std")] +impl std::str::FromStr for U128 { + type Err = &'static str; + fn from_str(s: &str) -> Result { + let inner = s + .parse::() + .map_err(|_| "Parse Balance from String failed")?; + Ok(Self(inner)) + } +} + +#[cfg(feature = "std")] +impl serde::ser::Serialize for U128 { + fn serialize(&self, serializer: S) -> Result + where + S: serde::ser::Serializer, + { + self.0.to_string().serialize(serializer) + } +} + +#[cfg(feature = "std")] +impl<'de, Balance: std::str::FromStr> serde::de::Deserialize<'de> for U128 { + fn deserialize(deserializer: D) -> Result + where + D: serde::de::Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + s.parse::().map_err(serde::de::Error::custom) + } +}