VM tracing and JSON RPC endpoint for it. (#1169)

* Groundwork for basic VM tracing.

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

* Create VMTracer trait.

* Rearchitected VM tracing to reflect existing tracing.

Should more or less work now.

* Integrated VM tracing into JSONRPC.

* Fix ethcore module tests.

* Add tests for VM tracing.

* Fix consensus test code.

* Fix mock tests.

* Added VM trace information for post-execution stuff.

* Fix max-value calls and add "creates" field to getTransaction.

* Tests for VM tracing.

* Don't implement the trait with unimplemented.

* Remove invlaid comment.

* Fix tests.
This commit is contained in:
Gav Wood
2016-06-02 12:40:31 +02:00
parent 7ad9c73c75
commit b17581d7de
36 changed files with 742 additions and 172 deletions

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

@@ -511,8 +511,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![])))
@@ -524,8 +524,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,99 @@ use std::ops::Deref;
use std::collections::BTreeMap;
use jsonrpc_core::*;
use ethcore::miner::MinerService;
use ethcore::transaction::{Transaction as EthTransaction, SignedTransaction, Action};
use ethcore::client::BlockChainClient;
use ethcore::trace::VMTrace;
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 {
fn vm_trace_to_object(t: &VMTrace) -> Value {
let mut ret = BTreeMap::new();
ret.insert("code".to_owned(), to_value(&t.code).unwrap());
let mut subs = t.subs.iter();
let mut next_sub = subs.next();
let ops = t.operations
.iter()
.enumerate()
.map(|(i, op)| {
let mut m = map![
"pc".to_owned() => to_value(&op.pc).unwrap(),
"cost".to_owned() => match op.gas_cost <= U256::from(!0u64) {
true => to_value(&op.gas_cost.low_u64()),
false => to_value(&op.gas_cost),
}.unwrap()
];
if let Some(ref ex) = op.executed {
let mut em = map![
"used".to_owned() => to_value(&ex.gas_used.low_u64()).unwrap(),
"push".to_owned() => to_value(&ex.stack_push).unwrap()
];
if let Some(ref md) = ex.mem_diff {
em.insert("mem".to_owned(), Value::Object(map![
"off".to_owned() => to_value(&md.offset).unwrap(),
"data".to_owned() => to_value(&md.data).unwrap()
]));
}
if let Some(ref sd) = ex.store_diff {
em.insert("store".to_owned(), Value::Object(map![
"key".to_owned() => to_value(&sd.location).unwrap(),
"val".to_owned() => to_value(&sd.value).unwrap()
]));
}
m.insert("ex".to_owned(), Value::Object(em));
}
if next_sub.is_some() && next_sub.unwrap().parent_step == i {
m.insert("sub".to_owned(), vm_trace_to_object(next_sub.unwrap()));
next_sub = subs.next();
}
Value::Object(m)
})
.collect::<Vec<_>>();
ret.insert("ops".to_owned(), Value::Array(ops));
Value::Object(ret)
}
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 +205,19 @@ impl<M> Ethcore for EthcoreClient<M> where M: MinerService + 'static {
let version = version_data();
to_value(&Bytes::new(version))
}
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 r = take_weak!(self.client).call(&signed, true);
if let Ok(executed) = r {
if let Some(vm_trace) = executed.vm_trace {
return Ok(vm_trace_to_object(&vm_trace));
}
}
Ok(Value::Null)
})
}
}

View File

@@ -19,13 +19,12 @@ use std::collections::HashMap;
use std::sync::Arc;
use std::str::FromStr;
use ethcore::client::{MiningBlockChainClient, BlockChainClient, Client, ClientConfig};
use ethcore::client::{BlockChainClient, Client, ClientConfig};
use ethcore::ids::BlockID;
use ethcore::spec::{Genesis, Spec};
use ethcore::block::Block;
use ethcore::views::BlockView;
use ethcore::ethereum;
use ethcore::transaction::{Transaction, Action};
use ethcore::miner::{MinerService, ExternalMiner, Miner};
use devtools::RandomTempPath;
use util::Hashable;

View File

@@ -202,7 +202,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: &MiningBlockChainClient, _t: &SignedTransaction) -> Result<Executed, ExecutionError> {
fn call(&self, _chain: &MiningBlockChainClient, _t: &SignedTransaction, _vm_tracing: bool) -> Result<Executed, ExecutionError> {
unimplemented!();
}

View File

@@ -365,7 +365,7 @@ fn rpc_eth_pending_transaction_by_hash() {
tester.miner.pending_transactions.lock().unwrap().insert(H256::zero(), tx);
}
let response = r#"{"jsonrpc":"2.0","result":{"blockHash":null,"blockNumber":null,"from":"0x0f65fe9276bc9a24ae7083ae28e2660ef72df99e","gas":"0x5208","gasPrice":"0x01","hash":"0x41df922fd0d4766fcc02e161f8295ec28522f329ae487f14d811e4b64c8d6e31","input":"0x","nonce":"0x00","to":"0x095e7baea6a6c7c4c2dfeb977efac326af552d87","transactionIndex":null,"value":"0x0a"},"id":1}"#;
let response = r#"{"jsonrpc":"2.0","result":{"blockHash":null,"blockNumber":null,"creates":null,"from":"0x0f65fe9276bc9a24ae7083ae28e2660ef72df99e","gas":"0x5208","gasPrice":"0x01","hash":"0x41df922fd0d4766fcc02e161f8295ec28522f329ae487f14d811e4b64c8d6e31","input":"0x","nonce":"0x00","to":"0x095e7baea6a6c7c4c2dfeb977efac326af552d87","transactionIndex":null,"value":"0x0a"},"id":1}"#;
let request = r#"{
"jsonrpc": "2.0",
"method": "eth_getTransactionByHash",
@@ -430,6 +430,7 @@ fn rpc_eth_call() {
contracts_created: vec![],
output: vec![0x12, 0x34, 0xff],
trace: None,
vm_trace: None,
});
let request = r#"{
@@ -463,6 +464,7 @@ fn rpc_eth_call_default_block() {
contracts_created: vec![],
output: vec![0x12, 0x34, 0xff],
trace: None,
vm_trace: None,
});
let request = r#"{
@@ -495,6 +497,7 @@ fn rpc_eth_estimate_gas() {
contracts_created: vec![],
output: vec![0x12, 0x34, 0xff],
trace: None,
vm_trace: None,
});
let request = r#"{
@@ -528,6 +531,7 @@ fn rpc_eth_estimate_gas_default_block() {
contracts_created: vec![],
output: vec![0x12, 0x34, 0xff],
trace: None,
vm_trace: None,
});
let request = r#"{

View File

@@ -18,6 +18,7 @@ use std::sync::Arc;
use std::str::FromStr;
use jsonrpc_core::IoHandler;
use v1::{Ethcore, EthcoreClient};
use ethcore::client::{TestBlockChainClient};
use ethcore::miner::MinerService;
use v1::tests::helpers::TestMinerService;
use util::numbers::*;
@@ -25,10 +26,15 @@ use rustc_serialize::hex::FromHex;
use util::log::RotatingLogger;
use util::network_settings::NetworkSettings;
fn blockchain_client() -> Arc<TestBlockChainClient> {
let client = TestBlockChainClient::new();
Arc::new(client)
}
fn miner_service() -> Arc<TestMinerService> {
Arc::new(TestMinerService::default())
}
fn logger() -> Arc<RotatingLogger> {
Arc::new(RotatingLogger::new("rpc=trace".to_owned()))
}
@@ -45,14 +51,15 @@ fn settings() -> Arc<NetworkSettings> {
})
}
fn ethcore_client(miner: &Arc<TestMinerService>) -> EthcoreClient<TestMinerService> {
EthcoreClient::new(&miner, logger(), settings())
fn ethcore_client(client: &Arc<TestBlockChainClient>, miner: &Arc<TestMinerService>) -> EthcoreClient<TestBlockChainClient, TestMinerService> {
EthcoreClient::new(&client, &miner, logger(), settings())
}
#[test]
fn rpc_ethcore_extra_data() {
let miner = miner_service();
let ethcore = ethcore_client(&miner).to_delegate();
let client = blockchain_client();
let ethcore = ethcore_client(&client, &miner).to_delegate();
let io = IoHandler::new();
io.add_delegate(ethcore);
@@ -68,7 +75,8 @@ fn rpc_ethcore_default_extra_data() {
use util::ToPretty;
let miner = miner_service();
let ethcore = ethcore_client(&miner).to_delegate();
let client = blockchain_client();
let ethcore = ethcore_client(&client, &miner).to_delegate();
let io = IoHandler::new();
io.add_delegate(ethcore);
@@ -81,7 +89,8 @@ fn rpc_ethcore_default_extra_data() {
#[test]
fn rpc_ethcore_gas_floor_target() {
let miner = miner_service();
let ethcore = ethcore_client(&miner).to_delegate();
let client = blockchain_client();
let ethcore = ethcore_client(&client, &miner).to_delegate();
let io = IoHandler::new();
io.add_delegate(ethcore);
@@ -94,7 +103,8 @@ fn rpc_ethcore_gas_floor_target() {
#[test]
fn rpc_ethcore_min_gas_price() {
let miner = miner_service();
let ethcore = ethcore_client(&miner).to_delegate();
let client = blockchain_client();
let ethcore = ethcore_client(&client, &miner).to_delegate();
let io = IoHandler::new();
io.add_delegate(ethcore);
@@ -107,7 +117,8 @@ fn rpc_ethcore_min_gas_price() {
#[test]
fn rpc_ethcore_set_min_gas_price() {
let miner = miner_service();
let ethcore = ethcore_client(&miner).to_delegate();
let client = blockchain_client();
let ethcore = ethcore_client(&client, &miner).to_delegate();
let io = IoHandler::new();
io.add_delegate(ethcore);
@@ -121,7 +132,8 @@ fn rpc_ethcore_set_min_gas_price() {
#[test]
fn rpc_ethcore_set_gas_floor_target() {
let miner = miner_service();
let ethcore = ethcore_client(&miner).to_delegate();
let client = blockchain_client();
let ethcore = ethcore_client(&client, &miner).to_delegate();
let io = IoHandler::new();
io.add_delegate(ethcore);
@@ -135,7 +147,8 @@ fn rpc_ethcore_set_gas_floor_target() {
#[test]
fn rpc_ethcore_set_extra_data() {
let miner = miner_service();
let ethcore = ethcore_client(&miner).to_delegate();
let client = blockchain_client();
let ethcore = ethcore_client(&client, &miner).to_delegate();
let io = IoHandler::new();
io.add_delegate(ethcore);
@@ -149,7 +162,8 @@ fn rpc_ethcore_set_extra_data() {
#[test]
fn rpc_ethcore_set_author() {
let miner = miner_service();
let ethcore = ethcore_client(&miner).to_delegate();
let client = blockchain_client();
let ethcore = ethcore_client(&client, &miner).to_delegate();
let io = IoHandler::new();
io.add_delegate(ethcore);
@@ -163,10 +177,11 @@ fn rpc_ethcore_set_author() {
#[test]
fn rpc_ethcore_dev_logs() {
let miner = miner_service();
let client = blockchain_client();
let logger = logger();
logger.append("a".to_owned());
logger.append("b".to_owned());
let ethcore = EthcoreClient::new(&miner, logger.clone(), settings()).to_delegate();
let ethcore = EthcoreClient::new(&client, &miner, logger.clone(), settings()).to_delegate();
let io = IoHandler::new();
io.add_delegate(ethcore);
@@ -179,7 +194,8 @@ fn rpc_ethcore_dev_logs() {
#[test]
fn rpc_ethcore_dev_logs_levels() {
let miner = miner_service();
let ethcore = ethcore_client(&miner).to_delegate();
let client = blockchain_client();
let ethcore = ethcore_client(&client, &miner).to_delegate();
let io = IoHandler::new();
io.add_delegate(ethcore);
@@ -191,7 +207,8 @@ fn rpc_ethcore_dev_logs_levels() {
#[test]
fn rpc_ethcore_set_transactions_limit() {
let miner = miner_service();
let ethcore = ethcore_client(&miner).to_delegate();
let client = blockchain_client();
let ethcore = ethcore_client(&client, &miner).to_delegate();
let io = IoHandler::new();
io.add_delegate(ethcore);
@@ -205,7 +222,8 @@ fn rpc_ethcore_set_transactions_limit() {
#[test]
fn rpc_ethcore_transactions_limit() {
let miner = miner_service();
let ethcore = ethcore_client(&miner).to_delegate();
let client = blockchain_client();
let ethcore = ethcore_client(&client, &miner).to_delegate();
let io = IoHandler::new();
io.add_delegate(ethcore);
@@ -218,7 +236,8 @@ fn rpc_ethcore_transactions_limit() {
#[test]
fn rpc_ethcore_net_chain() {
let miner = miner_service();
let ethcore = ethcore_client(&miner).to_delegate();
let client = blockchain_client();
let ethcore = ethcore_client(&client, &miner).to_delegate();
let io = IoHandler::new();
io.add_delegate(ethcore);
@@ -231,7 +250,8 @@ fn rpc_ethcore_net_chain() {
#[test]
fn rpc_ethcore_net_max_peers() {
let miner = miner_service();
let ethcore = ethcore_client(&miner).to_delegate();
let client = blockchain_client();
let ethcore = ethcore_client(&client, &miner).to_delegate();
let io = IoHandler::new();
io.add_delegate(ethcore);
@@ -244,7 +264,8 @@ fn rpc_ethcore_net_max_peers() {
#[test]
fn rpc_ethcore_net_port() {
let miner = miner_service();
let ethcore = ethcore_client(&miner).to_delegate();
let client = blockchain_client();
let ethcore = ethcore_client(&client, &miner).to_delegate();
let io = IoHandler::new();
io.add_delegate(ethcore);
@@ -257,7 +278,8 @@ fn rpc_ethcore_net_port() {
#[test]
fn rpc_ethcore_rpc_settings() {
let miner = miner_service();
let ethcore = ethcore_client(&miner).to_delegate();
let client = blockchain_client();
let ethcore = ethcore_client(&client, &miner).to_delegate();
let io = IoHandler::new();
io.add_delegate(ethcore);
@@ -270,7 +292,8 @@ fn rpc_ethcore_rpc_settings() {
#[test]
fn rpc_ethcore_node_name() {
let miner = miner_service();
let ethcore = ethcore_client(&miner).to_delegate();
let client = blockchain_client();
let ethcore = ethcore_client(&client, &miner).to_delegate();
let io = IoHandler::new();
io.add_delegate(ethcore);

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>;
/// Executes the given call and returns the VM trace for it.
fn vm_trace_call(&self, _: Params) -> Result<Value, Error>;
/// 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
}
}

View File

@@ -103,7 +103,7 @@ mod tests {
fn test_serialize_block_transactions() {
let t = BlockTransactions::Full(vec![Transaction::default()]);
let serialized = serde_json::to_string(&t).unwrap();
assert_eq!(serialized, r#"[{"hash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x00","blockHash":null,"blockNumber":null,"transactionIndex":null,"from":"0x0000000000000000000000000000000000000000","to":null,"value":"0x00","gasPrice":"0x00","gas":"0x00","input":"0x"}]"#);
assert_eq!(serialized, r#"[{"hash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x00","blockHash":null,"blockNumber":null,"transactionIndex":null,"from":"0x0000000000000000000000000000000000000000","to":null,"value":"0x00","gasPrice":"0x00","gas":"0x00","input":"0x","creates":null}]"#);
let t = BlockTransactions::Hashes(vec![H256::default()]);
let serialized = serde_json::to_string(&t).unwrap();

View File

@@ -15,6 +15,7 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
use util::numbers::*;
use ethcore::contract_address;
use ethcore::transaction::{LocalizedTransaction, Action, SignedTransaction};
use v1::types::{Bytes, OptionalValue};
@@ -46,7 +47,9 @@ pub struct Transaction {
/// Gas
pub gas: U256,
/// Data
pub input: Bytes
pub input: Bytes,
/// Creates contract
pub creates: OptionalValue<Address>,
}
impl From<LocalizedTransaction> for Transaction {
@@ -65,7 +68,11 @@ impl From<LocalizedTransaction> for Transaction {
value: t.value,
gas_price: t.gas_price,
gas: t.gas,
input: Bytes::new(t.data.clone())
input: Bytes::new(t.data.clone()),
creates: match t.action {
Action::Create => OptionalValue::Value(contract_address(&t.sender().unwrap(), &t.nonce)),
Action::Call(_) => OptionalValue::Null,
},
}
}
}
@@ -86,7 +93,11 @@ impl From<SignedTransaction> for Transaction {
value: t.value,
gas_price: t.gas_price,
gas: t.gas,
input: Bytes::new(t.data.clone())
input: Bytes::new(t.data.clone()),
creates: match t.action {
Action::Create => OptionalValue::Value(contract_address(&t.sender().unwrap(), &t.nonce)),
Action::Call(_) => OptionalValue::Null,
},
}
}
}
@@ -100,7 +111,7 @@ mod tests {
fn test_transaction_serialize() {
let t = Transaction::default();
let serialized = serde_json::to_string(&t).unwrap();
assert_eq!(serialized, r#"{"hash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x00","blockHash":null,"blockNumber":null,"transactionIndex":null,"from":"0x0000000000000000000000000000000000000000","to":null,"value":"0x00","gasPrice":"0x00","gas":"0x00","input":"0x"}"#);
assert_eq!(serialized, r#"{"hash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x00","blockHash":null,"blockNumber":null,"transactionIndex":null,"from":"0x0000000000000000000000000000000000000000","to":null,"value":"0x00","gasPrice":"0x00","gas":"0x00","input":"0x","creates":null}"#);
}
}