From 5486d7add2fdd8eaa0926d6bba4ae33da85380e4 Mon Sep 17 00:00:00 2001 From: Sergei Pepyakin Date: Wed, 30 Oct 2019 13:54:57 +0100 Subject: [PATCH] Implement contract_getStorage RPC API (#3944) --- substrate/node/runtime/src/lib.rs | 15 ++++ .../srml/contracts/rpc/runtime-api/src/lib.rs | 25 ++++++ substrate/srml/contracts/rpc/src/lib.rs | 87 ++++++++++++++++--- substrate/srml/contracts/src/exec.rs | 3 +- substrate/srml/contracts/src/lib.rs | 32 ++++++- 5 files changed, 147 insertions(+), 15 deletions(-) diff --git a/substrate/node/runtime/src/lib.rs b/substrate/node/runtime/src/lib.rs index 558c6e3be7..4f26143146 100644 --- a/substrate/node/runtime/src/lib.rs +++ b/substrate/node/runtime/src/lib.rs @@ -664,6 +664,21 @@ impl_runtime_apis! { Err(_) => ContractExecResult::Error, } } + + fn get_storage( + address: AccountId, + key: [u8; 32], + ) -> contracts_rpc_runtime_api::GetStorageResult { + Contracts::get_storage(address, key).map_err(|rpc_err| { + use contracts::GetStorageError; + use contracts_rpc_runtime_api::{GetStorageError as RpcGetStorageError}; + /// Map the contract error into the RPC layer error. + match rpc_err { + GetStorageError::ContractDoesntExist => RpcGetStorageError::ContractDoesntExist, + GetStorageError::IsTombstone => RpcGetStorageError::IsTombstone, + } + }) + } } impl transaction_payment_rpc_runtime_api::TransactionPaymentApi< diff --git a/substrate/srml/contracts/rpc/runtime-api/src/lib.rs b/substrate/srml/contracts/rpc/runtime-api/src/lib.rs index 7f01b8bdfa..054f110beb 100644 --- a/substrate/srml/contracts/rpc/runtime-api/src/lib.rs +++ b/substrate/srml/contracts/rpc/runtime-api/src/lib.rs @@ -45,6 +45,20 @@ pub enum ContractExecResult { Error, } +/// A result type of the get storage call. +/// +/// See [`ContractsApi::get_storage`] for more info. +pub type GetStorageResult = Result>, GetStorageError>; + +/// The possible errors that can happen querying the storage of a contract. +#[derive(Eq, PartialEq, Encode, Decode, RuntimeDebug)] +pub enum GetStorageError { + /// The given address doesn't point on a contract. + ContractDoesntExist, + /// The specified contract is a tombstone and thus cannot have any storage. + IsTombstone, +} + client::decl_runtime_apis! { /// The API to interact with contracts without using executive. pub trait ContractsApi where @@ -61,5 +75,16 @@ client::decl_runtime_apis! { gas_limit: u64, input_data: Vec, ) -> ContractExecResult; + + /// Query a given storage key in a given contract. + /// + /// Returns `Ok(Some(Vec))` if the storage value exists under the given key in the + /// specified account and `Ok(None)` if it doesn't. If the account specified by the address + /// doesn't exist, or doesn't have a contract or if the contract is a tombstone, then `Err` + /// is returned. + fn get_storage( + address: AccountId, + key: [u8; 32], + ) -> GetStorageResult; } } diff --git a/substrate/srml/contracts/rpc/src/lib.rs b/substrate/srml/contracts/rpc/src/lib.rs index ba50bd12f0..91783df996 100644 --- a/substrate/srml/contracts/rpc/src/lib.rs +++ b/substrate/srml/contracts/rpc/src/lib.rs @@ -18,24 +18,50 @@ use std::sync::Arc; -use serde::{Serialize, Deserialize}; use client::blockchain::HeaderBackend; use codec::Codec; use jsonrpc_core::{Error, ErrorCode, Result}; use jsonrpc_derive::rpc; -use primitives::Bytes; +use primitives::{H256, Bytes}; +use rpc_primitives::number; +use serde::{Deserialize, Serialize}; use sr_primitives::{ generic::BlockId, traits::{Block as BlockT, ProvideRuntimeApi}, }; -use rpc_primitives::number; -pub use srml_contracts_rpc_runtime_api::{ContractExecResult, ContractsApi as ContractsRuntimeApi}; pub use self::gen_client::Client as ContractsClient; +pub use srml_contracts_rpc_runtime_api::{ + self as runtime_api, ContractExecResult, ContractsApi as ContractsRuntimeApi, GetStorageResult, +}; + +const RUNTIME_ERROR: i64 = 1; +const CONTRACT_DOESNT_EXIST: i64 = 2; +const CONTRACT_IS_A_TOMBSTONE: i64 = 3; + +// A private newtype for converting `GetStorageError` into an RPC error. +struct GetStorageError(runtime_api::GetStorageError); +impl From for Error { + fn from(e: GetStorageError) -> Error { + use runtime_api::GetStorageError::*; + match e.0 { + ContractDoesntExist => Error { + code: ErrorCode::ServerError(CONTRACT_DOESNT_EXIST), + message: "The specified contract doesn't exist.".into(), + data: None, + }, + IsTombstone => Error { + code: ErrorCode::ServerError(CONTRACT_IS_A_TOMBSTONE), + message: "The contract is a tombstone and doesn't have any storage.".into(), + data: None, + } + } + } +} /// A struct that encodes RPC parameters required for a call to a smart-contract. #[derive(Serialize, Deserialize)] -#[serde(rename_all="camelCase")] +#[serde(rename_all = "camelCase")] #[serde(deny_unknown_fields)] pub struct CallRequest { origin: AccountId, @@ -60,6 +86,16 @@ pub trait ContractsApi { call_request: CallRequest, at: Option, ) -> Result; + + /// Returns the value under a specified storage `key` in a contract given by `address` param, + /// or `None` if it is not set. + #[rpc(name = "contracts_getStorage")] + fn get_storage( + &self, + address: AccountId, + key: H256, + at: Option, + ) -> Result>; } /// An implementation of contract specific RPC methods. @@ -71,13 +107,15 @@ pub struct Contracts { impl Contracts { /// Create new `Contracts` with the given reference to the client. pub fn new(client: Arc) -> Self { - Contracts { client, _marker: Default::default() } + Contracts { + client, + _marker: Default::default(), + } } } -const RUNTIME_ERROR: i64 = 1; - -impl ContractsApi<::Hash, AccountId, Balance> for Contracts +impl ContractsApi<::Hash, AccountId, Balance> + for Contracts where Block: BlockT, C: Send + Sync + 'static, @@ -95,15 +133,14 @@ where 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 - )); + self.client.info().best_hash)); let CallRequest { origin, dest, value, gas_limit, - input_data + input_data, } = call_request; let gas_limit = gas_limit.to_number().map_err(|e| Error { code: ErrorCode::InvalidParams, @@ -121,4 +158,30 @@ where Ok(exec_result) } + + fn get_storage( + &self, + address: AccountId, + key: H256, + at: Option<::Hash>, + ) -> Result> { + 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 get_storage_result = api + .get_storage(&at, address, key.into()) + .map_err(|e| + // Handle general API calling errors. + Error { + code: ErrorCode::ServerError(RUNTIME_ERROR), + message: "Runtime trapped while querying storage.".into(), + data: Some(format!("{:?}", e).into()), + })? + .map_err(GetStorageError)? + .map(Bytes); + + Ok(get_storage_result) + } } diff --git a/substrate/srml/contracts/src/exec.rs b/substrate/srml/contracts/src/exec.rs index 686b173ec7..08fd8999a4 100644 --- a/substrate/srml/contracts/src/exec.rs +++ b/substrate/srml/contracts/src/exec.rs @@ -29,6 +29,7 @@ pub type CallOf = ::Call; pub type MomentOf = <::Time as Time>::Moment; pub type SeedOf = ::Hash; pub type BlockNumberOf = ::BlockNumber; +pub type StorageKey = [u8; 32]; /// A type that represents a topic of an event. At the moment a hash is used. pub type TopicOf = ::Hash; @@ -84,8 +85,6 @@ macro_rules! try_or_exec_error { } } -pub type StorageKey = [u8; 32]; - /// An interface that provides access to the external environment in which the /// smart-contract is executed. /// diff --git a/substrate/srml/contracts/src/lib.rs b/substrate/srml/contracts/src/lib.rs index 8eaef951ba..df38747cc5 100644 --- a/substrate/srml/contracts/src/lib.rs +++ b/substrate/srml/contracts/src/lib.rs @@ -650,10 +650,19 @@ decl_module! { } } +/// The possible errors that can happen querying the storage of a contract. +pub enum GetStorageError { + /// The given address doesn't point on a contract. + ContractDoesntExist, + /// The specified contract is a tombstone and thus cannot have any storage. + IsTombstone, +} + +/// Public APIs provided by the contracts module. impl Module { /// Perform a call to a specified contract. /// - /// This function is similar to `Self::call`, but doesn't perform any lookups and better + /// This function is similar to `Self::call`, but doesn't perform any address lookups and better /// suitable for calling directly from Rust. pub fn bare_call( origin: T::AccountId, @@ -667,6 +676,27 @@ impl Module { }) } + /// Query storage of a specified contract under a specified key. + pub fn get_storage( + address: T::AccountId, + key: [u8; 32], + ) -> rstd::result::Result>, GetStorageError> { + let contract_info = >::get(&address) + .ok_or(GetStorageError::ContractDoesntExist)? + .get_alive() + .ok_or(GetStorageError::IsTombstone)?; + + let maybe_value = AccountDb::::get_storage( + &DirectAccountDb, + &address, + Some(&contract_info.trie_id), + &key, + ); + Ok(maybe_value) + } +} + +impl Module { fn execute_wasm( origin: T::AccountId, gas_limit: Gas,