From 8f7624f5cb7003ec0e2410a7c12c184b170e4c8b Mon Sep 17 00:00:00 2001 From: Arkadiy Paronyan Date: Thu, 28 Apr 2016 21:47:44 +0200 Subject: [PATCH] Support 'pending' block in RPC (#1007) * Support `pending` block in RPC * Forward calls from miner to client in case no pending block is available --- ethcore/src/client/client.rs | 4 ++ ethcore/src/client/mod.rs | 6 +- ethcore/src/client/test_client.rs | 6 +- ethcore/src/executive.rs | 3 +- miner/src/lib.rs | 17 ++++- miner/src/miner.rs | 53 ++++++++++++++++ rpc/src/v1/impls/eth.rs | 77 +++++++++++++++-------- rpc/src/v1/tests/eth.rs | 16 +++++ rpc/src/v1/tests/helpers/miner_service.rs | 27 +++++++- 9 files changed, 175 insertions(+), 34 deletions(-) diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index c3e8d942e..72d227a71 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -702,6 +702,10 @@ impl BlockChainClient for Client where V: Verifier { }) .collect() } + + fn last_hashes(&self) -> LastHashes { + self.build_last_hashes(self.chain.best_block_hash()) + } } impl MayPanic for Client { diff --git a/ethcore/src/client/mod.rs b/ethcore/src/client/mod.rs index c8bfe85ac..02885574f 100644 --- a/ethcore/src/client/mod.rs +++ b/ethcore/src/client/mod.rs @@ -25,7 +25,8 @@ pub use self::client::*; pub use self::config::{ClientConfig, BlockQueueConfig, BlockChainConfig}; pub use self::ids::{BlockId, TransactionId, UncleId}; pub use self::test_client::{TestBlockChainClient, EachBlockWith}; -pub use executive::Executed; +pub use executive::{Executed, Executive, TransactOptions}; +pub use env_info::{LastHashes, EnvInfo}; use std::collections::HashSet; use util::bytes::Bytes; @@ -132,5 +133,8 @@ pub trait BlockChainClient : Sync + Send { /// Executes a function providing it with a reference to an engine. fn engine(&self) -> &Engine; + + /// Get last hashes starting from best block. + fn last_hashes(&self) -> LastHashes; } diff --git a/ethcore/src/client/test_client.rs b/ethcore/src/client/test_client.rs index 339ae214b..82bd4b048 100644 --- a/ethcore/src/client/test_client.rs +++ b/ethcore/src/client/test_client.rs @@ -20,7 +20,7 @@ use std::sync::atomic::{AtomicUsize, Ordering as AtomicOrder}; use util::*; use transaction::{Transaction, LocalizedTransaction, SignedTransaction, Action}; use blockchain::TreeRoute; -use client::{BlockChainClient, BlockChainInfo, BlockStatus, BlockId, TransactionId, UncleId}; +use client::{BlockChainClient, BlockChainInfo, BlockStatus, BlockId, TransactionId, UncleId, LastHashes}; use header::{Header as BlockHeader, BlockNumber}; use filter::Filter; use log_entry::LocalizedLogEntry; @@ -268,6 +268,10 @@ impl BlockChainClient for TestBlockChainClient { unimplemented!(); } + fn last_hashes(&self) -> LastHashes { + unimplemented!(); + } + fn prepare_sealing(&self, _author: Address, _gas_floor_target: U256, _extra_data: Bytes, _transactions: Vec) -> (Option, HashSet) { (None, HashSet::new()) } diff --git a/ethcore/src/executive.rs b/ethcore/src/executive.rs index bd30e0fb6..d7753bfca 100644 --- a/ethcore/src/executive.rs +++ b/ethcore/src/executive.rs @@ -117,7 +117,7 @@ impl<'a> Executive<'a> { Externalities::new(self.state, self.info, self.engine, self.depth, origin_info, substate, output, tracer) } - /// This funtion should be used to execute transaction. + /// This function should be used to execute transaction. pub fn transact(&'a mut self, t: &SignedTransaction, options: TransactOptions) -> Result { let check = options.check_nonce; match options.tracing { @@ -126,6 +126,7 @@ impl<'a> Executive<'a> { } } + /// Execute transaction/call with tracing enabled pub fn transact_with_tracer(&'a mut self, t: &SignedTransaction, check_nonce: bool, mut tracer: T) -> Result where T: Tracer { let sender = try!(t.sender()); let nonce = self.state.nonce(&sender); diff --git a/miner/src/lib.rs b/miner/src/lib.rs index 1fcfce48f..8fd7640c2 100644 --- a/miner/src/lib.rs +++ b/miner/src/lib.rs @@ -61,7 +61,7 @@ pub use miner::{Miner}; pub use external::{ExternalMiner, ExternalMinerService}; use util::{H256, U256, Address, Bytes}; -use ethcore::client::{BlockChainClient}; +use ethcore::client::{BlockChainClient, Executed}; use ethcore::block::{ClosedBlock}; use ethcore::error::{Error}; use ethcore::transaction::SignedTransaction; @@ -145,6 +145,21 @@ pub trait MinerService : Send + Sync { /// Suggested gas limit. fn sensible_gas_limit(&self) -> U256 { x!(21000) } + + /// Account balance + fn balance(&self, chain: &BlockChainClient, address: &Address) -> U256; + + /// Call into contract code using pending state. + fn call(&self, chain: &BlockChainClient, t: &SignedTransaction) -> Result; + + /// Get storage value in pending state. + fn storage_at(&self, chain: &BlockChainClient, address: &Address, position: &H256) -> H256; + + /// Get account nonce in pending state. + fn nonce(&self, chain: &BlockChainClient, address: &Address) -> U256; + + /// Get contract code in pending state. + fn code(&self, chain: &BlockChainClient, address: &Address) -> Option; } /// Mining status diff --git a/miner/src/miner.rs b/miner/src/miner.rs index 1e6d30521..daf62c107 100644 --- a/miner/src/miner.rs +++ b/miner/src/miner.rs @@ -22,6 +22,7 @@ use ethcore::views::{BlockView, HeaderView}; use ethcore::client::{BlockChainClient, BlockId}; use ethcore::block::{ClosedBlock, IsBlock}; use ethcore::error::*; +use ethcore::client::{Executive, Executed, EnvInfo, TransactOptions}; use ethcore::transaction::SignedTransaction; use super::{MinerService, MinerStatus, TransactionQueue, AccountDetails, TransactionImportResult}; @@ -195,6 +196,58 @@ impl MinerService for Miner { } } + fn call(&self, chain: &BlockChainClient, t: &SignedTransaction) -> Result { + let sealing_work = self.sealing_work.lock().unwrap(); + match sealing_work.peek_last_ref() { + Some(work) => { + let block = work.block(); + let header = block.header(); + let last_hashes = chain.last_hashes(); + let env_info = EnvInfo { + number: header.number(), + author: header.author().clone(), + timestamp: header.timestamp(), + difficulty: header.difficulty().clone(), + last_hashes: last_hashes, + gas_used: U256::zero(), + gas_limit: U256::max_value(), + }; + // that's just a copy of the state. + let mut state = block.state().clone(); + let sender = try!(t.sender()); + let balance = state.balance(&sender); + // give the sender max balance + state.sub_balance(&sender, &balance); + state.add_balance(&sender, &U256::max_value()); + let options = TransactOptions { tracing: false, check_nonce: false }; + Executive::new(&mut state, &env_info, chain.engine()).transact(t, options) + }, + None => { + chain.call(t) + } + } + } + + fn balance(&self, chain: &BlockChainClient, address: &Address) -> U256 { + let sealing_work = self.sealing_work.lock().unwrap(); + sealing_work.peek_last_ref().map_or_else(|| chain.balance(address), |b| b.block().fields().state.balance(address)) + } + + fn storage_at(&self, chain: &BlockChainClient, address: &Address, position: &H256) -> H256 { + let sealing_work = self.sealing_work.lock().unwrap(); + sealing_work.peek_last_ref().map_or_else(|| chain.storage_at(address, position), |b| b.block().fields().state.storage_at(address, position)) + } + + fn nonce(&self, chain: &BlockChainClient, address: &Address) -> U256 { + let sealing_work = self.sealing_work.lock().unwrap(); + sealing_work.peek_last_ref().map_or_else(|| chain.nonce(address), |b| b.block().fields().state.nonce(address)) + } + + fn code(&self, chain: &BlockChainClient, address: &Address) -> Option { + let sealing_work = self.sealing_work.lock().unwrap(); + sealing_work.peek_last_ref().map_or_else(|| chain.code(address), |b| b.block().fields().state.code(address)) + } + fn set_author(&self, author: Address) { *self.author.write().unwrap() = author; } diff --git a/rpc/src/v1/impls/eth.rs b/rpc/src/v1/impls/eth.rs index f9872b2f7..51946b97b 100644 --- a/rpc/src/v1/impls/eth.rs +++ b/rpc/src/v1/impls/eth.rs @@ -200,17 +200,17 @@ fn params_len(params: &Params) -> usize { } } -fn from_params_discard_second(params: Params) -> Result<(F,), Error> where F: serde::de::Deserialize { +fn from_params_default_second(params: Params) -> Result<(F, BlockNumber, ), Error> where F: serde::de::Deserialize { match params_len(¶ms) { - 1 => from_params::<(F, )>(params), - _ => from_params::<(F, BlockNumber)>(params).map(|(f, _block_number)| (f, )), + 1 => from_params::<(F, )>(params).map(|(f,)| (f, BlockNumber::Latest)), + _ => from_params::<(F, BlockNumber)>(params), } } -fn from_params_discard_third(params: Params) -> Result<(F1, F2,), Error> where F1: serde::de::Deserialize, F2: serde::de::Deserialize { +fn from_params_default_third(params: Params) -> Result<(F1, F2, BlockNumber, ), Error> where F1: serde::de::Deserialize, F2: serde::de::Deserialize { match params_len(¶ms) { - 2 => from_params::<(F1, F2, )>(params), - _ => from_params::<(F1, F2, BlockNumber)>(params).map(|(f1, f2, _block_number)| (f1, f2, )), + 2 => from_params::<(F1, F2, )>(params).map(|(f1, f2)| (f1, f2, BlockNumber::Latest)), + _ => from_params::<(F1, F2, BlockNumber)>(params) } } @@ -293,18 +293,30 @@ impl Eth for EthClient } fn balance(&self, params: Params) -> Result { - from_params_discard_second(params).and_then(|(address, )| - to_value(&take_weak!(self.client).balance(&address))) + from_params_default_second(params) + .and_then(|(address, block_number,)| match block_number { + BlockNumber::Latest => to_value(&take_weak!(self.client).balance(&address)), + BlockNumber::Pending => to_value(&take_weak!(self.miner).balance(take_weak!(self.client).deref(), &address)), + _ => Err(Error::invalid_params()), + }) } fn storage_at(&self, params: Params) -> Result { - from_params_discard_third::(params).and_then(|(address, position, )| - to_value(&U256::from(take_weak!(self.client).storage_at(&address, &H256::from(position))))) + from_params_default_third::(params) + .and_then(|(address, position, block_number,)| match block_number { + BlockNumber::Pending => to_value(&U256::from(take_weak!(self.miner).storage_at(take_weak!(self.client).deref(), &address, &H256::from(position)))), + BlockNumber::Latest => to_value(&U256::from(take_weak!(self.client).storage_at(&address, &H256::from(position)))), + _ => Err(Error::invalid_params()), + }) } fn transaction_count(&self, params: Params) -> Result { - from_params_discard_second(params).and_then(|(address, )| - to_value(&take_weak!(self.client).nonce(&address))) + from_params_default_second(params) + .and_then(|(address, block_number,)| match block_number { + BlockNumber::Pending => to_value(&take_weak!(self.miner).nonce(take_weak!(self.client).deref(), &address)), + BlockNumber::Latest => to_value(&take_weak!(self.client).nonce(&address)), + _ => Err(Error::invalid_params()), + }) } fn block_transaction_count_by_hash(&self, params: Params) -> Result { @@ -341,10 +353,13 @@ impl Eth for EthClient }) } - // TODO: do not ignore block number param fn code_at(&self, params: Params) -> Result { - from_params_discard_second(params).and_then(|(address, )| - to_value(&take_weak!(self.client).code(&address).map_or_else(Bytes::default, Bytes::new))) + from_params_default_second(params) + .and_then(|(address, block_number,)| match block_number { + BlockNumber::Pending => to_value(&take_weak!(self.miner).code(take_weak!(self.client).deref(), &address).map_or_else(Bytes::default, Bytes::new)), + BlockNumber::Latest => to_value(&take_weak!(self.client).code(&address).map_or_else(Bytes::default, Bytes::new)), + _ => Err(Error::invalid_params()), + }) } fn block_by_hash(&self, params: Params) -> Result { @@ -502,21 +517,29 @@ impl Eth for EthClient fn call(&self, params: Params) -> Result { trace!(target: "jsonrpc", "call: {:?}", params); - from_params_discard_second(params).and_then(|(request, )| { - let signed = try!(self.sign_call(request)); - let client = take_weak!(self.client); - let output = client.call(&signed).map(|e| Bytes(e.output)).unwrap_or(Bytes::new(vec![])); - to_value(&output) - }) + from_params_default_second(params) + .and_then(|(request, block_number,)| { + let signed = try!(self.sign_call(request)); + let r = match block_number { + BlockNumber::Pending => take_weak!(self.miner).call(take_weak!(self.client).deref(), &signed), + BlockNumber::Latest => take_weak!(self.client).call(&signed), + _ => panic!("{:?}", block_number), + }; + to_value(&r.map(|e| Bytes(e.output)).unwrap_or(Bytes::new(vec![]))) + }) } fn estimate_gas(&self, params: Params) -> Result { - from_params_discard_second(params).and_then(|(request, )| { - let signed = try!(self.sign_call(request)); - let client = take_weak!(self.client); - let used = client.call(&signed).map(|res| res.gas_used + res.refunded).unwrap_or(From::from(0)); - to_value(&used) - }) + from_params_default_second(params) + .and_then(|(request, block_number,)| { + let signed = try!(self.sign_call(request)); + let r = match block_number { + BlockNumber::Pending => take_weak!(self.miner).call(take_weak!(self.client).deref(), &signed), + BlockNumber::Latest => take_weak!(self.client).call(&signed), + _ => return Err(Error::invalid_params()), + }; + to_value(&r.map(|res| res.gas_used + res.refunded).unwrap_or(From::from(0))) + }) } } diff --git a/rpc/src/v1/tests/eth.rs b/rpc/src/v1/tests/eth.rs index e0d6a86a0..327765d89 100644 --- a/rpc/src/v1/tests/eth.rs +++ b/rpc/src/v1/tests/eth.rs @@ -192,6 +192,22 @@ fn rpc_eth_balance() { assert_eq!(tester.io.handle_request(request), Some(response.to_owned())); } +#[ignore] //TODO: propert test +#[test] +fn rpc_eth_balance_pending() { + let tester = EthTester::default(); + + let request = r#"{ + "jsonrpc": "2.0", + "method": "eth_getBalance", + "params": ["0x0000000000000000000000000000000000000001", "latest"], + "id": 1 + }"#; + let response = r#"{"jsonrpc":"2.0","result":"0x","id":1}"#; + + assert_eq!(tester.io.handle_request(request), Some(response.to_owned())); +} + #[test] fn rpc_eth_storage_at() { let tester = EthTester::default(); diff --git a/rpc/src/v1/tests/helpers/miner_service.rs b/rpc/src/v1/tests/helpers/miner_service.rs index 23e029bf2..cd4157fa9 100644 --- a/rpc/src/v1/tests/helpers/miner_service.rs +++ b/rpc/src/v1/tests/helpers/miner_service.rs @@ -16,11 +16,11 @@ //! Test implementation of miner service. -use util::{Address, H256, Bytes, U256, FixedHash}; +use util::{Address, H256, Bytes, U256, FixedHash, Uint}; use util::standard::*; use ethcore::error::Error; -use ethcore::client::BlockChainClient; -use ethcore::block::ClosedBlock; +use ethcore::client::{BlockChainClient, Executed}; +use ethcore::block::{ClosedBlock, IsBlock}; use ethcore::transaction::SignedTransaction; use ethminer::{MinerService, MinerStatus, AccountDetails, TransactionImportResult}; @@ -174,4 +174,25 @@ impl MinerService for TestMinerService { fn submit_seal(&self, _chain: &BlockChainClient, _pow_hash: H256, _seal: Vec) -> Result<(), Error> { unimplemented!(); } + + fn balance(&self, _chain: &BlockChainClient, address: &Address) -> U256 { + self.latest_closed_block.lock().unwrap().as_ref().map_or_else(U256::zero, |b| b.block().fields().state.balance(address).clone()) + } + + fn call(&self, _chain: &BlockChainClient, _t: &SignedTransaction) -> Result { + unimplemented!(); + } + + fn storage_at(&self, _chain: &BlockChainClient, address: &Address, position: &H256) -> H256 { + self.latest_closed_block.lock().unwrap().as_ref().map_or_else(H256::default, |b| b.block().fields().state.storage_at(address, position).clone()) + } + + fn nonce(&self, _chain: &BlockChainClient, address: &Address) -> U256 { + self.latest_closed_block.lock().unwrap().as_ref().map_or_else(U256::zero, |b| b.block().fields().state.nonce(address).clone()) + } + + fn code(&self, _chain: &BlockChainClient, address: &Address) -> Option { + self.latest_closed_block.lock().unwrap().as_ref().map_or(None, |b| b.block().fields().state.code(address).clone()) + } + }