Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

add system_dryRun #6300

Merged
merged 12 commits into from
Jun 16, 2020
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions bin/node/rpc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,4 @@ sp-blockchain = { version = "2.0.0-rc3", path = "../../../primitives/blockchain"
sc-finality-grandpa = { version = "0.8.0-rc3", path = "../../../client/finality-grandpa" }
sc-finality-grandpa-rpc = { version = "0.8.0-rc3", path = "../../../client/finality-grandpa/rpc" }
sc-rpc-api = { version = "0.8.0-rc3", path = "../../../client/rpc-api" }
sp-block-builder = { version = "2.0.0-rc3", path = "../../../primitives/block-builder" }
9 changes: 5 additions & 4 deletions bin/node/rpc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@

#![warn(missing_docs)]

use std::{sync::Arc, fmt};
use std::sync::Arc;

use node_primitives::{Block, BlockNumber, AccountId, Index, Balance, Hash};
use node_runtime::UncheckedExtrinsic;
Expand All @@ -46,6 +46,7 @@ use sc_consensus_babe_rpc::BabeRpcHandler;
use sc_finality_grandpa::{SharedVoterState, SharedAuthoritySet};
use sc_finality_grandpa_rpc::GrandpaRpcHandler;
use sc_rpc_api::DenyUnsafe;
use sp_block_builder::BlockBuilder;

/// Light client extra dependencies.
pub struct LightDeps<C, F, P> {
Expand Down Expand Up @@ -104,7 +105,7 @@ pub fn create_full<C, P, M, SC>(
C::Api: pallet_contracts_rpc::ContractsRuntimeApi<Block, AccountId, Balance, BlockNumber>,
C::Api: pallet_transaction_payment_rpc::TransactionPaymentRuntimeApi<Block, Balance, UncheckedExtrinsic>,
C::Api: BabeApi<Block>,
<C::Api as sp_api::ApiErrorExt>::Error: fmt::Debug,
C::Api: BlockBuilder<Block>,
P: TransactionPool + 'static,
M: jsonrpc_core::Metadata + Default,
SC: SelectChain<Block> +'static,
Expand Down Expand Up @@ -133,7 +134,7 @@ pub fn create_full<C, P, M, SC>(
} = grandpa;

io.extend_with(
SystemApi::to_delegate(FullSystem::new(client.clone(), pool))
SystemApi::to_delegate(FullSystem::new(client.clone(), pool, deny_unsafe))
);
// Making synchronous calls in light client freezes the browser currently,
// more context: https://github.com/paritytech/substrate/pull/3480
Expand Down Expand Up @@ -185,7 +186,7 @@ pub fn create_light<C, P, M, F>(
} = deps;
let mut io = jsonrpc_core::IoHandler::default();
io.extend_with(
SystemApi::<AccountId, Index>::to_delegate(LightSystem::new(client, remote_blockchain, fetcher, pool))
SystemApi::<Hash, AccountId, Index>::to_delegate(LightSystem::new(client, remote_blockchain, fetcher, pool))
);

io
Expand Down
3 changes: 1 addition & 2 deletions client/consensus/babe/rpc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ use sp_api::{ProvideRuntimeApi, BlockId};
use sp_runtime::traits::{Block as BlockT, Header as _};
use sp_consensus::{SelectChain, Error as ConsensusError};
use sp_blockchain::{HeaderBackend, HeaderMetadata, Error as BlockChainError};
use std::{collections::HashMap, fmt, sync::Arc};
use std::{collections::HashMap, sync::Arc};

type FutureResult<T> = Box<dyn rpc_future::Future<Item = T, Error = RpcError> + Send>;

Expand Down Expand Up @@ -93,7 +93,6 @@ impl<B, C, SC> BabeApi for BabeRpcHandler<B, C, SC>
B: BlockT,
C: ProvideRuntimeApi<B> + HeaderBackend<B> + HeaderMetadata<B, Error=BlockChainError> + 'static,
C::Api: BabeRuntimeApi<B>,
<C::Api as sp_api::ApiErrorExt>::Error: fmt::Debug,
SC: SelectChain<B> + Clone + 'static,
{
fn epoch_authorship(&self) -> FutureResult<HashMap<AuthorityId, EpochAuthorship>> {
Expand Down
2 changes: 2 additions & 0 deletions utils/frame/rpc/system/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ frame-system-rpc-runtime-api = { version = "2.0.0-rc3", path = "../../../../fram
sp-core = { version = "2.0.0-rc3", path = "../../../../primitives/core" }
sp-blockchain = { version = "2.0.0-rc3", path = "../../../../primitives/blockchain" }
sp-transaction-pool = { version = "2.0.0-rc3", path = "../../../../primitives/transaction-pool" }
sp-block-builder = { version = "2.0.0-rc3", path = "../../../../primitives/block-builder" }
sc-rpc-api = { version = "0.8.0-rc3", path = "../../../../client/rpc-api" }

[dev-dependencies]
substrate-test-runtime-client = { version = "2.0.0-rc3", path = "../../../../test-utils/runtime/client" }
Expand Down
182 changes: 168 additions & 14 deletions utils/frame/rpc/system/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ use std::sync::Arc;
use codec::{self, Codec, Decode, Encode};
use sc_client_api::light::{future_header, RemoteBlockchain, Fetcher, RemoteCallRequest};
use jsonrpc_core::{
Error, ErrorCode,
futures::future::{result, Future},
Error as RpcError, ErrorCode,
futures::future::{self as rpc_future,result, Future},
};
use jsonrpc_derive::rpc;
use futures::future::{ready, TryFutureExt};
Expand All @@ -35,53 +35,78 @@ use sp_runtime::{
generic::BlockId,
traits,
};
use sp_core::hexdisplay::HexDisplay;
use sp_core::{hexdisplay::HexDisplay, Bytes};
use sp_transaction_pool::{TransactionPool, InPoolTransaction};
use sp_block_builder::BlockBuilder;
use sc_rpc_api::DenyUnsafe;

pub use frame_system_rpc_runtime_api::AccountNonceApi;
pub use self::gen_client::Client as SystemClient;

/// Future that resolves to account nonce.
pub type FutureResult<T> = Box<dyn Future<Item = T, Error = Error> + Send>;
pub type FutureResult<T> = Box<dyn Future<Item = T, Error = RpcError> + Send>;

/// System RPC methods.
#[rpc]
pub trait SystemApi<AccountId, Index> {
pub trait SystemApi<BlockHash, AccountId, Index> {
/// Returns the next valid index (aka nonce) for given account.
///
/// This method takes into consideration all pending transactions
/// currently in the pool and if no transactions are found in the pool
/// it fallbacks to query the index from the runtime (aka. state nonce).
#[rpc(name = "system_accountNextIndex", alias("account_nextIndex"))]
fn nonce(&self, account: AccountId) -> FutureResult<Index>;

/// Dry run an extrinsic at a given block. Return all encoded events emitted during execution.
#[rpc(name = "system_dryRun", alias("system_dryRunAt"))]
fn dry_run(&self, extrinsic: Bytes, at: Option<BlockHash>) -> FutureResult<Bytes>;
}

/// Error type of this RPC api.
pub enum Error {
/// The transaction was not decodable.
DecodeError,
/// The call to runtime failed.
RuntimeError,
}

const RUNTIME_ERROR: i64 = 1;
impl From<Error> for i64 {
fn from(e: Error) -> i64 {
match e {
Error::RuntimeError => 1,
Error::DecodeError => 2,
}
}
}

/// An implementation of System-specific RPC methods on full client.
pub struct FullSystem<P: TransactionPool, C, B> {
client: Arc<C>,
pool: Arc<P>,
deny_unsafe: DenyUnsafe,
_marker: std::marker::PhantomData<B>,
}

impl<P: TransactionPool, C, B> FullSystem<P, C, B> {
/// Create new `FullSystem` given client and transaction pool.
pub fn new(client: Arc<C>, pool: Arc<P>) -> Self {
pub fn new(client: Arc<C>, pool: Arc<P>, deny_unsafe: DenyUnsafe,) -> Self {
FullSystem {
client,
pool,
deny_unsafe,
_marker: Default::default(),
}
}
}

impl<P, C, Block, AccountId, Index> SystemApi<AccountId, Index> for FullSystem<P, C, Block>
impl<P, C, Block, AccountId, Index> SystemApi<<Block as traits::Block>::Hash, AccountId, Index>
for FullSystem<P, C, Block>
where
C: sp_api::ProvideRuntimeApi<Block>,
C: HeaderBackend<Block>,
C: Send + Sync + 'static,
C::Api: AccountNonceApi<Block, AccountId, Index>,
C::Api: BlockBuilder<Block>,
P: TransactionPool + 'static,
Block: traits::Block,
AccountId: Clone + std::fmt::Display + Codec,
Expand All @@ -93,8 +118,8 @@ where
let best = self.client.info().best_hash;
let at = BlockId::hash(best);

let nonce = api.account_nonce(&at, account.clone()).map_err(|e| Error {
code: ErrorCode::ServerError(RUNTIME_ERROR),
let nonce = api.account_nonce(&at, account.clone()).map_err(|e| RpcError {
code: ErrorCode::ServerError(Error::RuntimeError.into()),
message: "Unable to query nonce.".into(),
data: Some(format!("{:?}", e).into()),
})?;
Expand All @@ -104,6 +129,38 @@ where

Box::new(result(get_nonce()))
}

fn dry_run(&self, extrinsic: Bytes, at: Option<<Block as traits::Block>::Hash>) -> FutureResult<Bytes> {
if let Err(err) = self.deny_unsafe.check_if_safe() {
return Box::new(rpc_future::err(err.into()));
}

let dry_run = || {
let api = self.client.runtime_api();
let at = BlockId::<Block>::hash(at.unwrap_or_else(||
// If the block hash is not supplied assume the best block.
self.client.info().best_hash
));

let uxt: <Block as traits::Block>::Extrinsic = Decode::decode(&mut &*extrinsic).map_err(|e| RpcError {
code: ErrorCode::ServerError(Error::DecodeError.into()),
message: "Unable to dry run extrinsic.".into(),
data: Some(format!("{:?}", e).into()),
})?;

let result = api.apply_extrinsic(&at, uxt)
.map_err(|e| RpcError {
code: ErrorCode::ServerError(Error::RuntimeError.into()),
message: "Unable to dry run extrinsic.".into(),
data: Some(format!("{:?}", e).into()),
})?;

Ok(Encode::encode(&result).into())
};


Box::new(result(dry_run()))
}
}

/// An implementation of System-specific RPC methods on light client.
Expand Down Expand Up @@ -131,7 +188,8 @@ impl<P: TransactionPool, C, F, Block> LightSystem<P, C, F, Block> {
}
}

impl<P, C, F, Block, AccountId, Index> SystemApi<AccountId, Index> for LightSystem<P, C, F, Block>
impl<P, C, F, Block, AccountId, Index> SystemApi<<Block as traits::Block>::Hash, AccountId, Index>
for LightSystem<P, C, F, Block>
where
P: TransactionPool + 'static,
C: HeaderBackend<Block>,
Expand Down Expand Up @@ -165,8 +223,8 @@ where
).compat();
let future_nonce = future_nonce.and_then(|nonce| Decode::decode(&mut &nonce[..])
.map_err(|e| ClientError::CallResultDecode("Cannot decode account nonce", e)));
let future_nonce = future_nonce.map_err(|e| Error {
code: ErrorCode::ServerError(RUNTIME_ERROR),
let future_nonce = future_nonce.map_err(|e| RpcError {
code: ErrorCode::ServerError(Error::RuntimeError.into()),
message: "Unable to query nonce.".into(),
data: Some(format!("{:?}", e).into()),
});
Expand All @@ -176,6 +234,14 @@ where

Box::new(future_nonce)
}

fn dry_run(&self, _extrinsic: Bytes, _at: Option<<Block as traits::Block>::Hash>) -> FutureResult<Bytes> {
Box::new(result(Err(RpcError {
code: ErrorCode::MethodNotFound,
message: "Unable to dry run extrinsic.".into(),
data: None,
})))
}
}

/// Adjust account nonce from state, so that tx with the nonce will be
Expand Down Expand Up @@ -224,6 +290,7 @@ mod tests {
use futures::executor::block_on;
use substrate_test_runtime_client::{runtime::Transfer, AccountKeyring};
use sc_transaction_pool::{BasicPool, FullChainApi};
use sp_runtime::{ApplyExtrinsicResult, transaction_validity::{TransactionValidityError, InvalidTransaction}};

#[test]
fn should_return_next_nonce_for_some_account() {
Expand Down Expand Up @@ -255,12 +322,99 @@ mod tests {
let ext1 = new_transaction(1);
block_on(pool.submit_one(&BlockId::number(0), source, ext1)).unwrap();

let accounts = FullSystem::new(client, pool);
let accounts = FullSystem::new(client, pool, DenyUnsafe::Yes);

// when
let nonce = accounts.nonce(AccountKeyring::Alice.into());

// then
assert_eq!(nonce.wait().unwrap(), 2);
}

#[test]
fn dry_run_should_deny_unsafe() {
let _ = env_logger::try_init();

// given
let client = Arc::new(substrate_test_runtime_client::new());
let pool = Arc::new(
BasicPool::new(
Default::default(),
Arc::new(FullChainApi::new(client.clone())),
None,
).0
);

let accounts = FullSystem::new(client, pool, DenyUnsafe::Yes);

// when
let res = accounts.dry_run(vec![].into(), None);

// then
assert_eq!(res.wait(), Err(RpcError::method_not_found()));
}

#[test]
fn dry_run_should_work() {
let _ = env_logger::try_init();

// given
let client = Arc::new(substrate_test_runtime_client::new());
let pool = Arc::new(
BasicPool::new(
Default::default(),
Arc::new(FullChainApi::new(client.clone())),
None,
).0
);

let accounts = FullSystem::new(client, pool, DenyUnsafe::No);

let tx = Transfer {
from: AccountKeyring::Alice.into(),
to: AccountKeyring::Bob.into(),
amount: 5,
nonce: 0,
}.into_signed_tx();

// when
let res = accounts.dry_run(tx.encode().into(), None);

// then
let bytes = res.wait().unwrap().0;
let apply_res: ApplyExtrinsicResult = Decode::decode(&mut bytes.as_slice()).unwrap();
assert_eq!(apply_res, Ok(Ok(())));
}

#[test]
fn dry_run_should_indicate_error() {
let _ = env_logger::try_init();

// given
let client = Arc::new(substrate_test_runtime_client::new());
let pool = Arc::new(
BasicPool::new(
Default::default(),
Arc::new(FullChainApi::new(client.clone())),
None,
).0
);

let accounts = FullSystem::new(client, pool, DenyUnsafe::No);

let tx = Transfer {
from: AccountKeyring::Alice.into(),
to: AccountKeyring::Bob.into(),
amount: 5,
nonce: 100,
}.into_signed_tx();

// when
let res = accounts.dry_run(tx.encode().into(), None);

// then
let bytes = res.wait().unwrap().0;
let apply_res: ApplyExtrinsicResult = Decode::decode(&mut bytes.as_slice()).unwrap();
assert_eq!(apply_res, Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)));
}
}