diff --git a/parity/rpc_apis.rs b/parity/rpc_apis.rs index 6e922147b..8acbae7c3 100644 --- a/parity/rpc_apis.rs +++ b/parity/rpc_apis.rs @@ -291,6 +291,7 @@ impl FullDependencies { allow_pending_receipt_query: !self.geth_compatibility, send_block_number_in_get_work: !self.geth_compatibility, gas_price_percentile: self.gas_price_percentile, + allow_experimental_rpcs: self.experimental_rpcs, } ); handler.extend_with(client.to_delegate()); diff --git a/rpc/src/v1/impls/eth.rs b/rpc/src/v1/impls/eth.rs index 11e64227c..821745600 100644 --- a/rpc/src/v1/impls/eth.rs +++ b/rpc/src/v1/impls/eth.rs @@ -21,12 +21,12 @@ use std::time::{Instant, Duration, SystemTime, UNIX_EPOCH}; use std::sync::Arc; use rlp::Rlp; -use ethereum_types::{U256, H256, Address}; +use ethereum_types::{U256, H256, H160, Address}; use parking_lot::Mutex; use ethash::{self, SeedHashCompute}; use ethcore::account_provider::AccountProvider; -use ethcore::client::{BlockChainClient, BlockId, TransactionId, UncleId, StateOrBlock, StateClient, StateInfo, Call, EngineInfo}; +use ethcore::client::{BlockChainClient, BlockId, TransactionId, UncleId, StateOrBlock, StateClient, StateInfo, Call, EngineInfo, ProvingBlockChainClient}; use ethcore::filter::Filter as EthcoreFilter; use ethcore::header::{BlockNumber as EthBlockNumber}; use ethcore::miner::{self, MinerService}; @@ -35,6 +35,7 @@ use ethcore::encoded; use sync::SyncProvider; use miner::external::ExternalMinerService; use transaction::{SignedTransaction, LocalizedTransaction}; +use hash::keccak; use jsonrpc_core::{BoxFuture, Result}; use jsonrpc_core::futures::future; @@ -46,7 +47,7 @@ use v1::helpers::block_import::is_major_importing; use v1::traits::Eth; use v1::types::{ RichBlock, Block, BlockTransactions, BlockNumber, Bytes, SyncStatus, SyncInfo, - Transaction, CallRequest, Index, Filter, Log, Receipt, Work, + Transaction, CallRequest, Index, Filter, Log, Receipt, Work, EthAccount, StorageProof, H64 as RpcH64, H256 as RpcH256, H160 as RpcH160, U256 as RpcU256, block_number_to_id, U64 as RpcU64, }; @@ -64,6 +65,8 @@ pub struct EthClientOptions { pub send_block_number_in_get_work: bool, /// Gas Price Percentile used as default gas price. pub gas_price_percentile: usize, + /// Enable Experimental RPC-Calls + pub allow_experimental_rpcs: bool, } impl EthClientOptions { @@ -83,6 +86,7 @@ impl Default for EthClientOptions { allow_pending_receipt_query: true, send_block_number_in_get_work: true, gas_price_percentile: 50, + allow_experimental_rpcs: false, } } } @@ -450,7 +454,7 @@ fn check_known(client: &C, number: BlockNumber) -> Result<()> where C: BlockC const MAX_QUEUE_SIZE_TO_MINE_ON: usize = 4; // because uncles go back 6. impl Eth for EthClient where - C: miner::BlockChainClient + BlockChainClient + StateClient + Call + EngineInfo + 'static, + C: miner::BlockChainClient + StateClient + ProvingBlockChainClient + Call + EngineInfo + 'static, SN: SnapshotService + 'static, S: SyncProvider + 'static, M: MinerService + 'static, @@ -547,6 +551,50 @@ impl Eth for EthClient< Box::new(future::done(res)) } + fn proof(&self, address: RpcH160, values: Vec, num: Trailing) -> BoxFuture { + try_bf!(errors::require_experimental(self.options.allow_experimental_rpcs, "1186")); + + let a: H160 = address.clone().into(); + let key1 = keccak(a); + + let num = num.unwrap_or_default(); + let id = match num { + BlockNumber::Num(n) => BlockId::Number(n), + BlockNumber::Earliest => BlockId::Earliest, + BlockNumber::Latest => BlockId::Latest, + BlockNumber::Pending => { + warn!("`Pending` is deprecated and may be removed in future versions. Falling back to `Latest`"); + BlockId::Latest + } + }; + + try_bf!(check_known(&*self.client, num.clone())); + let res = match self.client.prove_account(key1, id) { + Some((proof,account)) => Ok(EthAccount { + address: address, + balance: account.balance.into(), + nonce: account.nonce.into(), + code_hash: account.code_hash.into(), + storage_hash: account.storage_root.into(), + account_proof: proof.into_iter().map(Bytes::new).collect(), + storage_proof: values.into_iter().filter_map(|storage_index| { + let key2: H256 = storage_index.into(); + self.client.prove_storage(key1, keccak(key2), id) + .map(|(storage_proof,storage_value)| StorageProof { + key: key2.into(), + value: storage_value.into(), + proof: storage_proof.into_iter().map(Bytes::new).collect() + }) + }) + .collect::>() + }), + None => Err(errors::state_pruned()), + }; + + Box::new(future::done(res)) + } + + fn storage_at(&self, address: RpcH160, pos: RpcU256, num: Trailing) -> BoxFuture { let address: Address = RpcH160::into(address); let position: U256 = RpcU256::into(pos); diff --git a/rpc/src/v1/impls/light/eth.rs b/rpc/src/v1/impls/light/eth.rs index 2a2563016..dffb88abf 100644 --- a/rpc/src/v1/impls/light/eth.rs +++ b/rpc/src/v1/impls/light/eth.rs @@ -47,7 +47,7 @@ use v1::helpers::light_fetch::{self, LightFetch}; use v1::traits::Eth; use v1::types::{ RichBlock, Block, BlockTransactions, BlockNumber, LightBlockNumber, Bytes, SyncStatus, SyncInfo, - Transaction, CallRequest, Index, Filter, Log, Receipt, Work, + Transaction, CallRequest, Index, Filter, Log, Receipt, Work, EthAccount, H64 as RpcH64, H256 as RpcH256, H160 as RpcH160, U256 as RpcU256, U64 as RpcU64, }; @@ -475,6 +475,10 @@ impl Eth for EthClient { })) } + fn proof(&self, _address: RpcH160, _values:Vec, _num: Trailing) -> BoxFuture { + Box::new(future::err(errors::unimplemented(None))) + } + fn compilers(&self) -> Result> { Err(errors::deprecated("Compilation functionality is deprecated.".to_string())) } diff --git a/rpc/src/v1/tests/eth.rs b/rpc/src/v1/tests/eth.rs index 39235c690..b0c599aa8 100644 --- a/rpc/src/v1/tests/eth.rs +++ b/rpc/src/v1/tests/eth.rs @@ -38,7 +38,7 @@ use parity_runtime::Runtime; use jsonrpc_core::IoHandler; use v1::helpers::dispatch::FullDispatcher; use v1::helpers::nonce; -use v1::impls::{EthClient, SigningUnsafeClient}; +use v1::impls::{EthClient, EthClientOptions, SigningUnsafeClient}; use v1::metadata::Metadata; use v1::tests::helpers::{TestSnapshotService, TestSyncProvider, Config}; use v1::traits::eth::Eth; @@ -140,7 +140,13 @@ impl EthTester { &opt_account_provider, &miner_service, &external_miner, - Default::default(), + EthClientOptions { + pending_nonce_from_queue: false, + allow_pending_receipt_query: true, + send_block_number_in_get_work: true, + gas_price_percentile: 50, + allow_experimental_rpcs: true, + }, ); let reservations = Arc::new(Mutex::new(nonce::Reservations::new(runtime.executor()))); @@ -198,6 +204,33 @@ fn eth_get_balance() { assert_eq!(tester.handler.handle_request_sync(req_new_acc).unwrap(), res_new_acc); } +#[test] +fn eth_get_proof() { + let chain = extract_chain!("BlockchainTests/bcWalletTest/wallet2outOf3txs"); + let tester = EthTester::from_chain(&chain); + // final account state + let req_latest = r#"{ + "jsonrpc": "2.0", + "method": "eth_getProof", + "params": ["0xaaaf5374fce5edbc8e2a8697c15331677e6ebaaa", [], "latest"], + "id": 1 + }"#; + + let res_latest = r#","address":"0xaaaf5374fce5edbc8e2a8697c15331677e6ebaaa","balance":"0x9","codeHash":"0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470","nonce":"0x0","storageHash":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","storageProof":[]},"id":1}"#.to_owned(); + assert!(tester.handler.handle_request_sync(req_latest).unwrap().to_string().ends_with(res_latest.as_str())); + + // non-existant account + let req_new_acc = r#"{ + "jsonrpc": "2.0", + "method": "eth_getProof", + "params": ["0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",[],"latest"], + "id": 3 + }"#; + + let res_new_acc = r#","address":"0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","balance":"0x0","codeHash":"0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470","nonce":"0x0","storageHash":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","storageProof":[]},"id":3}"#.to_owned(); + assert!(tester.handler.handle_request_sync(req_new_acc).unwrap().to_string().ends_with(res_new_acc.as_str())); +} + #[test] fn eth_block_number() { let chain = extract_chain!("BlockchainTests/bcGasPricerTest/RPC_API_Test"); diff --git a/rpc/src/v1/traits/eth.rs b/rpc/src/v1/traits/eth.rs index 0c0041bb7..817da88f7 100644 --- a/rpc/src/v1/traits/eth.rs +++ b/rpc/src/v1/traits/eth.rs @@ -18,7 +18,7 @@ use jsonrpc_core::{Result, BoxFuture}; use jsonrpc_macros::Trailing; -use v1::types::{RichBlock, BlockNumber, Bytes, CallRequest, Filter, FilterChanges, Index}; +use v1::types::{RichBlock, BlockNumber, Bytes, CallRequest, Filter, FilterChanges, Index, EthAccount}; use v1::types::{Log, Receipt, SyncStatus, Transaction, Work}; use v1::types::{H64, H160, H256, U256, U64}; @@ -69,6 +69,10 @@ build_rpc_trait! { #[rpc(name = "eth_getBalance")] fn balance(&self, H160, Trailing) -> BoxFuture; + /// Returns the account- and storage-values of the specified account including the Merkle-proof + #[rpc(name = "eth_getProof")] + fn proof(&self, H160, Vec, Trailing) -> BoxFuture; + /// Returns content of the storage at given address. #[rpc(name = "eth_getStorageAt")] fn storage_at(&self, H160, U256, Trailing) -> BoxFuture; diff --git a/rpc/src/v1/types/account_info.rs b/rpc/src/v1/types/account_info.rs index 5a0e2952a..487507de9 100644 --- a/rpc/src/v1/types/account_info.rs +++ b/rpc/src/v1/types/account_info.rs @@ -13,6 +13,7 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +use v1::types::{H160, H256, U256, Bytes}; /// Account information. #[derive(Debug, Default, Clone, PartialEq, Serialize)] @@ -21,6 +22,28 @@ pub struct AccountInfo { pub name: String, } +/// Datastructure with proof for one single storage-entry +#[derive(Debug, Default, Clone, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct StorageProof { + pub key: U256, + pub value: U256, + pub proof: Vec +} + +/// Account information. +#[derive(Debug, Default, Clone, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct EthAccount { + pub address: H160, + pub balance: U256, + pub nonce: U256, + pub code_hash: H256, + pub storage_hash: H256, + pub account_proof: Vec, + pub storage_proof: Vec, +} + /// Extended account information (used by `parity_allAccountInfo`). #[derive(Debug, Default, Clone, PartialEq, Serialize)] pub struct ExtAccountInfo { diff --git a/rpc/src/v1/types/mod.rs b/rpc/src/v1/types/mod.rs index b2e7c074b..68401fb0c 100644 --- a/rpc/src/v1/types/mod.rs +++ b/rpc/src/v1/types/mod.rs @@ -46,8 +46,9 @@ mod private_receipt; mod eip191; pub mod pubsub; + pub use self::eip191::{EIP191Version, PresignedTransaction}; -pub use self::account_info::{AccountInfo, ExtAccountInfo, HwAccountInfo}; +pub use self::account_info::{AccountInfo, ExtAccountInfo, HwAccountInfo, EthAccount, StorageProof}; pub use self::bytes::Bytes; pub use self::block::{RichBlock, Block, BlockTransactions, Header, RichHeader, Rich}; pub use self::block_number::{BlockNumber, LightBlockNumber, block_number_to_id};