From 8596a347ead1c053e50ebc6f412bacc9e50182ec Mon Sep 17 00:00:00 2001 From: Arkadiy Paronyan Date: Thu, 2 Jun 2016 11:49:56 +0200 Subject: [PATCH 1/3] Networking refactoring (#1172) * Networking refactoring * Make sure the same socket is reused * Safer atomic ordering * Replaced eq with == --- Cargo.lock | 7 +- util/Cargo.toml | 2 +- util/src/network/connection.rs | 59 ++------ util/src/network/handshake.rs | 54 ++------ util/src/network/host.rs | 245 ++++++++++----------------------- util/src/network/session.rs | 151 ++++++++++++++------ 6 files changed, 209 insertions(+), 309 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ce5c1f481..723bca872 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -390,7 +390,7 @@ dependencies = [ "rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", "serde 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", "sha3 0.1.0", - "slab 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "slab 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "target_info 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", "tiny-keccak 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1176,6 +1176,11 @@ name = "slab" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "slab" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "solicit" version = "0.4.4" diff --git a/util/Cargo.toml b/util/Cargo.toml index 7d03fd320..a88ffe037 100644 --- a/util/Cargo.toml +++ b/util/Cargo.toml @@ -25,7 +25,7 @@ elastic-array = "0.4" heapsize = "0.3" itertools = "0.4" crossbeam = "0.2" -slab = "0.1" +slab = "0.2" sha3 = { path = "sha3" } serde = "0.7.0" clippy = { version = "0.0.69", optional = true} diff --git a/util/src/network/connection.rs b/util/src/network/connection.rs index 589fc0106..3f20b8f7b 100644 --- a/util/src/network/connection.rs +++ b/util/src/network/connection.rs @@ -170,16 +170,16 @@ impl Connection { self.token } - /// Replace socket token - pub fn set_token(&mut self, token: StreamToken) { - self.token = token; - } - /// Get remote peer address pub fn remote_addr(&self) -> io::Result { self.socket.peer_addr() } + /// Get remote peer address string + pub fn remote_addr_str(&self) -> String { + self.socket.peer_addr().map(|a| a.to_string()).unwrap_or_else(|_| "Unknown".to_owned()) + } + /// Clone this connection. Clears the receiving buffer of the returned connection. pub fn try_clone(&self) -> io::Result { Ok(Connection { @@ -196,7 +196,7 @@ impl Connection { /// Register this connection with the IO event loop. pub fn register_socket(&self, reg: Token, event_loop: &mut EventLoop) -> io::Result<()> { trace!(target: "network", "connection register; token={:?}", reg); - if let Err(e) = event_loop.register(&self.socket, reg, self.interest, PollOpt::edge() | PollOpt::oneshot()) { + if let Err(e) = event_loop.register(&self.socket, reg, self.interest, PollOpt::edge() /* | PollOpt::oneshot() */) { // TODO: oneshot is broken on windows trace!(target: "network", "Failed to register {:?}, {:?}", reg, e); } Ok(()) @@ -205,7 +205,7 @@ impl Connection { /// Update connection registration. Should be called at the end of the IO handler. pub fn update_socket(&self, reg: Token, event_loop: &mut EventLoop) -> io::Result<()> { trace!(target: "network", "connection reregister; token={:?}", reg); - event_loop.reregister( &self.socket, reg, self.interest, PollOpt::edge() | PollOpt::oneshot()).or_else(|e| { + event_loop.reregister( &self.socket, reg, self.interest, PollOpt::edge() /* | PollOpt::oneshot() */ ).or_else(|e| { // TODO: oneshot is broken on windows trace!(target: "network", "Failed to reregister {:?}, {:?}", reg, e); Ok(()) }) @@ -246,7 +246,7 @@ enum EncryptedConnectionState { /// https://github.com/ethereum/devp2p/blob/master/rlpx.md#framing pub struct EncryptedConnection { /// Underlying tcp connection - connection: Connection, + pub connection: Connection, /// Egress data encryptor encoder: CtrMode, /// Ingress data decryptor @@ -266,27 +266,6 @@ pub struct EncryptedConnection { } impl EncryptedConnection { - - /// Get socket token - pub fn token(&self) -> StreamToken { - self.connection.token - } - - /// Replace socket token - pub fn set_token(&mut self, token: StreamToken) { - self.connection.set_token(token); - } - - /// Get remote peer address - pub fn remote_addr(&self) -> io::Result { - self.connection.remote_addr() - } - - /// Check if this connection has data to be sent. - pub fn is_sending(&self) -> bool { - self.connection.is_sending() - } - /// Create an encrypted connection out of the handshake. Consumes a handshake object. pub fn new(handshake: &mut Handshake) -> Result { let shared = try!(crypto::ecdh::agree(handshake.ecdhe.secret(), &handshake.remote_ephemeral)); @@ -323,8 +302,10 @@ impl EncryptedConnection { ingress_mac.update(&mac_material); ingress_mac.update(if handshake.originated { &handshake.ack_cipher } else { &handshake.auth_cipher }); + let old_connection = try!(handshake.connection.try_clone()); + let connection = ::std::mem::replace(&mut handshake.connection, old_connection); let mut enc = EncryptedConnection { - connection: try!(handshake.connection.try_clone()), + connection: connection, encoder: encoder, decoder: decoder, mac_encoder: mac_encoder, @@ -463,24 +444,6 @@ impl EncryptedConnection { try!(self.connection.writable()); Ok(()) } - - /// Register socket with the event lpop. This should be called at the end of the event loop. - pub fn register_socket(&self, reg: Token, event_loop: &mut EventLoop) -> Result<(), UtilError> { - try!(self.connection.register_socket(reg, event_loop)); - Ok(()) - } - - /// Update connection registration. This should be called at the end of the event loop. - pub fn update_socket(&self, reg: Token, event_loop: &mut EventLoop) -> Result<(), UtilError> { - try!(self.connection.update_socket(reg, event_loop)); - Ok(()) - } - - /// Delete connection registration. This should be called at the end of the event loop. - pub fn deregister_socket(&self, event_loop: &mut EventLoop) -> Result<(), UtilError> { - try!(self.connection.deregister_socket(event_loop)); - Ok(()) - } } #[test] diff --git a/util/src/network/handshake.rs b/util/src/network/handshake.rs index 123531d8d..e02da3d4c 100644 --- a/util/src/network/handshake.rs +++ b/util/src/network/handshake.rs @@ -16,7 +16,6 @@ use std::sync::Arc; use rand::random; -use mio::*; use mio::tcp::*; use hash::*; use rlp::*; @@ -102,21 +101,6 @@ impl Handshake { }) } - /// Get id of the remote node if known - pub fn id(&self) -> &NodeId { - &self.id - } - - /// Get stream token id - pub fn token(&self) -> StreamToken { - self.connection.token() - } - - /// Mark this handshake as inactive to be deleted lated. - pub fn set_expired(&mut self) { - self.expired = true; - } - /// Check if this handshake is expired. pub fn expired(&self) -> bool { self.expired @@ -177,7 +161,7 @@ impl Handshake { } /// Writabe IO handler. - pub fn writable(&mut self, io: &IoContext, _host: &HostInfo) -> Result<(), UtilError> where Message: Send + Clone { + pub fn writable(&mut self, io: &IoContext) -> Result<(), UtilError> where Message: Send + Clone { if !self.expired() { io.clear_timer(self.connection.token).unwrap(); try!(self.connection.writable()); @@ -188,28 +172,6 @@ impl Handshake { Ok(()) } - /// Register the socket with the event loop - pub fn register_socket>(&self, reg: Token, event_loop: &mut EventLoop) -> Result<(), UtilError> { - if !self.expired() { - try!(self.connection.register_socket(reg, event_loop)); - } - Ok(()) - } - - /// Update socket registration with the event loop. - pub fn update_socket>(&self, reg: Token, event_loop: &mut EventLoop) -> Result<(), UtilError> { - if !self.expired() { - try!(self.connection.update_socket(reg, event_loop)); - } - Ok(()) - } - - /// Delete registration - pub fn deregister_socket(&self, event_loop: &mut EventLoop) -> Result<(), UtilError> { - try!(self.connection.deregister_socket(event_loop)); - Ok(()) - } - fn set_auth(&mut self, host_secret: &Secret, sig: &[u8], remote_public: &[u8], remote_nonce: &[u8], remote_version: u64) -> Result<(), UtilError> { self.id.clone_from_slice(remote_public); self.remote_nonce.clone_from_slice(remote_nonce); @@ -222,7 +184,7 @@ impl Handshake { /// Parse, validate and confirm auth message fn read_auth(&mut self, secret: &Secret, data: &[u8]) -> Result<(), UtilError> { - trace!(target:"network", "Received handshake auth from {:?}", self.connection.socket.peer_addr()); + trace!(target:"network", "Received handshake auth from {:?}", self.connection.remote_addr_str()); if data.len() != V4_AUTH_PACKET_SIZE { debug!(target:"net", "Wrong auth packet size"); return Err(From::from(NetworkError::BadProtocol)); @@ -253,7 +215,7 @@ impl Handshake { } fn read_auth_eip8(&mut self, secret: &Secret, data: &[u8]) -> Result<(), UtilError> { - trace!(target:"network", "Received EIP8 handshake auth from {:?}", self.connection.socket.peer_addr()); + trace!(target:"network", "Received EIP8 handshake auth from {:?}", self.connection.remote_addr_str()); self.auth_cipher.extend_from_slice(data); let auth = try!(ecies::decrypt(secret, &self.auth_cipher[0..2], &self.auth_cipher[2..])); let rlp = UntrustedRlp::new(&auth); @@ -268,7 +230,7 @@ impl Handshake { /// Parse and validate ack message fn read_ack(&mut self, secret: &Secret, data: &[u8]) -> Result<(), UtilError> { - trace!(target:"network", "Received handshake auth to {:?}", self.connection.socket.peer_addr()); + trace!(target:"network", "Received handshake auth to {:?}", self.connection.remote_addr_str()); if data.len() != V4_ACK_PACKET_SIZE { debug!(target:"net", "Wrong ack packet size"); return Err(From::from(NetworkError::BadProtocol)); @@ -296,7 +258,7 @@ impl Handshake { } fn read_ack_eip8(&mut self, secret: &Secret, data: &[u8]) -> Result<(), UtilError> { - trace!(target:"network", "Received EIP8 handshake auth from {:?}", self.connection.socket.peer_addr()); + trace!(target:"network", "Received EIP8 handshake auth from {:?}", self.connection.remote_addr_str()); self.ack_cipher.extend_from_slice(data); let ack = try!(ecies::decrypt(secret, &self.ack_cipher[0..2], &self.ack_cipher[2..])); let rlp = UntrustedRlp::new(&ack); @@ -309,7 +271,7 @@ impl Handshake { /// Sends auth message fn write_auth(&mut self, secret: &Secret, public: &Public) -> Result<(), UtilError> { - trace!(target:"network", "Sending handshake auth to {:?}", self.connection.socket.peer_addr()); + trace!(target:"network", "Sending handshake auth to {:?}", self.connection.remote_addr_str()); let mut data = [0u8; /*Signature::SIZE*/ 65 + /*H256::SIZE*/ 32 + /*Public::SIZE*/ 64 + /*H256::SIZE*/ 32 + 1]; //TODO: use associated constants let len = data.len(); { @@ -336,7 +298,7 @@ impl Handshake { /// Sends ack message fn write_ack(&mut self) -> Result<(), UtilError> { - trace!(target:"network", "Sending handshake ack to {:?}", self.connection.socket.peer_addr()); + trace!(target:"network", "Sending handshake ack to {:?}", self.connection.remote_addr_str()); let mut data = [0u8; 1 + /*Public::SIZE*/ 64 + /*H256::SIZE*/ 32]; //TODO: use associated constants let len = data.len(); { @@ -355,7 +317,7 @@ impl Handshake { /// Sends EIP8 ack message fn write_ack_eip8(&mut self) -> Result<(), UtilError> { - trace!(target:"network", "Sending EIP8 handshake ack to {:?}", self.connection.socket.peer_addr()); + trace!(target:"network", "Sending EIP8 handshake ack to {:?}", self.connection.remote_addr_str()); let mut rlp = RlpStream::new_list(3); rlp.append(self.ecdhe.public()); rlp.append(&self.nonce); diff --git a/util/src/network/host.rs b/util/src/network/host.rs index 13b64eb3c..92a912a40 100644 --- a/util/src/network/host.rs +++ b/util/src/network/host.rs @@ -18,6 +18,7 @@ use std::net::{SocketAddr}; use std::collections::{HashMap}; use std::str::{FromStr}; use std::sync::*; +use std::sync::atomic::{AtomicUsize, Ordering as AtomicOrdering}; use std::ops::*; use std::cmp::min; use std::path::{Path, PathBuf}; @@ -31,7 +32,6 @@ use misc::version; use crypto::*; use sha3::Hashable; use rlp::*; -use network::handshake::Handshake; use network::session::{Session, SessionData}; use error::*; use io::*; @@ -44,8 +44,7 @@ use network::ip_utils::{map_external_address, select_public_address}; type Slab = ::slab::Slab; -const _DEFAULT_PORT: u16 = 30304; -const MAX_SESSIONS: usize = 1024; +const MAX_SESSIONS: usize = 1024 + MAX_HANDSHAKES; const MAX_HANDSHAKES: usize = 80; const MAX_HANDSHAKES_PER_ROUND: usize = 32; const MAINTENANCE_TIMEOUT: u64 = 1000; @@ -115,18 +114,17 @@ impl NetworkConfiguration { } // Tokens -const TCP_ACCEPT: usize = LAST_HANDSHAKE + 1; -const IDLE: usize = LAST_HANDSHAKE + 2; -const DISCOVERY: usize = LAST_HANDSHAKE + 3; -const DISCOVERY_REFRESH: usize = LAST_HANDSHAKE + 4; -const DISCOVERY_ROUND: usize = LAST_HANDSHAKE + 5; -const INIT_PUBLIC: usize = LAST_HANDSHAKE + 6; -const NODE_TABLE: usize = LAST_HANDSHAKE + 7; +const TCP_ACCEPT: usize = SYS_TIMER + 1; +const IDLE: usize = SYS_TIMER + 2; +const DISCOVERY: usize = SYS_TIMER + 3; +const DISCOVERY_REFRESH: usize = SYS_TIMER + 4; +const DISCOVERY_ROUND: usize = SYS_TIMER + 5; +const INIT_PUBLIC: usize = SYS_TIMER + 6; +const NODE_TABLE: usize = SYS_TIMER + 7; const FIRST_SESSION: usize = 0; const LAST_SESSION: usize = FIRST_SESSION + MAX_SESSIONS - 1; -const FIRST_HANDSHAKE: usize = LAST_SESSION + 1; -const LAST_HANDSHAKE: usize = FIRST_HANDSHAKE + MAX_HANDSHAKES - 1; -const USER_TIMER: usize = LAST_HANDSHAKE + 256; +const USER_TIMER: usize = LAST_SESSION + 256; +const SYS_TIMER: usize = LAST_SESSION + 1; /// Protocol handler level packet id pub type PacketId = u8; @@ -306,7 +304,6 @@ impl HostInfo { } type SharedSession = Arc>; -type SharedHandshake = Arc>; #[derive(Copy, Clone)] struct ProtocolTimer { @@ -318,7 +315,6 @@ struct ProtocolTimer { pub struct Host where Message: Send + Sync + Clone { pub info: RwLock, tcp_listener: Mutex, - handshakes: Arc>>, sessions: Arc>>, discovery: Mutex>, nodes: RwLock, @@ -327,6 +323,7 @@ pub struct Host where Message: Send + Sync + Clone { timer_counter: RwLock, stats: Arc, pinned_nodes: Vec, + num_sessions: AtomicUsize, } impl Host where Message: Send + Sync + Clone { @@ -370,7 +367,6 @@ impl Host where Message: Send + Sync + Clone { }), discovery: Mutex::new(None), tcp_listener: Mutex::new(tcp_listener), - handshakes: Arc::new(RwLock::new(Slab::new_starting_at(FIRST_HANDSHAKE, MAX_HANDSHAKES))), sessions: Arc::new(RwLock::new(Slab::new_starting_at(FIRST_SESSION, MAX_SESSIONS))), nodes: RwLock::new(NodeTable::new(path)), handlers: RwLock::new(HashMap::new()), @@ -378,6 +374,7 @@ impl Host where Message: Send + Sync + Clone { timer_counter: RwLock::new(USER_TIMER), stats: Arc::new(NetworkStats::default()), pinned_nodes: Vec::new(), + num_sessions: AtomicUsize::new(0), }; let boot_nodes = host.info.read().unwrap().config.boot_nodes.clone(); @@ -477,19 +474,19 @@ impl Host where Message: Send + Sync + Clone { } fn have_session(&self, id: &NodeId) -> bool { - self.sessions.read().unwrap().iter().any(|e| e.lock().unwrap().info.id.eq(&id)) + self.sessions.read().unwrap().iter().any(|e| e.lock().unwrap().info.id == Some(id.clone())) } fn session_count(&self) -> usize { - self.sessions.read().unwrap().count() + self.num_sessions.load(AtomicOrdering::Relaxed) } fn connecting_to(&self, id: &NodeId) -> bool { - self.handshakes.read().unwrap().iter().any(|e| e.lock().unwrap().id.eq(&id)) + self.sessions.read().unwrap().iter().any(|e| e.lock().unwrap().id() == Some(id)) } fn handshake_count(&self) -> usize { - self.handshakes.read().unwrap().count() + self.sessions.read().unwrap().count() - self.session_count() } fn keep_alive(&self, io: &IoContext>) { @@ -565,21 +562,31 @@ impl Host where Message: Send + Sync + Clone { } } }; - self.create_connection(socket, Some(id), io); + if let Err(e) = self.create_connection(socket, Some(id), io) { + debug!(target: "network", "Can't create connection: {:?}", e); + } } #[cfg_attr(feature="dev", allow(block_in_if_condition_stmt))] - fn create_connection(&self, socket: TcpStream, id: Option<&NodeId>, io: &IoContext>) { + fn create_connection(&self, socket: TcpStream, id: Option<&NodeId>, io: &IoContext>) -> Result<(), UtilError> { let nonce = self.info.write().unwrap().next_nonce(); - let mut handshakes = self.handshakes.write().unwrap(); - if handshakes.insert_with(|token| { - let mut handshake = Handshake::new(token, id, socket, &nonce, self.stats.clone()).expect("Can't create handshake"); - handshake.start(io, &self.info.read().unwrap(), id.is_some()).and_then(|_| io.register_stream(token)).unwrap_or_else (|e| { - debug!(target: "network", "Handshake create error: {:?}", e); - }); - Arc::new(Mutex::new(handshake)) - }).is_none() { - debug!(target: "network", "Max handshakes reached"); + let mut sessions = self.sessions.write().unwrap(); + let token = sessions.insert_with_opt(|token| { + match Session::new(io, socket, token, id, &nonce, self.stats.clone(), &self.info.read().unwrap()) { + Ok(s) => Some(Arc::new(Mutex::new(s))), + Err(e) => { + debug!(target: "network", "Session create error: {:?}", e); + None + } + } + }); + + match token { + Some(t) => io.register_stream(t), + None => { + debug!(target: "network", "Max sessions reached"); + Ok(()) + } } } @@ -594,19 +601,11 @@ impl Host where Message: Send + Sync + Clone { break }, }; - self.create_connection(socket, None, io); - } - io.update_registration(TCP_ACCEPT).expect("Error registering TCP listener"); - } - - fn handshake_writable(&self, token: StreamToken, io: &IoContext>) { - let handshake = { self.handshakes.read().unwrap().get(token).cloned() }; - if let Some(handshake) = handshake { - let mut h = handshake.lock().unwrap(); - if let Err(e) = h.writable(io, &self.info.read().unwrap()) { - trace!(target: "network", "Handshake write error: {}: {:?}", token, e); + if let Err(e) = self.create_connection(socket, None, io) { + debug!(target: "network", "Can't accept connection: {:?}", e); } } + io.update_registration(TCP_ACCEPT).expect("Error registering TCP listener"); } fn session_writable(&self, token: StreamToken, io: &IoContext>) { @@ -629,30 +628,6 @@ impl Host where Message: Send + Sync + Clone { self.kill_connection(token, io, true); } - fn handshake_readable(&self, token: StreamToken, io: &IoContext>) { - let mut create_session = false; - let mut kill = false; - let handshake = { self.handshakes.read().unwrap().get(token).cloned() }; - if let Some(handshake) = handshake { - let mut h = handshake.lock().unwrap(); - if let Err(e) = h.readable(io, &self.info.read().unwrap()) { - debug!(target: "network", "Handshake read error: {}: {:?}", token, e); - kill = true; - } - if h.done() { - create_session = true; - } - } - if kill { - self.kill_connection(token, io, true); - return; - } else if create_session { - self.start_session(token, io); - return; - } - io.update_registration(token).unwrap_or_else(|e| debug!(target: "network", "Token registration error: {:?}", e)); - } - fn session_readable(&self, token: StreamToken, io: &IoContext>) { let mut ready_data: Vec = Vec::new(); let mut packet_data: Option<(ProtocolId, PacketId, Vec)> = None; @@ -662,17 +637,37 @@ impl Host where Message: Send + Sync + Clone { let mut s = session.lock().unwrap(); match s.readable(io, &self.info.read().unwrap()) { Err(e) => { - trace!(target: "network", "Session read error: {}:{} ({:?}) {:?}", token, s.id(), s.remote_addr(), e); + trace!(target: "network", "Session read error: {}:{:?} ({:?}) {:?}", token, s.id(), s.remote_addr(), e); match e { UtilError::Network(NetworkError::Disconnect(DisconnectReason::UselessPeer)) | UtilError::Network(NetworkError::Disconnect(DisconnectReason::IncompatibleProtocol)) => { - self.nodes.write().unwrap().mark_as_useless(s.id()); + if let Some(id) = s.id() { + self.nodes.write().unwrap().mark_as_useless(id); + } } _ => (), } kill = true; }, Ok(SessionData::Ready) => { + if !s.info.originated { + let session_count = self.session_count(); + let ideal_peers = { self.info.read().unwrap().deref().config.ideal_peers }; + if session_count >= ideal_peers as usize { + s.disconnect(DisconnectReason::TooManyPeers); + return; + } + // Add it no node table + if let Ok(address) = s.remote_addr() { + let entry = NodeEntry { id: s.id().unwrap().clone(), endpoint: NodeEndpoint { address: address, udp_port: address.port() } }; + self.nodes.write().unwrap().add_node(Node::new(entry.id.clone(), entry.endpoint.clone())); + let mut discovery = self.discovery.lock().unwrap(); + if let Some(ref mut discovery) = *discovery.deref_mut() { + discovery.add_node(entry); + } + } + } + self.num_sessions.fetch_add(1, AtomicOrdering::SeqCst); for (p, _) in self.handlers.read().unwrap().iter() { if s.have_capability(p) { ready_data.push(p); @@ -697,6 +692,7 @@ impl Host where Message: Send + Sync + Clone { } for p in ready_data { let h = self.handlers.read().unwrap().get(p).unwrap().clone(); + self.stats.inc_sessions(); h.connected(&NetworkContext::new(io, p, session.clone(), self.sessions.clone()), &token); } if let Some((p, packet_id, data)) = packet_data { @@ -706,59 +702,6 @@ impl Host where Message: Send + Sync + Clone { io.update_registration(token).unwrap_or_else(|e| debug!(target: "network", "Token registration error: {:?}", e)); } - fn start_session(&self, token: StreamToken, io: &IoContext>) { - let mut handshakes = self.handshakes.write().unwrap(); - if handshakes.get(token).is_none() { - return; - } - - // turn a handshake into a session - let mut sessions = self.sessions.write().unwrap(); - let mut h = handshakes.get_mut(token).unwrap().lock().unwrap(); - if h.expired { - return; - } - io.deregister_stream(token).expect("Error deleting handshake registration"); - h.set_expired(); - let originated = h.originated; - let mut session = match Session::new(&mut h, &self.info.read().unwrap()) { - Ok(s) => s, - Err(e) => { - debug!(target: "network", "Session creation error: {:?}", e); - return; - } - }; - if !originated { - let session_count = sessions.count(); - let ideal_peers = { self.info.read().unwrap().deref().config.ideal_peers }; - if session_count >= ideal_peers as usize { - session.disconnect(DisconnectReason::TooManyPeers); - return; - } - } - let result = sessions.insert_with(move |session_token| { - session.set_token(session_token); - io.register_stream(session_token).expect("Error creating session registration"); - self.stats.inc_sessions(); - trace!(target: "network", "Creating session {} -> {}:{} ({:?})", token, session_token, session.id(), session.remote_addr()); - if !originated { - // Add it no node table - if let Ok(address) = session.remote_addr() { - let entry = NodeEntry { id: session.id().clone(), endpoint: NodeEndpoint { address: address, udp_port: address.port() } }; - self.nodes.write().unwrap().add_node(Node::new(entry.id.clone(), entry.endpoint.clone())); - let mut discovery = self.discovery.lock().unwrap(); - if let Some(ref mut discovery) = *discovery.deref_mut() { - discovery.add_node(entry); - } - } - } - Arc::new(Mutex::new(session)) - }); - if result.is_none() { - warn!("Max sessions reached"); - } - } - fn connection_timeout(&self, token: StreamToken, io: &IoContext>) { trace!(target: "network", "Connection timeout: {}", token); self.kill_connection(token, io, true) @@ -770,17 +713,6 @@ impl Host where Message: Send + Sync + Clone { let mut deregister = false; let mut expired_session = None; match token { - FIRST_HANDSHAKE ... LAST_HANDSHAKE => { - let handshakes = self.handshakes.write().unwrap(); - if let Some(handshake) = handshakes.get(token).cloned() { - let mut handshake = handshake.lock().unwrap(); - if !handshake.expired() { - handshake.set_expired(); - failure_id = Some(handshake.id().clone()); - deregister = true; - } - } - }, FIRST_SESSION ... LAST_SESSION => { let sessions = self.sessions.write().unwrap(); if let Some(session) = sessions.get(token).cloned() { @@ -790,12 +722,13 @@ impl Host where Message: Send + Sync + Clone { if s.is_ready() { for (p, _) in self.handlers.read().unwrap().iter() { if s.have_capability(p) { + self.num_sessions.fetch_sub(1, AtomicOrdering::SeqCst); to_disconnect.push(p); } } } s.set_expired(); - failure_id = Some(s.id().clone()); + failure_id = s.id().cloned(); } deregister = remote || s.done(); } @@ -821,20 +754,11 @@ impl Host where Message: Send + Sync + Clone { fn update_nodes(&self, io: &IoContext>, node_changes: TableUpdates) { let mut to_remove: Vec = Vec::new(); { - { - let handshakes = self.handshakes.write().unwrap(); - for c in handshakes.iter() { - let h = c.lock().unwrap(); - if node_changes.removed.contains(&h.id()) { - to_remove.push(h.token()); - } - } - } - { - let sessions = self.sessions.write().unwrap(); - for c in sessions.iter() { - let s = c.lock().unwrap(); - if node_changes.removed.contains(&s.id()) { + let sessions = self.sessions.write().unwrap(); + for c in sessions.iter() { + let s = c.lock().unwrap(); + if let Some(id) = s.id() { + if node_changes.removed.contains(id) { to_remove.push(s.token()); } } @@ -860,7 +784,6 @@ impl IoHandler> for Host where Messa trace!(target: "network", "Hup: {}", stream); match stream { FIRST_SESSION ... LAST_SESSION => self.connection_closed(stream, io), - FIRST_HANDSHAKE ... LAST_HANDSHAKE => self.connection_closed(stream, io), _ => warn!(target: "network", "Unexpected hup"), }; } @@ -868,7 +791,6 @@ impl IoHandler> for Host where Messa fn stream_readable(&self, io: &IoContext>, stream: StreamToken) { match stream { FIRST_SESSION ... LAST_SESSION => self.session_readable(stream, io), - FIRST_HANDSHAKE ... LAST_HANDSHAKE => self.handshake_readable(stream, io), DISCOVERY => { let node_changes = { self.discovery.lock().unwrap().as_mut().unwrap().readable() }; if let Some(node_changes) = node_changes { @@ -884,7 +806,6 @@ impl IoHandler> for Host where Messa fn stream_writable(&self, io: &IoContext>, stream: StreamToken) { match stream { FIRST_SESSION ... LAST_SESSION => self.session_writable(stream, io), - FIRST_HANDSHAKE ... LAST_HANDSHAKE => self.handshake_writable(stream, io), DISCOVERY => { self.discovery.lock().unwrap().as_mut().unwrap().writable(); io.update_registration(DISCOVERY).expect("Error updating discovery registration"); @@ -899,7 +820,6 @@ impl IoHandler> for Host where Messa INIT_PUBLIC => self.init_public_interface(io).unwrap_or_else(|e| warn!("Error initializing public interface: {:?}", e)), FIRST_SESSION ... LAST_SESSION => self.connection_timeout(token, io), - FIRST_HANDSHAKE ... LAST_HANDSHAKE => self.connection_timeout(token, io), DISCOVERY_REFRESH => { self.discovery.lock().unwrap().as_mut().unwrap().refresh(); io.update_registration(DISCOVERY).expect("Error updating discovery registration"); @@ -966,7 +886,9 @@ impl IoHandler> for Host where Messa let session = { self.sessions.read().unwrap().get(*peer).cloned() }; if let Some(session) = session { session.lock().unwrap().disconnect(DisconnectReason::DisconnectRequested); - self.nodes.write().unwrap().mark_as_useless(session.lock().unwrap().id()); + if let Some(id) = session.lock().unwrap().id() { + self.nodes.write().unwrap().mark_as_useless(id) + } } trace!(target: "network", "Disabling peer {}", peer); self.kill_connection(*peer, io, false); @@ -987,12 +909,6 @@ impl IoHandler> for Host where Messa session.lock().unwrap().register_socket(reg, event_loop).expect("Error registering socket"); } } - FIRST_HANDSHAKE ... LAST_HANDSHAKE => { - let connection = { self.handshakes.read().unwrap().get(stream).cloned() }; - if let Some(connection) = connection { - connection.lock().unwrap().register_socket(reg, event_loop).expect("Error registering socket"); - } - } DISCOVERY => self.discovery.lock().unwrap().as_ref().unwrap().register_socket(event_loop).expect("Error registering discovery socket"), TCP_ACCEPT => event_loop.register(self.tcp_listener.lock().unwrap().deref(), Token(TCP_ACCEPT), EventSet::all(), PollOpt::edge()).expect("Error registering stream"), _ => warn!("Unexpected stream registration") @@ -1008,13 +924,6 @@ impl IoHandler> for Host where Messa connections.remove(stream); } } - FIRST_HANDSHAKE ... LAST_HANDSHAKE => { - let mut connections = self.handshakes.write().unwrap(); - if let Some(connection) = connections.get(stream).cloned() { - connection.lock().unwrap().deregister_socket(event_loop).expect("Error deregistering socket"); - connections.remove(stream); - } - } DISCOVERY => (), _ => warn!("Unexpected stream deregistration") } @@ -1028,12 +937,6 @@ impl IoHandler> for Host where Messa connection.lock().unwrap().update_socket(reg, event_loop).expect("Error updating socket"); } } - FIRST_HANDSHAKE ... LAST_HANDSHAKE => { - let connection = { self.handshakes.read().unwrap().get(stream).cloned() }; - if let Some(connection) = connection { - connection.lock().unwrap().update_socket(reg, event_loop).expect("Error updating socket"); - } - } DISCOVERY => self.discovery.lock().unwrap().as_ref().unwrap().update_registration(event_loop).expect("Error reregistering discovery socket"), TCP_ACCEPT => event_loop.reregister(self.tcp_listener.lock().unwrap().deref(), Token(TCP_ACCEPT), EventSet::all(), PollOpt::edge()).expect("Error reregistering stream"), _ => warn!("Unexpected stream update") diff --git a/util/src/network/session.rs b/util/src/network/session.rs index 6c0a20a14..7b7f16c18 100644 --- a/util/src/network/session.rs +++ b/util/src/network/session.rs @@ -16,15 +16,19 @@ use std::net::SocketAddr; use std::io; +use std::sync::*; use mio::*; +use mio::tcp::*; use rlp::*; -use network::connection::{EncryptedConnection, Packet}; +use hash::*; +use network::connection::{EncryptedConnection, Packet, Connection}; use network::handshake::Handshake; use error::*; use io::{IoContext, StreamToken}; use network::error::{NetworkError, DisconnectReason}; use network::host::*; use network::node_table::NodeId; +use network::stats::NetworkStats; use time; const PING_TIMEOUT_SEC: u64 = 30; @@ -36,14 +40,18 @@ const PING_INTERVAL_SEC: u64 = 30; pub struct Session { /// Shared session information pub info: SessionInfo, - /// Underlying connection - connection: EncryptedConnection, /// Session ready flag. Set after successfull Hello packet exchange had_hello: bool, /// Session is no longer active flag. expired: bool, ping_time_ns: u64, pong_time_ns: Option, + state: State, +} + +enum State { + Handshake(Handshake), + Session(EncryptedConnection), } /// Structure used to report various session events. @@ -65,7 +73,7 @@ pub enum SessionData { /// Shared session information pub struct SessionInfo { /// Peer public key - pub id: NodeId, + pub id: Option, /// Peer client ID pub client_version: String, /// Peer RLPx protocol version @@ -74,6 +82,8 @@ pub struct SessionInfo { capabilities: Vec, /// Peer ping delay in milliseconds pub ping_ms: Option, + /// True if this session was originated by us. + pub originated: bool, } #[derive(Debug, PartialEq, Eq)] @@ -112,31 +122,52 @@ const PACKET_LAST: u8 = 0x7f; impl Session { /// Create a new session out of comepleted handshake. This clones the handshake connection object /// and leaves the handhsake in limbo to be deregistered from the event loop. - pub fn new(h: &mut Handshake, host: &HostInfo) -> Result { - let id = h.id.clone(); - let connection = try!(EncryptedConnection::new(h)); - let mut session = Session { - connection: connection, + pub fn new(io: &IoContext, socket: TcpStream, token: StreamToken, id: Option<&NodeId>, + nonce: &H256, stats: Arc, host: &HostInfo) -> Result + where Message: Send + Clone { + let originated = id.is_some(); + let mut handshake = Handshake::new(token, id, socket, &nonce, stats).expect("Can't create handshake"); + try!(handshake.start(io, host, originated)); + Ok(Session { + state: State::Handshake(handshake), had_hello: false, info: SessionInfo { - id: id, + id: id.cloned(), client_version: String::new(), protocol_version: 0, capabilities: Vec::new(), ping_ms: None, + originated: originated, }, ping_time_ns: 0, pong_time_ns: None, expired: false, + }) + } + + fn complete_handshake(&mut self, host: &HostInfo) -> Result<(), UtilError> { + let connection = if let State::Handshake(ref mut h) = self.state { + self.info.id = Some(h.id.clone()); + try!(EncryptedConnection::new(h)) + } else { + panic!("Unexpected state"); }; - try!(session.write_hello(host)); - try!(session.send_ping()); - Ok(session) + self.state = State::Session(connection); + try!(self.write_hello(host)); + try!(self.send_ping()); + Ok(()) + } + + fn connection(&self) -> &Connection { + match self.state { + State::Handshake(ref h) => &h.connection, + State::Session(ref s) => &s.connection, + } } /// Get id of the remote peer - pub fn id(&self) -> &NodeId { - &self.info.id + pub fn id(&self) -> Option<&NodeId> { + self.info.id.as_ref() } /// Check if session is ready to send/receive data @@ -151,21 +182,20 @@ impl Session { /// Check if this session is expired. pub fn expired(&self) -> bool { - self.expired + match self.state { + State::Handshake(ref h) => h.expired(), + _ => self.expired, + } } /// Check if this session is over and there is nothing to be sent. pub fn done(&self) -> bool { - self.expired() && !self.connection.is_sending() - } - /// Replace socket token - pub fn set_token(&mut self, token: StreamToken) { - self.connection.set_token(token); + self.expired() && !self.connection().is_sending() } /// Get remote peer address pub fn remote_addr(&self) -> io::Result { - self.connection.remote_addr() + self.connection().remote_addr() } /// Readable IO handler. Returns packet data if available. @@ -173,15 +203,37 @@ impl Session { if self.expired() { return Ok(SessionData::None) } - match try!(self.connection.readable(io)) { - Some(data) => Ok(try!(self.read_packet(data, host))), - None => Ok(SessionData::None) + let mut create_session = false; + let mut packet_data = None; + match self.state { + State::Handshake(ref mut h) => { + try!(h.readable(io, host)); + if h.done() { + create_session = true; + } + } + State::Session(ref mut c) => { + match try!(c.readable(io)) { + data @ Some(_) => packet_data = data, + None => return Ok(SessionData::None) + } + } } + if let Some(data) = packet_data { + return Ok(try!(self.read_packet(data, host))); + } + if create_session { + try!(self.complete_handshake(host)); + } + Ok(SessionData::None) } /// Writable IO handler. Sends pending packets. pub fn writable(&mut self, io: &IoContext, _host: &HostInfo) -> Result<(), UtilError> where Message: Send + Sync + Clone { - self.connection.writable(io) + match self.state { + State::Handshake(ref mut h) => h.writable(io), + State::Session(ref mut s) => s.writable(io), + } } /// Checks if peer supports given capability @@ -194,18 +246,20 @@ impl Session { if self.expired() { return Ok(()); } - try!(self.connection.register_socket(reg, event_loop)); + try!(self.connection().register_socket(reg, event_loop)); Ok(()) } /// Update registration with the event loop. Should be called at the end of the IO handler. pub fn update_socket(&self, reg:Token, event_loop: &mut EventLoop) -> Result<(), UtilError> { - self.connection.update_socket(reg, event_loop) + try!(self.connection().update_socket(reg, event_loop)); + Ok(()) } /// Delete registration pub fn deregister_socket(&self, event_loop: &mut EventLoop) -> Result<(), UtilError> { - self.connection.deregister_socket(event_loop) + try!(self.connection().deregister_socket(event_loop)); + Ok(()) } /// Send a protocol packet to peer. @@ -221,7 +275,7 @@ impl Session { while protocol != self.info.capabilities[i].protocol { i += 1; if i == self.info.capabilities.len() { - debug!(target: "net", "Unknown protocol: {:?}", protocol); + debug!(target: "network", "Unknown protocol: {:?}", protocol); return Ok(()) } } @@ -229,11 +283,14 @@ impl Session { let mut rlp = RlpStream::new(); rlp.append(&(pid as u32)); rlp.append_raw(data, 1); - self.connection.send_packet(&rlp.out()) + self.send(rlp) } /// Keep this session alive. Returns false if ping timeout happened pub fn keep_alive(&mut self, io: &IoContext) -> bool where Message: Send + Sync + Clone { + if let State::Handshake(_) = self.state { + return true; + } let timed_out = if let Some(pong) = self.pong_time_ns { pong - self.ping_time_ns > PING_TIMEOUT_SEC * 1000_000_000 } else { @@ -244,13 +301,13 @@ impl Session { if let Err(e) = self.send_ping() { debug!("Error sending ping message: {:?}", e); } - io.update_registration(self.token()).unwrap_or_else(|e| debug!(target: "net", "Session registration error: {:?}", e)); + io.update_registration(self.token()).unwrap_or_else(|e| debug!(target: "network", "Session registration error: {:?}", e)); } !timed_out } pub fn token(&self) -> StreamToken { - self.connection.token() + self.connection().token() } fn read_packet(&mut self, packet: Packet, host: &HostInfo) -> Result { @@ -288,7 +345,7 @@ impl Session { while packet_id < self.info.capabilities[i].id_offset { i += 1; if i == self.info.capabilities.len() { - debug!(target: "net", "Unknown packet: {:?}", packet_id); + debug!(target: "network", "Unknown packet: {:?}", packet_id); return Ok(SessionData::None) } } @@ -299,7 +356,7 @@ impl Session { Ok(SessionData::Packet { data: packet.data, protocol: protocol, packet_id: pid } ) }, _ => { - debug!(target: "net", "Unknown packet: {:?}", packet_id); + debug!(target: "network", "Unknown packet: {:?}", packet_id); Ok(SessionData::None) } } @@ -314,7 +371,7 @@ impl Session { .append(&host.capabilities) .append(&host.local_endpoint.address.port()) .append(host.id()); - self.connection.send_packet(&rlp.out()) + self.send(rlp) } fn read_hello(&mut self, rlp: &UntrustedRlp, host: &HostInfo) -> Result<(), UtilError> { @@ -384,11 +441,13 @@ impl Session { /// Disconnect this session pub fn disconnect(&mut self, reason: DisconnectReason) -> NetworkError { - let mut rlp = RlpStream::new(); - rlp.append(&(PACKET_DISCONNECT as u32)); - rlp.begin_list(1); - rlp.append(&(reason as u32)); - self.connection.send_packet(&rlp.out()).ok(); + if let State::Session(_) = self.state { + let mut rlp = RlpStream::new(); + rlp.append(&(PACKET_DISCONNECT as u32)); + rlp.begin_list(1); + rlp.append(&(reason as u32)); + self.send(rlp).ok(); + } NetworkError::Disconnect(reason) } @@ -400,7 +459,15 @@ impl Session { } fn send(&mut self, rlp: RlpStream) -> Result<(), UtilError> { - self.connection.send_packet(&rlp.out()) + match self.state { + State::Handshake(_) => { + warn!(target:"network", "Unexpected send request"); + }, + State::Session(ref mut s) => { + try!(s.send_packet(&rlp.out())) + }, + } + Ok(()) } } From 7ad9c73c7523d3183ec4e84ed4b66a9abeac741a Mon Sep 17 00:00:00 2001 From: Nikolay Volf Date: Thu, 2 Jun 2016 11:51:03 +0200 Subject: [PATCH 2/3] devtools helpers extended (#1186) * devtools extensions * some doc effort --- devtools/src/lib.rs | 2 ++ devtools/src/random_path.rs | 12 +++++++++- devtools/src/stop_guard.rs | 45 +++++++++++++++++++++++++++++++++++++ 3 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 devtools/src/stop_guard.rs diff --git a/devtools/src/lib.rs b/devtools/src/lib.rs index 3ab585d93..e37d5c528 100644 --- a/devtools/src/lib.rs +++ b/devtools/src/lib.rs @@ -21,6 +21,8 @@ extern crate rand; pub mod random_path; pub mod test_socket; +pub mod stop_guard; pub use random_path::*; pub use test_socket::*; +pub use stop_guard::*; diff --git a/devtools/src/random_path.rs b/devtools/src/random_path.rs index 990d375e3..09bee7e45 100644 --- a/devtools/src/random_path.rs +++ b/devtools/src/random_path.rs @@ -26,7 +26,11 @@ pub struct RandomTempPath { } pub fn random_filename() -> String { - (0..8).map(|_| ((random::() * 26.0) as u8 + 97) as char).collect() + random_str(8) +} + +pub fn random_str(len: usize) -> String { + (0..len).map(|_| ((random::() * 26.0) as u8 + 97) as char).collect() } impl RandomTempPath { @@ -54,6 +58,12 @@ impl RandomTempPath { pub fn as_str(&self) -> &str { self.path.to_str().unwrap() } + + pub fn new_in(&self, name: &str) -> String { + let mut path = self.path.clone(); + path.push(name); + path.to_str().unwrap().to_owned() + } } impl Drop for RandomTempPath { diff --git a/devtools/src/stop_guard.rs b/devtools/src/stop_guard.rs new file mode 100644 index 000000000..f1db59725 --- /dev/null +++ b/devtools/src/stop_guard.rs @@ -0,0 +1,45 @@ +// Copyright 2015, 2016 Ethcore (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +//! Stop guard mod + +use std::sync::Arc; +use std::sync::atomic::*; + +/// Stop guard that will set a stop flag on drop +pub struct StopGuard { + flag: Arc, +} + +impl StopGuard { + /// Create a stop guard + pub fn new() -> StopGuard { + StopGuard { + flag: Arc::new(AtomicBool::new(false)) + } + } + + /// Share stop guard between the threads + pub fn share(&self) -> Arc { + self.flag.clone() + } +} + +impl Drop for StopGuard { + fn drop(&mut self) { + self.flag.store(true, Ordering::Relaxed) + } +} From b17581d7ded39250602b6d15324ff5bff26f26c1 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Thu, 2 Jun 2016 12:40:31 +0200 Subject: [PATCH 3/3] 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. --- ethcore/res/ethereum/morden.json | 11 +- ethcore/src/client/client.rs | 8 +- ethcore/src/client/mod.rs | 3 +- ethcore/src/client/test_client.rs | 2 +- ethcore/src/evm/ext.rs | 6 + ethcore/src/evm/instructions.rs | 3 +- ethcore/src/evm/interpreter.rs | 63 +++++++- ethcore/src/evm/jit.rs | 1 + ethcore/src/evm/mod.rs | 1 + ethcore/src/executive.rs | 188 +++++++++++++++++----- ethcore/src/externalities.rs | 44 +++-- ethcore/src/json_tests/executive.rs | 36 +++-- ethcore/src/lib.rs | 2 + ethcore/src/miner/miner.rs | 7 +- ethcore/src/miner/mod.rs | 2 +- ethcore/src/state.rs | 8 +- ethcore/src/trace/executive_tracer.rs | 52 +++++- ethcore/src/trace/mod.rs | 27 +++- ethcore/src/trace/noop_tracer.rs | 24 ++- ethcore/src/types/executed.rs | 4 +- ethcore/src/types/trace_types/trace.rs | 164 +++++++++++++++++++ json/src/state/transaction.rs | 2 +- parity/rpc_apis.rs | 2 +- rpc/src/lib.rs | 1 + rpc/src/v1/impls/eth.rs | 8 +- rpc/src/v1/impls/ethcore.rs | 99 +++++++++++- rpc/src/v1/tests/eth.rs | 3 +- rpc/src/v1/tests/helpers/miner_service.rs | 2 +- rpc/src/v1/tests/mocked/eth.rs | 6 +- rpc/src/v1/tests/mocked/ethcore.rs | 61 ++++--- rpc/src/v1/traits/ethcore.rs | 4 + rpc/src/v1/types/block.rs | 2 +- rpc/src/v1/types/transaction.rs | 19 ++- util/src/common.rs | 7 + util/src/rlp/rlpstream.rs | 12 ++ util/src/rlp/untrusted_rlp.rs | 30 ++-- 36 files changed, 742 insertions(+), 172 deletions(-) diff --git a/ethcore/res/ethereum/morden.json b/ethcore/res/ethereum/morden.json index 1e385558d..0cc88ac64 100644 --- a/ethcore/res/ethereum/morden.json +++ b/ethcore/res/ethereum/morden.json @@ -34,17 +34,8 @@ "gasLimit": "0x2fefd8" }, "nodes": [ - "enode://b1217cbaa440e35ed471157123fe468e19e8b5ad5bedb4b1fdbcbdab6fb2f5ed3e95dd9c24a22a79fdb2352204cea207df27d92bfd21bfd41545e8b16f637499@104.44.138.37:30303", - "enode://7ee7195bfac561ec938a72cd84cd1a5d2b334415263feddc325b20b5010446fc6c361297d13decab4039028fa659c1e27cca1396574b87cc7b29eea2985e97fe@108.61.197.28:30303", - "enode://933c5d5470b77537e7d9c1ee686132b5032dd3e2a096d2f64d2004df4ce9fca4ad6da5e358edcc8f81e65f047e40045600181f5fb35066e771025f6cca8e7952@46.101.114.191:30303", - "enode://ad4028ba28783d5bf58f512cb4e24a8ce980d768177c4974e1140b16b925132c947349db9ca3646752891b382dafc839a0c0716c3764c1ed9d424f09d13d01cf@148.251.220.116:30303", - "enode://c54ddaacddc7029683c80edae91015520eb2712176fbe6fdb7a5a074659270638f1266cba1731681c7cb785bceb02ca8d8b23024e3ec736fc5579f2042be97ae@54.175.255.230:30303", - "enode://ceb5c0f85eb994dbe9693bf46d99b03f6b838d17cc74e68d5eb003171ff39e5f120b17f965b267c319303f94d80b9d994b77062fb1486d76ce95d9f3d8fe1cb4@46.101.122.141:30303", "enode://e731347db0521f3476e6bbbb83375dcd7133a1601425ebd15fd10f3835fd4c304fba6282087ca5a0deeafadf0aa0d4fd56c3323331901c1f38bd181c283e3e35@128.199.55.137:30303", - "enode://e941c58fed2709d792f552f408d2162c3d0a5597d22d1da617a9c9e6181f3251056a96adb45ae22eba70119355227298dc7e6dff805b092bae7da2f8564de422@85.25.217.23:30303", - "enode://f4b73c9d11a780293ff0ca7afa12c67797afdc33a4797a7c2ecc5b87e455b32a8b9e9804f2004072bac38350bf82d52521d1a09590d2079705fc8357aef2bf9c@71.202.223.50:56603", - "enode://1173eea53e0cb2b8da92423e44cf4cbafbc8ea16c1558cf06e18dfc5a2fc9b140cc802a4362b4c773fb1442541e6f2a225b200bb4c1f6b347e7510a50fa4873f@104.41.138.167:30300", - "enode://1aad341327808738ad34655611f1b13293c4155dde36c8e3788128829f15cc6db2da9435f29520553d4efc134aadc50115690194ac3af519aac7a388b524811e@109.188.125.2:30303" + "enode://ceb5c0f85eb994dbe9693bf46d99b03f6b838d17cc74e68d5eb003171ff39e5f120b17f965b267c319303f94d80b9d994b77062fb1486d76ce95d9f3d8fe1cb4@46.101.122.141:30303" ], "accounts": { "0000000000000000000000000000000000000001": { "balance": "1", "nonce": "1048576", "builtin": { "name": "ecrecover", "pricing": { "linear": { "base": 3000, "word": 0 } } } }, diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index 3aaeb02c3..3ddf7018a 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -436,7 +436,7 @@ impl Client where V: Verifier { } impl BlockChainClient for Client where V: Verifier { - fn call(&self, t: &SignedTransaction) -> Result { + fn call(&self, t: &SignedTransaction, vm_tracing: bool) -> Result { let header = self.block_header(BlockID::Latest).unwrap(); let view = HeaderView::new(&header); let last_hashes = self.build_last_hashes(view.hash()); @@ -456,10 +456,10 @@ impl BlockChainClient for Client where V: Verifier { ExecutionError::TransactionMalformed(message) })); let balance = state.balance(&sender); - // give the sender max balance + // give the sender a decent balance state.sub_balance(&sender, &balance); - state.add_balance(&sender, &U256::max_value()); - let options = TransactOptions { tracing: false, check_nonce: false }; + state.add_balance(&sender, &(U256::from(1) << 200)); + 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) } diff --git a/ethcore/src/client/mod.rs b/ethcore/src/client/mod.rs index 3f64cb620..84eea7494 100644 --- a/ethcore/src/client/mod.rs +++ b/ethcore/src/client/mod.rs @@ -157,7 +157,8 @@ pub trait BlockChainClient : Sync + Send { fn logs(&self, filter: Filter) -> Vec; /// Makes a non-persistent transaction call. - fn call(&self, t: &SignedTransaction) -> Result; + // TODO: should be able to accept blockchain location for call. + fn call(&self, t: &SignedTransaction, vm_tracing: bool) -> Result; /// Returns EvmFactory. fn vm_factory(&self) -> &EvmFactory; diff --git a/ethcore/src/client/test_client.rs b/ethcore/src/client/test_client.rs index 83799c78f..f3768d0a6 100644 --- a/ethcore/src/client/test_client.rs +++ b/ethcore/src/client/test_client.rs @@ -251,7 +251,7 @@ impl MiningBlockChainClient for TestBlockChainClient { } impl BlockChainClient for TestBlockChainClient { - fn call(&self, _t: &SignedTransaction) -> Result { + fn call(&self, _t: &SignedTransaction, _vm_tracing: bool) -> Result { Ok(self.execution_result.read().unwrap().clone().unwrap()) } diff --git a/ethcore/src/evm/ext.rs b/ethcore/src/evm/ext.rs index 4986b12c8..1c21e467a 100644 --- a/ethcore/src/evm/ext.rs +++ b/ethcore/src/evm/ext.rs @@ -105,4 +105,10 @@ pub trait Ext { /// Increments sstore refunds count by 1. fn inc_sstore_clears(&mut self); + + /// Prepare to trace an operation. Passthrough for the VM trace. + fn trace_prepare_execute(&mut self, _pc: usize, _instruction: u8, _gas_cost: &U256) -> bool { false } + + /// Trace the finalised execution of a single instruction. + fn trace_executed(&mut self, _gas_used: U256, _stack_push: &[U256], _mem_diff: Option<(usize, &[u8])>, _store_diff: Option<(U256, U256)>) {} } diff --git a/ethcore/src/evm/instructions.rs b/ethcore/src/evm/instructions.rs index 6a1a06ba9..c313726f1 100644 --- a/ethcore/src/evm/instructions.rs +++ b/ethcore/src/evm/instructions.rs @@ -124,6 +124,7 @@ pub struct InstructionInfo { pub side_effects: bool, pub tier: GasPriceTier } + impl InstructionInfo { pub fn new(name: &'static str, additional: usize, args: usize, ret: usize, side_effects: bool, tier: GasPriceTier) -> InstructionInfo { InstructionInfo { @@ -139,7 +140,7 @@ impl InstructionInfo { #[cfg_attr(rustfmt, rustfmt_skip)] /// Return details about specific instruction -pub fn get_info (instruction: Instruction) -> InstructionInfo { +pub fn get_info(instruction: Instruction) -> InstructionInfo { match instruction { STOP => InstructionInfo::new("STOP", 0, 0, 0, true, GasPriceTier::Zero), ADD => InstructionInfo::new("ADD", 0, 2, 1, false, GasPriceTier::VeryLow), diff --git a/ethcore/src/evm/interpreter.rs b/ethcore/src/evm/interpreter.rs index eb29ef257..9cf4d6034 100644 --- a/ethcore/src/evm/interpreter.rs +++ b/ethcore/src/evm/interpreter.rs @@ -17,8 +17,9 @@ ///! Rust VM implementation use common::*; +use trace::VMTracer; use super::instructions as instructions; -use super::instructions::Instruction; +use super::instructions::{Instruction, get_info}; use std::marker::Copy; use evm::{self, MessageCallResult, ContractCreateResult}; @@ -69,6 +70,8 @@ trait Stack { fn push(&mut self, elem: T); /// Get number of elements on Stack fn size(&self) -> usize; + /// Returns all data on stack. + fn peek_top(&mut self, no_of_elems: usize) -> &[T]; } struct VecStack { @@ -131,6 +134,11 @@ impl Stack for VecStack { fn size(&self) -> usize { self.stack.len() } + + fn peek_top(&mut self, no_from_top: usize) -> &[S] { + assert!(self.stack.len() >= no_from_top, "peek_top asked for more items than exist."); + &self.stack[self.stack.len() - no_from_top .. self.stack.len()] + } } trait Memory { @@ -293,10 +301,15 @@ impl evm::Evm for Interpreter { while reader.position < code.len() { let instruction = code[reader.position]; - reader.position += 1; // Calculate gas cost let (gas_cost, mem_size) = try!(self.get_gas_cost_mem(ext, instruction, &mut mem, &stack)); + + // TODO: make compile-time removable if too much of a performance hit. + let trace_executed = ext.trace_prepare_execute(reader.position, instruction, &gas_cost); + + reader.position += 1; + try!(self.verify_gas(¤t_gas, &gas_cost)); mem.expand(mem_size); current_gas = current_gas - gas_cost; //TODO: use operator -= @@ -311,10 +324,19 @@ impl evm::Evm for Interpreter { ); }); + let (mem_written, store_written) = match trace_executed { + true => (Self::mem_written(instruction, &stack), Self::store_written(instruction, &stack)), + false => (None, None), + }; + // Execute instruction let result = try!(self.exec_instruction( current_gas, ¶ms, ext, instruction, &mut reader, &mut mem, &mut stack - )); + )); + + if trace_executed { + ext.trace_executed(current_gas, stack.peek_top(get_info(instruction).ret), mem_written.map(|(o, s)| (o, &(mem[o..(o + s)]))), store_written); + } // Advance match result { @@ -485,6 +507,31 @@ impl Interpreter { } } + fn mem_written( + instruction: Instruction, + stack: &Stack + ) -> Option<(usize, usize)> { + match instruction { + instructions::MSTORE | instructions::MLOAD => Some((stack.peek(0).low_u64() as usize, 32)), + instructions::MSTORE8 => Some((stack.peek(0).low_u64() as usize, 1)), + instructions::CALLDATACOPY | instructions::CODECOPY => Some((stack.peek(0).low_u64() as usize, stack.peek(2).low_u64() as usize)), + instructions::EXTCODECOPY => Some((stack.peek(1).low_u64() as usize, stack.peek(3).low_u64() as usize)), + instructions::CALL | instructions::CALLCODE => Some((stack.peek(5).low_u64() as usize, stack.peek(6).low_u64() as usize)), + instructions::DELEGATECALL => Some((stack.peek(4).low_u64() as usize, stack.peek(5).low_u64() as usize)), + _ => None, + } + } + + fn store_written( + instruction: Instruction, + stack: &Stack + ) -> Option<(U256, U256)> { + match instruction { + instructions::SSTORE => Some((stack.peek(0).clone(), stack.peek(1).clone())), + _ => None, + } + } + fn mem_gas_cost(&self, schedule: &evm::Schedule, current_mem_size: usize, mem_size: &U256) -> Result<(U256, usize), evm::Error> { let gas_for_mem = |mem_size: U256| { let s = mem_size >> 5; @@ -833,10 +880,12 @@ impl Interpreter { } } - fn verify_instructions_requirements(&self, - info: &instructions::InstructionInfo, - stack_limit: usize, - stack: &Stack) -> Result<(), evm::Error> { + fn verify_instructions_requirements( + &self, + info: &instructions::InstructionInfo, + stack_limit: usize, + stack: &Stack + ) -> Result<(), evm::Error> { if !stack.has(info.args) { Err(evm::Error::StackUnderflow { instruction: info.name, diff --git a/ethcore/src/evm/jit.rs b/ethcore/src/evm/jit.rs index 6a22a1306..694c1668a 100644 --- a/ethcore/src/evm/jit.rs +++ b/ethcore/src/evm/jit.rs @@ -16,6 +16,7 @@ //! Just in time compiler execution environment. use common::*; +use trace::VMTracer; use evmjit; use evm; diff --git a/ethcore/src/evm/mod.rs b/ethcore/src/evm/mod.rs index b7816b99c..06ce5e7e8 100644 --- a/ethcore/src/evm/mod.rs +++ b/ethcore/src/evm/mod.rs @@ -33,3 +33,4 @@ pub use self::evm::{Evm, Error, Result}; pub use self::ext::{Ext, ContractCreateResult, MessageCallResult}; pub use self::factory::{Factory, VMType}; pub use self::schedule::Schedule; +pub use self::instructions::get_info; diff --git a/ethcore/src/executive.rs b/ethcore/src/executive.rs index 291b4dba2..7a48e435e 100644 --- a/ethcore/src/executive.rs +++ b/ethcore/src/executive.rs @@ -21,9 +21,8 @@ use engine::*; use evm::{self, Ext, Factory}; use externalities::*; use substate::*; -use trace::{Trace, Tracer, NoopTracer, ExecutiveTracer}; +use trace::{Trace, Tracer, NoopTracer, ExecutiveTracer, VMTrace, VMTracer, ExecutiveVMTracer, NoopVMTracer}; use crossbeam; - pub use types::executed::{Executed, ExecutionResult}; /// Max depth to avoid stack overflow (when it's reached we start a new thread with VM) @@ -43,6 +42,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, } @@ -80,21 +81,40 @@ impl<'a> Executive<'a> { } /// Creates `Externalities` from `Executive`. - pub fn as_externalities<'_, T>(&'_ mut self, origin_info: OriginInfo, substate: &'_ mut Substate, output: OutputPolicy<'_, '_>, tracer: &'_ mut T) -> Externalities<'_, T> where T: Tracer { - Externalities::new(self.state, self.info, self.engine, self.vm_factory, self.depth, origin_info, substate, output, tracer) + pub fn as_externalities<'_, T, V>( + &'_ mut self, + origin_info: OriginInfo, + substate: &'_ mut Substate, + output: OutputPolicy<'_, '_>, + tracer: &'_ mut T, + vm_tracer: &'_ mut V + ) -> Externalities<'_, T, V> where T: Tracer, V: VMTracer { + Externalities::new(self.state, self.info, self.engine, self.vm_factory, self.depth, origin_info, substate, output, tracer, vm_tracer) } /// This function should be used to execute transaction. pub fn transact(&'a mut self, t: &SignedTransaction, options: TransactOptions) -> Result { let check = options.check_nonce; match options.tracing { - true => self.transact_with_tracer(t, check, ExecutiveTracer::default()), - false => self.transact_with_tracer(t, check, NoopTracer), + true => match options.vm_tracing { + true => self.transact_with_tracer(t, check, ExecutiveTracer::default(), ExecutiveVMTracer::default()), + false => self.transact_with_tracer(t, check, ExecutiveTracer::default(), NoopVMTracer), + }, + false => match options.vm_tracing { + true => self.transact_with_tracer(t, check, NoopTracer, ExecutiveVMTracer::default()), + false => self.transact_with_tracer(t, check, NoopTracer, NoopVMTracer), + }, } } /// Execute transaction/call with tracing enabled - pub fn transact_with_tracer(&'a mut self, t: &SignedTransaction, check_nonce: bool, mut tracer: T) -> Result where T: Tracer { + pub fn transact_with_tracer( + &'a mut self, + t: &SignedTransaction, + check_nonce: bool, + mut tracer: T, + mut vm_tracer: V + ) -> Result where T: Tracer, V: VMTracer { let sender = try!(t.sender().map_err(|e| { let message = format!("Transaction malformed: {:?}", e); ExecutionError::TransactionMalformed(message) @@ -154,7 +174,7 @@ impl<'a> Executive<'a> { code: Some(t.data.clone()), data: None, }; - (self.create(params, &mut substate, &mut tracer), vec![]) + (self.create(params, &mut substate, &mut tracer, &mut vm_tracer), vec![]) }, Action::Call(ref address) => { let params = ActionParams { @@ -170,20 +190,26 @@ impl<'a> Executive<'a> { }; // TODO: move output upstream let mut out = vec![]; - (self.call(params, &mut substate, BytesRef::Flexible(&mut out), &mut tracer), out) + (self.call(params, &mut substate, BytesRef::Flexible(&mut out), &mut tracer, &mut vm_tracer), out) } }; // finalize here! - Ok(try!(self.finalize(t, substate, gas_left, output, tracer.traces().pop()))) + Ok(try!(self.finalize(t, substate, gas_left, output, tracer.traces().pop(), vm_tracer.drain()))) } - fn exec_vm(&mut self, params: ActionParams, unconfirmed_substate: &mut Substate, output_policy: OutputPolicy, tracer: &mut T) - -> evm::Result where T: Tracer { + fn exec_vm( + &mut self, + params: ActionParams, + unconfirmed_substate: &mut Substate, + output_policy: OutputPolicy, + tracer: &mut T, + vm_tracer: &mut V + ) -> evm::Result where T: Tracer, V: VMTracer { // Ordinary execution - keep VM in same thread if (self.depth + 1) % MAX_VM_DEPTH_FOR_THREAD != 0 { let vm_factory = self.vm_factory; - let mut ext = self.as_externalities(OriginInfo::from(¶ms), unconfirmed_substate, output_policy, tracer); + let mut ext = self.as_externalities(OriginInfo::from(¶ms), unconfirmed_substate, output_policy, tracer, vm_tracer); trace!(target: "executive", "ext.schedule.have_delegate_call: {}", ext.schedule().have_delegate_call); return vm_factory.create().exec(params, &mut ext); } @@ -193,7 +219,7 @@ impl<'a> Executive<'a> { // https://github.com/aturon/crossbeam/issues/16 crossbeam::scope(|scope| { let vm_factory = self.vm_factory; - let mut ext = self.as_externalities(OriginInfo::from(¶ms), unconfirmed_substate, output_policy, tracer); + let mut ext = self.as_externalities(OriginInfo::from(¶ms), unconfirmed_substate, output_policy, tracer, vm_tracer); scope.spawn(move || { vm_factory.create().exec(params, &mut ext) @@ -205,8 +231,14 @@ impl<'a> Executive<'a> { /// NOTE. It does not finalize the transaction (doesn't do refunds, nor suicides). /// Modifies the substate and the output. /// Returns either gas_left or `evm::Error`. - pub fn call(&mut self, params: ActionParams, substate: &mut Substate, mut output: BytesRef, tracer: &mut T) - -> evm::Result where T: Tracer { + pub fn call( + &mut self, + params: ActionParams, + substate: &mut Substate, + mut output: BytesRef, + tracer: &mut T, + vm_tracer: &mut V + ) -> evm::Result where T: Tracer, V: VMTracer { // backup used in case of running out of gas self.state.snapshot(); @@ -264,16 +296,22 @@ impl<'a> Executive<'a> { let trace_info = tracer.prepare_trace_call(¶ms); let mut trace_output = tracer.prepare_trace_output(); let mut subtracer = tracer.subtracer(); + let gas = params.gas; if params.code.is_some() { // part of substate that may be reverted let mut unconfirmed_substate = Substate::new(); + // TODO: make ActionParams pass by ref then avoid copy altogether. + let mut subvmtracer = vm_tracer.prepare_subtrace(params.code.as_ref().expect("scope is protected by params.code.is_some condition")); + let res = { - self.exec_vm(params, &mut unconfirmed_substate, OutputPolicy::Return(output, trace_output.as_mut()), &mut subtracer) + self.exec_vm(params, &mut unconfirmed_substate, OutputPolicy::Return(output, trace_output.as_mut()), &mut subtracer, &mut subvmtracer) }; + vm_tracer.done_subtrace(subvmtracer); + trace!(target: "executive", "res={:?}", res); let traces = subtracer.traces(); @@ -307,8 +345,13 @@ impl<'a> Executive<'a> { /// Creates contract with given contract params. /// NOTE. It does not finalize the transaction (doesn't do refunds, nor suicides). /// Modifies the substate. - pub fn create(&mut self, params: ActionParams, substate: &mut Substate, tracer: &mut T) -> evm::Result where T: - Tracer { + pub fn create( + &mut self, + params: ActionParams, + substate: &mut Substate, + tracer: &mut T, + vm_tracer: &mut V + ) -> evm::Result where T: Tracer, V: VMTracer { // backup used in case of running out of gas self.state.snapshot(); @@ -330,10 +373,14 @@ impl<'a> Executive<'a> { let gas = params.gas; let created = params.address.clone(); + let mut subvmtracer = vm_tracer.prepare_subtrace(¶ms.code.as_ref().expect("two ways into create (Externalities::create and Executive::transact_with_tracer); both place `Some(...)` `code` in `params`; qed")); + let res = { - self.exec_vm(params, &mut unconfirmed_substate, OutputPolicy::InitContract(trace_output.as_mut()), &mut subtracer) + self.exec_vm(params, &mut unconfirmed_substate, OutputPolicy::InitContract(trace_output.as_mut()), &mut subtracer, &mut subvmtracer) }; + vm_tracer.done_subtrace(subvmtracer); + match res { Ok(gas_left) => tracer.trace_create( trace_info, @@ -351,7 +398,15 @@ impl<'a> Executive<'a> { } /// Finalizes the transaction (does refunds and suicides). - fn finalize(&mut self, t: &SignedTransaction, substate: Substate, result: evm::Result, output: Bytes, trace: Option) -> ExecutionResult { + fn finalize( + &mut self, + t: &SignedTransaction, + substate: Substate, + result: evm::Result, + output: Bytes, + trace: Option, + vm_trace: Option + ) -> ExecutionResult { let schedule = self.engine.schedule(self.info); // refunds from SSTORE nonzero -> zero @@ -394,6 +449,7 @@ impl<'a> Executive<'a> { contracts_created: vec![], output: output, trace: trace, + vm_trace: vm_trace, }) }, _ => { @@ -406,6 +462,7 @@ impl<'a> Executive<'a> { contracts_created: substate.contracts_created, output: output, trace: trace, + vm_trace: vm_trace, }) }, } @@ -438,6 +495,7 @@ mod tests { use tests::helpers::*; use trace::trace; use trace::{Trace, Tracer, NoopTracer, ExecutiveTracer}; + use trace::{VMTrace, VMOperation, VMExecutedOperation, MemoryDiff, StorageDiff, VMTracer, NoopVMTracer, ExecutiveVMTracer}; #[test] fn test_contract_address() { @@ -466,7 +524,7 @@ mod tests { let gas_left = { let mut ex = Executive::new(&mut state, &info, &engine, &factory); - ex.create(params, &mut substate, &mut NoopTracer).unwrap() + ex.create(params, &mut substate, &mut NoopTracer, &mut NoopVMTracer).unwrap() }; assert_eq!(gas_left, U256::from(79_975)); @@ -525,7 +583,7 @@ mod tests { let gas_left = { let mut ex = Executive::new(&mut state, &info, &engine, &factory); - ex.create(params, &mut substate, &mut NoopTracer).unwrap() + ex.create(params, &mut substate, &mut NoopTracer, &mut NoopVMTracer).unwrap() }; assert_eq!(gas_left, U256::from(62_976)); @@ -542,7 +600,7 @@ mod tests { // 52 // 60 1d - push 29 // 60 03 - push 3 - // 60 17 - push 17 + // 60 17 - push 23 // f0 - create // 60 00 - push 0 // 55 sstore @@ -578,13 +636,16 @@ mod tests { let engine = TestEngine::new(5); let mut substate = Substate::new(); let mut tracer = ExecutiveTracer::default(); + let mut vm_tracer = ExecutiveVMTracer::default(); let gas_left = { let mut ex = Executive::new(&mut state, &info, &engine, &factory); let output = BytesRef::Fixed(&mut[0u8;0]); - ex.call(params, &mut substate, output, &mut tracer).unwrap() + ex.call(params, &mut substate, output, &mut tracer, &mut vm_tracer).unwrap() }; + assert_eq!(gas_left, U256::from(44_752)); + let expected_trace = vec![ Trace { depth: 0, action: trace::Action::Call(trace::Call { @@ -615,7 +676,39 @@ mod tests { }] }]; assert_eq!(tracer.traces(), expected_trace); - assert_eq!(gas_left, U256::from(44_752)); + + let expected_vm_trace = VMTrace { + parent_step: 0, + code: vec![124, 96, 16, 128, 96, 12, 96, 0, 57, 96, 0, 243, 0, 96, 0, 53, 84, 21, 96, 9, 87, 0, 91, 96, 32, 53, 96, 0, 53, 85, 96, 0, 82, 96, 29, 96, 3, 96, 23, 240, 96, 0, 85], + operations: vec![ + VMOperation { pc: 0, instruction: 124, gas_cost: 3.into(), executed: Some(VMExecutedOperation { gas_used: 99997.into(), stack_push: vec_into![U256::from_dec_str("2589892687202724018173567190521546555304938078595079151649957320078677").unwrap()], mem_diff: None, store_diff: None }) }, + VMOperation { pc: 30, instruction: 96, gas_cost: 3.into(), executed: Some(VMExecutedOperation { gas_used: 99994.into(), stack_push: vec_into![0], mem_diff: None, store_diff: None }) }, + VMOperation { pc: 32, instruction: 82, gas_cost: 6.into(), executed: Some(VMExecutedOperation { gas_used: 99988.into(), stack_push: vec_into![], mem_diff: Some(MemoryDiff { offset: 0, data: vec![0, 0, 0, 96, 16, 128, 96, 12, 96, 0, 57, 96, 0, 243, 0, 96, 0, 53, 84, 21, 96, 9, 87, 0, 91, 96, 32, 53, 96, 0, 53, 85] }), store_diff: None }) }, + VMOperation { pc: 33, instruction: 96, gas_cost: 3.into(), executed: Some(VMExecutedOperation { gas_used: 99985.into(), stack_push: vec_into![29], mem_diff: None, store_diff: None }) }, + VMOperation { pc: 35, instruction: 96, gas_cost: 3.into(), executed: Some(VMExecutedOperation { gas_used: 99982.into(), stack_push: vec_into![3], mem_diff: None, store_diff: None }) }, + VMOperation { pc: 37, instruction: 96, gas_cost: 3.into(), executed: Some(VMExecutedOperation { gas_used: 99979.into(), stack_push: vec_into![23], mem_diff: None, store_diff: None }) }, + VMOperation { pc: 39, instruction: 240, gas_cost: 32000.into(), executed: Some(VMExecutedOperation { gas_used: 67979.into(), stack_push: vec_into![U256::from_dec_str("1135198453258042933984631383966629874710669425204").unwrap()], mem_diff: None, store_diff: None }) }, + VMOperation { pc: 40, instruction: 96, gas_cost: 3.into(), executed: Some(VMExecutedOperation { gas_used: 64752.into(), stack_push: vec_into![0], mem_diff: None, store_diff: None }) }, + VMOperation { pc: 42, instruction: 85, gas_cost: 20000.into(), executed: Some(VMExecutedOperation { gas_used: 44752.into(), stack_push: vec_into![], mem_diff: None, store_diff: Some(StorageDiff { location: 0.into(), value: U256::from_dec_str("1135198453258042933984631383966629874710669425204").unwrap() }) }) } + ], + subs: vec![ + VMTrace { + parent_step: 7, + code: vec![96, 16, 128, 96, 12, 96, 0, 57, 96, 0, 243, 0, 96, 0, 53, 84, 21, 96, 9, 87, 0, 91, 96, 32, 53, 96, 0, 53, 85], + operations: vec![ + VMOperation { pc: 0, instruction: 96, gas_cost: 3.into(), executed: Some(VMExecutedOperation { gas_used: 67976.into(), stack_push: vec_into![16], mem_diff: None, store_diff: None }) }, + VMOperation { pc: 2, instruction: 128, gas_cost: 3.into(), executed: Some(VMExecutedOperation { gas_used: 67973.into(), stack_push: vec_into![16, 16], mem_diff: None, store_diff: None }) }, + VMOperation { pc: 3, instruction: 96, gas_cost: 3.into(), executed: Some(VMExecutedOperation { gas_used: 67970.into(), stack_push: vec_into![12], mem_diff: None, store_diff: None }) }, + VMOperation { pc: 5, instruction: 96, gas_cost: 3.into(), executed: Some(VMExecutedOperation { gas_used: 67967.into(), stack_push: vec_into![0], mem_diff: None, store_diff: None }) }, + VMOperation { pc: 7, instruction: 57, gas_cost: 9.into(), executed: Some(VMExecutedOperation { gas_used: 67958.into(), stack_push: vec_into![], mem_diff: Some(MemoryDiff { offset: 0, data: vec![96, 0, 53, 84, 21, 96, 9, 87, 0, 91, 96, 32, 53, 96, 0, 53] }), store_diff: None }) }, + VMOperation { pc: 8, instruction: 96, gas_cost: 3.into(), executed: Some(VMExecutedOperation { gas_used: 67955.into(), stack_push: vec_into![0], mem_diff: None, store_diff: None }) }, + VMOperation { pc: 10, instruction: 243, gas_cost: 0.into(), executed: Some(VMExecutedOperation { gas_used: 67955.into(), stack_push: vec_into![], mem_diff: None, store_diff: None }) } + ], + subs: vec![] + } + ] + }; + assert_eq!(vm_tracer.drain().unwrap(), expected_vm_trace); } evm_test!{test_create_contract: test_create_contract_jit, test_create_contract_int} @@ -650,12 +743,15 @@ mod tests { let engine = TestEngine::new(5); let mut substate = Substate::new(); let mut tracer = ExecutiveTracer::default(); + let mut vm_tracer = ExecutiveVMTracer::default(); let gas_left = { let mut ex = Executive::new(&mut state, &info, &engine, &factory); - ex.create(params.clone(), &mut substate, &mut tracer).unwrap() + ex.create(params.clone(), &mut substate, &mut tracer, &mut vm_tracer).unwrap() }; + assert_eq!(gas_left, U256::from(96_776)); + let expected_trace = vec![Trace { depth: 0, action: trace::Action::Create(trace::Create { @@ -671,9 +767,23 @@ mod tests { }), subs: vec![] }]; - assert_eq!(tracer.traces(), expected_trace); - assert_eq!(gas_left, U256::from(96_776)); + + let expected_vm_trace = VMTrace { + parent_step: 0, + code: vec![96, 16, 128, 96, 12, 96, 0, 57, 96, 0, 243, 0, 96, 0, 53, 84, 21, 96, 9, 87, 0, 91, 96, 32, 53, 96, 0, 53, 85], + operations: vec![ + VMOperation { pc: 0, instruction: 96, gas_cost: 3.into(), executed: Some(VMExecutedOperation { gas_used: 99997.into(), stack_push: vec_into![16], mem_diff: None, store_diff: None }) }, + VMOperation { pc: 2, instruction: 128, gas_cost: 3.into(), executed: Some(VMExecutedOperation { gas_used: 99994.into(), stack_push: vec_into![16, 16], mem_diff: None, store_diff: None }) }, + VMOperation { pc: 3, instruction: 96, gas_cost: 3.into(), executed: Some(VMExecutedOperation { gas_used: 99991.into(), stack_push: vec_into![12], mem_diff: None, store_diff: None }) }, + VMOperation { pc: 5, instruction: 96, gas_cost: 3.into(), executed: Some(VMExecutedOperation { gas_used: 99988.into(), stack_push: vec_into![0], mem_diff: None, store_diff: None }) }, + VMOperation { pc: 7, instruction: 57, gas_cost: 9.into(), executed: Some(VMExecutedOperation { gas_used: 99979.into(), stack_push: vec_into![], mem_diff: Some(MemoryDiff { offset: 0, data: vec![96, 0, 53, 84, 21, 96, 9, 87, 0, 91, 96, 32, 53, 96, 0, 53] }), store_diff: None }) }, + VMOperation { pc: 8, instruction: 96, gas_cost: 3.into(), executed: Some(VMExecutedOperation { gas_used: 99976.into(), stack_push: vec_into![0], mem_diff: None, store_diff: None }) }, + VMOperation { pc: 10, instruction: 243, gas_cost: 0.into(), executed: Some(VMExecutedOperation { gas_used: 99976.into(), stack_push: vec_into![], mem_diff: None, store_diff: None }) } + ], + subs: vec![] + }; + assert_eq!(vm_tracer.drain().unwrap(), expected_vm_trace); } evm_test!{test_create_contract_value_too_high: test_create_contract_value_too_high_jit, test_create_contract_value_too_high_int} @@ -722,7 +832,7 @@ mod tests { let gas_left = { let mut ex = Executive::new(&mut state, &info, &engine, &factory); - ex.create(params, &mut substate, &mut NoopTracer).unwrap() + ex.create(params, &mut substate, &mut NoopTracer, &mut NoopVMTracer).unwrap() }; assert_eq!(gas_left, U256::from(62_976)); @@ -774,7 +884,7 @@ mod tests { { let mut ex = Executive::new(&mut state, &info, &engine, &factory); - ex.create(params, &mut substate, &mut NoopTracer).unwrap(); + ex.create(params, &mut substate, &mut NoopTracer, &mut NoopVMTracer).unwrap(); } assert_eq!(substate.contracts_created.len(), 1); @@ -835,7 +945,7 @@ mod tests { let gas_left = { let mut ex = Executive::new(&mut state, &info, &engine, &factory); - ex.call(params, &mut substate, BytesRef::Fixed(&mut []), &mut NoopTracer).unwrap() + ex.call(params, &mut substate, BytesRef::Fixed(&mut []), &mut NoopTracer, &mut NoopVMTracer).unwrap() }; assert_eq!(gas_left, U256::from(73_237)); @@ -880,7 +990,7 @@ mod tests { let gas_left = { let mut ex = Executive::new(&mut state, &info, &engine, &factory); - ex.call(params, &mut substate, BytesRef::Fixed(&mut []), &mut NoopTracer).unwrap() + ex.call(params, &mut substate, BytesRef::Fixed(&mut []), &mut NoopTracer, &mut NoopVMTracer).unwrap() }; assert_eq!(gas_left, U256::from(59_870)); @@ -913,7 +1023,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 +1057,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 +1089,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 +1123,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 +1157,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) }; @@ -1082,7 +1192,7 @@ mod tests { let result = { let mut ex = Executive::new(&mut state, &info, &engine, &factory); - ex.create(params, &mut substate, &mut NoopTracer) + ex.create(params, &mut substate, &mut NoopTracer, &mut NoopVMTracer) }; match result { diff --git a/ethcore/src/externalities.rs b/ethcore/src/externalities.rs index 99d2eed72..675d1904b 100644 --- a/ethcore/src/externalities.rs +++ b/ethcore/src/externalities.rs @@ -21,7 +21,7 @@ use engine::*; use executive::*; use evm::{self, Schedule, Ext, ContractCreateResult, MessageCallResult, Factory}; use substate::*; -use trace::Tracer; +use trace::{Tracer, VMTracer}; /// Policy for handling output data on `RETURN` opcode. pub enum OutputPolicy<'a, 'b> { @@ -55,7 +55,7 @@ impl OriginInfo { } /// Implementation of evm Externalities. -pub struct Externalities<'a, T> where T: 'a + Tracer { +pub struct Externalities<'a, T, V> where T: 'a + Tracer, V: 'a + VMTracer { state: &'a mut State, env_info: &'a EnvInfo, engine: &'a Engine, @@ -66,10 +66,10 @@ pub struct Externalities<'a, T> where T: 'a + Tracer { schedule: Schedule, output: OutputPolicy<'a, 'a>, tracer: &'a mut T, + vm_tracer: &'a mut V, } -impl<'a, T> Externalities<'a, T> where T: 'a + Tracer { - +impl<'a, T, V> Externalities<'a, T, V> where T: 'a + Tracer, V: 'a + VMTracer { #[cfg_attr(feature="dev", allow(too_many_arguments))] /// Basic `Externalities` constructor. pub fn new(state: &'a mut State, @@ -81,6 +81,7 @@ impl<'a, T> Externalities<'a, T> where T: 'a + Tracer { substate: &'a mut Substate, output: OutputPolicy<'a, 'a>, tracer: &'a mut T, + vm_tracer: &'a mut V, ) -> Self { Externalities { state: state, @@ -93,11 +94,12 @@ impl<'a, T> Externalities<'a, T> where T: 'a + Tracer { schedule: engine.schedule(env_info), output: output, tracer: tracer, + vm_tracer: vm_tracer, } } } -impl<'a, T> Ext for Externalities<'a, T> where T: 'a + Tracer { +impl<'a, T, V> Ext for Externalities<'a, T, V> where T: 'a + Tracer, V: 'a + VMTracer { fn storage_at(&self, key: &H256) -> H256 { self.state.storage_at(&self.origin_info.address, key) } @@ -152,7 +154,7 @@ impl<'a, T> Ext for Externalities<'a, T> where T: 'a + Tracer { let mut ex = Executive::from_parent(self.state, self.env_info, self.engine, self.vm_factory, self.depth); // TODO: handle internal error separately - match ex.create(params, self.substate, self.tracer) { + match ex.create(params, self.substate, self.tracer, self.vm_tracer) { Ok(gas_left) => { self.substate.contracts_created.push(address.clone()); ContractCreateResult::Created(address, gas_left) @@ -190,7 +192,7 @@ impl<'a, T> Ext for Externalities<'a, T> where T: 'a + Tracer { let mut ex = Executive::from_parent(self.state, self.env_info, self.engine, self.vm_factory, self.depth); - match ex.call(params, self.substate, BytesRef::Fixed(output), self.tracer) { + match ex.call(params, self.substate, BytesRef::Fixed(output), self.tracer, self.vm_tracer) { Ok(gas_left) => MessageCallResult::Success(gas_left), _ => MessageCallResult::Failed } @@ -286,6 +288,14 @@ impl<'a, T> Ext for Externalities<'a, T> where T: 'a + Tracer { fn inc_sstore_clears(&mut self) { self.substate.sstore_clears_count = self.substate.sstore_clears_count + U256::one(); } + + fn trace_prepare_execute(&mut self, pc: usize, instruction: u8, gas_cost: &U256) -> bool { + self.vm_tracer.trace_prepare_execute(pc, instruction, gas_cost) + } + + fn trace_executed(&mut self, gas_used: U256, stack_push: &[U256], mem_diff: Option<(usize, &[u8])>, store_diff: Option<(U256, U256)>) { + self.vm_tracer.trace_executed(gas_used, stack_push, mem_diff, store_diff) + } } #[cfg(test)] @@ -297,7 +307,7 @@ mod tests { use substate::*; use tests::helpers::*; use super::*; - use trace::{NoopTracer}; + use trace::{NoopTracer, NoopVMTracer}; fn get_test_origin() -> OriginInfo { OriginInfo { @@ -349,9 +359,10 @@ mod tests { let mut setup = TestSetup::new(); let state = setup.state.reference_mut(); let mut tracer = NoopTracer; + let mut vm_tracer = NoopVMTracer; let vm_factory = Default::default(); - let ext = Externalities::new(state, &setup.env_info, &*setup.engine, &vm_factory, 0, get_test_origin(), &mut setup.sub_state, OutputPolicy::InitContract(None), &mut tracer); + let ext = Externalities::new(state, &setup.env_info, &*setup.engine, &vm_factory, 0, get_test_origin(), &mut setup.sub_state, OutputPolicy::InitContract(None), &mut tracer, &mut vm_tracer); assert_eq!(ext.env_info().number, 100); } @@ -361,9 +372,10 @@ mod tests { let mut setup = TestSetup::new(); let state = setup.state.reference_mut(); let mut tracer = NoopTracer; + let mut vm_tracer = NoopVMTracer; let vm_factory = Default::default(); - let ext = Externalities::new(state, &setup.env_info, &*setup.engine, &vm_factory, 0, get_test_origin(), &mut setup.sub_state, OutputPolicy::InitContract(None), &mut tracer); + let ext = Externalities::new(state, &setup.env_info, &*setup.engine, &vm_factory, 0, get_test_origin(), &mut setup.sub_state, OutputPolicy::InitContract(None), &mut tracer, &mut vm_tracer); let hash = ext.blockhash(&U256::from_str("0000000000000000000000000000000000000000000000000000000000120000").unwrap()); @@ -383,9 +395,10 @@ mod tests { } let state = setup.state.reference_mut(); let mut tracer = NoopTracer; + let mut vm_tracer = NoopVMTracer; let vm_factory = Default::default(); - let ext = Externalities::new(state, &setup.env_info, &*setup.engine, &vm_factory, 0, get_test_origin(), &mut setup.sub_state, OutputPolicy::InitContract(None), &mut tracer); + let ext = Externalities::new(state, &setup.env_info, &*setup.engine, &vm_factory, 0, get_test_origin(), &mut setup.sub_state, OutputPolicy::InitContract(None), &mut tracer, &mut vm_tracer); let hash = ext.blockhash(&U256::from_str("0000000000000000000000000000000000000000000000000000000000120000").unwrap()); @@ -398,9 +411,10 @@ mod tests { let mut setup = TestSetup::new(); let state = setup.state.reference_mut(); let mut tracer = NoopTracer; + let mut vm_tracer = NoopVMTracer; let vm_factory = Default::default(); - let mut ext = Externalities::new(state, &setup.env_info, &*setup.engine, &vm_factory, 0, get_test_origin(), &mut setup.sub_state, OutputPolicy::InitContract(None), &mut tracer); + let mut ext = Externalities::new(state, &setup.env_info, &*setup.engine, &vm_factory, 0, get_test_origin(), &mut setup.sub_state, OutputPolicy::InitContract(None), &mut tracer, &mut vm_tracer); let mut output = vec![]; @@ -423,10 +437,11 @@ mod tests { let mut setup = TestSetup::new(); let state = setup.state.reference_mut(); let mut tracer = NoopTracer; + let mut vm_tracer = NoopVMTracer; { let vm_factory = Default::default(); - let mut ext = Externalities::new(state, &setup.env_info, &*setup.engine, &vm_factory, 0, get_test_origin(), &mut setup.sub_state, OutputPolicy::InitContract(None), &mut tracer); + let mut ext = Externalities::new(state, &setup.env_info, &*setup.engine, &vm_factory, 0, get_test_origin(), &mut setup.sub_state, OutputPolicy::InitContract(None), &mut tracer, &mut vm_tracer); ext.log(log_topics, &log_data); } @@ -440,10 +455,11 @@ mod tests { let mut setup = TestSetup::new(); let state = setup.state.reference_mut(); let mut tracer = NoopTracer; + let mut vm_tracer = NoopVMTracer; { let vm_factory = Default::default(); - let mut ext = Externalities::new(state, &setup.env_info, &*setup.engine, &vm_factory, 0, get_test_origin(), &mut setup.sub_state, OutputPolicy::InitContract(None), &mut tracer); + let mut ext = Externalities::new(state, &setup.env_info, &*setup.engine, &vm_factory, 0, get_test_origin(), &mut setup.sub_state, OutputPolicy::InitContract(None), &mut tracer, &mut vm_tracer); ext.suicide(&refund_account); } diff --git a/ethcore/src/json_tests/executive.rs b/ethcore/src/json_tests/executive.rs index 9e9620169..c5d781ab2 100644 --- a/ethcore/src/json_tests/executive.rs +++ b/ethcore/src/json_tests/executive.rs @@ -25,6 +25,7 @@ use substate::*; use tests::helpers::*; use ethjson; use trace::{Tracer, NoopTracer}; +use trace::{VMTracer, NoopVMTracer}; #[derive(Debug, PartialEq)] struct CallCreate { @@ -48,32 +49,35 @@ impl From for CallCreate { /// Tiny wrapper around executive externalities. /// Stores callcreates. -struct TestExt<'a, T> where T: 'a + Tracer { - ext: Externalities<'a, T>, +struct TestExt<'a, T, V> where T: 'a + Tracer, V: 'a + VMTracer { + ext: Externalities<'a, T, V>, callcreates: Vec, contract_address: Address } -impl<'a, T> TestExt<'a, T> where T: 'a + Tracer { - fn new(state: &'a mut State, - info: &'a EnvInfo, - engine: &'a Engine, - vm_factory: &'a Factory, - depth: usize, - origin_info: OriginInfo, - substate: &'a mut Substate, - output: OutputPolicy<'a, 'a>, - address: Address, - tracer: &'a mut T) -> Self { +impl<'a, T, V> TestExt<'a, T, V> where T: 'a + Tracer, V: 'a + VMTracer { + fn new( + state: &'a mut State, + info: &'a EnvInfo, + engine: &'a Engine, + vm_factory: &'a Factory, + depth: usize, + origin_info: OriginInfo, + substate: &'a mut Substate, + output: OutputPolicy<'a, 'a>, + address: Address, + tracer: &'a mut T, + vm_tracer: &'a mut V, + ) -> Self { TestExt { contract_address: contract_address(&address, &state.nonce(&address)), - ext: Externalities::new(state, info, engine, vm_factory, depth, origin_info, substate, output, tracer), + ext: Externalities::new(state, info, engine, vm_factory, depth, origin_info, substate, output, tracer, vm_tracer), callcreates: vec![] } } } -impl<'a, T> Ext for TestExt<'a, T> where T: Tracer { +impl<'a, T, V> Ext for TestExt<'a, T, V> where T: Tracer, V: VMTracer { fn storage_at(&self, key: &H256) -> H256 { self.ext.storage_at(key) } @@ -186,6 +190,7 @@ fn do_json_test_for(vm_type: &VMType, json_data: &[u8]) -> Vec { let mut substate = Substate::new(); let mut tracer = NoopTracer; + let mut vm_tracer = NoopVMTracer; let mut output = vec![]; // execute @@ -201,6 +206,7 @@ fn do_json_test_for(vm_type: &VMType, json_data: &[u8]) -> Vec { OutputPolicy::Return(BytesRef::Flexible(&mut output), None), params.address.clone(), &mut tracer, + &mut vm_tracer, ); let evm = vm_factory.create(); let res = evm.exec(params, &mut ex); diff --git a/ethcore/src/lib.rs b/ethcore/src/lib.rs index 5cf9d50a1..cb7942f23 100644 --- a/ethcore/src/lib.rs +++ b/ethcore/src/lib.rs @@ -141,3 +141,5 @@ mod tests; mod json_tests; pub use types::*; +pub use evm::get_info; +pub use executive::contract_address; \ No newline at end of file diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index 66b17cd7c..46b5ae733 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -251,7 +251,7 @@ impl MinerService for Miner { } } - fn call(&self, chain: &MiningBlockChainClient, t: &SignedTransaction) -> Result { + fn call(&self, chain: &MiningBlockChainClient, t: &SignedTransaction, vm_tracing: bool) -> Result { let sealing_work = self.sealing_work.lock().unwrap(); match sealing_work.peek_last_ref() { Some(work) => { @@ -277,12 +277,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) } } } diff --git a/ethcore/src/miner/mod.rs b/ethcore/src/miner/mod.rs index 60b680f7a..8165dfbba 100644 --- a/ethcore/src/miner/mod.rs +++ b/ethcore/src/miner/mod.rs @@ -148,7 +148,7 @@ pub trait MinerService : Send + Sync { fn balance(&self, chain: &MiningBlockChainClient, address: &Address) -> U256; /// Call into contract code using pending state. - fn call(&self, chain: &MiningBlockChainClient, t: &SignedTransaction) -> Result; + fn call(&self, chain: &MiningBlockChainClient, t: &SignedTransaction, vm_tracing: bool) -> Result; /// Get storage value in pending state. fn storage_at(&self, chain: &MiningBlockChainClient, address: &Address, position: &H256) -> H256; diff --git a/ethcore/src/state.rs b/ethcore/src/state.rs index c099b17a5..51162bb8e 100644 --- a/ethcore/src/state.rs +++ b/ethcore/src/state.rs @@ -183,16 +183,14 @@ impl State { /// Add `incr` to the balance of account `a`. pub fn add_balance(&mut self, a: &Address, incr: &U256) { - let old = self.balance(a); + trace!(target: "state", "add_balance({}, {}): {}", a, incr, self.balance(a)); self.require(a, false).add_balance(incr); - trace!("state: add_balance({}, {}): {} -> {}\n", a, incr, old, self.balance(a)); } /// Subtract `decr` from the balance of account `a`. pub fn sub_balance(&mut self, a: &Address, decr: &U256) { - let old = self.balance(a); + trace!(target: "state", "sub_balance({}, {}): {}", a, decr, self.balance(a)); self.require(a, false).sub_balance(decr); - trace!("state: sub_balance({}, {}): {} -> {}\n", a, decr, old, self.balance(a)); } /// Subtracts `by` from the balance of `from` and adds it to that of `to`. @@ -222,7 +220,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. diff --git a/ethcore/src/trace/executive_tracer.rs b/ethcore/src/trace/executive_tracer.rs index a0bff1c72..a1a13cf43 100644 --- a/ethcore/src/trace/executive_tracer.rs +++ b/ethcore/src/trace/executive_tracer.rs @@ -18,13 +18,13 @@ use util::{Bytes, Address, U256}; use action_params::ActionParams; -use trace::trace::{Trace, Call, Create, Action, Res, CreateResult, CallResult}; -use trace::Tracer; +use trace::trace::{Trace, Call, Create, Action, Res, CreateResult, CallResult, VMTrace, VMOperation, VMExecutedOperation, MemoryDiff, StorageDiff}; +use trace::{Tracer, VMTracer}; /// Simple executive tracer. Traces all calls and creates. Ignores delegatecalls. #[derive(Default)] pub struct ExecutiveTracer { - traces: Vec + traces: Vec, } impl Tracer for ExecutiveTracer { @@ -40,8 +40,7 @@ impl Tracer for ExecutiveTracer { Some(vec![]) } - fn trace_call(&mut self, call: Option, gas_used: U256, output: Option, depth: usize, subs: - Vec, delegate_call: bool) { + fn trace_call(&mut self, call: Option, gas_used: U256, output: Option, depth: usize, subs: Vec, delegate_call: bool) { // don't trace if it's DELEGATECALL or CALLCODE. if delegate_call { return; @@ -106,3 +105,46 @@ impl Tracer for ExecutiveTracer { self.traces } } + +/// Simple VM tracer. Traces all operations. +#[derive(Default)] +pub struct ExecutiveVMTracer { + data: VMTrace, +} + +impl VMTracer for ExecutiveVMTracer { + fn trace_prepare_execute(&mut self, pc: usize, instruction: u8, gas_cost: &U256) -> bool { + self.data.operations.push(VMOperation { + pc: pc, + instruction: instruction, + gas_cost: gas_cost.clone(), + executed: None, + }); + true + } + + fn trace_executed(&mut self, gas_used: U256, stack_push: &[U256], mem_diff: Option<(usize, &[u8])>, store_diff: Option<(U256, U256)>) { + let ex = VMExecutedOperation { + gas_used: gas_used, + stack_push: stack_push.iter().cloned().collect(), + mem_diff: mem_diff.map(|(s, r)| MemoryDiff{ offset: s, data: r.iter().cloned().collect() }), + store_diff: store_diff.map(|(l, v)| StorageDiff{ location: l, value: v }), + }; + self.data.operations.last_mut().expect("trace_executed is always called after a trace_prepare_execute").executed = Some(ex); + } + + fn prepare_subtrace(&self, code: &Bytes) -> Self { + ExecutiveVMTracer { data: VMTrace { + parent_step: self.data.operations.len(), + code: code.clone(), + operations: vec![], + subs: vec![], + }} + } + + fn done_subtrace(&mut self, sub: Self) { + self.data.subs.push(sub.data); + } + + fn drain(mut self) -> Option { self.data.subs.pop() } +} diff --git a/ethcore/src/trace/mod.rs b/ethcore/src/trace/mod.rs index f51cf8ee5..53c062137 100644 --- a/ethcore/src/trace/mod.rs +++ b/ethcore/src/trace/mod.rs @@ -31,9 +31,9 @@ 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 self::noop_tracer::NoopTracer; -pub use self::executive_tracer::ExecutiveTracer; +pub use types::trace_types::trace::{Trace, VMTrace, VMOperation, VMExecutedOperation, MemoryDiff, StorageDiff}; +pub use self::noop_tracer::{NoopTracer, NoopVMTracer}; +pub use self::executive_tracer::{ExecutiveTracer, ExecutiveVMTracer}; pub use types::trace_types::filter::{Filter, AddressesFilter}; pub use self::import::ImportRequest; pub use self::localized::LocalizedTrace; @@ -81,13 +81,32 @@ pub trait Tracer: Send { /// Stores failed create trace. fn trace_failed_create(&mut self, create: Option, depth: usize, subs: Vec); - /// Spawn subracer which will be used to trace deeper levels of execution. + /// Spawn subtracer which will be used to trace deeper levels of execution. fn subtracer(&self) -> Self where Self: Sized; /// Consumes self and returns all traces. fn traces(self) -> Vec; } +/// Used by executive to build VM traces. +pub trait VMTracer: Send { + /// Trace the preparation to execute a single instruction. + /// @returns true if `trace_executed` should be called. + fn trace_prepare_execute(&mut self, _pc: usize, _instruction: u8, _gas_cost: &U256) -> bool { false } + + /// Trace the finalised execution of a single instruction. + fn trace_executed(&mut self, _gas_used: U256, _stack_push: &[U256], _mem_diff: Option<(usize, &[u8])>, _store_diff: Option<(U256, U256)>) {} + + /// Spawn subtracer which will be used to trace deeper levels of execution. + fn prepare_subtrace(&self, code: &Bytes) -> Self where Self: Sized; + + /// Spawn subtracer which will be used to trace deeper levels of execution. + fn done_subtrace(&mut self, sub: Self) where Self: Sized; + + /// Consumes self and returns the VM trace. + fn drain(self) -> Option; +} + /// `DbExtras` provides an interface to query extra data which is not stored in tracesdb, /// but necessary to work correctly. pub trait DatabaseExtras { diff --git a/ethcore/src/trace/noop_tracer.rs b/ethcore/src/trace/noop_tracer.rs index 2581e692b..ed0231b79 100644 --- a/ethcore/src/trace/noop_tracer.rs +++ b/ethcore/src/trace/noop_tracer.rs @@ -18,8 +18,8 @@ use util::{Bytes, Address, U256}; use action_params::ActionParams; -use trace::Tracer; -use trace::trace::{Trace, Call, Create}; +use trace::{Tracer, VMTracer}; +use trace::trace::{Trace, Call, Create, VMTrace}; /// Nonoperative tracer. Does not trace anything. pub struct NoopTracer; @@ -63,3 +63,23 @@ impl Tracer for NoopTracer { vec![] } } + +/// Nonoperative VM tracer. Does not trace anything. +pub struct NoopVMTracer; + +impl VMTracer for NoopVMTracer { + /// Trace the preparation to execute a single instruction. + fn trace_prepare_execute(&mut self, _pc: usize, _instruction: u8, _gas_cost: &U256) -> bool { false } + + /// Trace the finalised execution of a single instruction. + fn trace_executed(&mut self, _gas_used: U256, _stack_push: &[U256], _mem_diff: Option<(usize, &[u8])>, _store_diff: Option<(U256, U256)>) {} + + /// Spawn subtracer which will be used to trace deeper levels of execution. + fn prepare_subtrace(&self, _code: &Bytes) -> Self { NoopVMTracer } + + /// Spawn subtracer which will be used to trace deeper levels of execution. + fn done_subtrace(&mut self, _sub: Self) {} + + /// Consumes self and returns all VM traces. + fn drain(self) -> Option { None } +} diff --git a/ethcore/src/types/executed.rs b/ethcore/src/types/executed.rs index fcde5a392..823c9dda1 100644 --- a/ethcore/src/types/executed.rs +++ b/ethcore/src/types/executed.rs @@ -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, + /// The VM trace of this transaction. + pub vm_trace: Option, } /// Result of executing the transaction. diff --git a/ethcore/src/types/trace_types/trace.rs b/ethcore/src/types/trace_types/trace.rs index f8fe07360..243acaf64 100644 --- a/ethcore/src/types/trace_types/trace.rs +++ b/ethcore/src/types/trace_types/trace.rs @@ -349,6 +349,170 @@ impl Trace { } } +#[derive(Debug, Clone, PartialEq, Binary)] +/// A diff of some chunk of memory. +pub struct MemoryDiff { + /// Offset into memory the change begins. + pub offset: usize, + /// The changed data. + pub data: Bytes, +} + +impl Encodable for MemoryDiff { + fn rlp_append(&self, s: &mut RlpStream) { + s.begin_list(2); + s.append(&self.offset); + s.append(&self.data); + } +} + +impl Decodable for MemoryDiff { + fn decode(decoder: &D) -> Result where D: Decoder { + let d = decoder.as_rlp(); + Ok(MemoryDiff { + offset: try!(d.val_at(0)), + data: try!(d.val_at(1)), + }) + } +} + +#[derive(Debug, Clone, PartialEq, Binary)] +/// A diff of some storage value. +pub struct StorageDiff { + /// Which key in storage is changed. + pub location: U256, + /// What the value has been changed to. + pub value: U256, +} + +impl Encodable for StorageDiff { + fn rlp_append(&self, s: &mut RlpStream) { + s.begin_list(2); + s.append(&self.location); + s.append(&self.value); + } +} + +impl Decodable for StorageDiff { + fn decode(decoder: &D) -> Result where D: Decoder { + let d = decoder.as_rlp(); + Ok(StorageDiff { + location: try!(d.val_at(0)), + value: try!(d.val_at(1)), + }) + } +} + +#[derive(Debug, Clone, PartialEq, Binary)] +/// A record of an executed VM operation. +pub struct VMExecutedOperation { + /// The total gas used. + pub gas_used: U256, + /// The stack item placed, if any. + pub stack_push: Vec, + /// If altered, the memory delta. + pub mem_diff: Option, + /// The altered storage value, if any. + pub store_diff: Option, +} + +impl Encodable for VMExecutedOperation { + fn rlp_append(&self, s: &mut RlpStream) { + s.begin_list(4); + s.append(&self.gas_used); + s.append(&self.stack_push); + s.append(&self.mem_diff); + s.append(&self.store_diff); + } +} + +impl Decodable for VMExecutedOperation { + fn decode(decoder: &D) -> Result where D: Decoder { + let d = decoder.as_rlp(); + Ok(VMExecutedOperation { + gas_used: try!(d.val_at(0)), + stack_push: try!(d.val_at(1)), + mem_diff: try!(d.val_at(2)), + store_diff: try!(d.val_at(3)), + }) + } +} + +#[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, + /// Information concerning the execution of the operation. + pub executed: Option, +} + +impl Encodable for VMOperation { + fn rlp_append(&self, s: &mut RlpStream) { + s.begin_list(4); + s.append(&self.pc); + s.append(&self.instruction); + s.append(&self.gas_cost); + s.append(&self.executed); + } +} + +impl Decodable for VMOperation { + fn decode(decoder: &D) -> Result 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)), + executed: try!(d.val_at(3)), + }; + + Ok(res) + } +} + +#[derive(Debug, Clone, PartialEq, Binary, Default)] +/// A record of a full VM trace for a CALL/CREATE. +pub struct VMTrace { + /// The step (i.e. index into operations) at which this trace corresponds. + pub parent_step: usize, + /// The code to be executed. + pub code: Bytes, + /// The operations executed. + pub operations: Vec, + /// 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, +} + +impl Encodable for VMTrace { + fn rlp_append(&self, s: &mut RlpStream) { + s.begin_list(4); + s.append(&self.parent_step); + s.append(&self.code); + s.append(&self.operations); + s.append(&self.subs); + } +} + +impl Decodable for VMTrace { + fn decode(decoder: &D) -> Result where D: Decoder { + let d = decoder.as_rlp(); + let res = VMTrace { + parent_step: 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}; diff --git a/json/src/state/transaction.rs b/json/src/state/transaction.rs index 15626c224..62b8577d4 100644 --- a/json/src/state/transaction.rs +++ b/json/src/state/transaction.rs @@ -40,7 +40,7 @@ pub struct Transaction { /// To. pub to: MaybeEmpty
, /// Value. - pub value: Uint + pub value: Uint, } #[cfg(test)] diff --git a/parity/rpc_apis.rs b/parity/rpc_apis.rs index d9f0cd4eb..143d6b48f 100644 --- a/parity/rpc_apis.rs +++ b/parity/rpc_apis.rs @@ -153,7 +153,7 @@ pub fn setup_rpc(server: T, deps: Arc, apis: ApiSet } }, Api::Ethcore => { - 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()) }, Api::Traces => { server.add_delegate(TracesClient::new(&deps.client).to_delegate()) diff --git a/rpc/src/lib.rs b/rpc/src/lib.rs index ab5069334..b46a13197 100644 --- a/rpc/src/lib.rs +++ b/rpc/src/lib.rs @@ -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; diff --git a/rpc/src/v1/impls/eth.rs b/rpc/src/v1/impls/eth.rs index 83a788599..49cbafe27 100644 --- a/rpc/src/v1/impls/eth.rs +++ b/rpc/src/v1/impls/eth.rs @@ -511,8 +511,8 @@ impl Eth for EthClient 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 Eth for EthClient 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))) diff --git a/rpc/src/v1/impls/ethcore.rs b/rpc/src/v1/impls/ethcore.rs index e649e37f4..ca14ca021 100644 --- a/rpc/src/v1/impls/ethcore.rs +++ b/rpc/src/v1/impls/ethcore.rs @@ -15,7 +15,7 @@ // along with Parity. If not, see . //! 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 - where M: MinerService { +pub struct EthcoreClient where + C: BlockChainClient, + M: MinerService { + + client: Weak, miner: Weak, logger: Arc, settings: Arc, } -impl EthcoreClient where M: MinerService { +impl EthcoreClient where C: BlockChainClient, M: MinerService { /// Creates new `EthcoreClient`. - pub fn new(miner: &Arc, logger: Arc, settings: Arc) -> Self { + pub fn new(client: &Arc, miner: &Arc, logger: Arc, settings: Arc) -> 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 { + 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 Ethcore for EthcoreClient 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::>(); + ret.insert("ops".to_owned(), Value::Array(ops)); + Value::Object(ret) +} + +impl Ethcore for EthcoreClient where C: BlockChainClient + 'static, M: MinerService + 'static { fn set_min_gas_price(&self, params: Params) -> Result { from_params::<(U256,)>(params).and_then(|(gas_price,)| { @@ -135,4 +205,19 @@ impl Ethcore for EthcoreClient where M: MinerService + 'static { let version = version_data(); to_value(&Bytes::new(version)) } + + fn vm_trace_call(&self, params: Params) -> Result { + 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) + }) + } } diff --git a/rpc/src/v1/tests/eth.rs b/rpc/src/v1/tests/eth.rs index 93887fb63..a7f7920ad 100644 --- a/rpc/src/v1/tests/eth.rs +++ b/rpc/src/v1/tests/eth.rs @@ -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; diff --git a/rpc/src/v1/tests/helpers/miner_service.rs b/rpc/src/v1/tests/helpers/miner_service.rs index a53ca3a08..0f9327887 100644 --- a/rpc/src/v1/tests/helpers/miner_service.rs +++ b/rpc/src/v1/tests/helpers/miner_service.rs @@ -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 { + fn call(&self, _chain: &MiningBlockChainClient, _t: &SignedTransaction, _vm_tracing: bool) -> Result { unimplemented!(); } diff --git a/rpc/src/v1/tests/mocked/eth.rs b/rpc/src/v1/tests/mocked/eth.rs index 63e66f797..4f43be827 100644 --- a/rpc/src/v1/tests/mocked/eth.rs +++ b/rpc/src/v1/tests/mocked/eth.rs @@ -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#"{ diff --git a/rpc/src/v1/tests/mocked/ethcore.rs b/rpc/src/v1/tests/mocked/ethcore.rs index d51545d86..3bed1f0f0 100644 --- a/rpc/src/v1/tests/mocked/ethcore.rs +++ b/rpc/src/v1/tests/mocked/ethcore.rs @@ -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 { + let client = TestBlockChainClient::new(); + Arc::new(client) +} fn miner_service() -> Arc { Arc::new(TestMinerService::default()) } + fn logger() -> Arc { Arc::new(RotatingLogger::new("rpc=trace".to_owned())) } @@ -45,14 +51,15 @@ fn settings() -> Arc { }) } -fn ethcore_client(miner: &Arc) -> EthcoreClient { - EthcoreClient::new(&miner, logger(), settings()) +fn ethcore_client(client: &Arc, miner: &Arc) -> EthcoreClient { + 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); diff --git a/rpc/src/v1/traits/ethcore.rs b/rpc/src/v1/traits/ethcore.rs index 3646f6c5a..abc87bbb8 100644 --- a/rpc/src/v1/traits/ethcore.rs +++ b/rpc/src/v1/traits/ethcore.rs @@ -72,6 +72,8 @@ pub trait Ethcore: Sized + Send + Sync + 'static { /// Returns default extra data fn default_extra_data(&self, _: Params) -> Result; + /// Executes the given call and returns the VM trace for it. + fn vm_trace_call(&self, _: Params) -> Result; /// Should be used to convert object to io delegate. fn to_delegate(self) -> IoDelegate { @@ -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 } } diff --git a/rpc/src/v1/types/block.rs b/rpc/src/v1/types/block.rs index 5810c85e5..b86723357 100644 --- a/rpc/src/v1/types/block.rs +++ b/rpc/src/v1/types/block.rs @@ -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(); diff --git a/rpc/src/v1/types/transaction.rs b/rpc/src/v1/types/transaction.rs index 1c9a41084..6a9f0e590 100644 --- a/rpc/src/v1/types/transaction.rs +++ b/rpc/src/v1/types/transaction.rs @@ -15,6 +15,7 @@ // along with Parity. If not, see . 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
, } impl From for Transaction { @@ -65,7 +68,11 @@ impl From 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 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}"#); } } diff --git a/util/src/common.rs b/util/src/common.rs index 0e0cd7757..941b5b0a6 100644 --- a/util/src/common.rs +++ b/util/src/common.rs @@ -24,6 +24,13 @@ pub use vector::*; pub use numbers::*; pub use sha3::*; +#[macro_export] +macro_rules! vec_into { + ( $( $x:expr ),* ) => { + vec![ $( $x.into() ),* ] + } +} + #[macro_export] macro_rules! hash_map { () => { HashMap::new() }; diff --git a/util/src/rlp/rlpstream.rs b/util/src/rlp/rlpstream.rs index c2ff88c41..5da3a3822 100644 --- a/util/src/rlp/rlpstream.rs +++ b/util/src/rlp/rlpstream.rs @@ -338,6 +338,18 @@ impl Encodable for Vec where T: Encodable { } } +impl Encodable for Option where T: Encodable { + fn rlp_append(&self, s: &mut RlpStream) { + match *self { + None => { s.begin_list(0); }, + Some(ref x) => { + s.begin_list(1); + s.append_internal(x); + } + } + } +} + impl RlpEncodable for T where T: Encodable { fn rlp_append(&self, s: &mut RlpStream) { Encodable::rlp_append(self, s) diff --git a/util/src/rlp/untrusted_rlp.rs b/util/src/rlp/untrusted_rlp.rs index 1aa688aba..6109a643b 100644 --- a/util/src/rlp/untrusted_rlp.rs +++ b/util/src/rlp/untrusted_rlp.rs @@ -395,7 +395,7 @@ impl<'a> Decoder for BasicDecoder<'a> { } impl Decodable for T where T: FromBytes { - fn decode(decoder: &D) -> Result where D: Decoder { + fn decode(decoder: &D) -> Result where D: Decoder { decoder.read_value(| bytes | { Ok(try!(T::from_bytes(bytes))) }) @@ -403,13 +403,19 @@ impl Decodable for T where T: FromBytes { } impl Decodable for Vec where T: Decodable { - fn decode(decoder: &D) -> Result where D: Decoder { + fn decode(decoder: &D) -> Result where D: Decoder { decoder.as_rlp().iter().map(|d| T::decode(&BasicDecoder::new(d))).collect() } } +impl Decodable for Option where T: Decodable { + fn decode(decoder: &D) -> Result where D: Decoder { + decoder.as_rlp().iter().map(|d| T::decode(&BasicDecoder::new(d))).collect::, DecoderError>>().map(|mut a| a.pop()) + } +} + impl Decodable for Vec { - fn decode(decoder: &D) -> Result where D: Decoder { + fn decode(decoder: &D) -> Result where D: Decoder { decoder.read_value(| bytes | { let mut res = vec![]; res.extend_from_slice(bytes); @@ -418,22 +424,10 @@ impl Decodable for Vec { } } -impl Decodable for Option where T: Decodable { - fn decode(decoder: &D) -> Result where D: Decoder { - decoder.read_value(| bytes | { - let res = match bytes.len() { - 0 => None, - _ => Some(try!(T::decode(decoder))) - }; - Ok(res) - }) - } -} - macro_rules! impl_array_decodable { ($index_type:ty, $len:expr ) => ( impl Decodable for [T; $len] where T: Decodable { - fn decode(decoder: &D) -> Result where D: Decoder { + fn decode(decoder: &D) -> Result where D: Decoder { let decoders = decoder.as_rlp(); let mut result: [T; $len] = unsafe { ::std::mem::uninitialized() }; @@ -466,7 +460,7 @@ impl_array_decodable_recursive!( ); impl RlpDecodable for T where T: Decodable { - fn decode(decoder: &D) -> Result where D: Decoder { + fn decode(decoder: &D) -> Result where D: Decoder { Decodable::decode(decoder) } } @@ -489,7 +483,7 @@ impl FromBytes for DecodableU8 { } impl RlpDecodable for u8 { - fn decode(decoder: &D) -> Result where D: Decoder { + fn decode(decoder: &D) -> Result where D: Decoder { let u: DecodableU8 = try!(Decodable::decode(decoder)); Ok(u.0) }