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 { 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 header = self.block_header(BlockID::Latest).unwrap();
let view = HeaderView::new(&header); let view = HeaderView::new(&header);
let last_hashes = self.build_last_hashes(view.hash()); 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 // give the sender max balance
state.sub_balance(&sender, &balance); state.sub_balance(&sender, &balance);
state.add_balance(&sender, &U256::max_value()); 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) 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>; fn try_seal(&self, block: LockedBlock, seal: Vec<Bytes>) -> Result<SealedBlock, LockedBlock>;
/// Makes a non-persistent transaction call. /// 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. /// Returns EvmFactory.
fn vm_factory(&self) -> &EvmFactory; fn vm_factory(&self) -> &EvmFactory;

View File

@ -233,7 +233,7 @@ impl TestBlockChainClient {
} }
impl BlockChainClient for 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()) 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 { pub struct TransactOptions {
/// Enable call tracing. /// Enable call tracing.
pub tracing: bool, pub tracing: bool,
/// Enable VM tracing.
pub vm_tracing: bool,
/// Check transaction nonce before execution. /// Check transaction nonce before execution.
pub check_nonce: bool, pub check_nonce: bool,
} }
@ -394,6 +396,7 @@ impl<'a> Executive<'a> {
contracts_created: vec![], contracts_created: vec![],
output: output, output: output,
trace: trace, trace: trace,
vm_trace: None,
}) })
}, },
_ => { _ => {
@ -406,6 +409,7 @@ impl<'a> Executive<'a> {
contracts_created: substate.contracts_created, contracts_created: substate.contracts_created,
output: output, output: output,
trace: trace, trace: trace,
vm_trace: None,
}) })
}, },
} }
@ -913,7 +917,7 @@ mod tests {
let executed = { let executed = {
let mut ex = Executive::new(&mut state, &info, &engine, &factory); 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() ex.transact(&t, opts).unwrap()
}; };
@ -947,7 +951,7 @@ mod tests {
let res = { let res = {
let mut ex = Executive::new(&mut state, &info, &engine, &factory); 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) ex.transact(&t, opts)
}; };
@ -979,7 +983,7 @@ mod tests {
let res = { let res = {
let mut ex = Executive::new(&mut state, &info, &engine, &factory); 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) ex.transact(&t, opts)
}; };
@ -1013,7 +1017,7 @@ mod tests {
let res = { let res = {
let mut ex = Executive::new(&mut state, &info, &engine, &factory); 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) ex.transact(&t, opts)
}; };
@ -1047,7 +1051,7 @@ mod tests {
let res = { let res = {
let mut ex = Executive::new(&mut state, &info, &engine, &factory); 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) 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 { pub fn apply(&mut self, env_info: &EnvInfo, engine: &Engine, vm_factory: &EvmFactory, t: &SignedTransaction, tracing: bool) -> ApplyResult {
// let old = self.to_pod(); // 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)); let e = try!(Executive::new(self, env_info, engine, vm_factory).transact(t, options));
// TODO uncomment once to_pod() works correctly. // 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::config::{Config, Switch};
pub use self::db::TraceDB; pub use self::db::TraceDB;
pub use self::error::Error; 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::noop_tracer::NoopTracer;
pub use self::executive_tracer::ExecutiveTracer; pub use self::executive_tracer::ExecutiveTracer;
pub use types::trace_types::filter::{Filter, AddressesFilter}; pub use types::trace_types::filter::{Filter, AddressesFilter};

View File

@ -18,7 +18,7 @@
use util::numbers::*; use util::numbers::*;
use util::Bytes; use util::Bytes;
use trace::Trace; use trace::{Trace, VMTrace};
use types::log_entry::LogEntry; use types::log_entry::LogEntry;
use ipc::binary::BinaryConvertError; use ipc::binary::BinaryConvertError;
use std::fmt; use std::fmt;
@ -59,6 +59,8 @@ pub struct Executed {
pub output: Bytes, pub output: Bytes,
/// The trace of this transaction. /// The trace of this transaction.
pub trace: Option<Trace>, pub trace: Option<Trace>,
/// The VM trace of this transaction.
pub vm_trace: Option<VMTrace>,
} }
/// Result of executing the transaction. /// 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)] #[cfg(test)]
mod tests { mod tests {
use util::{Address, U256, FixedHash}; use util::{Address, U256, FixedHash};

View File

@ -158,7 +158,7 @@ pub trait MinerService : Send + Sync {
fn balance(&self, chain: &BlockChainClient, address: &Address) -> U256; fn balance(&self, chain: &BlockChainClient, address: &Address) -> U256;
/// Call into contract code using pending state. /// 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. /// Get storage value in pending state.
fn storage_at(&self, chain: &BlockChainClient, address: &Address, position: &H256) -> H256; 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(); let sealing_work = self.sealing_work.lock().unwrap();
match sealing_work.peek_last_ref() { match sealing_work.peek_last_ref() {
Some(work) => { Some(work) => {
@ -278,12 +278,13 @@ impl MinerService for Miner {
// give the sender max balance // give the sender max balance
state.sub_balance(&sender, &balance); state.sub_balance(&sender, &balance);
state.add_balance(&sender, &U256::max_value()); 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) Executive::new(&mut state, &env_info, self.engine(), chain.vm_factory()).transact(t, options)
}, },
None => { 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(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(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(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 { let start_result = match auth {
None => { None => {

View File

@ -110,7 +110,7 @@ fn setup_rpc_server(apis: Vec<&str>, deps: &Arc<Dependencies>) -> Server {
}, },
"ethcore" => { "ethcore" => {
modules.insert("ethcore".to_owned(), "1.0".to_owned()); 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" => { "traces" => {
modules.insert("traces".to_owned(), "1.0".to_owned()); modules.insert("traces".to_owned(), "1.0".to_owned());

View File

@ -26,6 +26,7 @@ extern crate serde;
extern crate serde_json; extern crate serde_json;
extern crate jsonrpc_core; extern crate jsonrpc_core;
extern crate jsonrpc_http_server; extern crate jsonrpc_http_server;
#[macro_use]
extern crate ethcore_util as util; extern crate ethcore_util as util;
extern crate ethcore; extern crate ethcore;
extern crate ethsync; 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,)| { .and_then(|(request, block_number,)| {
let signed = try!(self.sign_call(request)); let signed = try!(self.sign_call(request));
let r = match block_number { let r = match block_number {
BlockNumber::Pending => take_weak!(self.miner).call(take_weak!(self.client).deref(), &signed), BlockNumber::Pending => take_weak!(self.miner).call(take_weak!(self.client).deref(), &signed, false),
BlockNumber::Latest => take_weak!(self.client).call(&signed), BlockNumber::Latest => take_weak!(self.client).call(&signed, false),
_ => panic!("{:?}", block_number), _ => panic!("{:?}", block_number),
}; };
to_value(&r.map(|e| Bytes(e.output)).unwrap_or(Bytes::new(vec![]))) 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,)| { .and_then(|(request, block_number,)| {
let signed = try!(self.sign_call(request)); let signed = try!(self.sign_call(request));
let r = match block_number { let r = match block_number {
BlockNumber::Pending => take_weak!(self.miner).call(take_weak!(self.client).deref(), &signed), BlockNumber::Pending => take_weak!(self.miner).call(take_weak!(self.client).deref(), &signed, false),
BlockNumber::Latest => take_weak!(self.client).call(&signed), BlockNumber::Latest => take_weak!(self.client).call(&signed, false),
_ => return Err(Error::invalid_params()), _ => return Err(Error::invalid_params()),
}; };
to_value(&r.map(|res| res.gas_used + res.refunded).unwrap_or(From::from(0))) 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/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
//! Ethcore-specific rpc implementation. //! Ethcore-specific rpc implementation.
use util::{U256, Address, RotatingLogger}; use util::{U256, Address, RotatingLogger, FixedHash, Uint};
use util::network_settings::NetworkSettings; use util::network_settings::NetworkSettings;
use util::misc::version_data; use util::misc::version_data;
use std::sync::{Arc, Weak}; use std::sync::{Arc, Weak};
@ -23,29 +23,50 @@ use std::ops::Deref;
use std::collections::BTreeMap; use std::collections::BTreeMap;
use jsonrpc_core::*; use jsonrpc_core::*;
use ethminer::{MinerService}; use ethminer::{MinerService};
use ethcore::client::{BlockChainClient};
use ethcore::transaction::{Transaction as EthTransaction, SignedTransaction, Action};
use v1::traits::Ethcore; use v1::traits::Ethcore;
use v1::types::Bytes; use v1::types::{Bytes, CallRequest};
/// Ethcore implementation. /// Ethcore implementation.
pub struct EthcoreClient<M> pub struct EthcoreClient<C, M> where
where M: MinerService { C: BlockChainClient,
M: MinerService {
client: Weak<C>,
miner: Weak<M>, miner: Weak<M>,
logger: Arc<RotatingLogger>, logger: Arc<RotatingLogger>,
settings: Arc<NetworkSettings>, settings: Arc<NetworkSettings>,
} }
impl<M> EthcoreClient<M> where M: MinerService { impl<C, M> EthcoreClient<C, M> where C: BlockChainClient, M: MinerService {
/// Creates new `EthcoreClient`. /// 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 { EthcoreClient {
client: Arc::downgrade(client),
miner: Arc::downgrade(miner), miner: Arc::downgrade(miner),
logger: logger, logger: logger,
settings: settings, 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> { fn set_min_gas_price(&self, params: Params) -> Result<Value, Error> {
from_params::<(U256,)>(params).and_then(|(gas_price,)| { 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(); let version = version_data();
to_value(&Bytes::new(version)) 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()) 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!(); unimplemented!();
} }

View File

@ -72,6 +72,8 @@ pub trait Ethcore: Sized + Send + Sync + 'static {
/// Returns default extra data /// Returns default extra data
fn default_extra_data(&self, _: Params) -> Result<Value, Error> { rpc_unimplemented!() } 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. /// Should be used to convert object to io delegate.
fn to_delegate(self) -> IoDelegate<Self> { 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_nodeName", Ethcore::node_name);
delegate.add_method("ethcore_defaultExtraData", Ethcore::default_extra_data); delegate.add_method("ethcore_defaultExtraData", Ethcore::default_extra_data);
delegate.add_method("ethcore_vmTraceCall", Ethcore::vm_trace_call);
delegate delegate
} }
} }