RPC endpoint for VM tracing and ser/de types ready.

This commit is contained in:
Gav Wood 2016-05-28 16:52:33 +02:00
parent 42e4c2d51c
commit c1ed520de0
17 changed files with 167 additions and 30 deletions

View File

@ -421,7 +421,7 @@ impl<V> Client<V> where V: Verifier {
}
impl<V> BlockChainClient for Client<V> where V: Verifier {
fn call(&self, t: &SignedTransaction) -> Result<Executed, ExecutionError> {
fn call(&self, t: &SignedTransaction, vm_tracing: bool) -> Result<Executed, ExecutionError> {
let header = self.block_header(BlockID::Latest).unwrap();
let view = HeaderView::new(&header);
let last_hashes = self.build_last_hashes(view.hash());
@ -444,7 +444,7 @@ impl<V> BlockChainClient for Client<V> where V: Verifier {
// 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 };
let options = TransactOptions { tracing: false, vm_tracing: vm_tracing, check_nonce: false };
Executive::new(&mut state, &env_info, self.engine.deref().deref(), &self.vm_factory).transact(t, options)
}

View File

@ -164,7 +164,8 @@ pub trait BlockChainClient : Sync + Send {
fn try_seal(&self, block: LockedBlock, seal: Vec<Bytes>) -> Result<SealedBlock, LockedBlock>;
/// Makes a non-persistent transaction call.
fn call(&self, t: &SignedTransaction) -> Result<Executed, ExecutionError>;
// TODO: should be able to accept blockchain location for call.
fn call(&self, t: &SignedTransaction, vm_tracing: bool) -> Result<Executed, ExecutionError>;
/// Returns EvmFactory.
fn vm_factory(&self) -> &EvmFactory;

View File

@ -233,7 +233,7 @@ impl TestBlockChainClient {
}
impl BlockChainClient for TestBlockChainClient {
fn call(&self, _t: &SignedTransaction) -> Result<Executed, ExecutionError> {
fn call(&self, _t: &SignedTransaction, _vm_tracing: bool) -> Result<Executed, ExecutionError> {
Ok(self.execution_result.read().unwrap().clone().unwrap())
}

View File

@ -43,6 +43,8 @@ pub fn contract_address(address: &Address, nonce: &U256) -> Address {
pub struct TransactOptions {
/// Enable call tracing.
pub tracing: bool,
/// Enable VM tracing.
pub vm_tracing: bool,
/// Check transaction nonce before execution.
pub check_nonce: bool,
}
@ -394,6 +396,7 @@ impl<'a> Executive<'a> {
contracts_created: vec![],
output: output,
trace: trace,
vm_trace: None,
})
},
_ => {
@ -406,6 +409,7 @@ impl<'a> Executive<'a> {
contracts_created: substate.contracts_created,
output: output,
trace: trace,
vm_trace: None,
})
},
}
@ -913,7 +917,7 @@ mod tests {
let executed = {
let mut ex = Executive::new(&mut state, &info, &engine, &factory);
let opts = TransactOptions { check_nonce: true, tracing: false };
let opts = TransactOptions { check_nonce: true, tracing: false, vm_tracing: false };
ex.transact(&t, opts).unwrap()
};
@ -947,7 +951,7 @@ mod tests {
let res = {
let mut ex = Executive::new(&mut state, &info, &engine, &factory);
let opts = TransactOptions { check_nonce: true, tracing: false };
let opts = TransactOptions { check_nonce: true, tracing: false, vm_tracing: false };
ex.transact(&t, opts)
};
@ -979,7 +983,7 @@ mod tests {
let res = {
let mut ex = Executive::new(&mut state, &info, &engine, &factory);
let opts = TransactOptions { check_nonce: true, tracing: false };
let opts = TransactOptions { check_nonce: true, tracing: false, vm_tracing: false };
ex.transact(&t, opts)
};
@ -1013,7 +1017,7 @@ mod tests {
let res = {
let mut ex = Executive::new(&mut state, &info, &engine, &factory);
let opts = TransactOptions { check_nonce: true, tracing: false };
let opts = TransactOptions { check_nonce: true, tracing: false, vm_tracing: false };
ex.transact(&t, opts)
};
@ -1047,7 +1051,7 @@ mod tests {
let res = {
let mut ex = Executive::new(&mut state, &info, &engine, &factory);
let opts = TransactOptions { check_nonce: true, tracing: false };
let opts = TransactOptions { check_nonce: true, tracing: false, vm_tracing: false };
ex.transact(&t, opts)
};

View File

@ -222,7 +222,7 @@ impl State {
pub fn apply(&mut self, env_info: &EnvInfo, engine: &Engine, vm_factory: &EvmFactory, t: &SignedTransaction, tracing: bool) -> ApplyResult {
// let old = self.to_pod();
let options = TransactOptions { tracing: tracing, check_nonce: true };
let options = TransactOptions { tracing: tracing, vm_tracing: false, check_nonce: true };
let e = try!(Executive::new(self, env_info, engine, vm_factory).transact(t, options));
// TODO uncomment once to_pod() works correctly.

View File

@ -31,7 +31,7 @@ pub use self::block::BlockTraces;
pub use self::config::{Config, Switch};
pub use self::db::TraceDB;
pub use self::error::Error;
pub use types::trace_types::trace::Trace;
pub use types::trace_types::trace::{Trace, VMTrace};
pub use self::noop_tracer::NoopTracer;
pub use self::executive_tracer::ExecutiveTracer;
pub use types::trace_types::filter::{Filter, AddressesFilter};

View File

@ -18,7 +18,7 @@
use util::numbers::*;
use util::Bytes;
use trace::Trace;
use trace::{Trace, VMTrace};
use types::log_entry::LogEntry;
use ipc::binary::BinaryConvertError;
use std::fmt;
@ -59,6 +59,8 @@ pub struct Executed {
pub output: Bytes,
/// The trace of this transaction.
pub trace: Option<Trace>,
/// The VM trace of this transaction.
pub vm_trace: Option<VMTrace>,
}
/// Result of executing the transaction.

View File

@ -349,6 +349,94 @@ impl Trace {
}
}
#[derive(Debug, Clone, PartialEq, Binary)]
/// A record of the execution of a single VM operation.
pub struct VMOperation {
/// The program counter.
pub pc: usize,
/// The instruction executed.
pub instruction: u8,
/// The gas cost for this instruction.
pub gas_cost: U256,
/// The total gas used.
pub gas_used: U256,
/// The stack.
pub stack: Vec<U256>,
/// Altered storage value. TODO: should be option.
// pub storage_diff: Option<(U256, U256)>,
/// If altered, the new memory image.
pub new_memory: Bytes,
}
impl Encodable for VMOperation {
fn rlp_append(&self, s: &mut RlpStream) {
s.begin_list(6);
s.append(&self.pc);
s.append(&self.instruction);
s.append(&self.gas_cost);
s.append(&self.gas_used);
s.append(&self.stack);
// s.append(&self.storage_diff);
s.append(&self.new_memory);
}
}
impl Decodable for VMOperation {
fn decode<D>(decoder: &D) -> Result<Self, DecoderError> where D: Decoder {
let d = decoder.as_rlp();
let res = VMOperation {
pc: try!(d.val_at(0)),
instruction: try!(d.val_at(1)),
gas_cost: try!(d.val_at(2)),
gas_used: try!(d.val_at(3)),
stack: try!(d.val_at(4)),
// storage_diff: try!(d.val_at(5)),
new_memory: try!(d.val_at(6)),
};
Ok(res)
}
}
#[derive(Debug, Clone, PartialEq, Binary)]
/// A record of a full VM trace for a CALL/CREATE.
pub struct VMTrace {
/// The number of EVM execution environments active when this action happened; 0 if it's
/// the outer action of the transaction.
pub depth: usize,
/// The code to be executed.
pub code: Bytes,
/// The operations executed.
pub operations: Vec<VMOperation>,
/// The sub traces for each interior action performed as part of this call/create.
/// Thre is a 1:1 correspondance between these and a CALL/CREATE/CALLCODE/DELEGATECALL instruction.
pub subs: Vec<VMTrace>,
}
impl Encodable for VMTrace {
fn rlp_append(&self, s: &mut RlpStream) {
s.begin_list(4);
s.append(&self.depth);
s.append(&self.code);
s.append(&self.operations);
s.append(&self.subs);
}
}
impl Decodable for VMTrace {
fn decode<D>(decoder: &D) -> Result<Self, DecoderError> where D: Decoder {
let d = decoder.as_rlp();
let res = VMTrace {
depth: try!(d.val_at(0)),
code: try!(d.val_at(1)),
operations: try!(d.val_at(2)),
subs: try!(d.val_at(3)),
};
Ok(res)
}
}
#[cfg(test)]
mod tests {
use util::{Address, U256, FixedHash};

View File

@ -158,7 +158,7 @@ pub trait MinerService : Send + Sync {
fn balance(&self, chain: &BlockChainClient, address: &Address) -> U256;
/// Call into contract code using pending state.
fn call(&self, chain: &BlockChainClient, t: &SignedTransaction) -> Result<Executed, ExecutionError>;
fn call(&self, chain: &BlockChainClient, t: &SignedTransaction, vm_tracing: bool) -> Result<Executed, ExecutionError>;
/// Get storage value in pending state.
fn storage_at(&self, chain: &BlockChainClient, address: &Address, position: &H256) -> H256;

View File

@ -252,7 +252,7 @@ impl MinerService for Miner {
}
}
fn call(&self, chain: &BlockChainClient, t: &SignedTransaction) -> Result<Executed, ExecutionError> {
fn call(&self, chain: &BlockChainClient, t: &SignedTransaction, vm_tracing: bool) -> Result<Executed, ExecutionError> {
let sealing_work = self.sealing_work.lock().unwrap();
match sealing_work.peek_last_ref() {
Some(work) => {
@ -278,12 +278,13 @@ impl MinerService for Miner {
// 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 };
let options = TransactOptions { tracing: false, vm_tracing: vm_tracing, check_nonce: false };
// TODO: use vm_trace here.
Executive::new(&mut state, &env_info, self.engine(), chain.vm_factory()).transact(t, options)
},
None => {
chain.call(t)
chain.call(t, vm_tracing)
}
}
}

View File

@ -101,7 +101,7 @@ pub fn setup_dapps_server(
server.add_delegate(EthClient::new(&deps.client, &deps.sync, &deps.secret_store, &deps.miner, &deps.external_miner).to_delegate());
server.add_delegate(EthFilterClient::new(&deps.client, &deps.miner).to_delegate());
server.add_delegate(PersonalClient::new(&deps.secret_store, &deps.client, &deps.miner).to_delegate());
server.add_delegate(EthcoreClient::new(&deps.miner, deps.logger.clone(), deps.settings.clone()).to_delegate());
server.add_delegate(EthcoreClient::new(&deps.client, &deps.miner, deps.logger.clone(), deps.settings.clone()).to_delegate());
let start_result = match auth {
None => {

View File

@ -110,7 +110,7 @@ fn setup_rpc_server(apis: Vec<&str>, deps: &Arc<Dependencies>) -> Server {
},
"ethcore" => {
modules.insert("ethcore".to_owned(), "1.0".to_owned());
server.add_delegate(EthcoreClient::new(&deps.miner, deps.logger.clone(), deps.settings.clone()).to_delegate())
server.add_delegate(EthcoreClient::new(&deps.client, &deps.miner, deps.logger.clone(), deps.settings.clone()).to_delegate())
},
"traces" => {
modules.insert("traces".to_owned(), "1.0".to_owned());

View File

@ -26,6 +26,7 @@ extern crate serde;
extern crate serde_json;
extern crate jsonrpc_core;
extern crate jsonrpc_http_server;
#[macro_use]
extern crate ethcore_util as util;
extern crate ethcore;
extern crate ethsync;

View File

@ -522,8 +522,8 @@ impl<C, S, A, M, EM> Eth for EthClient<C, S, A, M, EM> where
.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),
BlockNumber::Pending => take_weak!(self.miner).call(take_weak!(self.client).deref(), &signed, false),
BlockNumber::Latest => take_weak!(self.client).call(&signed, false),
_ => panic!("{:?}", block_number),
};
to_value(&r.map(|e| Bytes(e.output)).unwrap_or(Bytes::new(vec![])))
@ -535,8 +535,8 @@ impl<C, S, A, M, EM> Eth for EthClient<C, S, A, M, EM> where
.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),
BlockNumber::Pending => take_weak!(self.miner).call(take_weak!(self.client).deref(), &signed, false),
BlockNumber::Latest => take_weak!(self.client).call(&signed, false),
_ => return Err(Error::invalid_params()),
};
to_value(&r.map(|res| res.gas_used + res.refunded).unwrap_or(From::from(0)))

View File

@ -15,7 +15,7 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
//! Ethcore-specific rpc implementation.
use util::{U256, Address, RotatingLogger};
use util::{U256, Address, RotatingLogger, FixedHash, Uint};
use util::network_settings::NetworkSettings;
use util::misc::version_data;
use std::sync::{Arc, Weak};
@ -23,29 +23,50 @@ use std::ops::Deref;
use std::collections::BTreeMap;
use jsonrpc_core::*;
use ethminer::{MinerService};
use ethcore::client::{BlockChainClient};
use ethcore::transaction::{Transaction as EthTransaction, SignedTransaction, Action};
use v1::traits::Ethcore;
use v1::types::Bytes;
use v1::types::{Bytes, CallRequest};
/// Ethcore implementation.
pub struct EthcoreClient<M>
where M: MinerService {
pub struct EthcoreClient<C, M> where
C: BlockChainClient,
M: MinerService {
client: Weak<C>,
miner: Weak<M>,
logger: Arc<RotatingLogger>,
settings: Arc<NetworkSettings>,
}
impl<M> EthcoreClient<M> where M: MinerService {
impl<C, M> EthcoreClient<C, M> where C: BlockChainClient, M: MinerService {
/// Creates new `EthcoreClient`.
pub fn new(miner: &Arc<M>, logger: Arc<RotatingLogger>, settings: Arc<NetworkSettings>) -> Self {
pub fn new(client: &Arc<C>, miner: &Arc<M>, logger: Arc<RotatingLogger>, settings: Arc<NetworkSettings>) -> Self {
EthcoreClient {
client: Arc::downgrade(client),
miner: Arc::downgrade(miner),
logger: logger,
settings: settings,
}
}
// TODO: share with eth.rs
fn sign_call(&self, request: CallRequest) -> Result<SignedTransaction, Error> {
let client = take_weak!(self.client);
let miner = take_weak!(self.miner);
let from = request.from.unwrap_or(Address::zero());
Ok(EthTransaction {
nonce: request.nonce.unwrap_or_else(|| client.latest_nonce(&from)),
action: request.to.map_or(Action::Create, Action::Call),
gas: request.gas.unwrap_or(U256::from(50_000_000)),
gas_price: request.gas_price.unwrap_or_else(|| miner.sensible_gas_price()),
value: request.value.unwrap_or_else(U256::zero),
data: request.data.map_or_else(Vec::new, |d| d.to_vec())
}.fake_sign(from))
}
}
impl<M> Ethcore for EthcoreClient<M> where M: MinerService + 'static {
impl<C, M> Ethcore for EthcoreClient<C, M> where C: BlockChainClient + 'static, M: MinerService + 'static {
fn set_min_gas_price(&self, params: Params) -> Result<Value, Error> {
from_params::<(U256,)>(params).and_then(|(gas_price,)| {
@ -135,4 +156,19 @@ impl<M> Ethcore for EthcoreClient<M> where M: MinerService + 'static {
let version = version_data();
to_value(&Bytes::new(version))
}
//pub type VMTraceFunctionBox = Box<FnMut(usize, u8, U256, U256) + Send>;
fn vm_trace_call(&self, params: Params) -> Result<Value, Error> {
trace!(target: "jsonrpc", "vm_trace_call: {:?}", params);
from_params(params)
.and_then(|(request,)| {
let signed = try!(self.sign_call(request));
let _ = take_weak!(self.client).call(&signed, true);
// TODO: construct JSON trace from _.vm_trace.
let mut ret = Vec::new();
ret.push(Value::Object(map!["foo".to_owned() => Value::String("var".to_owned())]));
Ok(Value::Array(ret))
})
}
}

View File

@ -191,7 +191,7 @@ impl MinerService for TestMinerService {
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<Executed, ExecutionError> {
fn call(&self, _chain: &BlockChainClient, _t: &SignedTransaction, _vm_tracing: bool) -> Result<Executed, ExecutionError> {
unimplemented!();
}

View File

@ -72,6 +72,8 @@ pub trait Ethcore: Sized + Send + Sync + 'static {
/// Returns default extra data
fn default_extra_data(&self, _: Params) -> Result<Value, Error> { rpc_unimplemented!() }
/// Executes the given call and returns the VM trace for it.
fn vm_trace_call(&self, _: Params) -> Result<Value, Error> { rpc_unimplemented!() }
/// Should be used to convert object to io delegate.
fn to_delegate(self) -> IoDelegate<Self> {
@ -95,6 +97,8 @@ pub trait Ethcore: Sized + Send + Sync + 'static {
delegate.add_method("ethcore_nodeName", Ethcore::node_name);
delegate.add_method("ethcore_defaultExtraData", Ethcore::default_extra_data);
delegate.add_method("ethcore_vmTraceCall", Ethcore::vm_trace_call);
delegate
}
}