Multi-call RPC (#6195)

* Removing duplicated pending state accessors in miner.

* Merge miner+client call.

* Multicall & multicall RPC.

* Sensible defaults.

* Fix tests.
This commit is contained in:
Tomasz Drwięga 2017-08-04 15:58:14 +02:00 committed by Marek Kotewicz
parent 62153b1ff0
commit f157461ee1
22 changed files with 308 additions and 271 deletions

View File

@ -906,7 +906,7 @@ impl Client {
pub fn state_at(&self, id: BlockId) -> Option<State<StateDB>> {
// fast path for latest state.
match id.clone() {
BlockId::Pending => return self.miner.pending_state().or_else(|| Some(self.state())),
BlockId::Pending => return self.miner.pending_state(self.chain.read().best_block_number()).or_else(|| Some(self.state())),
BlockId::Latest => return Some(self.state()),
_ => {},
}
@ -1055,19 +1055,20 @@ impl Client {
self.history
}
fn block_hash(chain: &BlockChain, id: BlockId) -> Option<H256> {
fn block_hash(chain: &BlockChain, miner: &Miner, id: BlockId) -> Option<H256> {
match id {
BlockId::Hash(hash) => Some(hash),
BlockId::Number(number) => chain.block_hash(number),
BlockId::Earliest => chain.block_hash(0),
BlockId::Latest | BlockId::Pending => Some(chain.best_block_hash()),
BlockId::Latest => Some(chain.best_block_hash()),
BlockId::Pending => miner.pending_block_header(chain.best_block_number()).map(|header| header.hash())
}
}
fn transaction_address(&self, id: TransactionId) -> Option<TransactionAddress> {
match id {
TransactionId::Hash(ref hash) => self.chain.read().transaction_address(hash),
TransactionId::Location(id, index) => Self::block_hash(&self.chain.read(), id).map(|hash| TransactionAddress {
TransactionId::Location(id, index) => Self::block_hash(&self.chain.read(), &self.miner, id).map(|hash| TransactionAddress {
block_hash: hash,
index: index,
})
@ -1110,6 +1111,31 @@ impl Client {
data: data,
}.fake_sign(from)
}
fn do_call(&self, env_info: &EnvInfo, state: &mut State<StateDB>, increase_balance: bool, t: &SignedTransaction, analytics: CallAnalytics) -> Result<Executed, CallError> {
let original_state = if analytics.state_diffing { Some(state.clone()) } else { None };
// give the sender a sufficient balance (if calling in pending block)
if increase_balance {
let sender = t.sender();
let balance = state.balance(&sender).map_err(ExecutionError::from)?;
let needed_balance = t.value + t.gas * t.gas_price;
if balance < needed_balance {
state.add_balance(&sender, &(needed_balance - balance), state::CleanupMode::NoEmpty)
.map_err(ExecutionError::from)?;
}
}
let options = TransactOptions { tracing: analytics.transaction_tracing, vm_tracing: analytics.vm_tracing, check_nonce: false };
let mut ret = Executive::new(state, env_info, &*self.engine).transact_virtual(t, options)?;
// TODO gav move this into Executive.
if let Some(original) = original_state {
ret.state_diff = Some(state.diff_from(original).map_err(ExecutionError::from)?);
}
Ok(ret)
}
}
impl snapshot::DatabaseRestore for Client {
@ -1134,23 +1160,31 @@ impl snapshot::DatabaseRestore for Client {
}
impl BlockChainClient for Client {
fn call(&self, t: &SignedTransaction, block: BlockId, analytics: CallAnalytics) -> Result<Executed, CallError> {
fn call(&self, transaction: &SignedTransaction, analytics: CallAnalytics, block: BlockId) -> Result<Executed, CallError> {
let mut env_info = self.env_info(block).ok_or(CallError::StatePruned)?;
env_info.gas_limit = U256::max_value();
// that's just a copy of the state.
let mut state = self.state_at(block).ok_or(CallError::StatePruned)?;
let original_state = if analytics.state_diffing { Some(state.clone()) } else { None };
let options = TransactOptions { tracing: analytics.transaction_tracing, vm_tracing: analytics.vm_tracing, check_nonce: false };
let mut ret = Executive::new(&mut state, &env_info, &*self.engine).transact_virtual(t, options)?;
self.do_call(&env_info, &mut state, block == BlockId::Pending, transaction, analytics)
}
// TODO gav move this into Executive.
if let Some(original) = original_state {
ret.state_diff = Some(state.diff_from(original).map_err(ExecutionError::from)?);
fn call_many(&self, transactions: &[(SignedTransaction, CallAnalytics)], block: BlockId) -> Result<Vec<Executed>, CallError> {
let mut env_info = self.env_info(block).ok_or(CallError::StatePruned)?;
env_info.gas_limit = U256::max_value();
// that's just a copy of the state.
let mut state = self.state_at(block).ok_or(CallError::StatePruned)?;
let mut results = Vec::with_capacity(transactions.len());
for &(ref t, analytics) in transactions {
let ret = self.do_call(&env_info, &mut state, block == BlockId::Pending, t, analytics)?;
env_info.gas_used = ret.cumulative_gas_used;
results.push(ret);
}
Ok(ret)
Ok(results)
}
fn estimate_gas(&self, t: &SignedTransaction, block: BlockId) -> Result<U256, CallError> {
@ -1303,7 +1337,16 @@ impl BlockChainClient for Client {
fn block_header(&self, id: BlockId) -> Option<::encoded::Header> {
let chain = self.chain.read();
Self::block_hash(&chain, id).and_then(|hash| chain.block_header_data(&hash))
if let BlockId::Pending = id {
if let Some(block) = self.miner.pending_block(chain.best_block_number()) {
return Some(encoded::Header::new(block.header.rlp(Seal::Without)));
}
// fall back to latest
return self.block_header(BlockId::Latest);
}
Self::block_hash(&chain, &self.miner, id).and_then(|hash| chain.block_header_data(&hash))
}
fn block_number(&self, id: BlockId) -> Option<BlockNumber> {
@ -1311,30 +1354,48 @@ impl BlockChainClient for Client {
BlockId::Number(number) => Some(number),
BlockId::Hash(ref hash) => self.chain.read().block_number(hash),
BlockId::Earliest => Some(0),
BlockId::Latest | BlockId::Pending => Some(self.chain.read().best_block_number()),
BlockId::Latest => Some(self.chain.read().best_block_number()),
BlockId::Pending => Some(self.chain.read().best_block_number() + 1),
}
}
fn block_body(&self, id: BlockId) -> Option<encoded::Body> {
let chain = self.chain.read();
Self::block_hash(&chain, id).and_then(|hash| chain.block_body(&hash))
if let BlockId::Pending = id {
if let Some(block) = self.miner.pending_block(chain.best_block_number()) {
return Some(encoded::Body::new(BlockChain::block_to_body(&block.rlp_bytes(Seal::Without))));
}
// fall back to latest
return self.block_body(BlockId::Latest);
}
Self::block_hash(&chain, &self.miner, id).and_then(|hash| chain.block_body(&hash))
}
fn block(&self, id: BlockId) -> Option<encoded::Block> {
let chain = self.chain.read();
if let BlockId::Pending = id {
if let Some(block) = self.miner.pending_block() {
if let Some(block) = self.miner.pending_block(chain.best_block_number()) {
return Some(encoded::Block::new(block.rlp_bytes(Seal::Without)));
}
// fall back to latest
return self.block(BlockId::Latest);
}
let chain = self.chain.read();
Self::block_hash(&chain, id).and_then(|hash| {
Self::block_hash(&chain, &self.miner, id).and_then(|hash| {
chain.block(&hash)
})
}
fn block_status(&self, id: BlockId) -> BlockStatus {
if let BlockId::Pending = id {
return BlockStatus::Pending;
}
let chain = self.chain.read();
match Self::block_hash(&chain, id) {
match Self::block_hash(&chain, &self.miner, id) {
Some(ref hash) if chain.is_known(hash) => BlockStatus::InChain,
Some(hash) => self.block_queue.status(&hash).into(),
None => BlockStatus::Unknown
@ -1342,13 +1403,18 @@ impl BlockChainClient for Client {
}
fn block_total_difficulty(&self, id: BlockId) -> Option<U256> {
if let BlockId::Pending = id {
if let Some(block) = self.miner.pending_block() {
return Some(*block.header.difficulty() + self.block_total_difficulty(BlockId::Latest).expect("blocks in chain have details; qed"));
}
}
let chain = self.chain.read();
Self::block_hash(&chain, id).and_then(|hash| chain.block_details(&hash)).map(|d| d.total_difficulty)
if let BlockId::Pending = id {
let latest_difficulty = self.block_total_difficulty(BlockId::Latest).expect("blocks in chain have details; qed");
let pending_difficulty = self.miner.pending_block_header(chain.best_block_number()).map(|header| *header.difficulty());
if let Some(difficulty) = pending_difficulty {
return Some(difficulty + latest_difficulty);
}
// fall back to latest
return Some(latest_difficulty);
}
Self::block_hash(&chain, &self.miner, id).and_then(|hash| chain.block_details(&hash)).map(|d| d.total_difficulty)
}
fn nonce(&self, address: &Address, id: BlockId) -> Option<U256> {
@ -1361,7 +1427,7 @@ impl BlockChainClient for Client {
fn block_hash(&self, id: BlockId) -> Option<H256> {
let chain = self.chain.read();
Self::block_hash(&chain, id)
Self::block_hash(&chain, &self.miner, id)
}
fn code(&self, address: &Address, id: BlockId) -> Option<Option<Bytes>> {
@ -1526,7 +1592,8 @@ impl BlockChainClient for Client {
if self.chain.read().is_known(&unverified.hash()) {
return Err(BlockImportError::Import(ImportError::AlreadyInChain));
}
if self.block_status(BlockId::Hash(unverified.parent_hash())) == BlockStatus::Unknown {
let status = self.block_status(BlockId::Hash(unverified.parent_hash()));
if status == BlockStatus::Unknown || status == BlockStatus::Pending {
return Err(BlockImportError::Block(BlockError::UnknownParent(unverified.parent_hash())));
}
}
@ -1540,7 +1607,8 @@ impl BlockChainClient for Client {
if self.chain.read().is_known(&header.hash()) {
return Err(BlockImportError::Import(ImportError::AlreadyInChain));
}
if self.block_status(BlockId::Hash(header.parent_hash())) == BlockStatus::Unknown {
let status = self.block_status(BlockId::Hash(header.parent_hash()));
if status == BlockStatus::Unknown || status == BlockStatus::Pending {
return Err(BlockImportError::Block(BlockError::UnknownParent(header.parent_hash())));
}
}
@ -1686,7 +1754,7 @@ impl BlockChainClient for Client {
fn call_contract(&self, block_id: BlockId, address: Address, data: Bytes) -> Result<Bytes, String> {
let transaction = self.contract_call_tx(block_id, address, data);
self.call(&transaction, block_id, Default::default())
self.call(&transaction, Default::default(), block_id)
.map_err(|e| format!("{:?}", e))
.map(|executed| {
executed.output

View File

@ -401,10 +401,18 @@ impl MiningBlockChainClient for TestBlockChainClient {
}
impl BlockChainClient for TestBlockChainClient {
fn call(&self, _t: &SignedTransaction, _block: BlockId, _analytics: CallAnalytics) -> Result<Executed, CallError> {
fn call(&self, _t: &SignedTransaction, _analytics: CallAnalytics, _block: BlockId) -> Result<Executed, CallError> {
self.execution_result.read().clone().unwrap()
}
fn call_many(&self, txs: &[(SignedTransaction, CallAnalytics)], block: BlockId) -> Result<Vec<Executed>, CallError> {
let mut res = Vec::with_capacity(txs.len());
for &(ref tx, analytics) in txs {
res.push(self.call(tx, analytics, block)?);
}
Ok(res)
}
fn estimate_gas(&self, _t: &SignedTransaction, _block: BlockId) -> Result<U256, CallError> {
Ok(21000.into())
}
@ -423,7 +431,7 @@ impl BlockChainClient for TestBlockChainClient {
fn nonce(&self, address: &Address, id: BlockId) -> Option<U256> {
match id {
BlockId::Latest => Some(self.nonces.read().get(address).cloned().unwrap_or(self.spec.params().account_start_nonce)),
BlockId::Latest | BlockId::Pending => Some(self.nonces.read().get(address).cloned().unwrap_or(self.spec.params().account_start_nonce)),
_ => None,
}
}
@ -438,16 +446,15 @@ impl BlockChainClient for TestBlockChainClient {
fn code(&self, address: &Address, id: BlockId) -> Option<Option<Bytes>> {
match id {
BlockId::Latest => Some(self.code.read().get(address).cloned()),
BlockId::Latest | BlockId::Pending => Some(self.code.read().get(address).cloned()),
_ => None,
}
}
fn balance(&self, address: &Address, id: BlockId) -> Option<U256> {
if let BlockId::Latest = id {
Some(self.balances.read().get(address).cloned().unwrap_or_else(U256::zero))
} else {
None
match id {
BlockId::Latest | BlockId::Pending => Some(self.balances.read().get(address).cloned().unwrap_or_else(U256::zero)),
_ => None,
}
}
@ -456,10 +463,9 @@ impl BlockChainClient for TestBlockChainClient {
}
fn storage_at(&self, address: &Address, position: &H256, id: BlockId) -> Option<H256> {
if let BlockId::Latest = id {
Some(self.storage.read().get(&(address.clone(), position.clone())).cloned().unwrap_or_else(H256::new))
} else {
None
match id {
BlockId::Latest | BlockId::Pending => Some(self.storage.read().get(&(address.clone(), position.clone())).cloned().unwrap_or_else(H256::new)),
_ => None,
}
}
@ -548,7 +554,8 @@ impl BlockChainClient for TestBlockChainClient {
match id {
BlockId::Number(number) if (number as usize) < self.blocks.read().len() => BlockStatus::InChain,
BlockId::Hash(ref hash) if self.blocks.read().get(hash).is_some() => BlockStatus::InChain,
BlockId::Latest | BlockId::Pending | BlockId::Earliest => BlockStatus::InChain,
BlockId::Latest | BlockId::Earliest => BlockStatus::InChain,
BlockId::Pending => BlockStatus::Pending,
_ => BlockStatus::Unknown,
}
}

View File

@ -182,7 +182,11 @@ pub trait BlockChainClient : Sync + Send {
fn logs(&self, filter: Filter) -> Vec<LocalizedLogEntry>;
/// Makes a non-persistent transaction call.
fn call(&self, t: &SignedTransaction, block: BlockId, analytics: CallAnalytics) -> Result<Executed, CallError>;
fn call(&self, tx: &SignedTransaction, analytics: CallAnalytics, block: BlockId) -> Result<Executed, CallError>;
/// Makes multiple non-persistent but dependent transaction calls.
/// Returns a vector of successes or a failure if any of the transaction fails.
fn call_many(&self, txs: &[(SignedTransaction, CallAnalytics)], block: BlockId) -> Result<Vec<Executed>, CallError>;
/// Estimates how much gas will be necessary for a call.
fn estimate_gas(&self, t: &SignedTransaction, block: BlockId) -> Result<U256, CallError>;

View File

@ -1050,7 +1050,7 @@ mod tests {
client.miner().import_own_transaction(client.as_ref(), transaction.into()).unwrap();
// Propose
let proposal = Some(client.miner().pending_block().unwrap().header.bare_hash());
let proposal = Some(client.miner().pending_block(0).unwrap().header.bare_hash());
// Propose timeout
engine.step();

View File

@ -21,8 +21,8 @@ use std::sync::Arc;
use util::*;
use using_queue::{UsingQueue, GetAction};
use account_provider::{AccountProvider, SignError as AccountError};
use state::{State, CleanupMode};
use client::{MiningBlockChainClient, Executive, Executed, EnvInfo, TransactOptions, BlockId, CallAnalytics, TransactionId};
use state::State;
use client::{MiningBlockChainClient, BlockId, TransactionId};
use client::TransactionImportResult;
use executive::contract_address;
use block::{ClosedBlock, IsBlock, Block};
@ -39,7 +39,7 @@ use miner::local_transactions::{Status as LocalTransactionStatus};
use miner::service_transaction_checker::ServiceTransactionChecker;
use price_info::{Client as PriceInfoClient, PriceInfo};
use price_info::fetch::Client as FetchClient;
use header::BlockNumber;
use header::{Header, BlockNumber};
/// Different possible definitions for pending transaction set.
#[derive(Debug, PartialEq)]
@ -331,13 +331,28 @@ impl Miner {
}
/// Get `Some` `clone()` of the current pending block's state or `None` if we're not sealing.
pub fn pending_state(&self) -> Option<State<::state_db::StateDB>> {
self.sealing_work.lock().queue.peek_last_ref().map(|b| b.block().fields().state.clone())
pub fn pending_state(&self, latest_block_number: BlockNumber) -> Option<State<::state_db::StateDB>> {
self.map_pending_block(|b| b.state().clone(), latest_block_number)
}
/// Get `Some` `clone()` of the current pending block or `None` if we're not sealing.
pub fn pending_block(&self) -> Option<Block> {
self.sealing_work.lock().queue.peek_last_ref().map(|b| b.to_base())
pub fn pending_block(&self, latest_block_number: BlockNumber) -> Option<Block> {
self.map_pending_block(|b| b.to_base(), latest_block_number)
}
/// Get `Some` `clone()` of the current pending block header or `None` if we're not sealing.
pub fn pending_block_header(&self, latest_block_number: BlockNumber) -> Option<Header> {
self.map_pending_block(|b| b.header().clone(), latest_block_number)
}
fn map_pending_block<F, T>(&self, f: F, latest_block_number: BlockNumber) -> Option<T> where
F: FnOnce(&ClosedBlock) -> T,
{
self.from_pending_block(
latest_block_number,
|| None,
|block| Some(f(block)),
)
}
#[cfg_attr(feature="dev", allow(match_same_arms))]
@ -679,7 +694,7 @@ impl Miner {
#[cfg_attr(feature="dev", allow(wrong_self_convention))]
#[cfg_attr(feature="dev", allow(redundant_closure))]
fn from_pending_block<H, F, G>(&self, latest_block_number: BlockNumber, from_chain: F, map_block: G) -> H
where F: Fn() -> H, G: Fn(&ClosedBlock) -> H {
where F: Fn() -> H, G: FnOnce(&ClosedBlock) -> H {
let sealing_work = self.sealing_work.lock();
sealing_work.queue.peek_last_ref().map_or_else(
|| from_chain(),
@ -717,84 +732,6 @@ impl MinerService for Miner {
}
}
fn call(&self, client: &MiningBlockChainClient, t: &SignedTransaction, analytics: CallAnalytics) -> Result<Executed, CallError> {
let sealing_work = self.sealing_work.lock();
match sealing_work.queue.peek_last_ref() {
Some(work) => {
let block = work.block();
// TODO: merge this code with client.rs's fn call somwhow.
let header = block.header();
let last_hashes = Arc::new(client.last_hashes());
let env_info = EnvInfo {
number: header.number(),
author: *header.author(),
timestamp: header.timestamp(),
difficulty: *header.difficulty(),
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 original_state = if analytics.state_diffing { Some(state.clone()) } else { None };
let sender = t.sender();
let balance = state.balance(&sender).map_err(ExecutionError::from)?;
let needed_balance = t.value + t.gas * t.gas_price;
if balance < needed_balance {
// give the sender a sufficient balance
state.add_balance(&sender, &(needed_balance - balance), CleanupMode::NoEmpty)
.map_err(ExecutionError::from)?;
}
let options = TransactOptions { tracing: analytics.transaction_tracing, vm_tracing: analytics.vm_tracing, check_nonce: false };
let mut ret = Executive::new(&mut state, &env_info, &*self.engine).transact(t, options)?;
// TODO gav move this into Executive.
if let Some(original) = original_state {
ret.state_diff = Some(state.diff_from(original).map_err(ExecutionError::from)?);
}
Ok(ret)
},
None => client.call(t, BlockId::Latest, analytics)
}
}
// TODO: The `chain.latest_x` actually aren't infallible, they just panic on corruption.
// TODO: return trie::Result<T> here, or other.
fn balance(&self, chain: &MiningBlockChainClient, address: &Address) -> Option<U256> {
self.from_pending_block(
chain.chain_info().best_block_number,
|| Some(chain.latest_balance(address)),
|b| b.block().fields().state.balance(address).ok(),
)
}
fn storage_at(&self, chain: &MiningBlockChainClient, address: &Address, position: &H256) -> Option<H256> {
self.from_pending_block(
chain.chain_info().best_block_number,
|| Some(chain.latest_storage_at(address, position)),
|b| b.block().fields().state.storage_at(address, position).ok(),
)
}
fn nonce(&self, chain: &MiningBlockChainClient, address: &Address) -> Option<U256> {
self.from_pending_block(
chain.chain_info().best_block_number,
|| Some(chain.latest_nonce(address)),
|b| b.block().fields().state.nonce(address).ok(),
)
}
fn code(&self, chain: &MiningBlockChainClient, address: &Address) -> Option<Option<Bytes>> {
self.from_pending_block(
chain.chain_info().best_block_number,
|| Some(chain.latest_code(address)),
|b| b.block().fields().state.code(address).ok().map(|c| c.map(|c| (&*c).clone()))
)
}
fn set_author(&self, author: Address) {
if self.engine.seals_internally().is_some() {
let mut sealing_work = self.sealing_work.lock();
@ -1466,14 +1403,14 @@ mod tests {
miner.update_sealing(&*client);
client.flush_queue();
assert!(miner.pending_block().is_none());
assert!(miner.pending_block(0).is_none());
assert_eq!(client.chain_info().best_block_number, 3 as BlockNumber);
assert_eq!(miner.import_own_transaction(&*client, PendingTransaction::new(transaction_with_network_id(spec.network_id()).into(), None)).unwrap(), TransactionImportResult::Current);
miner.update_sealing(&*client);
client.flush_queue();
assert!(miner.pending_block().is_none());
assert!(miner.pending_block(0).is_none());
assert_eq!(client.chain_info().best_block_number, 4 as BlockNumber);
}

View File

@ -62,12 +62,12 @@ pub use self::stratum::{Stratum, Error as StratumError, Options as StratumOption
use std::collections::BTreeMap;
use util::{H256, U256, Address, Bytes};
use client::{MiningBlockChainClient, Executed, CallAnalytics};
use client::{MiningBlockChainClient};
use block::ClosedBlock;
use header::BlockNumber;
use receipt::{RichReceipt, Receipt};
use error::{Error, CallError};
use transaction::{UnverifiedTransaction, PendingTransaction, SignedTransaction};
use error::{Error};
use transaction::{UnverifiedTransaction, PendingTransaction};
/// Miner client API
pub trait MinerService : Send + Sync {
@ -185,21 +185,6 @@ pub trait MinerService : Send + Sync {
/// Suggested gas limit.
fn sensible_gas_limit(&self) -> U256 { 21000.into() }
/// Latest account balance in pending state.
fn balance(&self, chain: &MiningBlockChainClient, address: &Address) -> Option<U256>;
/// Call into contract code using pending state.
fn call(&self, chain: &MiningBlockChainClient, t: &SignedTransaction, analytics: CallAnalytics) -> Result<Executed, CallError>;
/// Get storage value in pending state.
fn storage_at(&self, chain: &MiningBlockChainClient, address: &Address, position: &H256) -> Option<H256>;
/// Get account nonce in pending state.
fn nonce(&self, chain: &MiningBlockChainClient, address: &Address) -> Option<U256>;
/// Get contract code in pending state.
fn code(&self, chain: &MiningBlockChainClient, address: &Address) -> Option<Option<Bytes>>;
}
/// Mining status

View File

@ -23,6 +23,8 @@ pub enum BlockStatus {
Queued,
/// Known as bad.
Bad,
/// Pending block.
Pending,
/// Unknown.
Unknown,
}

View File

@ -264,6 +264,7 @@ fn check_known<C>(client: &C, number: BlockNumber) -> Result<(), Error> where C:
match client.block_status(number.into()) {
BlockStatus::InChain => Ok(()),
BlockStatus::Pending => Ok(()),
_ => Err(errors::unknown_block()),
}
}
@ -361,20 +362,12 @@ impl<C, SN: ?Sized, S: ?Sized, M, EM> Eth for EthClient<C, SN, S, M, EM> where
fn balance(&self, address: RpcH160, num: Trailing<BlockNumber>) -> BoxFuture<RpcU256, Error> {
let address = address.into();
let res = match num.unwrap_or_default() {
BlockNumber::Pending => {
match self.miner.balance(&*self.client, &address) {
Some(balance) => Ok(balance.into()),
None => Err(errors::database("latest balance missing"))
}
}
id => {
try_bf!(check_known(&*self.client, id.clone()));
match self.client.balance(&address, id.into()) {
Some(balance) => Ok(balance.into()),
None => Err(errors::state_pruned()),
}
}
let id = num.unwrap_or_default();
try_bf!(check_known(&*self.client, id.clone()));
let res = match self.client.balance(&address, id.into()) {
Some(balance) => Ok(balance.into()),
None => Err(errors::state_pruned()),
};
future::done(res).boxed()
@ -384,20 +377,12 @@ impl<C, SN: ?Sized, S: ?Sized, M, EM> Eth for EthClient<C, SN, S, M, EM> where
let address: Address = RpcH160::into(address);
let position: U256 = RpcU256::into(pos);
let res = match num.unwrap_or_default() {
BlockNumber::Pending => {
match self.miner.storage_at(&*self.client, &address, &H256::from(position)) {
Some(s) => Ok(s.into()),
None => Err(errors::database("latest storage missing"))
}
}
id => {
try_bf!(check_known(&*self.client, id.clone()));
match self.client.storage_at(&address, &H256::from(position), id.into()) {
Some(s) => Ok(s.into()),
None => Err(errors::state_pruned()),
}
}
let id = num.unwrap_or_default();
try_bf!(check_known(&*self.client, id.clone()));
let res = match self.client.storage_at(&address, &H256::from(position), id.into()) {
Some(s) => Ok(s.into()),
None => Err(errors::state_pruned()),
};
future::done(res).boxed()
@ -410,18 +395,12 @@ impl<C, SN: ?Sized, S: ?Sized, M, EM> Eth for EthClient<C, SN, S, M, EM> where
BlockNumber::Pending if self.options.pending_nonce_from_queue => {
let nonce = self.miner.last_nonce(&address)
.map(|n| n + 1.into())
.or_else(|| self.miner.nonce(&*self.client, &address));
.or_else(|| self.client.nonce(&address, BlockNumber::Pending.into()));
match nonce {
Some(nonce) => Ok(nonce.into()),
None => Err(errors::database("latest nonce missing"))
}
}
BlockNumber::Pending => {
match self.miner.nonce(&*self.client, &address) {
Some(nonce) => Ok(nonce.into()),
None => Err(errors::database("latest nonce missing"))
}
}
id => {
try_bf!(check_known(&*self.client, id.clone()));
match self.client.nonce(&address, id.into()) {
@ -468,20 +447,12 @@ impl<C, SN: ?Sized, S: ?Sized, M, EM> Eth for EthClient<C, SN, S, M, EM> where
fn code_at(&self, address: RpcH160, num: Trailing<BlockNumber>) -> BoxFuture<Bytes, Error> {
let address: Address = RpcH160::into(address);
let res = match num.unwrap_or_default() {
BlockNumber::Pending => {
match self.miner.code(&*self.client, &address) {
Some(code) => Ok(code.map_or_else(Bytes::default, Bytes::new)),
None => Err(errors::database("latest code missing"))
}
}
id => {
try_bf!(check_known(&*self.client, id.clone()));
match self.client.code(&address, id.into()) {
Some(code) => Ok(code.map_or_else(Bytes::default, Bytes::new)),
None => Err(errors::state_pruned()),
}
}
let id = num.unwrap_or_default();
try_bf!(check_known(&*self.client, id.clone()));
let res = match self.client.code(&address, id.into()) {
Some(code) => Ok(code.map_or_else(Bytes::default, Bytes::new)),
None => Err(errors::state_pruned()),
};
future::done(res).boxed()
@ -648,10 +619,8 @@ impl<C, SN: ?Sized, S: ?Sized, M, EM> Eth for EthClient<C, SN, S, M, EM> where
Err(e) => return future::err(e).boxed(),
};
let result = match num.unwrap_or_default() {
BlockNumber::Pending => self.miner.call(&*self.client, &signed, Default::default()),
num => self.client.call(&signed, num.into(), Default::default()),
};
let num = num.unwrap_or_default();
let result = self.client.call(&signed, Default::default(), num.into());
future::done(result
.map(|b| b.output.into())

View File

@ -39,7 +39,7 @@ use v1::helpers::light_fetch::LightFetch;
use v1::metadata::Metadata;
use v1::traits::Parity;
use v1::types::{
Bytes, U256, H160, H256, H512,
Bytes, U256, H160, H256, H512, CallRequest,
Peers, Transaction, RpcSettings, Histogram,
TransactionStats, LocalTransactionStatus,
BlockNumber, ConsensusCapability, VersionInfo,
@ -389,4 +389,8 @@ impl Parity for ParityClient {
fn ipfs_cid(&self, content: Bytes) -> Result<String, Error> {
ipfs::cid(content)
}
fn call(&self, _requests: Vec<CallRequest>, _block: Trailing<BlockNumber>) -> BoxFuture<Vec<Bytes>, Error> {
future::err(errors::light_unimplemented(None)).boxed()
}
}

View File

@ -20,7 +20,7 @@ use jsonrpc_core::Error;
use jsonrpc_macros::Trailing;
use v1::traits::Traces;
use v1::helpers::errors;
use v1::types::{TraceFilter, LocalizedTrace, BlockNumber, Index, CallRequest, Bytes, TraceResults, H256};
use v1::types::{TraceFilter, LocalizedTrace, BlockNumber, Index, CallRequest, Bytes, TraceResults, TraceOptions, H256};
/// Traces api implementation.
// TODO: all calling APIs should be possible w. proved remote TX execution.
@ -43,15 +43,19 @@ impl Traces for TracesClient {
Err(errors::light_unimplemented(None))
}
fn call(&self, _request: CallRequest, _flags: Vec<String>, _block: Trailing<BlockNumber>) -> Result<TraceResults, Error> {
fn call(&self, _request: CallRequest, _flags: TraceOptions, _block: Trailing<BlockNumber>) -> Result<TraceResults, Error> {
Err(errors::light_unimplemented(None))
}
fn raw_transaction(&self, _raw_transaction: Bytes, _flags: Vec<String>, _block: Trailing<BlockNumber>) -> Result<TraceResults, Error> {
fn call_many(&self, _request: Vec<(CallRequest, TraceOptions)>, _block: Trailing<BlockNumber>) -> Result<Vec<TraceResults>, Error> {
Err(errors::light_unimplemented(None))
}
fn replay_transaction(&self, _transaction_hash: H256, _flags: Vec<String>) -> Result<TraceResults, Error> {
fn raw_transaction(&self, _raw_transaction: Bytes, _flags: TraceOptions, _block: Trailing<BlockNumber>) -> Result<TraceResults, Error> {
Err(errors::light_unimplemented(None))
}
fn replay_transaction(&self, _transaction_hash: H256, _flags: TraceOptions) -> Result<TraceResults, Error> {
Err(errors::light_unimplemented(None))
}
}

View File

@ -28,22 +28,23 @@ use crypto::ecies;
use ethkey::{Brain, Generator};
use ethstore::random_phrase;
use ethsync::{SyncProvider, ManageNetwork};
use ethcore::account_provider::AccountProvider;
use ethcore::client::{MiningBlockChainClient};
use ethcore::ids::BlockId;
use ethcore::miner::MinerService;
use ethcore::client::{MiningBlockChainClient};
use ethcore::mode::Mode;
use ethcore::account_provider::AccountProvider;
use ethcore::transaction::SignedTransaction;
use updater::{Service as UpdateService};
use crypto::DEFAULT_MAC;
use jsonrpc_core::Error;
use jsonrpc_macros::Trailing;
use v1::helpers::{self, errors, ipfs, SigningQueue, SignerService, NetworkSettings};
use v1::helpers::{self, errors, fake_sign, ipfs, SigningQueue, SignerService, NetworkSettings};
use v1::helpers::accounts::unwrap_provider;
use v1::metadata::Metadata;
use v1::traits::Parity;
use v1::types::{
Bytes, U256, H160, H256, H512,
Bytes, U256, H160, H256, H512, CallRequest,
Peers, Transaction, RpcSettings, Histogram,
TransactionStats, LocalTransactionStatus,
BlockNumber, ConsensusCapability, VersionInfo,
@ -409,4 +410,23 @@ impl<C, M, S: ?Sized, U> Parity for ParityClient<C, M, S, U> where
fn ipfs_cid(&self, content: Bytes) -> Result<String, Error> {
ipfs::cid(content)
}
fn call(&self, requests: Vec<CallRequest>, block: Trailing<BlockNumber>) -> BoxFuture<Vec<Bytes>, Error> {
let requests: Result<Vec<(SignedTransaction, _)>, Error> = requests
.into_iter()
.map(|request| Ok((
fake_sign::sign_call(&self.client, &self.miner, request.into())?,
Default::default()
)))
.collect();
let block = block.unwrap_or_default();
let requests = try_bf!(requests);
let result = self.client.call_many(&requests, block.into())
.map(|res| res.into_iter().map(|res| res.output.into()).collect())
.map_err(errors::call);
future::done(result).boxed()
}
}

View File

@ -27,9 +27,9 @@ use jsonrpc_core::Error;
use jsonrpc_macros::Trailing;
use v1::traits::Traces;
use v1::helpers::{errors, fake_sign};
use v1::types::{TraceFilter, LocalizedTrace, BlockNumber, Index, CallRequest, Bytes, TraceResults, H256};
use v1::types::{TraceFilter, LocalizedTrace, BlockNumber, Index, CallRequest, Bytes, TraceResults, TraceOptions, H256};
fn to_call_analytics(flags: Vec<String>) -> CallAnalytics {
fn to_call_analytics(flags: TraceOptions) -> CallAnalytics {
CallAnalytics {
transaction_tracing: flags.contains(&("trace".to_owned())),
vm_tracing: flags.contains(&("vmTrace".to_owned())),
@ -79,29 +79,45 @@ impl<C, M> Traces for TracesClient<C, M> where C: MiningBlockChainClient + 'stat
.map(LocalizedTrace::from))
}
fn call(&self, request: CallRequest, flags: Vec<String>, block: Trailing<BlockNumber>) -> Result<TraceResults, Error> {
fn call(&self, request: CallRequest, flags: TraceOptions, block: Trailing<BlockNumber>) -> Result<TraceResults, Error> {
let block = block.unwrap_or_default();
let request = CallRequest::into(request);
let signed = fake_sign::sign_call(&self.client, &self.miner, request)?;
self.client.call(&signed, block.into(), to_call_analytics(flags))
self.client.call(&signed, to_call_analytics(flags), block.into())
.map(TraceResults::from)
.map_err(errors::call)
}
fn raw_transaction(&self, raw_transaction: Bytes, flags: Vec<String>, block: Trailing<BlockNumber>) -> Result<TraceResults, Error> {
fn call_many(&self, requests: Vec<(CallRequest, TraceOptions)>, block: Trailing<BlockNumber>) -> Result<Vec<TraceResults>, Error> {
let block = block.unwrap_or_default();
let requests = requests.into_iter()
.map(|(request, flags)| {
let request = CallRequest::into(request);
let signed = fake_sign::sign_call(&self.client, &self.miner, request)?;
Ok((signed, to_call_analytics(flags)))
})
.collect::<Result<Vec<_>, _>>()?;
self.client.call_many(&requests, block.into())
.map(|results| results.into_iter().map(TraceResults::from).collect())
.map_err(errors::call)
}
fn raw_transaction(&self, raw_transaction: Bytes, flags: TraceOptions, block: Trailing<BlockNumber>) -> Result<TraceResults, Error> {
let block = block.unwrap_or_default();
let tx = UntrustedRlp::new(&raw_transaction.into_vec()).as_val().map_err(|e| errors::invalid_params("Transaction is not valid RLP", e))?;
let signed = SignedTransaction::new(tx).map_err(errors::transaction)?;
self.client.call(&signed, block.into(), to_call_analytics(flags))
self.client.call(&signed, to_call_analytics(flags), block.into())
.map(TraceResults::from)
.map_err(errors::call)
}
fn replay_transaction(&self, transaction_hash: H256, flags: Vec<String>) -> Result<TraceResults, Error> {
fn replay_transaction(&self, transaction_hash: H256, flags: TraceOptions) -> Result<TraceResults, Error> {
self.client.replay(TransactionId::Hash(transaction_hash.into()), to_call_analytics(flags))
.map(TraceResults::from)
.map_err(errors::call)

View File

@ -19,9 +19,9 @@
use std::collections::{BTreeMap, HashMap};
use std::collections::hash_map::Entry;
use util::{Address, H256, Bytes, U256, RwLock, Mutex};
use ethcore::error::{Error, CallError};
use ethcore::client::{MiningBlockChainClient, Executed, CallAnalytics};
use ethcore::block::{ClosedBlock, IsBlock};
use ethcore::error::Error;
use ethcore::client::MiningBlockChainClient;
use ethcore::block::ClosedBlock;
use ethcore::header::BlockNumber;
use ethcore::transaction::{UnverifiedTransaction, SignedTransaction, PendingTransaction};
use ethcore::receipt::{Receipt, RichReceipt};
@ -280,41 +280,6 @@ impl MinerService for TestMinerService {
unimplemented!();
}
fn balance(&self, _chain: &MiningBlockChainClient, address: &Address) -> Option<U256> {
self.latest_closed_block.lock()
.as_ref()
.map(|b| b.block().fields().state.balance(address))
.map(|b| b.ok())
.unwrap_or(Some(U256::default()))
}
fn call(&self, _chain: &MiningBlockChainClient, _t: &SignedTransaction, _analytics: CallAnalytics) -> Result<Executed, CallError> {
unimplemented!();
}
fn storage_at(&self, _chain: &MiningBlockChainClient, address: &Address, position: &H256) -> Option<H256> {
self.latest_closed_block.lock()
.as_ref()
.map(|b| b.block().fields().state.storage_at(address, position))
.map(|s| s.ok())
.unwrap_or(Some(H256::default()))
}
fn nonce(&self, _chain: &MiningBlockChainClient, address: &Address) -> Option<U256> {
// we assume all transactions are in a pending block, ignoring the
// reality of gas limits.
Some(self.last_nonce(address).unwrap_or(U256::zero()))
}
fn code(&self, _chain: &MiningBlockChainClient, address: &Address) -> Option<Option<Bytes>> {
self.latest_closed_block.lock()
.as_ref()
.map(|b| b.block().fields().state.code(address))
.map(|c| c.ok())
.unwrap_or(None)
.map(|c| c.map(|c| (&*c).clone()))
}
fn sensible_gas_price(&self) -> U256 {
20000000000u64.into()
}

View File

@ -432,10 +432,7 @@ fn rpc_eth_balance_pending() {
"id": 1
}"#;
// the TestMinerService doesn't communicate with the the TestBlockChainClient in any way.
// if this returns zero, we know that the "pending" call is being properly forwarded to the
// miner.
let response = r#"{"jsonrpc":"2.0","result":"0x0","id":1}"#;
let response = r#"{"jsonrpc":"2.0","result":"0x5","id":1}"#;
assert_eq!(tester.io.handle_request_sync(request), Some(response.to_owned()));
}

View File

@ -16,10 +16,10 @@
use std::sync::Arc;
use ethcore_logger::RotatingLogger;
use util::Address;
use util::{Address, U256};
use ethsync::ManageNetwork;
use ethcore::account_provider::AccountProvider;
use ethcore::client::{TestBlockChainClient};
use ethcore::client::{TestBlockChainClient, Executed};
use ethcore::miner::LocalTransactionStatus;
use ethstore::ethkey::{Generator, Random};
@ -504,3 +504,40 @@ fn rpc_parity_cid() {
assert_eq!(io.handle_request_sync(request), Some(response.to_owned()));
}
#[test]
fn rpc_parity_call() {
let deps = Dependencies::new();
deps.client.set_execution_result(Ok(Executed {
exception: None,
gas: U256::zero(),
gas_used: U256::from(0xff30),
refunded: U256::from(0x5),
cumulative_gas_used: U256::zero(),
logs: vec![],
contracts_created: vec![],
output: vec![0x12, 0x34, 0xff],
trace: vec![],
vm_trace: None,
state_diff: None,
}));
let io = deps.default_client();
let request = r#"{
"jsonrpc": "2.0",
"method": "parity_call",
"params": [[{
"from": "0xb60e8dd61c5d32be8058bb8eb970870f07233155",
"to": "0xd46e8dd67c5d32be8058bb8eb970870f07244567",
"gas": "0x76c0",
"gasPrice": "0x9184e72a000",
"value": "0x9184e72a",
"data": "0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675"
}],
"latest"],
"id": 1
}"#;
let response = r#"{"jsonrpc":"2.0","result":["0x1234ff"],"id":1}"#;
assert_eq!(io.handle_request_sync(request), Some(response.to_owned()));
}

View File

@ -170,6 +170,16 @@ fn rpc_trace_call() {
assert_eq!(tester.io.handle_request_sync(request), Some(response.to_owned()));
}
#[test]
fn rpc_trace_multi_call() {
let tester = io();
let request = r#"{"jsonrpc":"2.0","method":"trace_callMany","params":[[[{}, ["stateDiff", "vmTrace", "trace"]]]],"id":1}"#;
let response = r#"{"jsonrpc":"2.0","result":[{"output":"0x010203","stateDiff":null,"trace":[],"vmTrace":null}],"id":1}"#;
assert_eq!(tester.io.handle_request_sync(request), Some(response.to_owned()));
}
#[test]
fn rpc_trace_call_state_pruned() {
let tester = io();

View File

@ -23,7 +23,7 @@ use jsonrpc_macros::Trailing;
use futures::BoxFuture;
use v1::types::{
H160, H256, H512, U256, Bytes,
H160, H256, H512, U256, Bytes, CallRequest,
Peers, Transaction, RpcSettings, Histogram,
TransactionStats, LocalTransactionStatus,
BlockNumber, ConsensusCapability, VersionInfo,
@ -203,5 +203,9 @@ build_rpc_trait! {
/// Get IPFS CIDv0 given protobuf encoded bytes.
#[rpc(name = "parity_cidV0")]
fn ipfs_cid(&self, Bytes) -> Result<String, Error>;
/// Call contract, returning the output data.
#[rpc(async, name = "parity_call")]
fn call(&self, Vec<CallRequest>, Trailing<BlockNumber>) -> BoxFuture<Vec<Bytes>, Error>;
}
}

View File

@ -18,7 +18,7 @@
use jsonrpc_core::Error;
use jsonrpc_macros::Trailing;
use v1::types::{TraceFilter, LocalizedTrace, BlockNumber, Index, CallRequest, Bytes, TraceResults, H256};
use v1::types::{TraceFilter, LocalizedTrace, BlockNumber, Index, CallRequest, Bytes, TraceResults, H256, TraceOptions};
build_rpc_trait! {
/// Traces specific rpc interface.
@ -41,14 +41,18 @@ build_rpc_trait! {
/// Executes the given call and returns a number of possible traces for it.
#[rpc(name = "trace_call")]
fn call(&self, CallRequest, Vec<String>, Trailing<BlockNumber>) -> Result<TraceResults, Error>;
fn call(&self, CallRequest, TraceOptions, Trailing<BlockNumber>) -> Result<TraceResults, Error>;
/// Executes all given calls and returns a number of possible traces for each of it.
#[rpc(name = "trace_callMany")]
fn call_many(&self, Vec<(CallRequest, TraceOptions)>, Trailing<BlockNumber>) -> Result<Vec<TraceResults>, Error>;
/// Executes the given raw transaction and returns a number of possible traces for it.
#[rpc(name = "trace_rawTransaction")]
fn raw_transaction(&self, Bytes, Vec<String>, Trailing<BlockNumber>) -> Result<TraceResults, Error>;
fn raw_transaction(&self, Bytes, TraceOptions, Trailing<BlockNumber>) -> Result<TraceResults, Error>;
/// Executes the transaction with the given hash and returns a number of possible traces for it.
#[rpc(name = "trace_replayTransaction")]
fn replay_transaction(&self, H256, Vec<String>) -> Result<TraceResults, Error>;
fn replay_transaction(&self, H256, TraceOptions) -> Result<TraceResults, Error>;
}
}

View File

@ -78,3 +78,7 @@ pub use self::transaction_request::TransactionRequest;
pub use self::transaction_condition::TransactionCondition;
pub use self::uint::{U128, U256};
pub use self::work::Work;
// TODO [ToDr] Refactor to a proper type Vec of enums?
/// Expected tracing type.
pub type TraceOptions = Vec<String>;

View File

@ -19,7 +19,7 @@ use std::time::Duration;
use futures::{Future, Select, BoxFuture, Poll, Async};
use tokio_core::reactor::{Handle, Timeout};
type DeadlineBox<F> where F: Future = BoxFuture<DeadlineStatus<F::Item>, F::Error>;
type DeadlineBox<F> = BoxFuture<DeadlineStatus<<F as Future>::Item>, <F as Future>::Error>;
/// Complete a passed future or fail if it is not completed within timeout.
pub fn deadline<F, T>(duration: Duration, handle: &Handle, future: F) -> Result<Deadline<F>, io::Error>
@ -82,4 +82,4 @@ mod tests {
core.turn(Some(Duration::from_millis(3)));
assert_eq!(deadline.wait().unwrap(), DeadlineStatus::Meet(()));
}
}
}

View File

@ -267,7 +267,7 @@ impl BlockDownloader {
BlockStatus::Bad => {
return Err(BlockDownloaderImportError::Invalid);
},
BlockStatus::Unknown => {
BlockStatus::Unknown | BlockStatus::Pending => {
headers.push(hdr.as_raw().to_vec());
hashes.push(hash);
}

View File

@ -995,7 +995,7 @@ impl ChainSync {
BlockStatus::Queued => {
trace!(target: "sync", "New hash block already queued {:?}", hash);
},
BlockStatus::Unknown => {
BlockStatus::Unknown | BlockStatus::Pending => {
new_hashes.push(hash.clone());
if number > max_height {
trace!(target: "sync", "New unknown block hash {:?}", hash);