diff --git a/Cargo.lock b/Cargo.lock index eb644202ae382..b081bd26a54fb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3479,6 +3479,7 @@ dependencies = [ "pallet-society", "pallet-staking", "pallet-staking-reward-curve", + "pallet-staking-rpc-runtime-api", "pallet-state-trie-migration", "pallet-sudo", "pallet-timestamp", @@ -4826,6 +4827,7 @@ dependencies = [ "jsonrpsee", "mmr-rpc", "node-primitives", + "pallet-staking-rpc", "pallet-transaction-payment-rpc", "sc-chain-spec", "sc-client-api", @@ -6395,6 +6397,32 @@ dependencies = [ "sp-arithmetic", ] +[[package]] +name = "pallet-staking-rpc" +version = "4.0.0-dev" +dependencies = [ + "jsonrpsee", + "pallet-staking-rpc-runtime-api", + "parity-scale-codec", + "sp-api", + "sp-blockchain", + "sp-core", + "sp-rpc", + "sp-runtime", + "sp-weights", +] + +[[package]] +name = "pallet-staking-rpc-runtime-api" +version = "4.0.0-dev" +dependencies = [ + "pallet-staking", + "parity-scale-codec", + "sp-api", + "sp-runtime", + "sp-weights", +] + [[package]] name = "pallet-state-trie-migration" version = "4.0.0-dev" diff --git a/Cargo.toml b/Cargo.toml index 8f55d8e527ecd..4ab4e4cf340ce 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -141,6 +141,8 @@ members = [ "frame/staking", "frame/staking/reward-curve", "frame/staking/reward-fn", + "frame/staking/rpc", + "frame/staking/rpc/runtime-api", "frame/state-trie-migration", "frame/sudo", "frame/root-offences", diff --git a/bin/node/rpc/Cargo.toml b/bin/node/rpc/Cargo.toml index f34922a287dfe..48252c722ddb7 100644 --- a/bin/node/rpc/Cargo.toml +++ b/bin/node/rpc/Cargo.toml @@ -16,6 +16,7 @@ targets = ["x86_64-unknown-linux-gnu"] jsonrpsee = { version = "0.16.2", features = ["server"] } node-primitives = { version = "2.0.0", path = "../primitives" } pallet-transaction-payment-rpc = { version = "4.0.0-dev", path = "../../../frame/transaction-payment/rpc/" } +pallet-staking-rpc = { version = "4.0.0-dev", path = "../../../frame/staking/rpc/" } mmr-rpc = { version = "4.0.0-dev", path = "../../../client/merkle-mountain-range/rpc/" } sc-chain-spec = { version = "4.0.0-dev", path = "../../../client/chain-spec" } sc-client-api = { version = "4.0.0-dev", path = "../../../client/api" } diff --git a/bin/node/rpc/src/lib.rs b/bin/node/rpc/src/lib.rs index 0dc5ba4039b00..1bd2b26445a60 100644 --- a/bin/node/rpc/src/lib.rs +++ b/bin/node/rpc/src/lib.rs @@ -110,6 +110,7 @@ where C::Api: substrate_frame_rpc_system::AccountNonceApi, C::Api: mmr_rpc::MmrRuntimeApi::Hash, BlockNumber>, C::Api: pallet_transaction_payment_rpc::TransactionPaymentRuntimeApi, + C::Api: pallet_staking_rpc::StakingRuntimeApi, C::Api: BabeApi, C::Api: BlockBuilder, P: TransactionPool + 'static, @@ -118,6 +119,7 @@ where B::State: sc_client_api::backend::StateBackend>, { use mmr_rpc::{Mmr, MmrApiServer}; + use pallet_staking_rpc::{Staking, StakingApiServer}; use pallet_transaction_payment_rpc::{TransactionPayment, TransactionPaymentApiServer}; use sc_consensus_babe_rpc::{Babe, BabeApiServer}; use sc_finality_grandpa_rpc::{Grandpa, GrandpaApiServer}; @@ -150,6 +152,7 @@ where // These RPCs should use an asynchronous caller instead. io.merge(Mmr::new(client.clone()).into_rpc())?; io.merge(TransactionPayment::new(client.clone()).into_rpc())?; + io.merge(Staking::new(client.clone()).into_rpc())?; io.merge( Babe::new( client.clone(), diff --git a/bin/node/runtime/Cargo.toml b/bin/node/runtime/Cargo.toml index 7037be9d7c54d..56f4b42abc92e 100644 --- a/bin/node/runtime/Cargo.toml +++ b/bin/node/runtime/Cargo.toml @@ -96,6 +96,7 @@ pallet-session = { version = "4.0.0-dev", features = [ "historical" ], path = ". pallet-session-benchmarking = { version = "4.0.0-dev", path = "../../../frame/session/benchmarking", default-features = false, optional = true } pallet-staking = { version = "4.0.0-dev", default-features = false, path = "../../../frame/staking" } pallet-staking-reward-curve = { version = "4.0.0-dev", default-features = false, path = "../../../frame/staking/reward-curve" } +pallet-staking-rpc-runtime-api = { version = "4.0.0-dev", default-features = false, path = "../../../frame/staking/rpc/runtime-api/" } pallet-state-trie-migration = { version = "4.0.0-dev", default-features = false, path = "../../../frame/state-trie-migration" } pallet-scheduler = { version = "4.0.0-dev", default-features = false, path = "../../../frame/scheduler" } pallet-society = { version = "4.0.0-dev", default-features = false, path = "../../../frame/society" } diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index a9e93a16f0713..8df59e0129fd9 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -2132,6 +2132,16 @@ impl_runtime_apis! { } } + impl pallet_staking_rpc_runtime_api::StakingApi for Runtime { + fn query_nominations_quota() -> u32 { + Staking::query_nominations_quota() + } + + fn query_points_to_balance(points: Balance, pool_id: u32) -> Result { + NominationPools::query_points_to_balance(points, pool_id) + } + } + impl pallet_mmr::primitives::MmrApi< Block, mmr::Hash, diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 99bfc8b8c36a5..a83fddc0ccf38 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -2140,6 +2140,15 @@ pub mod pallet { } impl Pallet { + // Query points to balance for a given pool. Used for custom RPC call. + pub fn query_points_to_balance(points: BalanceOf, pool_id: u32) -> Result, ()> { + if let Some(pool) = BondedPool::::get(pool_id).defensive() { + Ok(pool.points_to_balance(points)) + } else { + Err(()) + } + } + /// Returns the pending rewards for the specified `member_account`. /// /// In the case of error, `None` is returned. diff --git a/frame/staking/rpc/Cargo.toml b/frame/staking/rpc/Cargo.toml new file mode 100644 index 0000000000000..cf9c192456355 --- /dev/null +++ b/frame/staking/rpc/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "pallet-staking-rpc" +version = "4.0.0-dev" +authors = ["Parity Technologies "] +edition = "2021" +license = "Apache-2.0" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +description = "RPC interface for the staking pallet." +readme = "README.md" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0" } +jsonrpsee = { version = "0.16.2", features = ["client-core", "server", "macros"] } +pallet-staking-rpc-runtime-api = { version = "4.0.0-dev", path = "./runtime-api" } +sp-api = { version = "4.0.0-dev", path = "../../../primitives/api" } +sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } +sp-core = { version = "7.0.0", path = "../../../primitives/core" } +sp-rpc = { version = "6.0.0", path = "../../../primitives/rpc" } +sp-runtime = { version = "7.0.0", path = "../../../primitives/runtime" } +sp-weights = { version = "4.0.0", path = "../../../primitives/weights" } diff --git a/frame/staking/rpc/README.md b/frame/staking/rpc/README.md new file mode 100644 index 0000000000000..8a5a617d1a370 --- /dev/null +++ b/frame/staking/rpc/README.md @@ -0,0 +1,3 @@ +RPC interface for the staking pallet. + +License: Apache-2.0 diff --git a/frame/staking/rpc/runtime-api/Cargo.toml b/frame/staking/rpc/runtime-api/Cargo.toml new file mode 100644 index 0000000000000..351cd37b0991c --- /dev/null +++ b/frame/staking/rpc/runtime-api/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "pallet-staking-rpc-runtime-api" +version = "4.0.0-dev" +authors = ["Parity Technologies "] +edition = "2021" +license = "Apache-2.0" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +description = "RPC runtime API for transaction payment FRAME pallet" +readme = "README.md" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +pallet-staking = { version = "4.0.0-dev", default-features = false, path = "../../../staking" } +sp-api = { version = "4.0.0-dev", default-features = false, path = "../../../../primitives/api" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../../../primitives/runtime" } +sp-weights = { version = "4.0.0", default-features = false, path = "../../../../primitives/weights" } + +[features] +default = ["std"] +std = [ + "codec/std", + "pallet-staking/std", + "sp-api/std", + "sp-runtime/std", + "sp-weights/std", +] diff --git a/frame/staking/rpc/runtime-api/README.md b/frame/staking/rpc/runtime-api/README.md new file mode 100644 index 0000000000000..a999e519f8cbf --- /dev/null +++ b/frame/staking/rpc/runtime-api/README.md @@ -0,0 +1,3 @@ +Runtime API definition for the staking pallet. + +License: Apache-2.0 diff --git a/frame/staking/rpc/runtime-api/src/lib.rs b/frame/staking/rpc/runtime-api/src/lib.rs new file mode 100644 index 0000000000000..81bc11e10b974 --- /dev/null +++ b/frame/staking/rpc/runtime-api/src/lib.rs @@ -0,0 +1,33 @@ +// This file is part of Substrate. + +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Runtime API definition for the staking pallet. + +#![cfg_attr(not(feature = "std"), no_std)] + +use codec::Codec; +use sp_runtime::traits::MaybeDisplay; + +sp_api::decl_runtime_apis! { + #[api_version(1)] + pub trait StakingApi where + Balance: Codec + MaybeDisplay, + { + fn query_nominations_quota() -> u32; + fn query_points_to_balance(points: Balance, pool_id: u32) -> Result; + } +} diff --git a/frame/staking/rpc/src/lib.rs b/frame/staking/rpc/src/lib.rs new file mode 100644 index 0000000000000..5a76cf0d9ba7c --- /dev/null +++ b/frame/staking/rpc/src/lib.rs @@ -0,0 +1,113 @@ +// This file is part of Substrate. + +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! RPC interface for the staking pallet. + +use std::sync::Arc; + +use codec::Codec; +use jsonrpsee::{ + core::RpcResult, + proc_macros::rpc, + types::error::{CallError, ErrorObject}, +}; +use sp_api::ProvideRuntimeApi; +use sp_blockchain::HeaderBackend; +use sp_rpc::number::NumberOrHex; +use sp_runtime::{ + generic::BlockId, + traits::{Block as BlockT, MaybeDisplay}, +}; + +pub use pallet_staking_rpc_runtime_api::StakingApi as StakingRuntimeApi; + +#[rpc(client, server)] +pub trait StakingApi { + #[method(name = "staking_nominationsQuota")] + fn query_nominations_quota(&self) -> RpcResult; + #[method(name = "nominationPools_pointsToBalance")] + fn query_points_to_balance(&self, balance: Balance, pool_id: u32) -> RpcResult; +} + +pub struct Staking { + client: Arc, + _marker: std::marker::PhantomData

, +} + +impl Staking { + pub fn new(client: Arc) -> Self { + Self { client, _marker: Default::default() } + } +} + +/// Error type of this RPC api. +pub enum ApiError { + /// The call to runtime failed. + RuntimeError, +} + +impl From for i32 { + fn from(e: ApiError) -> i32 { + match e { + ApiError::RuntimeError => 1, + } + } +} + +impl StakingApiServer for Staking +where + Block: BlockT, + C: ProvideRuntimeApi + HeaderBackend + Send + Sync + 'static, + C::Api: StakingRuntimeApi, + Balance: Codec + MaybeDisplay + Copy + TryInto + Send + Sync + 'static, +{ + fn query_nominations_quota(&self) -> RpcResult { + let api = self.client.runtime_api(); + let at = BlockId::hash(self.client.info().best_hash); + + let runtime_api_result = api.query_nominations_quota(&at); + + Ok(runtime_api_result.map_err(|e| { + CallError::Custom(ErrorObject::owned( + ApiError::RuntimeError.into(), + "Unable to query the nominations quota.", + Some(e.to_string()), + )) + })?) + } + + fn query_points_to_balance(&self, balance: Balance, pool_id: u32) -> RpcResult { + let api = self.client.runtime_api(); + let at = BlockId::hash(self.client.info().best_hash); + + let result = api.query_points_to_balance(&at, balance, pool_id).map_err(|e| { + CallError::Custom(ErrorObject::owned( + ApiError::RuntimeError.into(), + "Unable to query the points to balance conversion for pool.", + Some(e.to_string()), + )) + })?; + + Ok(result.map_err(|_| { + CallError::Custom(ErrorObject::owned( + ApiError::RuntimeError.into(), + "Pool ID not found", + None::, + )) + })?) + } +} diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index db9aeba6fb58e..67efdb9ebfc36 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -59,6 +59,11 @@ use super::{pallet::*, STAKING_ID}; const NPOS_MAX_ITERATIONS_COEFFICIENT: u32 = 2; impl Pallet { + /// Query nominations quota for a given balance. Used for custom RPC call. + pub fn query_nominations_quota() -> u32 { + T::MaxNominations::get() + } + /// The total balance that can be slashed from a stash account as of right now. pub fn slashable_balance_of(stash: &T::AccountId) -> BalanceOf { // Weight note: consider making the stake accessible through stash.