diff --git a/.travis.yml b/.travis.yml index 0f0766ee4..230e7862f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -33,7 +33,7 @@ env: global: # GH_TOKEN - secure: bumJASbZSU8bxJ0EyPUJmu16AiV9EXOpyOj86Jlq/Ty9CfwGqsSXt96uDyE+OUJf34RUFQMsw0nk37/zC4lcn6kqk2wpuH3N/o85Zo/cVZY/NusBWLQqtT5VbYWsV+u2Ua4Tmmsw8yVYQhYwU2ZOejNpflL+Cs9XGgORp1L+/gMRMC2y5Se6ZhwnKPQlRJ8LGsG1dzjQULxzADIt3/zuspNBS8a2urJwlHfGMkvHDoUWCviP/GXoSqw3TZR7FmKyxE19I8n9+iSvm9+oZZquvcgfUxMHn8Gq/b44UbPvjtFOg2yam4xdWXF/RyWCHdc/R9EHorSABeCbefIsm+zcUF3/YQxwpSxM4IZEeH2rTiC7dcrsKw3XsO16xFQz5YI5Bay+CT/wTdMmJd7DdYz7Dyf+pOvcM9WOf/zorxYWSBOMYy0uzbusU2iyIghQ82s7E/Ahg+WARtPgkuTLSB5aL1oCTBKHqQscMr7lo5Ti6RpWLxEdTQMBznc+bMr+6dEtkEcG9zqc6cE9XX+ox3wTU6+HVMfQ1ltCntJ4UKcw3A6INEbw9wgocQa812CIASQ2fE+SCAbz6JxBjIAlFUnD1lUB7S8PdMPwn9plfQgKQ2A5YZqg6FnBdf0rQXIJYxQWKHXj/rBHSUCT0tHACDlzTA+EwWggvkP5AGIxRxm8jhw= - - TARGETS="-p ethash -p ethcore-util -p ethcore -p ethsync -p ethcore-rpc -p parity -p ethminer -p ethjson -p ethcore-dapps -p ethcore-signer" + - TARGETS="-p ethash -p ethcore-util -p ethcore -p ethsync -p ethcore-rpc -p parity -p ethjson -p ethcore-dapps -p ethcore-signer" - ARCHIVE_SUFFIX="-${TRAVIS_OS_NAME}-${TRAVIS_TAG}" - KCOV_FEATURES="" - KCOV_CMD="./kcov-master/tmp/usr/local/bin/kcov --exclude-pattern /usr/,/.cargo,/root/.multirust,src/tests,util/json-tests,util/src/network/tests,sync/src/tests,ethcore/src/tests,ethcore/src/evm/tests target/kcov" @@ -72,7 +72,6 @@ after_success: | $KCOV_CMD target/debug/deps/ethcore_rpc-* && $KCOV_CMD target/debug/deps/ethcore_dapps-* && $KCOV_CMD target/debug/deps/ethcore_signer-* && - $KCOV_CMD target/debug/deps/ethminer-* && $KCOV_CMD target/debug/deps/ethjson-* && $KCOV_CMD target/debug/parity-* && [ $TRAVIS_BRANCH = master ] && diff --git a/Cargo.lock b/Cargo.lock index e1f2a28b7..9f27c6b2d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,7 +17,6 @@ dependencies = [ "ethcore-rpc 1.2.0", "ethcore-signer 1.2.0", "ethcore-util 1.2.0", - "ethminer 1.2.0", "ethsync 1.2.0", "fdlimit 0.1.0", "hyper 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -255,6 +254,7 @@ dependencies = [ "lazy_static 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "num_cpus 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "rayon 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "rust-crypto 0.2.35 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", "syntex 0.33.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -272,6 +272,7 @@ dependencies = [ "jsonrpc-core 2.0.5 (registry+https://github.com/rust-lang/crates.io-index)", "jsonrpc-http-server 5.1.0 (git+https://github.com/ethcore/jsonrpc-http-server.git)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "mime_guess 1.6.1 (registry+https://github.com/rust-lang/crates.io-index)", "parity-dapps 0.3.0 (git+https://github.com/ethcore/parity-dapps-rs.git)", "parity-dapps-builtins 0.5.0 (git+https://github.com/ethcore/parity-dapps-builtins-rs.git)", "parity-dapps-dao 0.3.0 (git+https://github.com/ethcore/parity-dapps-dao-rs.git)", @@ -334,7 +335,6 @@ dependencies = [ "ethcore-devtools 1.2.0", "ethcore-util 1.2.0", "ethjson 0.1.0", - "ethminer 1.2.0", "ethsync 1.2.0", "json-ipc-server 0.1.0 (git+https://github.com/ethcore/json-ipc-server.git)", "jsonrpc-core 2.0.5 (registry+https://github.com/rust-lang/crates.io-index)", @@ -391,7 +391,7 @@ dependencies = [ "rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", "serde 0.7.7 (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)", @@ -410,20 +410,6 @@ dependencies = [ "syntex 0.33.0 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "ethminer" -version = "1.2.0" -dependencies = [ - "clippy 0.0.69 (registry+https://github.com/rust-lang/crates.io-index)", - "env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "ethcore 1.2.0", - "ethcore-util 1.2.0", - "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", - "rayon 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", - "rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "ethsync" version = "1.2.0" @@ -432,7 +418,6 @@ dependencies = [ "env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", "ethcore 1.2.0", "ethcore-util 1.2.0", - "ethminer 1.2.0", "heapsize 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", @@ -874,7 +859,7 @@ dependencies = [ [[package]] name = "parity-dapps-builtins" version = "0.5.0" -source = "git+https://github.com/ethcore/parity-dapps-builtins-rs.git#eb86a2954f04d3aa5547a8c4bb77ae7aad09bf55" +source = "git+https://github.com/ethcore/parity-dapps-builtins-rs.git#8bbf0421e376f9496d70adc62c1c6d7f492df817" dependencies = [ "parity-dapps 0.3.0 (git+https://github.com/ethcore/parity-dapps-rs.git)", ] @@ -1182,6 +1167,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/Cargo.toml b/Cargo.toml index e93069c98..e4b8eeea2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,7 +27,6 @@ clippy = { version = "0.0.69", optional = true} ethcore = { path = "ethcore" } ethcore-util = { path = "util" } ethsync = { path = "sync" } -ethminer = { path = "miner" } ethcore-devtools = { path = "devtools" } ethcore-rpc = { path = "rpc", optional = true } ethcore-signer = { path = "signer", optional = true } @@ -46,7 +45,7 @@ default-features = false default = ["rpc", "dapps", "ethcore-signer"] rpc = ["ethcore-rpc"] dapps = ["ethcore-dapps"] -dev = ["clippy", "ethcore/dev", "ethcore-util/dev", "ethsync/dev", "ethcore-rpc/dev", "ethminer/dev", +dev = ["clippy", "ethcore/dev", "ethcore-util/dev", "ethsync/dev", "ethcore-rpc/dev", "ethcore-dapps/dev", "ethcore-signer/dev"] travis-beta = ["ethcore/json-tests"] travis-nightly = ["ethcore/json-tests", "dev"] diff --git a/README.md b/README.md index 903794e80..193cbeb8f 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ -# ethcore +# [Parity](https://ethcore.io/parity.html) +### Fast, light, and robust Ethereum implementation [![Build Status][travis-image]][travis-url] [![Coverage Status][coveralls-image]][coveralls-url] [![Join the chat at https://gitter.im/trogdoro/xiki][gitter-image]][gitter-url] [![GPLv3][license-image]][license-url] @@ -11,30 +12,64 @@ [license-image]: https://img.shields.io/badge/license-GPL%20v3-green.svg [license-url]: http://www.gnu.org/licenses/gpl-3.0.en.html -[Documentation](http://ethcore.github.io/parity/ethcore/index.html) +[Internal Documentation](http://ethcore.github.io/parity/ethcore/index.html) -### Building from source +---- -First (if you don't already have it) get multirust: +## About Parity + +Parity's goal is to be the fastest, lightest, and most secure Ethereum client. We are developing Parity using the sophisticated and +cutting-edge Rust programming language. Parity is licensed under the GPLv3, and can be used for all your Ethereum needs. + +By default, Parity will run a JSONRPC server on `127.0.0.1:8545`. This is fully configurable and supports a number +of RPC APIs. + +Parity also runs a server for running decentralized apps, or "Dapps", on `http://127.0.0.1:8080`. +This includes a few useful Dapps, including Ethereum Wallet, Maker OTC, and a node status page. +In a near-future release, it will be easy to install Dapps and use them through this web interface. + +If you run into an issue while using parity, feel free to file one in this repository +or hop on our [gitter chat room]([gitter-url]) to ask a question. We are glad to help! + +Parity's current release is 1.1. You can download it at https://ethcore.io/parity.html or follow the instructions +below to build from source. + +---- + +## Building from source + +Parity is fully compatible with Stable Rust. + +We recommend installing Rust through [multirust](https://github.com/brson/multirust). If you don't already have multirust, you can install it like this: - Linux: ```bash -curl -sf https://raw.githubusercontent.com/brson/multirust/master/quick-install.sh | sh +$ curl -sf https://raw.githubusercontent.com/brson/multirust/master/quick-install.sh | sh ``` - OSX with Homebrew: ```bash -brew update && brew install multirust -multirust default stable +$ brew update && brew install multirust +$ multirust default stable ``` Then, download and build Parity: ```bash # download Parity code -git clone https://github.com/ethcore/parity -cd parity +$ git clone https://github.com/ethcore/parity +$ cd parity # build in release mode -cargo build --release +$ cargo build --release ``` + +This will produce an executable in the `target/release` subdirectory. +Either run `cd target/release`, or copy `target/release/parity` to another location. + +To get started, just run +```bash +$ parity +``` + +and parity will begin syncing the Ethereum blockchain. \ No newline at end of file diff --git a/cov.sh b/cov.sh index 084e95284..a13db50b9 100755 --- a/cov.sh +++ b/cov.sh @@ -22,7 +22,6 @@ cargo test \ -p ethsync \ -p ethcore-rpc \ -p parity \ - -p ethminer \ -p ethcore-signer \ -p ethcore-dapps \ --no-run || exit $? @@ -37,5 +36,4 @@ kcov --exclude-pattern $EXCLUDE --include-pattern src --verify target/coverage t kcov --exclude-pattern $EXCLUDE --include-pattern src --verify target/coverage target/debug/deps/ethcore_rpc-* kcov --exclude-pattern $EXCLUDE --include-pattern src --verify target/coverage target/debug/deps/ethcore_signer-* kcov --exclude-pattern $EXCLUDE --include-pattern src --verify target/coverage target/debug/deps/ethcore_dapps-* -kcov --exclude-pattern $EXCLUDE --include-pattern src --verify target/coverage target/debug/deps/ethminer-* xdg-open target/coverage/index.html diff --git a/dapps/Cargo.toml b/dapps/Cargo.toml index 5372c74a9..5a68bb751 100644 --- a/dapps/Cargo.toml +++ b/dapps/Cargo.toml @@ -27,6 +27,7 @@ parity-dapps-builtins = { git = "https://github.com/ethcore/parity-dapps-builtin parity-dapps-wallet = { git = "https://github.com/ethcore/parity-dapps-wallet-rs.git", version = "0.5.0", optional = true } parity-dapps-dao = { git = "https://github.com/ethcore/parity-dapps-dao-rs.git", version = "0.3.0", optional = true } parity-dapps-makerotc = { git = "https://github.com/ethcore/parity-dapps-makerotc-rs.git", version = "0.2.0", optional = true } +mime_guess = { version = "1.6.1" } clippy = { version = "0.0.69", optional = true} [build-dependencies] diff --git a/dapps/src/api/api.rs b/dapps/src/api/api.rs index c460dcf20..95b01d442 100644 --- a/dapps/src/api/api.rs +++ b/dapps/src/api/api.rs @@ -15,7 +15,7 @@ // along with Parity. If not, see . use std::sync::Arc; -use endpoint::{Endpoint, Endpoints, Handler, EndpointPath}; +use endpoint::{Endpoint, Endpoints, EndpointInfo, Handler, EndpointPath}; use api::response::as_json; @@ -23,8 +23,8 @@ pub struct RestApi { endpoints: Arc, } -#[derive(Debug, PartialEq, Serialize)] -struct App { +#[derive(Debug, PartialEq, Serialize, Deserialize)] +pub struct App { pub id: String, pub name: String, pub description: String, @@ -34,6 +34,19 @@ struct App { pub icon_url: String, } +impl App { + fn from_info(id: &str, info: &EndpointInfo) -> Self { + App { + id: id.to_owned(), + name: info.name.to_owned(), + description: info.description.to_owned(), + version: info.version.to_owned(), + author: info.author.to_owned(), + icon_url: info.icon_url.to_owned(), + } + } +} + impl RestApi { pub fn new(endpoints: Arc) -> Box { Box::new(RestApi { @@ -43,14 +56,7 @@ impl RestApi { fn list_apps(&self) -> Vec { self.endpoints.iter().filter_map(|(ref k, ref e)| { - e.info().map(|ref info| App { - id: k.to_owned().clone(), - name: info.name.to_owned(), - description: info.description.to_owned(), - version: info.version.to_owned(), - author: info.author.to_owned(), - icon_url: info.icon_url.to_owned(), - }) + e.info().map(|ref info| App::from_info(k, info)) }).collect() } } diff --git a/dapps/src/api/mod.rs.in b/dapps/src/api/mod.rs.in index 0eff6b397..a069c06b0 100644 --- a/dapps/src/api/mod.rs.in +++ b/dapps/src/api/mod.rs.in @@ -18,3 +18,4 @@ mod api; mod response; pub use self::api::RestApi; +pub use self::api::App; diff --git a/dapps/src/apps/fs.rs b/dapps/src/apps/fs.rs new file mode 100644 index 000000000..fa3b0ab4c --- /dev/null +++ b/dapps/src/apps/fs.rs @@ -0,0 +1,116 @@ +// 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 . + +use serde_json; +use std::io; +use std::io::Read; +use std::fs; +use std::path::PathBuf; +use page::LocalPageEndpoint; +use endpoint::{Endpoints, EndpointInfo}; +use api::App; + +struct LocalDapp { + id: String, + path: PathBuf, + info: EndpointInfo, +} + +fn local_dapps(dapps_path: String) -> Vec { + let files = fs::read_dir(dapps_path.as_str()); + if let Err(e) = files { + warn!(target: "dapps", "Unable to load local dapps from: {}. Reason: {:?}", dapps_path, e); + return vec![]; + } + + let files = files.expect("Check is done earlier"); + files.map(|dir| { + let entry = try!(dir); + let file_type = try!(entry.file_type()); + + // skip files + if file_type.is_file() { + return Err(io::Error::new(io::ErrorKind::NotFound, "Not a file")); + } + + // take directory name and path + entry.file_name().into_string() + .map(|name| (name, entry.path())) + .map_err(|e| { + info!(target: "dapps", "Unable to load dapp: {:?}. Reason: {:?}", entry.path(), e); + io::Error::new(io::ErrorKind::NotFound, "Invalid name") + }) + }) + .filter_map(|m| { + if let Err(ref e) = m { + debug!(target: "dapps", "Ignoring local dapp: {:?}", e); + } + m.ok() + }) + .map(|(name, path)| { + // try to get manifest file + let info = read_manifest(&name, path.clone()); + LocalDapp { + id: name, + path: path, + info: info, + } + }) + .collect() +} + +fn read_manifest(name: &str, mut path: PathBuf) -> EndpointInfo { + path.push("manifest.json"); + + fs::File::open(path.clone()) + .map_err(|e| format!("{:?}", e)) + .and_then(|mut f| { + // Reat file + let mut s = String::new(); + try!(f.read_to_string(&mut s).map_err(|e| format!("{:?}", e))); + // Try to deserialize manifest + serde_json::from_str::(&s).map_err(|e| format!("{:?}", e)) + }) + .map(|app| EndpointInfo { + name: app.name, + description: app.description, + version: app.version, + author: app.author, + icon_url: app.icon_url, + }) + .unwrap_or_else(|e| { + warn!(target: "dapps", "Cannot read manifest file at: {:?}. Error: {:?}", path, e); + + EndpointInfo { + name: name.into(), + description: name.into(), + version: "0.0.0".into(), + author: "?".into(), + icon_url: "icon.png".into(), + } + }) +} + +pub fn local_endpoints(dapps_path: String) -> Endpoints { + let mut pages = Endpoints::new(); + for dapp in local_dapps(dapps_path) { + pages.insert( + dapp.id, + Box::new(LocalPageEndpoint::new(dapp.path, dapp.info)) + ); + } + pages +} diff --git a/dapps/src/apps.rs b/dapps/src/apps/mod.rs similarity index 93% rename from dapps/src/apps.rs rename to dapps/src/apps/mod.rs index 130b20fb9..7f849cf65 100644 --- a/dapps/src/apps.rs +++ b/dapps/src/apps/mod.rs @@ -19,10 +19,11 @@ use page::PageEndpoint; use proxypac::ProxyPac; use parity_dapps::WebApp; +mod fs; + extern crate parity_dapps_status; extern crate parity_dapps_builtins; - pub const DAPPS_DOMAIN : &'static str = ".parity"; pub const RPC_PATH : &'static str = "rpc"; pub const API_PATH : &'static str = "api"; @@ -36,22 +37,24 @@ pub fn utils() -> Box { Box::new(PageEndpoint::with_prefix(parity_dapps_builtins::App::default(), UTILS_PATH.to_owned())) } -pub fn all_endpoints() -> Endpoints { - let mut pages = Endpoints::new(); - pages.insert("proxy".into(), ProxyPac::boxed()); - +pub fn all_endpoints(dapps_path: String) -> Endpoints { + // fetch fs dapps at first to avoid overwriting builtins + let mut pages = fs::local_endpoints(dapps_path); // Home page needs to be safe embed // because we use Cross-Origin LocalStorage. // TODO [ToDr] Account naming should be moved to parity. pages.insert("home".into(), Box::new( PageEndpoint::new_safe_to_embed(parity_dapps_builtins::App::default()) )); + pages.insert("proxy".into(), ProxyPac::boxed()); insert::(&mut pages, "status"); insert::(&mut pages, "parity"); + // Optional dapps wallet_page(&mut pages); daodapp_page(&mut pages); makerotc_page(&mut pages); + pages } diff --git a/dapps/src/endpoint.rs b/dapps/src/endpoint.rs index 28ca6ea11..592bc7f8f 100644 --- a/dapps/src/endpoint.rs +++ b/dapps/src/endpoint.rs @@ -30,17 +30,17 @@ pub struct EndpointPath { pub port: u16, } -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Clone)] pub struct EndpointInfo { - pub name: &'static str, - pub description: &'static str, - pub version: &'static str, - pub author: &'static str, - pub icon_url: &'static str, + pub name: String, + pub description: String, + pub version: String, + pub author: String, + pub icon_url: String, } pub trait Endpoint : Send + Sync { - fn info(&self) -> Option { None } + fn info(&self) -> Option<&EndpointInfo> { None } fn to_handler(&self, path: EndpointPath) -> Box>; } diff --git a/dapps/src/lib.rs b/dapps/src/lib.rs index 27c215108..a7fbd5963 100644 --- a/dapps/src/lib.rs +++ b/dapps/src/lib.rs @@ -52,6 +52,8 @@ extern crate serde_json; extern crate jsonrpc_core; extern crate jsonrpc_http_server; extern crate parity_dapps; +extern crate ethcore_rpc; +extern crate mime_guess; mod endpoint; mod apps; @@ -66,37 +68,41 @@ use std::net::SocketAddr; use std::collections::HashMap; use jsonrpc_core::{IoHandler, IoDelegate}; use router::auth::{Authorization, NoAuth, HttpBasicAuth}; +use ethcore_rpc::Extendable; static DAPPS_DOMAIN : &'static str = ".parity"; /// Webapps HTTP+RPC server build. pub struct ServerBuilder { + dapps_path: String, handler: Arc, } +impl Extendable for ServerBuilder { + fn add_delegate(&self, delegate: IoDelegate) { + self.handler.add_delegate(delegate); + } +} + impl ServerBuilder { /// Construct new dapps server - pub fn new() -> Self { + pub fn new(dapps_path: String) -> Self { ServerBuilder { + dapps_path: dapps_path, handler: Arc::new(IoHandler::new()) } } - /// Add io delegate. - pub fn add_delegate(&self, delegate: IoDelegate) where D: Send + Sync + 'static { - self.handler.add_delegate(delegate); - } - /// Asynchronously start server with no authentication, /// returns result with `Server` handle on success or an error. pub fn start_unsecure_http(&self, addr: &SocketAddr) -> Result { - Server::start_http(addr, NoAuth, self.handler.clone()) + Server::start_http(addr, NoAuth, self.handler.clone(), self.dapps_path.clone()) } /// Asynchronously start server with `HTTP Basic Authentication`, /// return result with `Server` handle on success or an error. pub fn start_basic_auth_http(&self, addr: &SocketAddr, username: &str, password: &str) -> Result { - Server::start_http(addr, HttpBasicAuth::single_user(username, password), self.handler.clone()) + Server::start_http(addr, HttpBasicAuth::single_user(username, password), self.handler.clone(), self.dapps_path.clone()) } } @@ -107,10 +113,10 @@ pub struct Server { } impl Server { - fn start_http(addr: &SocketAddr, authorization: A, handler: Arc) -> Result { + fn start_http(addr: &SocketAddr, authorization: A, handler: Arc, dapps_path: String) -> Result { let panic_handler = Arc::new(Mutex::new(None)); let authorization = Arc::new(authorization); - let endpoints = Arc::new(apps::all_endpoints()); + let endpoints = Arc::new(apps::all_endpoints(dapps_path)); let special = Arc::new({ let mut special = HashMap::new(); special.insert(router::SpecialEndpoint::Rpc, rpc::rpc(handler, panic_handler.clone())); diff --git a/dapps/src/page/builtin.rs b/dapps/src/page/builtin.rs new file mode 100644 index 000000000..1c7ca32d4 --- /dev/null +++ b/dapps/src/page/builtin.rs @@ -0,0 +1,154 @@ +// 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 . + +use page::handler; +use std::sync::Arc; +use endpoint::{Endpoint, EndpointInfo, EndpointPath, Handler}; +use parity_dapps::{WebApp, File, Info}; + +pub struct PageEndpoint { + /// Content of the files + pub app: Arc, + /// Prefix to strip from the path (when `None` deducted from `app_id`) + pub prefix: Option, + /// Safe to be loaded in frame by other origin. (use wisely!) + safe_to_embed: bool, + info: EndpointInfo, +} + +impl PageEndpoint { + /// Creates new `PageEndpoint` for builtin (compile time) Dapp. + pub fn new(app: T) -> Self { + let info = app.info(); + PageEndpoint { + app: Arc::new(app), + prefix: None, + safe_to_embed: false, + info: EndpointInfo::from(info), + } + } + + /// Create new `PageEndpoint` and specify prefix that should be removed before looking for a file. + /// It's used only for special endpoints (i.e. `/parity-utils/`) + /// So `/parity-utils/inject.js` will be resolved to `/inject.js` is prefix is set. + pub fn with_prefix(app: T, prefix: String) -> Self { + let info = app.info(); + PageEndpoint { + app: Arc::new(app), + prefix: Some(prefix), + safe_to_embed: false, + info: EndpointInfo::from(info), + } + } + + /// Creates new `PageEndpoint` which can be safely used in iframe + /// even from different origin. It might be dangerous (clickjacking). + /// Use wisely! + pub fn new_safe_to_embed(app: T) -> Self { + let info = app.info(); + PageEndpoint { + app: Arc::new(app), + prefix: None, + safe_to_embed: true, + info: EndpointInfo::from(info), + } + } +} + +impl Endpoint for PageEndpoint { + + fn info(&self) -> Option<&EndpointInfo> { + Some(&self.info) + } + + fn to_handler(&self, path: EndpointPath) -> Box { + Box::new(handler::PageHandler { + app: BuiltinDapp::new(self.app.clone()), + prefix: self.prefix.clone(), + path: path, + file: None, + safe_to_embed: self.safe_to_embed, + }) + } +} + +impl From for EndpointInfo { + fn from(info: Info) -> Self { + EndpointInfo { + name: info.name.into(), + description: info.description.into(), + author: info.author.into(), + icon_url: info.icon_url.into(), + version: info.version.into(), + } + } +} + +struct BuiltinDapp { + app: Arc, +} + +impl BuiltinDapp { + fn new(app: Arc) -> Self { + BuiltinDapp { + app: app, + } + } +} + +impl handler::Dapp for BuiltinDapp { + type DappFile = BuiltinDappFile; + + fn file(&self, path: &str) -> Option { + self.app.file(path).map(|_| { + BuiltinDappFile { + app: self.app.clone(), + path: path.into(), + write_pos: 0, + } + }) + } +} + +struct BuiltinDappFile { + app: Arc, + path: String, + write_pos: usize, +} + +impl BuiltinDappFile { + fn file(&self) -> &File { + self.app.file(&self.path).expect("Check is done when structure is created.") + } +} + +impl handler::DappFile for BuiltinDappFile { + fn content_type(&self) -> &str { + self.file().content_type + } + + fn is_drained(&self) -> bool { + self.write_pos == self.file().content.len() + } + + fn next_chunk(&mut self) -> &[u8] { + &self.file().content[self.write_pos..] + } + + fn bytes_written(&mut self, bytes: usize) { + self.write_pos += bytes; + } +} diff --git a/dapps/src/page/handler.rs b/dapps/src/page/handler.rs new file mode 100644 index 000000000..5167c85c8 --- /dev/null +++ b/dapps/src/page/handler.rs @@ -0,0 +1,207 @@ +// 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 . + +use std::io::Write; +use hyper::header; +use hyper::server; +use hyper::uri::RequestUri; +use hyper::net::HttpStream; +use hyper::status::StatusCode; +use hyper::{Decoder, Encoder, Next}; +use endpoint::EndpointPath; + +/// Represents a file that can be sent to client. +/// Implementation should keep track of bytes already sent internally. +pub trait DappFile: Send { + /// Returns a content-type of this file. + fn content_type(&self) -> &str; + + /// Checks if all bytes from that file were written. + fn is_drained(&self) -> bool; + + /// Fetch next chunk to write to the client. + fn next_chunk(&mut self) -> &[u8]; + + /// How many files have been written to the client. + fn bytes_written(&mut self, bytes: usize); +} + +/// Dapp as a (dynamic) set of files. +pub trait Dapp: Send + 'static { + /// File type + type DappFile: DappFile; + + /// Returns file under given path. + fn file(&self, path: &str) -> Option; +} + +/// A handler for a single webapp. +/// Resolves correct paths and serves as a plumbing code between +/// hyper server and dapp. +pub struct PageHandler { + /// A Dapp. + pub app: T, + /// File currently being served (or `None` if file does not exist). + pub file: Option, + /// Optional prefix to strip from path. + pub prefix: Option, + /// Requested path. + pub path: EndpointPath, + /// Flag indicating if the file can be safely embeded (put in iframe). + pub safe_to_embed: bool, +} + +impl PageHandler { + fn extract_path(&self, path: &str) -> String { + let app_id = &self.path.app_id; + let prefix = "/".to_owned() + self.prefix.as_ref().unwrap_or(app_id); + let prefix_with_slash = prefix.clone() + "/"; + let query_pos = path.find('?').unwrap_or_else(|| path.len()); + + // Index file support + match path == "/" || path == &prefix || path == &prefix_with_slash { + true => "index.html".to_owned(), + false => if path.starts_with(&prefix_with_slash) { + path[prefix_with_slash.len()..query_pos].to_owned() + } else if path.starts_with("/") { + path[1..query_pos].to_owned() + } else { + path[0..query_pos].to_owned() + } + } + } +} + +impl server::Handler for PageHandler { + fn on_request(&mut self, req: server::Request) -> Next { + self.file = match *req.uri() { + RequestUri::AbsolutePath(ref path) => { + self.app.file(&self.extract_path(path)) + }, + RequestUri::AbsoluteUri(ref url) => { + self.app.file(&self.extract_path(url.path())) + }, + _ => None, + }; + Next::write() + } + + fn on_request_readable(&mut self, _decoder: &mut Decoder) -> Next { + Next::write() + } + + fn on_response(&mut self, res: &mut server::Response) -> Next { + if let Some(ref f) = self.file { + res.set_status(StatusCode::Ok); + res.headers_mut().set(header::ContentType(f.content_type().parse().unwrap())); + if !self.safe_to_embed { + res.headers_mut().set_raw("X-Frame-Options", vec![b"SAMEORIGIN".to_vec()]); + } + Next::write() + } else { + res.set_status(StatusCode::NotFound); + Next::write() + } + } + + fn on_response_writable(&mut self, encoder: &mut Encoder) -> Next { + match self.file { + None => Next::end(), + Some(ref f) if f.is_drained() => Next::end(), + Some(ref mut f) => match encoder.write(f.next_chunk()) { + Ok(bytes) => { + f.bytes_written(bytes); + Next::write() + }, + Err(e) => match e.kind() { + ::std::io::ErrorKind::WouldBlock => Next::write(), + _ => Next::end(), + }, + } + } + } +} + + + +#[cfg(test)] +mod test { + use super::*; + + pub struct TestWebAppFile; + + impl DappFile for TestWebAppFile { + fn content_type(&self) -> &str { + unimplemented!() + } + + fn is_drained(&self) -> bool { + unimplemented!() + } + + fn next_chunk(&mut self) -> &[u8] { + unimplemented!() + } + + fn bytes_written(&mut self, _bytes: usize) { + unimplemented!() + } + } + + #[derive(Default)] + pub struct TestWebapp; + + impl Dapp for TestWebapp { + type DappFile = TestWebAppFile; + + fn file(&self, _path: &str) -> Option { + None + } + } +} + +#[test] +fn should_extract_path_with_appid() { + + // given + let path1 = "/"; + let path2= "/test.css"; + let path3 = "/app/myfile.txt"; + let path4 = "/app/myfile.txt?query=123"; + let page_handler = PageHandler { + app: test::TestWebapp, + prefix: None, + path: EndpointPath { + app_id: "app".to_owned(), + host: "".to_owned(), + port: 8080 + }, + file: None, + safe_to_embed: true, + }; + + // when + let res1 = page_handler.extract_path(path1); + let res2 = page_handler.extract_path(path2); + let res3 = page_handler.extract_path(path3); + let res4 = page_handler.extract_path(path4); + + // then + assert_eq!(&res1, "index.html"); + assert_eq!(&res2, "test.css"); + assert_eq!(&res3, "myfile.txt"); + assert_eq!(&res4, "myfile.txt"); +} diff --git a/dapps/src/page/local.rs b/dapps/src/page/local.rs new file mode 100644 index 000000000..52e32bf5e --- /dev/null +++ b/dapps/src/page/local.rs @@ -0,0 +1,118 @@ +// 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 . + +use mime_guess; +use std::io::{Seek, Read, SeekFrom}; +use std::fs; +use std::path::PathBuf; +use page::handler; +use endpoint::{Endpoint, EndpointInfo, EndpointPath, Handler}; + +pub struct LocalPageEndpoint { + path: PathBuf, + info: EndpointInfo, +} + +impl LocalPageEndpoint { + pub fn new(path: PathBuf, info: EndpointInfo) -> Self { + LocalPageEndpoint { + path: path, + info: info, + } + } +} + +impl Endpoint for LocalPageEndpoint { + fn info(&self) -> Option<&EndpointInfo> { + Some(&self.info) + } + + fn to_handler(&self, path: EndpointPath) -> Box { + Box::new(handler::PageHandler { + app: LocalDapp::new(self.path.clone()), + prefix: None, + path: path, + file: None, + safe_to_embed: false, + }) + } +} + +struct LocalDapp { + path: PathBuf, +} + +impl LocalDapp { + fn new(path: PathBuf) -> Self { + LocalDapp { + path: path + } + } +} + +impl handler::Dapp for LocalDapp { + type DappFile = LocalFile; + + fn file(&self, file_path: &str) -> Option { + let mut path = self.path.clone(); + for part in file_path.split('/') { + path.push(part); + } + // Check if file exists + fs::File::open(path.clone()).ok().map(|file| { + let content_type = mime_guess::guess_mime_type(path); + let len = file.metadata().ok().map_or(0, |meta| meta.len()); + LocalFile { + content_type: content_type.to_string(), + buffer: [0; 4096], + file: file, + pos: 0, + len: len, + } + }) + } +} + +struct LocalFile { + content_type: String, + buffer: [u8; 4096], + file: fs::File, + len: u64, + pos: u64, +} + +impl handler::DappFile for LocalFile { + fn content_type(&self) -> &str { + &self.content_type + } + + fn is_drained(&self) -> bool { + self.pos == self.len + } + + fn next_chunk(&mut self) -> &[u8] { + let _ = self.file.seek(SeekFrom::Start(self.pos)); + if let Ok(n) = self.file.read(&mut self.buffer) { + &self.buffer[0..n] + } else { + &self.buffer[0..0] + } + } + + fn bytes_written(&mut self, bytes: usize) { + self.pos += bytes as u64; + } +} diff --git a/dapps/src/page/mod.rs b/dapps/src/page/mod.rs index 819988310..349c979c7 100644 --- a/dapps/src/page/mod.rs +++ b/dapps/src/page/mod.rs @@ -14,216 +14,11 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -use std::sync::Arc; -use std::io::Write; -use hyper::uri::RequestUri; -use hyper::server; -use hyper::header; -use hyper::status::StatusCode; -use hyper::net::HttpStream; -use hyper::{Decoder, Encoder, Next}; -use endpoint::{Endpoint, EndpointInfo, EndpointPath}; -use parity_dapps::{WebApp, Info}; -pub struct PageEndpoint { - /// Content of the files - pub app: Arc, - /// Prefix to strip from the path (when `None` deducted from `app_id`) - pub prefix: Option, - /// Safe to be loaded in frame by other origin. (use wisely!) - safe_to_embed: bool, -} +mod builtin; +mod local; +mod handler; -impl PageEndpoint { - pub fn new(app: T) -> Self { - PageEndpoint { - app: Arc::new(app), - prefix: None, - safe_to_embed: false, - } - } +pub use self::local::LocalPageEndpoint; +pub use self::builtin::PageEndpoint; - pub fn with_prefix(app: T, prefix: String) -> Self { - PageEndpoint { - app: Arc::new(app), - prefix: Some(prefix), - safe_to_embed: false, - } - } - - /// Creates new `PageEndpoint` which can be safely used in iframe - /// even from different origin. It might be dangerous (clickjacking). - /// Use wisely! - pub fn new_safe_to_embed(app: T) -> Self { - PageEndpoint { - app: Arc::new(app), - prefix: None, - safe_to_embed: true, - } - } -} - -impl Endpoint for PageEndpoint { - - fn info(&self) -> Option { - Some(EndpointInfo::from(self.app.info())) - } - - fn to_handler(&self, path: EndpointPath) -> Box> { - Box::new(PageHandler { - app: self.app.clone(), - prefix: self.prefix.clone(), - path: path, - file: None, - write_pos: 0, - safe_to_embed: self.safe_to_embed, - }) - } -} - -impl From for EndpointInfo { - fn from(info: Info) -> Self { - EndpointInfo { - name: info.name, - description: info.description, - author: info.author, - icon_url: info.icon_url, - version: info.version, - } - } -} - -struct PageHandler { - app: Arc, - prefix: Option, - path: EndpointPath, - file: Option, - write_pos: usize, - safe_to_embed: bool, -} - -impl PageHandler { - fn extract_path(&self, path: &str) -> String { - let app_id = &self.path.app_id; - let prefix = "/".to_owned() + self.prefix.as_ref().unwrap_or(app_id); - let prefix_with_slash = prefix.clone() + "/"; - let query_pos = path.find('?').unwrap_or_else(|| path.len()); - - // Index file support - match path == "/" || path == &prefix || path == &prefix_with_slash { - true => "index.html".to_owned(), - false => if path.starts_with(&prefix_with_slash) { - path[prefix_with_slash.len()..query_pos].to_owned() - } else if path.starts_with("/") { - path[1..query_pos].to_owned() - } else { - path[0..query_pos].to_owned() - } - } - } -} - -impl server::Handler for PageHandler { - fn on_request(&mut self, req: server::Request) -> Next { - self.file = match *req.uri() { - RequestUri::AbsolutePath(ref path) => { - Some(self.extract_path(path)) - }, - RequestUri::AbsoluteUri(ref url) => { - Some(self.extract_path(url.path())) - }, - _ => None, - }; - Next::write() - } - - fn on_request_readable(&mut self, _decoder: &mut Decoder) -> Next { - Next::write() - } - - fn on_response(&mut self, res: &mut server::Response) -> Next { - if let Some(f) = self.file.as_ref().and_then(|f| self.app.file(f)) { - res.set_status(StatusCode::Ok); - res.headers_mut().set(header::ContentType(f.content_type.parse().unwrap())); - if !self.safe_to_embed { - res.headers_mut().set_raw("X-Frame-Options", vec![b"SAMEORIGIN".to_vec()]); - } - Next::write() - } else { - res.set_status(StatusCode::NotFound); - Next::write() - } - } - - fn on_response_writable(&mut self, encoder: &mut Encoder) -> Next { - let (wrote, res) = { - let file = self.file.as_ref().and_then(|f| self.app.file(f)); - match file { - None => (None, Next::end()), - Some(f) if self.write_pos == f.content.len() => (None, Next::end()), - Some(f) => match encoder.write(&f.content[self.write_pos..]) { - Ok(bytes) => (Some(bytes), Next::write()), - Err(e) => match e.kind() { - ::std::io::ErrorKind::WouldBlock => (None, Next::write()), - _ => (None, Next::end()) - }, - } - } - }; - if let Some(bytes) = wrote { - self.write_pos += bytes; - } - res - } -} - - -#[cfg(test)] -use parity_dapps::File; - -#[cfg(test)] -#[derive(Default)] -struct TestWebapp; - -#[cfg(test)] -impl WebApp for TestWebapp { - fn file(&self, _path: &str) -> Option<&File> { - None - } - fn info(&self) -> Info { - unimplemented!() - } -} - -#[test] -fn should_extract_path_with_appid() { - // given - let path1 = "/"; - let path2= "/test.css"; - let path3 = "/app/myfile.txt"; - let path4 = "/app/myfile.txt?query=123"; - let page_handler = PageHandler { - app: Arc::new(TestWebapp), - prefix: None, - path: EndpointPath { - app_id: "app".to_owned(), - host: "".to_owned(), - port: 8080 - }, - file: None, - write_pos: 0, - safe_to_embed: true, - }; - - // when - let res1 = page_handler.extract_path(path1); - let res2 = page_handler.extract_path(path2); - let res3 = page_handler.extract_path(path3); - let res4 = page_handler.extract_path(path4); - - // then - assert_eq!(&res1, "index.html"); - assert_eq!(&res2, "test.css"); - assert_eq!(&res3, "myfile.txt"); - assert_eq!(&res4, "myfile.txt"); -} 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/miner/build.rs b/devtools/src/stop_guard.rs similarity index 58% rename from miner/build.rs rename to devtools/src/stop_guard.rs index 41b9a1b3e..f1db59725 100644 --- a/miner/build.rs +++ b/devtools/src/stop_guard.rs @@ -14,12 +14,32 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -extern crate rustc_version; +//! Stop guard mod -use rustc_version::{version_meta, Channel}; +use std::sync::Arc; +use std::sync::atomic::*; -fn main() { - if let Channel::Nightly = version_meta().channel { - println!("cargo:rustc-cfg=nightly"); +/// 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) } } diff --git a/doc.sh b/doc.sh index 0b75f6c38..fb39ef272 100755 --- a/doc.sh +++ b/doc.sh @@ -10,4 +10,3 @@ cargo doc --no-deps --verbose \ -p ethcore-signer \ -p ethcore-dapps \ -p parity \ - -p ethminer diff --git a/ethcore/Cargo.toml b/ethcore/Cargo.toml index 34a010794..1de24ee32 100644 --- a/ethcore/Cargo.toml +++ b/ethcore/Cargo.toml @@ -29,6 +29,7 @@ ethcore-devtools = { path = "../devtools" } ethjson = { path = "../json" } bloomchain = "0.1" "ethcore-ipc" = { path = "../ipc/rpc" } +rayon = "0.3.1" [features] jit = ["evmjit"] 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/account.rs b/ethcore/src/account.rs index 7ba213393..5e0dc335a 100644 --- a/ethcore/src/account.rs +++ b/ethcore/src/account.rs @@ -51,8 +51,6 @@ impl Account { } } - #[cfg(test)] - #[cfg(feature = "json-tests")] /// General constructor. pub fn from_pod(pod: PodAccount) -> Account { Account { diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index 8fb0de9eb..97f8dd5a4 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -37,7 +37,7 @@ use filter::Filter; use log_entry::LocalizedLogEntry; use block_queue::{BlockQueue, BlockQueueInfo}; use blockchain::{BlockChain, BlockProvider, TreeRoute, ImportRoute}; -use client::{BlockID, TransactionID, UncleID, TraceId, ClientConfig, BlockChainClient, TraceFilter}; +use client::{BlockID, TransactionID, UncleID, TraceId, ClientConfig, BlockChainClient, MiningBlockChainClient, TraceFilter, CallAnalytics}; use client::Error as ClientError; use env_info::EnvInfo; use executive::{Executive, Executed, TransactOptions, contract_address}; @@ -48,6 +48,7 @@ use trace; pub use types::blockchain_info::BlockChainInfo; pub use types::block_status::BlockStatus; use evm::Factory as EvmFactory; +use miner::{Miner, MinerService, TransactionImportResult, AccountDetails}; impl fmt::Display for BlockChainInfo { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { @@ -90,6 +91,7 @@ pub struct Client where V: Verifier { panic_handler: Arc, verifier: PhantomData, vm_factory: Arc, + miner: Arc, } const HISTORY: u64 = 1200; @@ -102,8 +104,8 @@ const CLIENT_DB_VER_STR: &'static str = "5.3"; impl Client { /// Create a new client with given spec and DB path. - pub fn new(config: ClientConfig, spec: Spec, path: &Path, message_channel: IoChannel ) -> Result, ClientError> { - Client::::new_with_verifier(config, spec, path, message_channel) + pub fn new(config: ClientConfig, spec: Spec, path: &Path, miner: Arc, message_channel: IoChannel ) -> Result, ClientError> { + Client::::new_with_verifier(config, spec, path, miner, message_channel) } } @@ -126,7 +128,14 @@ pub fn append_path(path: &Path, item: &str) -> String { impl Client where V: Verifier { /// Create a new client with given spec and DB path and custom verifier. - pub fn new_with_verifier(config: ClientConfig, spec: Spec, path: &Path, message_channel: IoChannel ) -> Result>, ClientError> { + pub fn new_with_verifier( + config: ClientConfig, + spec: Spec, + path: &Path, + miner: Arc, + message_channel: IoChannel) + -> Result>, ClientError> + { let path = get_db_path(path, config.pruning, spec.genesis_header().hash()); let gb = spec.genesis_block(); let chain = Arc::new(BlockChain::new(config.blockchain, &gb, &path)); @@ -155,6 +164,7 @@ impl Client where V: Verifier { panic_handler: panic_handler, verifier: PhantomData, vm_factory: Arc::new(EvmFactory::new(config.vm_type)), + miner: miner, }; Ok(Arc::new(client)) @@ -328,6 +338,11 @@ impl Client where V: Verifier { { if !imported_blocks.is_empty() && self.block_queue.queue_info().is_empty() { let (enacted, retracted) = self.calculate_enacted_retracted(import_results); + + if self.queue_info().is_empty() { + self.miner.chain_new_blocks(self, &imported_blocks, &invalid_blocks, &enacted, &retracted); + } + io.send(NetworkIoMessage::User(SyncMessage::NewChainBlocks { imported: imported_blocks, invalid: invalid_blocks, @@ -339,7 +354,7 @@ impl Client where V: Verifier { { if self.chain_info().best_block_hash != original_best { - io.send(NetworkIoMessage::User(SyncMessage::NewChainHead)).unwrap(); + self.miner.update_sealing(self); } } @@ -414,14 +429,14 @@ impl Client where V: Verifier { TransactionID::Hash(ref hash) => self.chain.transaction_address(hash), TransactionID::Location(id, index) => Self::block_hash(&self.chain, id).map(|hash| TransactionAddress { block_hash: hash, - index: index + index: index, }) } } } impl BlockChainClient for Client where V: Verifier { - fn call(&self, t: &SignedTransaction) -> Result { + fn call(&self, t: &SignedTransaction, analytics: CallAnalytics) -> Result { let header = self.block_header(BlockID::Latest).unwrap(); let view = HeaderView::new(&header); let last_hashes = self.build_last_hashes(view.hash()); @@ -441,88 +456,27 @@ impl BlockChainClient for Client where V: Verifier { ExecutionError::TransactionMalformed(message) })); let balance = state.balance(&sender); - // 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 }; - Executive::new(&mut state, &env_info, self.engine.deref().deref(), &self.vm_factory).transact(t, options) - } - - // TODO [todr] Should be moved to miner crate eventually. - fn try_seal(&self, block: LockedBlock, seal: Vec) -> Result { - block.try_seal(self.engine.deref().deref(), seal) + let needed_balance = t.value + t.gas * t.gas_price; + if balance < needed_balance { + // give the sender a sufficient balance + state.add_balance(&sender, &(needed_balance - balance)); + } + let options = TransactOptions { tracing: false, vm_tracing: analytics.vm_tracing, check_nonce: false }; + let mut ret = Executive::new(&mut state, &env_info, self.engine.deref().deref(), &self.vm_factory).transact(t, options); + + // TODO gav move this into Executive. + if analytics.state_diffing { + if let Ok(ref mut x) = ret { + x.state_diff = Some(state.diff_from(self.state())); + } + } + ret } fn vm_factory(&self) -> &EvmFactory { &self.vm_factory } - // TODO [todr] Should be moved to miner crate eventually. - fn prepare_sealing(&self, author: Address, gas_floor_target: U256, extra_data: Bytes, transactions: Vec) - -> (Option, HashSet) { - let engine = self.engine.deref().deref(); - let h = self.chain.best_block_hash(); - let mut invalid_transactions = HashSet::new(); - - let mut b = OpenBlock::new( - engine, - &self.vm_factory, - false, // TODO: this will need to be parameterised once we want to do immediate mining insertion. - self.state_db.lock().unwrap().boxed_clone(), - match self.chain.block_header(&h) { Some(ref x) => x, None => { return (None, invalid_transactions) } }, - self.build_last_hashes(h.clone()), - author, - gas_floor_target, - extra_data, - ); - - // Add uncles - self.chain - .find_uncle_headers(&h, engine.maximum_uncle_age()) - .unwrap() - .into_iter() - .take(engine.maximum_uncle_count()) - .foreach(|h| { - b.push_uncle(h).unwrap(); - }); - - // Add transactions - let block_number = b.block().header().number(); - let min_tx_gas = U256::from(self.engine.schedule(&b.env_info()).tx_gas); - - for tx in transactions { - // Push transaction to block - let hash = tx.hash(); - let import = b.push_transaction(tx, None); - - match import { - Err(Error::Execution(ExecutionError::BlockGasLimitReached { gas_limit, gas_used, .. })) => { - trace!(target: "miner", "Skipping adding transaction to block because of gas limit: {:?}", hash); - // Exit early if gas left is smaller then min_tx_gas - if gas_limit - gas_used < min_tx_gas { - break; - } - }, - Err(e) => { - invalid_transactions.insert(hash); - trace!(target: "miner", - "Error adding transaction to block: number={}. transaction_hash={:?}, Error: {:?}", - block_number, hash, e); - }, - _ => {} - } - } - - // And close - let b = b.close(); - trace!(target: "miner", "Sealing: number={}, hash={}, diff={}", - b.block().header().number(), - b.hash(), - b.block().header().difficulty() - ); - (Some(b), invalid_transactions) - } - fn block_header(&self, id: BlockID) -> Option { Self::block_hash(&self.chain, id).and_then(|hash| self.chain.block(&hash).map(|bytes| BlockView::new(&bytes).rlp().at(0).as_raw().to_vec())) } @@ -561,7 +515,6 @@ impl BlockChainClient for Client where V: Verifier { self.state_at(id).map(|s| s.nonce(address)) } - fn block_hash(&self, id: BlockID) -> Option { Self::block_hash(&self.chain, id) } @@ -774,6 +727,89 @@ impl BlockChainClient for Client where V: Verifier { fn last_hashes(&self) -> LastHashes { self.build_last_hashes(self.chain.best_block_hash()) } + + fn import_transactions(&self, transactions: Vec) -> Vec> { + let fetch_account = |a: &Address| AccountDetails { + nonce: self.latest_nonce(a), + balance: self.latest_balance(a), + }; + self.miner.import_transactions(transactions, fetch_account) + } + + fn all_transactions(&self) -> Vec { + self.miner.all_transactions() + } +} + +impl MiningBlockChainClient for Client where V: Verifier { + fn prepare_sealing(&self, author: Address, gas_floor_target: U256, extra_data: Bytes, transactions: Vec) + -> (Option, HashSet) { + let engine = self.engine.deref().deref(); + let h = self.chain.best_block_hash(); + let mut invalid_transactions = HashSet::new(); + + let mut b = OpenBlock::new( + engine, + &self.vm_factory, + false, // TODO: this will need to be parameterised once we want to do immediate mining insertion. + self.state_db.lock().unwrap().boxed_clone(), + match self.chain.block_header(&h) { Some(ref x) => x, None => { return (None, invalid_transactions) } }, + self.build_last_hashes(h.clone()), + author, + gas_floor_target, + extra_data, + ); + + // Add uncles + self.chain + .find_uncle_headers(&h, engine.maximum_uncle_age()) + .unwrap() + .into_iter() + .take(engine.maximum_uncle_count()) + .foreach(|h| { + b.push_uncle(h).unwrap(); + }); + + // Add transactions + let block_number = b.block().header().number(); + let min_tx_gas = U256::from(self.engine.schedule(&b.env_info()).tx_gas); + + for tx in transactions { + // Push transaction to block + let hash = tx.hash(); + let import = b.push_transaction(tx, None); + + match import { + Err(Error::Execution(ExecutionError::BlockGasLimitReached { gas_limit, gas_used, .. })) => { + trace!(target: "miner", "Skipping adding transaction to block because of gas limit: {:?}", hash); + // Exit early if gas left is smaller then min_tx_gas + if gas_limit - gas_used < min_tx_gas { + break; + } + }, + Err(e) => { + invalid_transactions.insert(hash); + trace!(target: "miner", + "Error adding transaction to block: number={}. transaction_hash={:?}, Error: {:?}", + block_number, hash, e); + }, + _ => {} + } + } + + // And close + let b = b.close(); + trace!(target: "miner", "Sealing: number={}, hash={}, diff={}", + b.block().header().number(), + b.hash(), + b.block().header().difficulty() + ); + (Some(b), invalid_transactions) + } + + fn try_seal(&self, block: LockedBlock, seal: Vec) -> Result { + block.try_seal(self.engine.deref().deref(), seal) + } } impl MayPanic for Client { diff --git a/ethcore/src/client/mod.rs b/ethcore/src/client/mod.rs index 8e0a7b2dd..0dffb1a1c 100644 --- a/ethcore/src/client/mod.rs +++ b/ethcore/src/client/mod.rs @@ -46,6 +46,17 @@ use error::{ImportResult, ExecutionError}; use receipt::LocalizedReceipt; use trace::LocalizedTrace; use evm::Factory as EvmFactory; +use miner::{TransactionImportResult}; +use error::Error as EthError; + +/// Options concerning what analytics we run on the call. +#[derive(Eq, PartialEq, Default, Clone, Copy, Debug)] +pub struct CallAnalytics { + /// Make a VM trace. + pub vm_tracing: bool, + /// Make a diff. + pub state_diffing: bool, +} /// Blockchain database client. Owns and manages a blockchain and a block queue. pub trait BlockChainClient : Sync + Send { @@ -154,17 +165,9 @@ pub trait BlockChainClient : Sync + Send { /// Returns logs matching given filter. fn logs(&self, filter: Filter) -> Vec; - // TODO [todr] Should be moved to miner crate eventually. - /// Returns ClosedBlock prepared for sealing. - fn prepare_sealing(&self, author: Address, gas_floor_target: U256, extra_data: Bytes, transactions: Vec) - -> (Option, HashSet); - - // TODO [todr] Should be moved to miner crate eventually. - /// Attempts to seal given block. Returns `SealedBlock` on success and the same block in case of error. - fn try_seal(&self, block: LockedBlock, seal: Vec) -> Result; - /// 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, analytics: CallAnalytics) -> Result; /// Returns EvmFactory. fn vm_factory(&self) -> &EvmFactory; @@ -183,5 +186,20 @@ pub trait BlockChainClient : Sync + Send { /// Get last hashes starting from best block. fn last_hashes(&self) -> LastHashes; + + /// import transactions from network/other 3rd party + fn import_transactions(&self, transactions: Vec) -> Vec>; + + /// list all transactions + fn all_transactions(&self) -> Vec; } +/// Extended client interface used for mining +pub trait MiningBlockChainClient : BlockChainClient { + /// Attempts to seal given block. Returns `SealedBlock` on success and the same block in case of error. + fn try_seal(&self, block: LockedBlock, seal: Vec) -> Result; + + /// Returns ClosedBlock prepared for sealing. + fn prepare_sealing(&self, author: Address, gas_floor_target: U256, extra_data: Bytes, transactions: Vec) + -> (Option, HashSet); +} diff --git a/ethcore/src/client/test_client.rs b/ethcore/src/client/test_client.rs index de2973029..17905a905 100644 --- a/ethcore/src/client/test_client.rs +++ b/ethcore/src/client/test_client.rs @@ -20,7 +20,7 @@ use std::sync::atomic::{AtomicUsize, Ordering as AtomicOrder}; use util::*; use transaction::{Transaction, LocalizedTransaction, SignedTransaction, Action}; use blockchain::TreeRoute; -use client::{BlockChainClient, BlockChainInfo, BlockStatus, BlockID, TransactionID, UncleID, TraceId, TraceFilter, LastHashes}; +use client::{BlockChainClient, MiningBlockChainClient, BlockChainInfo, BlockStatus, BlockID, TransactionID, UncleID, TraceId, TraceFilter, LastHashes, CallAnalytics}; use header::{Header as BlockHeader, BlockNumber}; use filter::Filter; use log_entry::LocalizedLogEntry; @@ -28,6 +28,7 @@ use receipt::{Receipt, LocalizedReceipt}; use blockchain::extras::BlockReceipts; use error::{ImportResult}; use evm::Factory as EvmFactory; +use miner::{Miner, MinerService}; use block_queue::BlockQueueInfo; use block::{SealedBlock, ClosedBlock, LockedBlock}; @@ -35,6 +36,9 @@ use executive::Executed; use error::{ExecutionError}; use trace::LocalizedTrace; +use miner::{TransactionImportResult, AccountDetails}; +use error::Error as EthError; + /// Test client. pub struct TestBlockChainClient { /// Blocks. @@ -61,6 +65,8 @@ pub struct TestBlockChainClient { pub receipts: RwLock>, /// Block queue size. pub queue_size: AtomicUsize, + /// Miner + pub miner: Arc, } #[derive(Clone)] @@ -99,6 +105,7 @@ impl TestBlockChainClient { execution_result: RwLock::new(None), receipts: RwLock::new(HashMap::new()), queue_size: AtomicUsize::new(0), + miner: Arc::new(Miner::default()), }; client.add_blocks(1, EachBlockWith::Nothing); // add genesis block client.genesis_hash = client.last_hash.read().unwrap().clone(); @@ -232,8 +239,19 @@ impl TestBlockChainClient { } } +impl MiningBlockChainClient for TestBlockChainClient { + fn try_seal(&self, block: LockedBlock, _seal: Vec) -> Result { + Err(block) + } + + + fn prepare_sealing(&self, _author: Address, _gas_floor_target: U256, _extra_data: Bytes, _transactions: Vec) -> (Option, HashSet) { + (None, HashSet::new()) + } +} + impl BlockChainClient for TestBlockChainClient { - fn call(&self, _t: &SignedTransaction) -> Result { + fn call(&self, _t: &SignedTransaction, _analytics: CallAnalytics) -> Result { Ok(self.execution_result.read().unwrap().clone().unwrap()) } @@ -296,14 +314,6 @@ impl BlockChainClient for TestBlockChainClient { unimplemented!(); } - fn prepare_sealing(&self, _author: Address, _gas_floor_target: U256, _extra_data: Bytes, _transactions: Vec) -> (Option, HashSet) { - (None, HashSet::new()) - } - - fn try_seal(&self, block: LockedBlock, _seal: Vec) -> Result { - Err(block) - } - fn block_header(&self, id: BlockID) -> Option { self.block_hash(id).and_then(|hash| self.blocks.read().unwrap().get(&hash).map(|r| Rlp::new(r).at(0).as_raw().to_vec())) } @@ -476,4 +486,19 @@ impl BlockChainClient for TestBlockChainClient { fn block_traces(&self, _trace: BlockID) -> Option> { unimplemented!(); } + + fn import_transactions(&self, transactions: Vec) -> Vec> { + let nonces = self.nonces.read().unwrap(); + let balances = self.balances.read().unwrap(); + let fetch_account = |a: &Address| AccountDetails { + nonce: nonces[a], + balance: balances[a], + }; + + self.miner.import_transactions(transactions, &fetch_account) + } + + fn all_transactions(&self) -> Vec { + self.miner.all_transactions() + } } diff --git a/ethcore/src/evm/evm.rs b/ethcore/src/evm/evm.rs index b6c2debc5..740774f38 100644 --- a/ethcore/src/evm/evm.rs +++ b/ethcore/src/evm/evm.rs @@ -63,13 +63,43 @@ pub enum Error { Internal, } -/// Evm result. -/// -/// Returns `gas_left` if execution is successful, otherwise error. -pub type Result = result::Result; +/// A specialized version of Result over EVM errors. +pub type Result = ::std::result::Result; -/// Evm interface. +/// Gas Left: either it is a known value, or it needs to be computed by processing +/// a return instruction. +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] +pub enum GasLeft<'a> { + /// Known gas left + Known(U256), + /// Return instruction must be processed. + NeedsReturn(U256, &'a [u8]), +} + +/// Types that can be "finalized" using an EVM. +/// +/// In practice, this is just used to define an inherent impl on +/// `Reult>`. +pub trait Finalize { + /// Consume the externalities, call return if necessary, and produce a final amount of gas left. + fn finalize(self, ext: E) -> Result; +} + +impl<'a> Finalize for Result> { + fn finalize(self, ext: E) -> Result { + match self { + Ok(GasLeft::Known(gas)) => Ok(gas), + Ok(GasLeft::NeedsReturn(gas, ret_code)) => ext.ret(&gas, ret_code), + Err(err) => Err(err), + } + } +} + +/// Evm interface pub trait Evm { /// This function should be used to execute transaction. - fn exec(&self, params: ActionParams, ext: &mut Ext) -> Result; + /// + /// It returns either an error, a known amount of gas left, or parameters to be used + /// to compute the final gas left. + fn exec(&mut self, params: ActionParams, ext: &mut Ext) -> Result; } diff --git a/ethcore/src/evm/ext.rs b/ethcore/src/evm/ext.rs index 4986b12c8..0aaa4dac6 100644 --- a/ethcore/src/evm/ext.rs +++ b/ethcore/src/evm/ext.rs @@ -17,7 +17,7 @@ //! Interface for Evm externalities. use util::common::*; -use evm::{Schedule, Error}; +use evm::{self, Schedule}; use env_info::*; /// Result of externalities create function. @@ -85,7 +85,7 @@ pub trait Ext { /// Should be called when transaction calls `RETURN` opcode. /// Returns gas_left if cost of returning the data is not too high. - fn ret(&mut self, gas: &U256, data: &[u8]) -> Result; + fn ret(self, gas: &U256, data: &[u8]) -> evm::Result where Self: Sized; /// Should be called when contract commits suicide. /// Address to which funds should be refunded. @@ -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/factory.rs b/ethcore/src/evm/factory.rs index 3e60e8808..f55e064c7 100644 --- a/ethcore/src/evm/factory.rs +++ b/ethcore/src/evm/factory.rs @@ -89,10 +89,10 @@ impl Factory { pub fn create(&self) -> Box { match self.evm { VMType::Jit => { - Box::new(super::jit::JitEvm) + Box::new(super::jit::JitEvm::default()) }, VMType::Interpreter => { - Box::new(super::interpreter::Interpreter) + Box::new(super::interpreter::Interpreter::default()) } } } @@ -102,7 +102,7 @@ impl Factory { pub fn create(&self) -> Box { match self.evm { VMType::Interpreter => { - Box::new(super::interpreter::Interpreter) + Box::new(super::interpreter::Interpreter::default()) } } } 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..1514b3e2e 100644 --- a/ethcore/src/evm/interpreter.rs +++ b/ethcore/src/evm/interpreter.rs @@ -17,10 +17,11 @@ ///! 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}; +use evm::{self, MessageCallResult, ContractCreateResult, GasLeft}; #[cfg(not(feature = "evm-debug"))] macro_rules! evm_debug { @@ -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 { @@ -271,21 +279,26 @@ enum InstructionResult { GasLeft(U256), UnusedGas(U256), JumpToPosition(U256), - StopExecutionWithGasLeft(U256), - StopExecution + // gas left, init_orf, init_size + StopExecutionNeedsReturn(U256, U256, U256), + StopExecution, } /// Intepreter EVM implementation -pub struct Interpreter; +#[derive(Default)] +pub struct Interpreter { + mem: Vec, +} impl evm::Evm for Interpreter { - fn exec(&self, params: ActionParams, ext: &mut evm::Ext) -> evm::Result { + fn exec(&mut self, params: ActionParams, ext: &mut evm::Ext) -> evm::Result { + self.mem.clear(); + let code = ¶ms.code.as_ref().unwrap(); let valid_jump_destinations = self.find_jump_destinations(&code); let mut current_gas = params.gas; let mut stack = VecStack::with_capacity(ext.schedule().stack_limit, U256::zero()); - let mut mem = vec![]; let mut reader = CodeReader { position: 0, code: &code @@ -293,12 +306,17 @@ 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)); + let (gas_cost, mem_size) = try!(self.get_gas_cost_mem(ext, instruction, &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); + self.mem.expand(mem_size); current_gas = current_gas - gas_cost; //TODO: use operator -= evm_debug!({ @@ -311,10 +329,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 - )); + current_gas, ¶ms, ext, instruction, &mut reader, &mut stack + )); + + if trace_executed { + ext.trace_executed(current_gas, stack.peek_top(get_info(instruction).ret), mem_written.map(|(o, s)| (o, &(self.mem[o..(o + s)]))), store_written); + } // Advance match result { @@ -332,29 +359,25 @@ impl evm::Evm for Interpreter { let pos = try!(self.verify_jump(position, &valid_jump_destinations)); reader.position = pos; }, - InstructionResult::StopExecutionWithGasLeft(gas_left) => { - current_gas = gas_left; - reader.position = code.len(); + InstructionResult::StopExecutionNeedsReturn(gas, off, size) => { + return Ok(GasLeft::NeedsReturn(gas, self.mem.read_slice(off, size))); }, - InstructionResult::StopExecution => { - reader.position = code.len(); - } + InstructionResult::StopExecution => break, } } - Ok(current_gas) + Ok(GasLeft::Known(current_gas)) } } impl Interpreter { #[cfg_attr(feature="dev", allow(cyclomatic_complexity))] fn get_gas_cost_mem( - &self, + &mut self, ext: &evm::Ext, instruction: Instruction, - mem: &mut Memory, stack: &Stack - ) -> Result<(U256, usize), evm::Error> { + ) -> evm::Result<(U256, usize)> { let schedule = ext.schedule(); let info = instructions::get_info(instruction); @@ -470,12 +493,12 @@ impl Interpreter { Ok((gas, 0)) }, InstructionCost::GasMem(gas, mem_size) => { - let (mem_gas, new_mem_size) = try!(self.mem_gas_cost(schedule, mem.size(), &mem_size)); + let (mem_gas, new_mem_size) = try!(self.mem_gas_cost(schedule, self.mem.size(), &mem_size)); let gas = overflowing!(gas.overflowing_add(mem_gas)); Ok((gas, new_mem_size)) }, InstructionCost::GasMemCopy(gas, mem_size, copy) => { - let (mem_gas, new_mem_size) = try!(self.mem_gas_cost(schedule, mem.size(), &mem_size)); + let (mem_gas, new_mem_size) = try!(self.mem_gas_cost(schedule, self.mem.size(), &mem_size)); let copy = overflowing!(add_u256_usize(©, 31)); let copy_gas = U256::from(schedule.copy_gas) * (copy / U256::from(32)); let gas = overflowing!(gas.overflowing_add(copy_gas)); @@ -485,7 +508,32 @@ impl Interpreter { } } - fn mem_gas_cost(&self, schedule: &evm::Schedule, current_mem_size: usize, mem_size: &U256) -> Result<(U256, usize), evm::Error> { + 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) -> evm::Result<(U256, usize)> { let gas_for_mem = |mem_size: U256| { let s = mem_size >> 5; // s * memory_gas + s * s / quad_coeff_div @@ -510,11 +558,11 @@ impl Interpreter { }, req_mem_size_rounded.low_u64() as usize)) } - fn mem_needed_const(&self, mem: &U256, add: usize) -> Result { + fn mem_needed_const(&self, mem: &U256, add: usize) -> evm::Result { Ok(overflowing!(mem.overflowing_add(U256::from(add)))) } - fn mem_needed(&self, offset: &U256, size: &U256) -> Result { + fn mem_needed(&self, offset: &U256, size: &U256) -> evm::Result { if self.is_zero(size) { return Ok(U256::zero()); } @@ -524,15 +572,14 @@ impl Interpreter { #[cfg_attr(feature="dev", allow(too_many_arguments))] fn exec_instruction( - &self, + &mut self, gas: Gas, params: &ActionParams, ext: &mut evm::Ext, instruction: Instruction, code: &mut CodeReader, - mem: &mut Memory, stack: &mut Stack - ) -> Result { + ) -> evm::Result { match instruction { instructions::JUMP => { let jump = stack.pop_back(); @@ -557,7 +604,7 @@ impl Interpreter { let init_off = stack.pop_back(); let init_size = stack.pop_back(); - let contract_code = mem.read_slice(init_off, init_size); + let contract_code = self.mem.read_slice(init_off, init_size); let can_create = ext.balance(¶ms.address) >= endowment && ext.depth() < ext.schedule().max_depth; if !can_create { @@ -624,8 +671,8 @@ impl Interpreter { let call_result = { // we need to write and read from memory in the same time // and we don't want to copy - let input = unsafe { ::std::mem::transmute(mem.read_slice(in_off, in_size)) }; - let output = mem.writeable_slice(out_off, out_size); + let input = unsafe { ::std::mem::transmute(self.mem.read_slice(in_off, in_size)) }; + let output = self.mem.writeable_slice(out_off, out_size); ext.call(&call_gas, sender_address, receive_address, value, input, &code_address, output) }; @@ -643,11 +690,8 @@ impl Interpreter { instructions::RETURN => { let init_off = stack.pop_back(); let init_size = stack.pop_back(); - let return_code = mem.read_slice(init_off, init_size); - let gas_left = try!(ext.ret(&gas, &return_code)); - return Ok(InstructionResult::StopExecutionWithGasLeft( - gas_left - )); + + return Ok(InstructionResult::StopExecutionNeedsReturn(gas, init_off, init_size)) }, instructions::STOP => { return Ok(InstructionResult::StopExecution); @@ -666,7 +710,7 @@ impl Interpreter { .iter() .map(H256::from) .collect(); - ext.log(topics, mem.read_slice(offset, size)); + ext.log(topics, self.mem.read_slice(offset, size)); }, instructions::PUSH1...instructions::PUSH32 => { let bytes = instructions::get_push_bytes(instruction); @@ -674,26 +718,26 @@ impl Interpreter { stack.push(val); }, instructions::MLOAD => { - let word = mem.read(stack.pop_back()); + let word = self.mem.read(stack.pop_back()); stack.push(U256::from(word)); }, instructions::MSTORE => { let offset = stack.pop_back(); let word = stack.pop_back(); - mem.write(offset, word); + Memory::write(&mut self.mem, offset, word); }, instructions::MSTORE8 => { let offset = stack.pop_back(); let byte = stack.pop_back(); - mem.write_byte(offset, byte); + self.mem.write_byte(offset, byte); }, instructions::MSIZE => { - stack.push(U256::from(mem.size())); + stack.push(U256::from(self.mem.size())); }, instructions::SHA3 => { let offset = stack.pop_back(); let size = stack.pop_back(); - let sha3 = mem.read_slice(offset, size).sha3(); + let sha3 = self.mem.read_slice(offset, size).sha3(); stack.push(U256::from(sha3.as_slice())); }, instructions::SLOAD => { @@ -766,15 +810,15 @@ impl Interpreter { stack.push(U256::from(len)); }, instructions::CALLDATACOPY => { - self.copy_data_to_memory(mem, stack, ¶ms.data.clone().unwrap_or_else(|| vec![])); + self.copy_data_to_memory(stack, ¶ms.data.clone().unwrap_or_else(|| vec![])); }, instructions::CODECOPY => { - self.copy_data_to_memory(mem, stack, ¶ms.code.clone().unwrap_or_else(|| vec![])); + self.copy_data_to_memory(stack, ¶ms.code.clone().unwrap_or_else(|| vec![])); }, instructions::EXTCODECOPY => { let address = u256_to_address(&stack.pop_back()); let code = ext.extcode(&address); - self.copy_data_to_memory(mem, stack, &code); + self.copy_data_to_memory(stack, &code); }, instructions::GASPRICE => { stack.push(params.gas_price.clone()); @@ -806,7 +850,7 @@ impl Interpreter { Ok(InstructionResult::Ok) } - fn copy_data_to_memory(&self, mem: &mut Memory, stack: &mut Stack, data: &[u8]) { + fn copy_data_to_memory(&mut self, stack: &mut Stack, data: &[u8]) { let dest_offset = stack.pop_back(); let source_offset = stack.pop_back(); let size = stack.pop_back(); @@ -815,9 +859,9 @@ impl Interpreter { let output_end = match source_offset > source_size || size > source_size || source_offset + size > source_size { true => { let zero_slice = if source_offset > source_size { - mem.writeable_slice(dest_offset, size) + self.mem.writeable_slice(dest_offset, size) } else { - mem.writeable_slice(dest_offset + source_size - source_offset, source_offset + size - source_size) + self.mem.writeable_slice(dest_offset + source_size - source_offset, source_offset + size - source_size) }; for i in zero_slice.iter_mut() { *i = 0; @@ -829,14 +873,16 @@ impl Interpreter { if source_offset < source_size { let output_begin = source_offset.low_u64() as usize; - mem.write_slice(dest_offset, &data[output_begin..output_end]); + self.mem.write_slice(dest_offset, &data[output_begin..output_end]); } } - 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 + ) -> evm::Result<()> { if !stack.has(info.args) { Err(evm::Error::StackUnderflow { instruction: info.name, @@ -854,14 +900,14 @@ impl Interpreter { } } - fn verify_gas(&self, current_gas: &U256, gas_cost: &U256) -> Result<(), evm::Error> { + fn verify_gas(&self, current_gas: &U256, gas_cost: &U256) -> evm::Result<()> { match current_gas < gas_cost { true => Err(evm::Error::OutOfGas), false => Ok(()) } } - fn verify_jump(&self, jump_u: U256, valid_jump_destinations: &HashSet) -> Result { + fn verify_jump(&self, jump_u: U256, valid_jump_destinations: &HashSet) -> evm::Result { let jump = jump_u.low_u64() as usize; if valid_jump_destinations.contains(&jump) && jump_u < U256::from(!0 as usize) { @@ -885,7 +931,7 @@ impl Interpreter { } } - fn exec_stack_instruction(&self, instruction: Instruction, stack: &mut Stack) -> Result<(), evm::Error> { + fn exec_stack_instruction(&self, instruction: Instruction, stack: &mut Stack) -> evm::Result<()> { match instruction { instructions::DUP1...instructions::DUP16 => { let position = instructions::get_dup_position(instruction); @@ -1136,7 +1182,7 @@ fn address_to_u256(value: Address) -> U256 { #[test] fn test_mem_gas_cost() { // given - let interpreter = Interpreter; + let interpreter = Interpreter::default(); let schedule = evm::Schedule::default(); let current_mem_size = 5; let mem_size = !U256::zero(); @@ -1159,7 +1205,7 @@ mod tests { #[test] fn test_find_jump_destinations() { // given - let interpreter = Interpreter; + let interpreter = Interpreter::default(); let code = "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff5b01600055".from_hex().unwrap(); // when @@ -1172,7 +1218,7 @@ mod tests { #[test] fn test_calculate_mem_cost() { // given - let interpreter = Interpreter; + let interpreter = Interpreter::default(); let schedule = evm::Schedule::default(); let current_mem_size = 0; let mem_size = U256::from(5); diff --git a/ethcore/src/evm/jit.rs b/ethcore/src/evm/jit.rs index 6a22a1306..d46ad917f 100644 --- a/ethcore/src/evm/jit.rs +++ b/ethcore/src/evm/jit.rs @@ -16,8 +16,9 @@ //! Just in time compiler execution environment. use common::*; +use trace::VMTracer; use evmjit; -use evm; +use evm::{self, Error, GasLeft}; /// Should be used to convert jit types to ethcore trait FromJit: Sized { @@ -106,8 +107,8 @@ impl IntoJit for Address { } /// Externalities adapter. Maps callbacks from evmjit to externalities trait. -/// -/// Evmjit doesn't have to know about children execution failures. +/// +/// Evmjit doesn't have to know about children execution failures. /// This adapter 'catches' them and moves upstream. struct ExtAdapter<'a> { ext: &'a mut evm::Ext, @@ -165,7 +166,7 @@ impl<'a> evmjit::Ext for ExtAdapter<'a> { init_beg: *const u8, init_size: u64, address: *mut evmjit::H256) { - + let gas = unsafe { U256::from(*io_gas) }; let value = unsafe { U256::from_jit(&*value) }; let code = unsafe { slice::from_raw_parts(init_beg, init_size as usize) }; @@ -240,9 +241,9 @@ impl<'a> evmjit::Ext for ExtAdapter<'a> { } match self.ext.call( - &call_gas, + &call_gas, &sender_address, - &receive_address, + &receive_address, value, unsafe { slice::from_raw_parts(in_beg, in_size as usize) }, &code_address, @@ -283,7 +284,7 @@ impl<'a> evmjit::Ext for ExtAdapter<'a> { if !topic4.is_null() { topics.push(H256::from_jit(&*topic4)); } - + let bytes_ref: &[u8] = slice::from_raw_parts(beg, size as usize); self.ext.log(topics, bytes_ref); } @@ -300,10 +301,13 @@ impl<'a> evmjit::Ext for ExtAdapter<'a> { } } -pub struct JitEvm; +#[derive(Default)] +pub struct JitEvm { + ctxt: Option, +} impl evm::Evm for JitEvm { - fn exec(&self, params: ActionParams, ext: &mut evm::Ext) -> evm::Result { + fn exec(&mut self, params: ActionParams, ext: &mut evm::Ext) -> evm::Result { // Dirty hack. This is unsafe, but we interact with ffi, so it's justified. let ext_adapter: ExtAdapter<'static> = unsafe { ::std::mem::transmute(ExtAdapter::new(ext, params.address.clone())) }; let mut ext_handle = evmjit::ExtHandle::new(ext_adapter); @@ -342,15 +346,17 @@ impl evm::Evm for JitEvm { // don't really know why jit timestamp is int.. data.timestamp = ext.env_info().timestamp as i64; - let mut context = unsafe { evmjit::ContextHandle::new(data, schedule, &mut ext_handle) }; + self.context = Some(unsafe { evmjit::ContextHandle::new(data, schedule, &mut ext_handle) }); + let context = self.context.as_ref_mut().unwrap(); let res = context.exec(); - + match res { - evmjit::ReturnCode::Stop => Ok(U256::from(context.gas_left())), - evmjit::ReturnCode::Return => ext.ret(&U256::from(context.gas_left()), context.output_data()), - evmjit::ReturnCode::Suicide => { + evmjit::ReturnCode::Stop => Ok(GasLeft::Known(U256::from(context.gas_left()))), + evmjit::ReturnCode::Return => + Ok(GasLeft::NeedsReturn(U256::from(context.gas_left()), context.output_data())), + evmjit::ReturnCode::Suicide => { ext.suicide(&Address::from_jit(&context.suicide_refund_address())); - Ok(U256::from(context.gas_left())) + Ok(GasLeft::Known(U256::from(context.gas_left()))) }, evmjit::ReturnCode::OutOfGas => Err(evm::Error::OutOfGas), _err => Err(evm::Error::Internal) @@ -371,7 +377,7 @@ fn test_to_and_from_h256() { let h = H256::from_str("d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3").unwrap(); let j: ::evmjit::I256 = h.clone().into_jit(); let h2 = H256::from_jit(&j); - + assert_eq!(h, h2); let j: ::evmjit::H256 = h.clone().into_jit(); diff --git a/ethcore/src/evm/mod.rs b/ethcore/src/evm/mod.rs index b7816b99c..5e7b67cfb 100644 --- a/ethcore/src/evm/mod.rs +++ b/ethcore/src/evm/mod.rs @@ -29,7 +29,8 @@ mod jit; #[cfg(test)] mod tests; -pub use self::evm::{Evm, Error, Result}; +pub use self::evm::{Evm, Error, Finalize, GasLeft, 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/evm/tests.rs b/ethcore/src/evm/tests.rs index 445c0be41..ba156e6dd 100644 --- a/ethcore/src/evm/tests.rs +++ b/ethcore/src/evm/tests.rs @@ -15,8 +15,7 @@ // along with Parity. If not, see . use common::*; -use evm; -use evm::{Ext, Schedule, Factory, VMType, ContractCreateResult, MessageCallResult}; +use evm::{self, Ext, Schedule, Factory, GasLeft, VMType, ContractCreateResult, MessageCallResult}; use std::fmt::Debug; struct FakeLogEntry { @@ -58,6 +57,15 @@ struct FakeExt { calls: HashSet, } +// similar to the normal `finalize` function, but ignoring NeedsReturn. +fn test_finalize(res: Result) -> Result { + match res { + Ok(GasLeft::Known(gas)) => Ok(gas), + Ok(GasLeft::NeedsReturn(_, _)) => unimplemented!(), // since ret is unimplemented. + Err(e) => Err(e), + } +} + impl FakeExt { fn new() -> Self { FakeExt::default() @@ -136,7 +144,7 @@ impl Ext for FakeExt { }); } - fn ret(&mut self, _gas: &U256, _data: &[u8]) -> result::Result { + fn ret(self, _gas: &U256, _data: &[u8]) -> evm::Result { unimplemented!(); } @@ -173,8 +181,8 @@ fn test_stack_underflow() { let mut ext = FakeExt::new(); let err = { - let vm : Box = Box::new(super::interpreter::Interpreter); - vm.exec(params, &mut ext).unwrap_err() + let mut vm : Box = Box::new(super::interpreter::Interpreter::default()); + test_finalize(vm.exec(params, &mut ext)).unwrap_err() }; match err { @@ -200,8 +208,8 @@ fn test_add(factory: super::Factory) { let mut ext = FakeExt::new(); let gas_left = { - let vm = factory.create(); - vm.exec(params, &mut ext).unwrap() + let mut vm = factory.create(); + test_finalize(vm.exec(params, &mut ext)).unwrap() }; assert_eq!(gas_left, U256::from(79_988)); @@ -220,8 +228,8 @@ fn test_sha3(factory: super::Factory) { let mut ext = FakeExt::new(); let gas_left = { - let vm = factory.create(); - vm.exec(params, &mut ext).unwrap() + let mut vm = factory.create(); + test_finalize(vm.exec(params, &mut ext)).unwrap() }; assert_eq!(gas_left, U256::from(79_961)); @@ -240,8 +248,8 @@ fn test_address(factory: super::Factory) { let mut ext = FakeExt::new(); let gas_left = { - let vm = factory.create(); - vm.exec(params, &mut ext).unwrap() + let mut vm = factory.create(); + test_finalize(vm.exec(params, &mut ext)).unwrap() }; assert_eq!(gas_left, U256::from(79_995)); @@ -262,8 +270,8 @@ fn test_origin(factory: super::Factory) { let mut ext = FakeExt::new(); let gas_left = { - let vm = factory.create(); - vm.exec(params, &mut ext).unwrap() + let mut vm = factory.create(); + test_finalize(vm.exec(params, &mut ext)).unwrap() }; assert_eq!(gas_left, U256::from(79_995)); @@ -284,8 +292,8 @@ fn test_sender(factory: super::Factory) { let mut ext = FakeExt::new(); let gas_left = { - let vm = factory.create(); - vm.exec(params, &mut ext).unwrap() + let mut vm = factory.create(); + test_finalize(vm.exec(params, &mut ext)).unwrap() }; assert_eq!(gas_left, U256::from(79_995)); @@ -319,8 +327,8 @@ fn test_extcodecopy(factory: super::Factory) { ext.codes.insert(sender, sender_code); let gas_left = { - let vm = factory.create(); - vm.exec(params, &mut ext).unwrap() + let mut vm = factory.create(); + test_finalize(vm.exec(params, &mut ext)).unwrap() }; assert_eq!(gas_left, U256::from(79_935)); @@ -339,8 +347,8 @@ fn test_log_empty(factory: super::Factory) { let mut ext = FakeExt::new(); let gas_left = { - let vm = factory.create(); - vm.exec(params, &mut ext).unwrap() + let mut vm = factory.create(); + test_finalize(vm.exec(params, &mut ext)).unwrap() }; assert_eq!(gas_left, U256::from(99_619)); @@ -371,8 +379,8 @@ fn test_log_sender(factory: super::Factory) { let mut ext = FakeExt::new(); let gas_left = { - let vm = factory.create(); - vm.exec(params, &mut ext).unwrap() + let mut vm = factory.create(); + test_finalize(vm.exec(params, &mut ext)).unwrap() }; assert_eq!(gas_left, U256::from(98_974)); @@ -396,8 +404,8 @@ fn test_blockhash(factory: super::Factory) { ext.blockhashes.insert(U256::zero(), blockhash.clone()); let gas_left = { - let vm = factory.create(); - vm.exec(params, &mut ext).unwrap() + let mut vm = factory.create(); + test_finalize(vm.exec(params, &mut ext)).unwrap() }; assert_eq!(gas_left, U256::from(79_974)); @@ -418,8 +426,8 @@ fn test_calldataload(factory: super::Factory) { let mut ext = FakeExt::new(); let gas_left = { - let vm = factory.create(); - vm.exec(params, &mut ext).unwrap() + let mut vm = factory.create(); + test_finalize(vm.exec(params, &mut ext)).unwrap() }; assert_eq!(gas_left, U256::from(79_991)); @@ -439,8 +447,8 @@ fn test_author(factory: super::Factory) { ext.info.author = author; let gas_left = { - let vm = factory.create(); - vm.exec(params, &mut ext).unwrap() + let mut vm = factory.create(); + test_finalize(vm.exec(params, &mut ext)).unwrap() }; assert_eq!(gas_left, U256::from(79_995)); @@ -459,8 +467,8 @@ fn test_timestamp(factory: super::Factory) { ext.info.timestamp = timestamp; let gas_left = { - let vm = factory.create(); - vm.exec(params, &mut ext).unwrap() + let mut vm = factory.create(); + test_finalize(vm.exec(params, &mut ext)).unwrap() }; assert_eq!(gas_left, U256::from(79_995)); @@ -479,8 +487,8 @@ fn test_number(factory: super::Factory) { ext.info.number = number; let gas_left = { - let vm = factory.create(); - vm.exec(params, &mut ext).unwrap() + let mut vm = factory.create(); + test_finalize(vm.exec(params, &mut ext)).unwrap() }; assert_eq!(gas_left, U256::from(79_995)); @@ -499,8 +507,8 @@ fn test_difficulty(factory: super::Factory) { ext.info.difficulty = difficulty; let gas_left = { - let vm = factory.create(); - vm.exec(params, &mut ext).unwrap() + let mut vm = factory.create(); + test_finalize(vm.exec(params, &mut ext)).unwrap() }; assert_eq!(gas_left, U256::from(79_995)); @@ -519,8 +527,8 @@ fn test_gas_limit(factory: super::Factory) { ext.info.gas_limit = gas_limit; let gas_left = { - let vm = factory.create(); - vm.exec(params, &mut ext).unwrap() + let mut vm = factory.create(); + test_finalize(vm.exec(params, &mut ext)).unwrap() }; assert_eq!(gas_left, U256::from(79_995)); @@ -537,8 +545,8 @@ fn test_mul(factory: super::Factory) { let mut ext = FakeExt::new(); let gas_left = { - let vm = factory.create(); - vm.exec(params, &mut ext).unwrap() + let mut vm = factory.create(); + test_finalize(vm.exec(params, &mut ext)).unwrap() }; assert_store(&ext, 0, "000000000000000000000000000000000000000000000000734349397b853383"); @@ -555,8 +563,8 @@ fn test_sub(factory: super::Factory) { let mut ext = FakeExt::new(); let gas_left = { - let vm = factory.create(); - vm.exec(params, &mut ext).unwrap() + let mut vm = factory.create(); + test_finalize(vm.exec(params, &mut ext)).unwrap() }; assert_store(&ext, 0, "0000000000000000000000000000000000000000000000000000012364ad0302"); @@ -573,8 +581,8 @@ fn test_div(factory: super::Factory) { let mut ext = FakeExt::new(); let gas_left = { - let vm = factory.create(); - vm.exec(params, &mut ext).unwrap() + let mut vm = factory.create(); + test_finalize(vm.exec(params, &mut ext)).unwrap() }; assert_store(&ext, 0, "000000000000000000000000000000000000000000000000000000000002e0ac"); @@ -591,8 +599,8 @@ fn test_div_zero(factory: super::Factory) { let mut ext = FakeExt::new(); let gas_left = { - let vm = factory.create(); - vm.exec(params, &mut ext).unwrap() + let mut vm = factory.create(); + test_finalize(vm.exec(params, &mut ext)).unwrap() }; assert_store(&ext, 0, "0000000000000000000000000000000000000000000000000000000000000000"); @@ -609,8 +617,8 @@ fn test_mod(factory: super::Factory) { let mut ext = FakeExt::new(); let gas_left = { - let vm = factory.create(); - vm.exec(params, &mut ext).unwrap() + let mut vm = factory.create(); + test_finalize(vm.exec(params, &mut ext)).unwrap() }; assert_store(&ext, 0, "0000000000000000000000000000000000000000000000000000000000076b4b"); @@ -628,8 +636,8 @@ fn test_smod(factory: super::Factory) { let mut ext = FakeExt::new(); let gas_left = { - let vm = factory.create(); - vm.exec(params, &mut ext).unwrap() + let mut vm = factory.create(); + test_finalize(vm.exec(params, &mut ext)).unwrap() }; assert_store(&ext, 0, "0000000000000000000000000000000000000000000000000000000000076b4b"); @@ -647,8 +655,8 @@ fn test_sdiv(factory: super::Factory) { let mut ext = FakeExt::new(); let gas_left = { - let vm = factory.create(); - vm.exec(params, &mut ext).unwrap() + let mut vm = factory.create(); + test_finalize(vm.exec(params, &mut ext)).unwrap() }; assert_store(&ext, 0, "000000000000000000000000000000000000000000000000000000000002e0ac"); @@ -666,8 +674,8 @@ fn test_exp(factory: super::Factory) { let mut ext = FakeExt::new(); let gas_left = { - let vm = factory.create(); - vm.exec(params, &mut ext).unwrap() + let mut vm = factory.create(); + test_finalize(vm.exec(params, &mut ext)).unwrap() }; assert_store(&ext, 0, "90fd23767b60204c3d6fc8aec9e70a42a3f127140879c133a20129a597ed0c59"); @@ -686,8 +694,8 @@ fn test_comparison(factory: super::Factory) { let mut ext = FakeExt::new(); let gas_left = { - let vm = factory.create(); - vm.exec(params, &mut ext).unwrap() + let mut vm = factory.create(); + test_finalize(vm.exec(params, &mut ext)).unwrap() }; assert_store(&ext, 0, "0000000000000000000000000000000000000000000000000000000000000000"); @@ -707,8 +715,8 @@ fn test_signed_comparison(factory: super::Factory) { let mut ext = FakeExt::new(); let gas_left = { - let vm = factory.create(); - vm.exec(params, &mut ext).unwrap() + let mut vm = factory.create(); + test_finalize(vm.exec(params, &mut ext)).unwrap() }; assert_store(&ext, 0, "0000000000000000000000000000000000000000000000000000000000000000"); @@ -728,8 +736,8 @@ fn test_bitops(factory: super::Factory) { let mut ext = FakeExt::new(); let gas_left = { - let vm = factory.create(); - vm.exec(params, &mut ext).unwrap() + let mut vm = factory.create(); + test_finalize(vm.exec(params, &mut ext)).unwrap() }; assert_store(&ext, 0, "00000000000000000000000000000000000000000000000000000000000000f0"); @@ -751,8 +759,8 @@ fn test_addmod_mulmod(factory: super::Factory) { let mut ext = FakeExt::new(); let gas_left = { - let vm = factory.create(); - vm.exec(params, &mut ext).unwrap() + let mut vm = factory.create(); + test_finalize(vm.exec(params, &mut ext)).unwrap() }; assert_store(&ext, 0, "0000000000000000000000000000000000000000000000000000000000000001"); @@ -772,8 +780,8 @@ fn test_byte(factory: super::Factory) { let mut ext = FakeExt::new(); let gas_left = { - let vm = factory.create(); - vm.exec(params, &mut ext).unwrap() + let mut vm = factory.create(); + test_finalize(vm.exec(params, &mut ext)).unwrap() }; assert_store(&ext, 0, "0000000000000000000000000000000000000000000000000000000000000000"); @@ -791,8 +799,8 @@ fn test_signextend(factory: super::Factory) { let mut ext = FakeExt::new(); let gas_left = { - let vm = factory.create(); - vm.exec(params, &mut ext).unwrap() + let mut vm = factory.create(); + test_finalize(vm.exec(params, &mut ext)).unwrap() }; assert_store(&ext, 0, "0000000000000000000000000000000000000000000000000000000000000fff"); @@ -811,8 +819,8 @@ fn test_badinstruction_int() { let mut ext = FakeExt::new(); let err = { - let vm = factory.create(); - vm.exec(params, &mut ext).unwrap_err() + let mut vm = factory.create(); + test_finalize(vm.exec(params, &mut ext)).unwrap_err() }; match err { @@ -831,8 +839,8 @@ fn test_pop(factory: super::Factory) { let mut ext = FakeExt::new(); let gas_left = { - let vm = factory.create(); - vm.exec(params, &mut ext).unwrap() + let mut vm = factory.create(); + test_finalize(vm.exec(params, &mut ext)).unwrap() }; assert_store(&ext, 0, "00000000000000000000000000000000000000000000000000000000000000f0"); @@ -851,8 +859,8 @@ fn test_extops(factory: super::Factory) { let mut ext = FakeExt::new(); let gas_left = { - let vm = factory.create(); - vm.exec(params, &mut ext).unwrap() + let mut vm = factory.create(); + test_finalize(vm.exec(params, &mut ext)).unwrap() }; assert_store(&ext, 0, "0000000000000000000000000000000000000000000000000000000000000004"); // PC / CALLDATASIZE @@ -874,8 +882,8 @@ fn test_jumps(factory: super::Factory) { let mut ext = FakeExt::new(); let gas_left = { - let vm = factory.create(); - vm.exec(params, &mut ext).unwrap() + let mut vm = factory.create(); + test_finalize(vm.exec(params, &mut ext)).unwrap() }; assert_eq!(ext.sstore_clears, 1); @@ -903,8 +911,8 @@ fn test_calls(factory: super::Factory) { }; let gas_left = { - let vm = factory.create(); - vm.exec(params, &mut ext).unwrap() + let mut vm = factory.create(); + test_finalize(vm.exec(params, &mut ext)).unwrap() }; assert_set_contains(&ext.calls, &FakeCall { diff --git a/ethcore/src/executive.rs b/ethcore/src/executive.rs index 291b4dba2..fdc1c7d18 100644 --- a/ethcore/src/executive.rs +++ b/ethcore/src/executive.rs @@ -18,12 +18,11 @@ use common::*; use state::*; use engine::*; -use evm::{self, Ext, Factory}; +use evm::{self, Ext, Factory, Finalize}; 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,22 +190,28 @@ 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); + return vm_factory.create().exec(params, &mut ext).finalize(ext); } // Start in new thread to reset stack @@ -193,10 +219,10 @@ 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) + vm_factory.create().exec(params, &mut ext).finalize(ext) }) }).join() } @@ -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,8 @@ impl<'a> Executive<'a> { contracts_created: vec![], output: output, trace: trace, + vm_trace: vm_trace, + state_diff: None, }) }, _ => { @@ -406,12 +463,14 @@ impl<'a> Executive<'a> { contracts_created: substate.contracts_created, output: output, trace: trace, + vm_trace: vm_trace, + state_diff: None, }) }, } } - fn enact_result(&mut self, result: &evm::Result, substate: &mut Substate, un_substate: Substate) { + fn enact_result(&mut self, result: &evm::Result, substate: &mut Substate, un_substate: Substate) { match *result { Err(evm::Error::OutOfGas) | Err(evm::Error::BadJumpDestination {..}) @@ -438,6 +497,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 +526,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 +585,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 +602,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 +638,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 +678,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 +745,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 +769,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 +834,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 +886,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 +947,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 +992,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 +1025,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 +1059,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 +1091,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 +1125,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 +1159,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 +1194,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..66509440a 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 } @@ -201,7 +203,8 @@ impl<'a, T> Ext for Externalities<'a, T> where T: 'a + Tracer { } #[cfg_attr(feature="dev", allow(match_ref_pats))] - fn ret(&mut self, gas: &U256, data: &[u8]) -> Result { + fn ret(mut self, gas: &U256, data: &[u8]) -> evm::Result + where Self: Sized { let handle_copy = |to: &mut Option<&mut Bytes>| { to.as_mut().map(|b| **b = data.to_owned()); }; @@ -210,20 +213,14 @@ impl<'a, T> Ext for Externalities<'a, T> where T: 'a + Tracer { handle_copy(copy); let len = cmp::min(slice.len(), data.len()); - unsafe { - ptr::copy(data.as_ptr(), slice.as_mut_ptr(), len); - } + (&mut slice[..len]).copy_from_slice(&data[..len]); Ok(*gas) }, OutputPolicy::Return(BytesRef::Flexible(ref mut vec), ref mut copy) => { handle_copy(copy); vec.clear(); - vec.reserve(data.len()); - unsafe { - ptr::copy(data.as_ptr(), vec.as_mut_ptr(), data.len()); - vec.set_len(data.len()); - } + vec.extend_from_slice(data); Ok(*gas) }, OutputPolicy::InitContract(ref mut copy) => { @@ -238,11 +235,8 @@ impl<'a, T> Ext for Externalities<'a, T> where T: 'a + Tracer { handle_copy(copy); let mut code = vec![]; - code.reserve(data.len()); - unsafe { - ptr::copy(data.as_ptr(), code.as_mut_ptr(), data.len()); - code.set_len(data.len()); - } + code.extend_from_slice(data); + self.state.init_code(&self.origin_info.address, code); Ok(*gas - return_cost) } @@ -286,6 +280,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 +299,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 +351,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 +364,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 +387,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 +403,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 +429,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 +447,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/chain.rs b/ethcore/src/json_tests/chain.rs index a1154f6a9..53052d8dc 100644 --- a/ethcore/src/json_tests/chain.rs +++ b/ethcore/src/json_tests/chain.rs @@ -22,6 +22,7 @@ use tests::helpers::*; use devtools::*; use spec::Genesis; use ethjson; +use miner::Miner; pub fn json_chain_test(json_data: &[u8], era: ChainEra) -> Vec { init_log(); @@ -53,7 +54,7 @@ pub fn json_chain_test(json_data: &[u8], era: ChainEra) -> Vec { let temp = RandomTempPath::new(); { - let client = Client::new(ClientConfig::default(), spec, temp.as_path(), IoChannel::disconnected()).unwrap(); + let client = Client::new(ClientConfig::default(), spec, temp.as_path(), Arc::new(Miner::default()), IoChannel::disconnected()).unwrap(); for b in &blockchain.blocks_rlp() { if Block::is_good(&b) { let _ = client.import_block(b.clone()); diff --git a/ethcore/src/json_tests/executive.rs b/ethcore/src/json_tests/executive.rs index 9e9620169..f4a34a33e 100644 --- a/ethcore/src/json_tests/executive.rs +++ b/ethcore/src/json_tests/executive.rs @@ -19,14 +19,15 @@ use state::*; use executive::*; use engine::*; use evm; -use evm::{Schedule, Ext, Factory, VMType, ContractCreateResult, MessageCallResult}; +use evm::{Schedule, Ext, Factory, Finalize, VMType, ContractCreateResult, MessageCallResult}; use externalities::*; use substate::*; use tests::helpers::*; use ethjson; use trace::{Tracer, NoopTracer}; +use trace::{VMTracer, NoopVMTracer}; -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Clone)] struct CallCreate { data: Bytes, destination: Option
, @@ -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) } @@ -129,7 +133,7 @@ impl<'a, T> Ext for TestExt<'a, T> where T: Tracer { self.ext.log(topics, data) } - fn ret(&mut self, gas: &U256, data: &[u8]) -> Result { + fn ret(self, gas: &U256, data: &[u8]) -> Result { self.ext.ret(gas, data) } @@ -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,10 +206,13 @@ 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 mut evm = vm_factory.create(); let res = evm.exec(params, &mut ex); - (res, ex.callcreates) + // a return in finalize will not alter callcreates + let callcreates = ex.callcreates.clone(); + (res.finalize(ex), callcreates) }; match res { diff --git a/ethcore/src/json_tests/state.rs b/ethcore/src/json_tests/state.rs index 5cc611491..5307fb319 100644 --- a/ethcore/src/json_tests/state.rs +++ b/ethcore/src/json_tests/state.rs @@ -16,8 +16,7 @@ use super::test_common::*; use tests::helpers::*; -use pod_state::*; -use state_diff::*; +use pod_state::{self, PodState}; use ethereum; use ethjson; @@ -71,7 +70,7 @@ pub fn json_chain_test(json_data: &[u8], era: ChainEra) -> Vec { let our_post = state.to_pod(); println!("Got:\n{}", our_post); println!("Expect:\n{}", post); - println!("Diff ---expect -> +++got:\n{}", StateDiff::diff_pod(&post, &our_post)); + println!("Diff ---expect -> +++got:\n{}", pod_state::diff_pod(&post, &our_post)); } if let Ok(r) = res { diff --git a/ethcore/src/lib.rs b/ethcore/src/lib.rs index 1d4ddadbc..f69048a95 100644 --- a/ethcore/src/lib.rs +++ b/ethcore/src/lib.rs @@ -90,6 +90,7 @@ extern crate crossbeam; extern crate ethjson; extern crate bloomchain; #[macro_use] extern crate ethcore_ipc as ipc; +extern crate rayon; #[cfg(test)] extern crate ethcore_devtools as devtools; #[cfg(feature = "jit" )] extern crate evmjit; @@ -109,6 +110,7 @@ pub mod views; pub mod pod_state; pub mod engine; pub mod migrations; +pub mod miner; mod blooms; mod db; @@ -117,8 +119,6 @@ mod basic_types; #[macro_use] mod evm; mod env_info; mod pod_account; -mod account_diff; -mod state_diff; mod state; mod account; mod account_db; @@ -139,3 +139,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/miner/src/external.rs b/ethcore/src/miner/external.rs similarity index 100% rename from miner/src/external.rs rename to ethcore/src/miner/external.rs diff --git a/miner/src/miner.rs b/ethcore/src/miner/miner.rs similarity index 88% rename from miner/src/miner.rs rename to ethcore/src/miner/miner.rs index b40fdf7c8..5dc3864c3 100644 --- a/miner/src/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -18,17 +18,16 @@ use rayon::prelude::*; use std::sync::atomic::AtomicBool; use util::*; -use util::keys::store::AccountProvider; -use ethcore::views::{BlockView, HeaderView}; -use ethcore::client::{BlockChainClient, BlockID}; -use ethcore::block::{ClosedBlock, IsBlock}; -use ethcore::error::*; -use ethcore::client::{Executive, Executed, EnvInfo, TransactOptions}; -use ethcore::transaction::SignedTransaction; -use ethcore::receipt::Receipt; -use ethcore::spec::Spec; -use ethcore::engine::Engine; -use super::{MinerService, MinerStatus, TransactionQueue, AccountDetails, TransactionImportResult, TransactionOrigin}; +use util::keys::store::{AccountProvider}; +use views::{BlockView, HeaderView}; +use client::{MiningBlockChainClient, Executive, Executed, EnvInfo, TransactOptions, BlockChainClient, BlockID, CallAnalytics}; +use block::{ClosedBlock, IsBlock}; +use error::*; +use transaction::SignedTransaction; +use receipt::{Receipt}; +use spec::Spec; +use engine::Engine; +use miner::{MinerService, MinerStatus, TransactionQueue, AccountDetails, TransactionImportResult, TransactionOrigin}; /// Keeps track of transactions using priority queue and holds currently mined block. pub struct Miner { @@ -104,7 +103,7 @@ impl Miner { /// Prepares new block for sealing including top transactions from queue. #[cfg_attr(feature="dev", allow(match_same_arms))] #[cfg_attr(feature="dev", allow(cyclomatic_complexity))] - fn prepare_sealing(&self, chain: &BlockChainClient) { + fn prepare_sealing(&self, chain: &MiningBlockChainClient) { trace!(target: "miner", "prepare_sealing: entering"); let transactions = self.transaction_queue.lock().unwrap().top_transactions(); let mut sealing_work = self.sealing_work.lock().unwrap(); @@ -205,14 +204,14 @@ impl Miner { trace!(target: "miner", "prepare_sealing: leaving (last={:?})", sealing_work.peek_last_ref().map(|b| b.block().fields().header.hash())); } - fn update_gas_limit(&self, chain: &BlockChainClient) { + fn update_gas_limit(&self, chain: &MiningBlockChainClient) { let gas_limit = HeaderView::new(&chain.best_block_header()).gas_limit(); let mut queue = self.transaction_queue.lock().unwrap(); queue.set_gas_limit(gas_limit); } /// Returns true if we had to prepare new pending block - fn enable_and_prepare_sealing(&self, chain: &BlockChainClient) -> bool { + fn enable_and_prepare_sealing(&self, chain: &MiningBlockChainClient) -> bool { trace!(target: "miner", "enable_and_prepare_sealing: entering"); let have_work = self.sealing_work.lock().unwrap().peek_last_ref().is_some(); trace!(target: "miner", "enable_and_prepare_sealing: have_work={}", have_work); @@ -236,7 +235,7 @@ const SEALING_TIMEOUT_IN_BLOCKS : u64 = 5; impl MinerService for Miner { - fn clear_and_reset(&self, chain: &BlockChainClient) { + fn clear_and_reset(&self, chain: &MiningBlockChainClient) { self.transaction_queue.lock().unwrap().clear(); self.update_sealing(chain); } @@ -251,11 +250,13 @@ impl MinerService for Miner { } } - fn call(&self, chain: &BlockChainClient, t: &SignedTransaction) -> Result { + fn call(&self, chain: &MiningBlockChainClient, t: &SignedTransaction, analytics: CallAnalytics) -> Result { let sealing_work = self.sealing_work.lock().unwrap(); match sealing_work.peek_last_ref() { Some(work) => { let block = work.block(); + + // TODO: merge this code with client.rs's fn call somwhow. let header = block.header(); let last_hashes = chain.last_hashes(); let env_info = EnvInfo { @@ -274,20 +275,29 @@ impl MinerService for Miner { ExecutionError::TransactionMalformed(message) })); let balance = state.balance(&sender); - // 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 }; - - Executive::new(&mut state, &env_info, self.engine(), chain.vm_factory()).transact(t, options) + let needed_balance = t.value + t.gas * t.gas_price; + if balance < needed_balance { + // give the sender a sufficient balance + state.add_balance(&sender, &(needed_balance - balance)); + } + let options = TransactOptions { tracing: false, vm_tracing: analytics.vm_tracing, check_nonce: false }; + let mut ret = Executive::new(&mut state, &env_info, self.engine(), chain.vm_factory()).transact(t, options); + + // TODO gav move this into Executive. + if analytics.state_diffing { + if let Ok(ref mut x) = ret { + x.state_diff = Some(state.diff_from(block.state().clone())); + } + } + ret }, None => { - chain.call(t) + chain.call(t, analytics) } } } - fn balance(&self, chain: &BlockChainClient, address: &Address) -> U256 { + fn balance(&self, chain: &MiningBlockChainClient, address: &Address) -> U256 { let sealing_work = self.sealing_work.lock().unwrap(); sealing_work.peek_last_ref().map_or_else( || chain.latest_balance(address), @@ -295,7 +305,7 @@ impl MinerService for Miner { ) } - fn storage_at(&self, chain: &BlockChainClient, address: &Address, position: &H256) -> H256 { + fn storage_at(&self, chain: &MiningBlockChainClient, address: &Address, position: &H256) -> H256 { let sealing_work = self.sealing_work.lock().unwrap(); sealing_work.peek_last_ref().map_or_else( || chain.latest_storage_at(address, position), @@ -303,12 +313,12 @@ impl MinerService for Miner { ) } - fn nonce(&self, chain: &BlockChainClient, address: &Address) -> U256 { + fn nonce(&self, chain: &MiningBlockChainClient, address: &Address) -> U256 { let sealing_work = self.sealing_work.lock().unwrap(); sealing_work.peek_last_ref().map_or_else(|| chain.latest_nonce(address), |b| b.block().fields().state.nonce(address)) } - fn code(&self, chain: &BlockChainClient, address: &Address) -> Option { + fn code(&self, chain: &MiningBlockChainClient, address: &Address) -> Option { let sealing_work = self.sealing_work.lock().unwrap(); sealing_work.peek_last_ref().map_or_else(|| chain.code(address), |b| b.block().fields().state.code(address)) } @@ -375,7 +385,7 @@ impl MinerService for Miner { .collect() } - fn import_own_transaction(&self, chain: &BlockChainClient, transaction: SignedTransaction, fetch_account: T) -> + fn import_own_transaction(&self, chain: &MiningBlockChainClient, transaction: SignedTransaction, fetch_account: T) -> Result where T: Fn(&Address) -> AccountDetails { let hash = transaction.hash(); @@ -469,7 +479,7 @@ impl MinerService for Miner { self.transaction_queue.lock().unwrap().last_nonce(address) } - fn update_sealing(&self, chain: &BlockChainClient) { + fn update_sealing(&self, chain: &MiningBlockChainClient) { if self.sealing_enabled.load(atomic::Ordering::Relaxed) { let current_no = chain.chain_info().best_block_number; let has_local_transactions = self.transaction_queue.lock().unwrap().has_local_pending_transactions(); @@ -489,7 +499,7 @@ impl MinerService for Miner { } } - fn map_sealing_work(&self, chain: &BlockChainClient, f: F) -> Option where F: FnOnce(&ClosedBlock) -> T { + fn map_sealing_work(&self, chain: &MiningBlockChainClient, f: F) -> Option where F: FnOnce(&ClosedBlock) -> T { trace!(target: "miner", "map_sealing_work: entering"); self.enable_and_prepare_sealing(chain); trace!(target: "miner", "map_sealing_work: sealing prepared"); @@ -499,7 +509,7 @@ impl MinerService for Miner { ret.map(f) } - fn submit_seal(&self, chain: &BlockChainClient, pow_hash: H256, seal: Vec) -> Result<(), Error> { + fn submit_seal(&self, chain: &MiningBlockChainClient, pow_hash: H256, seal: Vec) -> Result<(), Error> { if let Some(b) = self.sealing_work.lock().unwrap().take_used_if(|b| &b.hash() == &pow_hash) { match chain.try_seal(b.lock(), seal) { Err(_) => { @@ -522,8 +532,8 @@ impl MinerService for Miner { } } - fn chain_new_blocks(&self, chain: &BlockChainClient, _imported: &[H256], _invalid: &[H256], enacted: &[H256], retracted: &[H256]) { - fn fetch_transactions(chain: &BlockChainClient, hash: &H256) -> Vec { + fn chain_new_blocks(&self, chain: &MiningBlockChainClient, _imported: &[H256], _invalid: &[H256], enacted: &[H256], retracted: &[H256]) { + fn fetch_transactions(chain: &MiningBlockChainClient, hash: &H256) -> Vec { let block = chain .block(BlockID::Hash(*hash)) // Client should send message after commit to db and inserting to chain. @@ -584,13 +594,13 @@ impl MinerService for Miner { #[cfg(test)] mod tests { - use MinerService; - use super::{Miner}; + use super::super::MinerService; + use super::Miner; use util::*; - use ethcore::client::{TestBlockChainClient, EachBlockWith}; - use ethcore::block::*; + use client::{TestBlockChainClient, EachBlockWith}; + use block::*; - // TODO [ToDr] To uncomment when TestBlockChainClient can actually return a ClosedBlock. + // TODO [ToDr] To uncomment` when TestBlockChainClient can actually return a ClosedBlock. #[ignore] #[test] fn should_prepare_block_to_seal() { diff --git a/miner/src/lib.rs b/ethcore/src/miner/mod.rs similarity index 76% rename from miner/src/lib.rs rename to ethcore/src/miner/mod.rs index f91811948..71727f51d 100644 --- a/miner/src/lib.rs +++ b/ethcore/src/miner/mod.rs @@ -26,12 +26,10 @@ //! ```rust //! extern crate ethcore_util as util; //! extern crate ethcore; -//! extern crate ethminer; //! use std::env; //! use util::network::{NetworkService, NetworkConfiguration}; //! use ethcore::client::{Client, ClientConfig}; -//! use ethcore::ethereum; -//! use ethminer::{Miner, MinerService}; +//! use ethcore::miner::{Miner, MinerService}; //! //! fn main() { //! let miner: Miner = Miner::default(); @@ -43,30 +41,21 @@ //! } //! ``` - -#[macro_use] -extern crate log; -#[macro_use] -extern crate ethcore_util as util; -extern crate ethcore; -extern crate env_logger; -extern crate rayon; - mod miner; mod external; mod transaction_queue; -pub use transaction_queue::{TransactionQueue, AccountDetails, TransactionImportResult, TransactionOrigin}; -pub use miner::{Miner}; -pub use external::{ExternalMiner, ExternalMinerService}; +pub use self::transaction_queue::{TransactionQueue, AccountDetails, TransactionImportResult, TransactionOrigin}; +pub use self::miner::{Miner}; +pub use self::external::{ExternalMiner, ExternalMinerService}; use std::collections::BTreeMap; use util::{H256, U256, Address, Bytes}; -use ethcore::client::{BlockChainClient, Executed}; -use ethcore::block::ClosedBlock; -use ethcore::receipt::Receipt; -use ethcore::error::{Error, ExecutionError}; -use ethcore::transaction::SignedTransaction; +use client::{MiningBlockChainClient, Executed, CallAnalytics}; +use block::ClosedBlock; +use receipt::Receipt; +use error::{Error, ExecutionError}; +use transaction::SignedTransaction; /// Miner client API pub trait MinerService : Send + Sync { @@ -110,7 +99,7 @@ pub trait MinerService : Send + Sync { where T: Fn(&Address) -> AccountDetails, Self: Sized; /// Imports own (node owner) transaction to queue. - fn import_own_transaction(&self, chain: &BlockChainClient, transaction: SignedTransaction, fetch_account: T) -> + fn import_own_transaction(&self, chain: &MiningBlockChainClient, transaction: SignedTransaction, fetch_account: T) -> Result where T: Fn(&Address) -> AccountDetails, Self: Sized; @@ -118,20 +107,20 @@ pub trait MinerService : Send + Sync { fn pending_transactions_hashes(&self) -> Vec; /// Removes all transactions from the queue and restart mining operation. - fn clear_and_reset(&self, chain: &BlockChainClient); + fn clear_and_reset(&self, chain: &MiningBlockChainClient); /// Called when blocks are imported to chain, updates transactions queue. - fn chain_new_blocks(&self, chain: &BlockChainClient, imported: &[H256], invalid: &[H256], enacted: &[H256], retracted: &[H256]); + fn chain_new_blocks(&self, chain: &MiningBlockChainClient, imported: &[H256], invalid: &[H256], enacted: &[H256], retracted: &[H256]); /// New chain head event. Restart mining operation. - fn update_sealing(&self, chain: &BlockChainClient); + fn update_sealing(&self, chain: &MiningBlockChainClient); /// Submit `seal` as a valid solution for the header of `pow_hash`. /// Will check the seal, but not actually insert the block into the chain. - fn submit_seal(&self, chain: &BlockChainClient, pow_hash: H256, seal: Vec) -> Result<(), Error>; + fn submit_seal(&self, chain: &MiningBlockChainClient, pow_hash: H256, seal: Vec) -> Result<(), Error>; /// Get the sealing work package and if `Some`, apply some transform. - fn map_sealing_work(&self, chain: &BlockChainClient, f: F) -> Option + fn map_sealing_work(&self, chain: &MiningBlockChainClient, f: F) -> Option where F: FnOnce(&ClosedBlock) -> T, Self: Sized; /// Query pending transactions for hash. @@ -156,19 +145,19 @@ pub trait MinerService : Send + Sync { fn sensible_gas_limit(&self) -> U256 { 21000.into() } /// Latest account balance in pending state. - fn balance(&self, chain: &BlockChainClient, address: &Address) -> U256; + fn balance(&self, chain: &MiningBlockChainClient, address: &Address) -> U256; /// Call into contract code using pending state. - fn call(&self, chain: &BlockChainClient, t: &SignedTransaction) -> Result; + fn call(&self, chain: &MiningBlockChainClient, t: &SignedTransaction, analytics: CallAnalytics) -> Result; /// Get storage value in pending state. - fn storage_at(&self, chain: &BlockChainClient, address: &Address, position: &H256) -> H256; + fn storage_at(&self, chain: &MiningBlockChainClient, address: &Address, position: &H256) -> H256; /// Get account nonce in pending state. - fn nonce(&self, chain: &BlockChainClient, address: &Address) -> U256; + fn nonce(&self, chain: &MiningBlockChainClient, address: &Address) -> U256; /// Get contract code in pending state. - fn code(&self, chain: &BlockChainClient, address: &Address) -> Option; + fn code(&self, chain: &MiningBlockChainClient, address: &Address) -> Option; } /// Mining status diff --git a/miner/src/transaction_queue.rs b/ethcore/src/miner/transaction_queue.rs similarity index 99% rename from miner/src/transaction_queue.rs rename to ethcore/src/miner/transaction_queue.rs index fc62c411e..8f34d78d1 100644 --- a/miner/src/transaction_queue.rs +++ b/ethcore/src/miner/transaction_queue.rs @@ -26,13 +26,12 @@ //! ```rust //! extern crate ethcore_util as util; //! extern crate ethcore; -//! extern crate ethminer; //! extern crate rustc_serialize; //! //! use util::crypto::KeyPair; //! use util::hash::Address; //! use util::numbers::{Uint, U256}; -//! use ethminer::{TransactionQueue, AccountDetails, TransactionOrigin}; +//! use ethcore::miner::{TransactionQueue, AccountDetails, TransactionOrigin}; //! use ethcore::transaction::*; //! use rustc_serialize::hex::FromHex; //! @@ -89,8 +88,8 @@ use std::collections::{HashMap, BTreeSet}; use util::numbers::{Uint, U256}; use util::hash::{Address, H256}; use util::table::*; -use ethcore::transaction::*; -use ethcore::error::{Error, TransactionError}; +use transaction::*; +use error::{Error, TransactionError}; /// Transaction origin #[derive(Clone, Copy, Debug, PartialEq, Eq)] @@ -778,8 +777,8 @@ mod test { extern crate rustc_serialize; use util::table::*; use util::*; - use ethcore::transaction::*; - use ethcore::error::{Error, TransactionError}; + use transaction::*; + use error::{Error, TransactionError}; use super::*; use super::{TransactionSet, TransactionOrder, VerifiedTransaction}; diff --git a/ethcore/src/pod_account.rs b/ethcore/src/pod_account.rs index 65146296a..d04833cab 100644 --- a/ethcore/src/pod_account.rs +++ b/ethcore/src/pod_account.rs @@ -18,6 +18,7 @@ use util::*; use account::*; use account_db::*; use ethjson; +use types::account_diff::*; #[derive(Debug, Clone, PartialEq, Eq)] /// An account, expressed as Plain-Old-Data (hence the name). @@ -106,17 +107,58 @@ impl fmt::Display for PodAccount { } } +/// Determine difference between two optionally existant `Account`s. Returns None +/// if they are the same. +pub fn diff_pod(pre: Option<&PodAccount>, post: Option<&PodAccount>) -> Option { + match (pre, post) { + (None, Some(x)) => Some(AccountDiff { + balance: Diff::Born(x.balance), + nonce: Diff::Born(x.nonce), + code: Diff::Born(x.code.clone()), + storage: x.storage.iter().map(|(k, v)| (k.clone(), Diff::Born(v.clone()))).collect(), + }), + (Some(x), None) => Some(AccountDiff { + balance: Diff::Died(x.balance), + nonce: Diff::Died(x.nonce), + code: Diff::Died(x.code.clone()), + storage: x.storage.iter().map(|(k, v)| (k.clone(), Diff::Died(v.clone()))).collect(), + }), + (Some(pre), Some(post)) => { + let storage: Vec<_> = pre.storage.keys().merge(post.storage.keys()) + .filter(|k| pre.storage.get(k).unwrap_or(&H256::new()) != post.storage.get(k).unwrap_or(&H256::new())) + .collect(); + let r = AccountDiff { + balance: Diff::new(pre.balance, post.balance), + nonce: Diff::new(pre.nonce, post.nonce), + code: Diff::new(pre.code.clone(), post.code.clone()), + storage: storage.into_iter().map(|k| + (k.clone(), Diff::new( + pre.storage.get(&k).cloned().unwrap_or_else(H256::new), + post.storage.get(&k).cloned().unwrap_or_else(H256::new) + ))).collect(), + }; + if r.balance.is_same() && r.nonce.is_same() && r.code.is_same() && r.storage.is_empty() { + None + } else { + Some(r) + } + }, + _ => None, + } +} + + #[cfg(test)] mod test { use common::*; - use account_diff::*; - use super::*; + use types::account_diff::*; + use super::{PodAccount, diff_pod}; #[test] fn existence() { let a = PodAccount{balance: 69.into(), nonce: 0.into(), code: vec![], storage: map![]}; - assert_eq!(AccountDiff::diff_pod(Some(&a), Some(&a)), None); - assert_eq!(AccountDiff::diff_pod(None, Some(&a)), Some(AccountDiff{ + assert_eq!(diff_pod(Some(&a), Some(&a)), None); + assert_eq!(diff_pod(None, Some(&a)), Some(AccountDiff{ balance: Diff::Born(69.into()), nonce: Diff::Born(0.into()), code: Diff::Born(vec![]), @@ -128,7 +170,7 @@ mod test { fn basic() { let a = PodAccount{balance: 69.into(), nonce: 0.into(), code: vec![], storage: map![]}; let b = PodAccount{balance: 42.into(), nonce: 1.into(), code: vec![], storage: map![]}; - assert_eq!(AccountDiff::diff_pod(Some(&a), Some(&b)), Some(AccountDiff { + assert_eq!(diff_pod(Some(&a), Some(&b)), Some(AccountDiff { balance: Diff::Changed(69.into(), 42.into()), nonce: Diff::Changed(0.into(), 1.into()), code: Diff::Same, @@ -140,7 +182,7 @@ mod test { fn code() { let a = PodAccount{balance: 0.into(), nonce: 0.into(), code: vec![], storage: map![]}; let b = PodAccount{balance: 0.into(), nonce: 1.into(), code: vec![0], storage: map![]}; - assert_eq!(AccountDiff::diff_pod(Some(&a), Some(&b)), Some(AccountDiff { + assert_eq!(diff_pod(Some(&a), Some(&b)), Some(AccountDiff { balance: Diff::Same, nonce: Diff::Changed(0.into(), 1.into()), code: Diff::Changed(vec![], vec![0]), @@ -162,7 +204,7 @@ mod test { code: vec![], storage: map_into![1 => 1, 2 => 3, 3 => 0, 5 => 0, 7 => 7, 8 => 0, 9 => 9] }; - assert_eq!(AccountDiff::diff_pod(Some(&a), Some(&b)), Some(AccountDiff { + assert_eq!(diff_pod(Some(&a), Some(&b)), Some(AccountDiff { balance: Diff::Same, nonce: Diff::Same, code: Diff::Same, diff --git a/ethcore/src/pod_state.rs b/ethcore/src/pod_state.rs index d9d0cf764..76dac214b 100644 --- a/ethcore/src/pod_state.rs +++ b/ethcore/src/pod_state.rs @@ -17,7 +17,8 @@ //! State of all accounts in the system expressed in Plain Old Data. use util::*; -use pod_account::*; +use pod_account::{self, PodAccount}; +use types::state_diff::StateDiff; use ethjson; /// State of all accounts in the system expressed in Plain Old Data. @@ -29,7 +30,6 @@ impl PodState { pub fn new() -> PodState { Default::default() } /// Contruct a new object from the `m`. - #[cfg(test)] pub fn from(m: BTreeMap) -> PodState { PodState(m) } /// Get the underlying map. @@ -41,8 +41,6 @@ impl PodState { } /// Drain object to get the underlying map. - #[cfg(test)] - #[cfg(feature = "json-tests")] pub fn drain(self) -> BTreeMap { self.0 } } @@ -72,3 +70,83 @@ impl fmt::Display for PodState { } } +/// Calculate and return diff between `pre` state and `post` state. +pub fn diff_pod(pre: &PodState, post: &PodState) -> StateDiff { + StateDiff(pre.get().keys().merge(post.get().keys()).filter_map(|acc| pod_account::diff_pod(pre.get().get(acc), post.get().get(acc)).map(|d|(acc.clone(), d))).collect()) +} + +#[cfg(test)] +mod test { + use common::*; + use types::state_diff::*; + use types::account_diff::*; + use pod_account::PodAccount; + use super::PodState; + + #[test] + fn create_delete() { + let a = PodState::from(map![ 1.into() => PodAccount::new(69.into(), 0.into(), vec![], map![]) ]); + assert_eq!(super::diff_pod(&a, &PodState::new()), StateDiff(map![ + 1.into() => AccountDiff{ + balance: Diff::Died(69.into()), + nonce: Diff::Died(0.into()), + code: Diff::Died(vec![]), + storage: map![], + } + ])); + assert_eq!(super::diff_pod(&PodState::new(), &a), StateDiff(map![ + 1.into() => AccountDiff{ + balance: Diff::Born(69.into()), + nonce: Diff::Born(0.into()), + code: Diff::Born(vec![]), + storage: map![], + } + ])); + } + + #[test] + fn create_delete_with_unchanged() { + let a = PodState::from(map![ 1.into() => PodAccount::new(69.into(), 0.into(), vec![], map![]) ]); + let b = PodState::from(map![ + 1.into() => PodAccount::new(69.into(), 0.into(), vec![], map![]), + 2.into() => PodAccount::new(69.into(), 0.into(), vec![], map![]) + ]); + assert_eq!(super::diff_pod(&a, &b), StateDiff(map![ + 2.into() => AccountDiff{ + balance: Diff::Born(69.into()), + nonce: Diff::Born(0.into()), + code: Diff::Born(vec![]), + storage: map![], + } + ])); + assert_eq!(super::diff_pod(&b, &a), StateDiff(map![ + 2.into() => AccountDiff{ + balance: Diff::Died(69.into()), + nonce: Diff::Died(0.into()), + code: Diff::Died(vec![]), + storage: map![], + } + ])); + } + + #[test] + fn change_with_unchanged() { + let a = PodState::from(map![ + 1.into() => PodAccount::new(69.into(), 0.into(), vec![], map![]), + 2.into() => PodAccount::new(69.into(), 0.into(), vec![], map![]) + ]); + let b = PodState::from(map![ + 1.into() => PodAccount::new(69.into(), 1.into(), vec![], map![]), + 2.into() => PodAccount::new(69.into(), 0.into(), vec![], map![]) + ]); + assert_eq!(super::diff_pod(&a, &b), StateDiff(map![ + 1.into() => AccountDiff{ + balance: Diff::Same, + nonce: Diff::Changed(0.into(), 1.into()), + code: Diff::Same, + storage: map![], + } + ])); + } + +} diff --git a/ethcore/src/service.rs b/ethcore/src/service.rs index 38bd873b8..13f1c9f74 100644 --- a/ethcore/src/service.rs +++ b/ethcore/src/service.rs @@ -21,6 +21,7 @@ use util::panics::*; use spec::Spec; use error::*; use client::{Client, ClientConfig}; +use miner::Miner; /// Message type for external and internal events #[derive(Clone)] @@ -54,14 +55,14 @@ pub struct ClientService { impl ClientService { /// Start the service in a separate thread. - pub fn start(config: ClientConfig, spec: Spec, net_config: NetworkConfiguration, db_path: &Path) -> Result { + pub fn start(config: ClientConfig, spec: Spec, net_config: NetworkConfiguration, db_path: &Path, miner: Arc) -> Result { let panic_handler = PanicHandler::new_in_arc(); let mut net_service = try!(NetworkService::start(net_config)); panic_handler.forward_from(&net_service); info!("Starting {}", net_service.host_info()); info!("Configured for {} using {:?} engine", spec.name, spec.engine.name()); - let client = try!(Client::new(config, spec, db_path, net_service.io().channel())); + let client = try!(Client::new(config, spec, db_path, miner, net_service.io().channel())); panic_handler.forward_from(client.deref()); let client_io = Arc::new(ClientIoHandler { client: client.clone() @@ -141,12 +142,14 @@ mod tests { use util::network::*; use devtools::*; use client::ClientConfig; + use std::sync::Arc; + use miner::Miner; #[test] fn it_can_be_started() { let spec = get_test_spec(); let temp_path = RandomTempPath::new(); - let service = ClientService::start(ClientConfig::default(), spec, NetworkConfiguration::new_local(), &temp_path.as_path()); + let service = ClientService::start(ClientConfig::default(), spec, NetworkConfiguration::new_local(), &temp_path.as_path(), Arc::new(Miner::default())); assert!(service.is_ok()); } } diff --git a/ethcore/src/state.rs b/ethcore/src/state.rs index c099b17a5..06a365bdd 100644 --- a/ethcore/src/state.rs +++ b/ethcore/src/state.rs @@ -20,13 +20,9 @@ use executive::{Executive, TransactOptions}; use evm::Factory as EvmFactory; use account_db::*; use trace::Trace; -#[cfg(test)] -#[cfg(feature = "json-tests")] use pod_account::*; -#[cfg(test)] -#[cfg(feature = "json-tests")] -use pod_state::PodState; -//use state_diff::*; // TODO: uncomment once to_pod() works correctly. +use pod_state::{self, PodState}; +use types::state_diff::StateDiff; /// Used to return information about an `State::apply` operation. pub struct ApplyOutcome { @@ -183,16 +179,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,11 +216,11 @@ 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. -// trace!("Applied transaction. Diff:\n{}\n", StateDiff::diff_pod(&old, &self.to_pod())); +// trace!("Applied transaction. Diff:\n{}\n", state_diff::diff_pod(&old, &self.to_pod())); self.commit(); let receipt = Receipt::new(self.root().clone(), e.cumulative_gas_used, e.logs); // trace!("Transaction receipt: {:?}", receipt); @@ -277,12 +271,11 @@ impl State { } } - #[cfg(test)] - #[cfg(feature = "json-tests")] /// Populate a PodAccount map from this state. pub fn to_pod(&self) -> PodState { assert!(self.snapshots.borrow().is_empty()); // TODO: handle database rather than just the cache. + // will need fat db. PodState::from(self.cache.borrow().iter().fold(BTreeMap::new(), |mut m, (add, opt)| { if let Some(ref acc) = *opt { m.insert(add.clone(), PodAccount::from_account(acc)); @@ -291,6 +284,25 @@ impl State { })) } + fn query_pod(&mut self, query: &PodState) { + for (ref address, ref pod_account) in query.get() { + if self.get(address, true).is_some() { + for (ref key, _) in &pod_account.storage { + self.storage_at(address, key); + } + } + } + } + + /// Returns a `StateDiff` describing the difference from `orig` to `self`. + /// Consumes self. + pub fn diff_from(&self, orig: State) -> StateDiff { + let pod_state_post = self.to_pod(); + let mut state_pre = orig; + state_pre.query_pod(&pod_state_post); + pod_state::diff_pod(&state_pre.to_pod(), &pod_state_post) + } + /// Pull account `a` in our cache from the trie DB and return it. /// `require_code` requires that the code be cached, too. fn get<'a>(&'a self, a: &Address, require_code: bool) -> &'a Option { diff --git a/ethcore/src/tests/client.rs b/ethcore/src/tests/client.rs index 4a71bf7d7..d6fab71f0 100644 --- a/ethcore/src/tests/client.rs +++ b/ethcore/src/tests/client.rs @@ -14,16 +14,17 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -use client::{BlockChainClient, Client, ClientConfig, BlockID}; +use client::{BlockChainClient, MiningBlockChainClient, Client, ClientConfig, BlockID}; use block::IsBlock; use tests::helpers::*; use common::*; use devtools::*; +use miner::Miner; #[test] fn imports_from_empty() { let dir = RandomTempPath::new(); - let client = Client::new(ClientConfig::default(), get_test_spec(), dir.as_path(), IoChannel::disconnected()).unwrap(); + let client = Client::new(ClientConfig::default(), get_test_spec(), dir.as_path(), Arc::new(Miner::default()), IoChannel::disconnected()).unwrap(); client.import_verified_blocks(&IoChannel::disconnected()); client.flush_queue(); } @@ -41,7 +42,7 @@ fn returns_state_root_basic() { #[test] fn imports_good_block() { let dir = RandomTempPath::new(); - let client = Client::new(ClientConfig::default(), get_test_spec(), dir.as_path(), IoChannel::disconnected()).unwrap(); + let client = Client::new(ClientConfig::default(), get_test_spec(), dir.as_path(), Arc::new(Miner::default()), IoChannel::disconnected()).unwrap(); let good_block = get_good_dummy_block(); if let Err(_) = client.import_block(good_block) { panic!("error importing block being good by definition"); @@ -56,7 +57,7 @@ fn imports_good_block() { #[test] fn query_none_block() { let dir = RandomTempPath::new(); - let client = Client::new(ClientConfig::default(), get_test_spec(), dir.as_path(), IoChannel::disconnected()).unwrap(); + let client = Client::new(ClientConfig::default(), get_test_spec(), dir.as_path(), Arc::new(Miner::default()), IoChannel::disconnected()).unwrap(); let non_existant = client.block_header(BlockID::Number(188)); assert!(non_existant.is_none()); diff --git a/ethcore/src/tests/helpers.rs b/ethcore/src/tests/helpers.rs index 22282ccdd..3b27ef4a4 100644 --- a/ethcore/src/tests/helpers.rs +++ b/ethcore/src/tests/helpers.rs @@ -23,6 +23,7 @@ use evm::Schedule; use engine::*; use ethereum; use devtools::*; +use miner::Miner; #[cfg(feature = "json-tests")] pub enum ChainEra { @@ -139,7 +140,7 @@ pub fn create_test_block_with_data(header: &Header, transactions: &[&SignedTrans pub fn generate_dummy_client(block_number: u32) -> GuardedTempResult> { let dir = RandomTempPath::new(); - let client = Client::new(ClientConfig::default(), get_test_spec(), dir.as_path(), IoChannel::disconnected()).unwrap(); + let client = Client::new(ClientConfig::default(), get_test_spec(), dir.as_path(), Arc::new(Miner::default()), IoChannel::disconnected()).unwrap(); let test_spec = get_test_spec(); let test_engine = &test_spec.engine; let state_root = test_spec.genesis_header().state_root; @@ -205,7 +206,7 @@ pub fn push_blocks_to_client(client: &Arc, timestamp_salt: u64, starting pub fn get_test_client_with_blocks(blocks: Vec) -> GuardedTempResult> { let dir = RandomTempPath::new(); - let client = Client::new(ClientConfig::default(), get_test_spec(), dir.as_path(), IoChannel::disconnected()).unwrap(); + let client = Client::new(ClientConfig::default(), get_test_spec(), dir.as_path(), Arc::new(Miner::default()), IoChannel::disconnected()).unwrap(); for block in &blocks { if let Err(_) = client.import_block(block.clone()) { panic!("panic importing block which is well-formed"); 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/account_diff.rs b/ethcore/src/types/account_diff.rs similarity index 68% rename from ethcore/src/account_diff.rs rename to ethcore/src/types/account_diff.rs index c02b7ec7b..49fc51110 100644 --- a/ethcore/src/account_diff.rs +++ b/ethcore/src/types/account_diff.rs @@ -17,10 +17,48 @@ //! Diff between two accounts. use util::*; -#[cfg(test)] -use pod_account::*; -#[derive(Debug,Clone,PartialEq,Eq)] +#[derive(Debug, PartialEq, Eq, Clone)] +/// Diff type for specifying a change (or not). +pub enum Diff where T: Eq { + /// Both sides are the same. + Same, + /// Left (pre, source) side doesn't include value, right side (post, destination) does. + Born(T), + /// Both sides include data; it chaged value between them. + Changed(T, T), + /// Left (pre, source) side does include value, right side (post, destination) does not. + Died(T), +} + +impl Diff where T: Eq { + /// Construct new object with given `pre` and `post`. + pub fn new(pre: T, post: T) -> Self { if pre == post { Diff::Same } else { Diff::Changed(pre, post) } } + + /// Get the before value, if there is one. + pub fn pre(&self) -> Option<&T> { match *self { Diff::Died(ref x) | Diff::Changed(ref x, _) => Some(x), _ => None } } + + /// Get the after value, if there is one. + pub fn post(&self) -> Option<&T> { match *self { Diff::Born(ref x) | Diff::Changed(_, ref x) => Some(x), _ => None } } + + /// Determine whether there was a change or not. + pub fn is_same(&self) -> bool { match *self { Diff::Same => true, _ => false }} +} + +#[derive(Debug, PartialEq, Eq, Clone)] +/// Account diff. +pub struct AccountDiff { + /// Change in balance, allowed to be `Diff::Same`. + pub balance: Diff, + /// Change in nonce, allowed to be `Diff::Same`. + pub nonce: Diff, // Allowed to be Same + /// Change in code, allowed to be `Diff::Same`. + pub code: Diff, // Allowed to be Same + /// Change in storage, values are not allowed to be `Diff::Same`. + pub storage: BTreeMap>, +} + +#[derive(Debug, PartialEq, Eq, Clone)] /// Change in existance type. // TODO: include other types of change. pub enum Existance { @@ -43,19 +81,6 @@ impl fmt::Display for Existance { } } -#[derive(Debug,Clone,PartialEq,Eq)] -/// Account diff. -pub struct AccountDiff { - /// Change in balance, allowed to be `Diff::Same`. - pub balance: Diff, - /// Change in nonce, allowed to be `Diff::Same`. - pub nonce: Diff, // Allowed to be Same - /// Change in code, allowed to be `Diff::Same`. - pub code: Diff, // Allowed to be Same - /// Change in storage, values are not allowed to be `Diff::Same`. - pub storage: BTreeMap>, -} - impl AccountDiff { /// Get `Existance` projection. pub fn existance(&self) -> Existance { @@ -65,47 +90,6 @@ impl AccountDiff { _ => Existance::Alive, } } - - #[cfg(test)] - /// Determine difference between two optionally existant `Account`s. Returns None - /// if they are the same. - pub fn diff_pod(pre: Option<&PodAccount>, post: Option<&PodAccount>) -> Option { - match (pre, post) { - (None, Some(x)) => Some(AccountDiff { - balance: Diff::Born(x.balance), - nonce: Diff::Born(x.nonce), - code: Diff::Born(x.code.clone()), - storage: x.storage.iter().map(|(k, v)| (k.clone(), Diff::Born(v.clone()))).collect(), - }), - (Some(x), None) => Some(AccountDiff { - balance: Diff::Died(x.balance), - nonce: Diff::Died(x.nonce), - code: Diff::Died(x.code.clone()), - storage: x.storage.iter().map(|(k, v)| (k.clone(), Diff::Died(v.clone()))).collect(), - }), - (Some(pre), Some(post)) => { - let storage: Vec<_> = pre.storage.keys().merge(post.storage.keys()) - .filter(|k| pre.storage.get(k).unwrap_or(&H256::new()) != post.storage.get(k).unwrap_or(&H256::new())) - .collect(); - let r = AccountDiff { - balance: Diff::new(pre.balance, post.balance), - nonce: Diff::new(pre.nonce, post.nonce), - code: Diff::new(pre.code.clone(), post.code.clone()), - storage: storage.into_iter().map(|k| - (k.clone(), Diff::new( - pre.storage.get(&k).cloned().unwrap_or_else(H256::new), - post.storage.get(&k).cloned().unwrap_or_else(H256::new) - ))).collect(), - }; - if r.balance.is_same() && r.nonce.is_same() && r.code.is_same() && r.storage.is_empty() { - None - } else { - Some(r) - } - }, - _ => None, - } - } } // TODO: refactor into something nicer. diff --git a/ethcore/src/types/executed.rs b/ethcore/src/types/executed.rs index fcde5a392..4d31b9fe5 100644 --- a/ethcore/src/types/executed.rs +++ b/ethcore/src/types/executed.rs @@ -18,15 +18,16 @@ use util::numbers::*; use util::Bytes; -use trace::Trace; +use trace::{Trace, VMTrace}; use types::log_entry::LogEntry; +use types::state_diff::StateDiff; use ipc::binary::BinaryConvertError; use std::fmt; use std::mem; use std::collections::VecDeque; /// Transaction execution receipt. -#[derive(Debug, PartialEq, Clone, Binary)] +#[derive(Debug, PartialEq, Clone)] pub struct Executed { /// Gas paid up front for execution of transaction. pub gas: U256, @@ -59,6 +60,10 @@ pub struct Executed { pub output: Bytes, /// The trace of this transaction. pub trace: Option, + /// The VM trace of this transaction. + pub vm_trace: Option, + /// The state diff, if we traced it. + pub state_diff: Option, } /// Result of executing the transaction. diff --git a/ethcore/src/types/mod.rs.in b/ethcore/src/types/mod.rs.in index 1f67a9184..b51e9e57b 100644 --- a/ethcore/src/types/mod.rs.in +++ b/ethcore/src/types/mod.rs.in @@ -23,3 +23,5 @@ pub mod log_entry; pub mod trace_types; pub mod executed; pub mod block_status; +pub mod account_diff; +pub mod state_diff; diff --git a/ethcore/src/types/state_diff.rs b/ethcore/src/types/state_diff.rs new file mode 100644 index 000000000..4257d5b07 --- /dev/null +++ b/ethcore/src/types/state_diff.rs @@ -0,0 +1,49 @@ +// 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 . + +//! State diff module. + +use util::*; +use account_diff::*; + +#[derive(Debug, PartialEq, Eq, Clone)] +/// Expression for the delta between two system states. Encoded the +/// delta of every altered account. +pub struct StateDiff (pub BTreeMap); + +impl StateDiff { + /// Get the actual data. + pub fn get(&self) -> &BTreeMap { + &self.0 + } +} + +impl fmt::Display for StateDiff { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + for (add, acc) in &self.0 { + try!(write!(f, "{} {}: {}", acc.existance(), add, acc)); + } + Ok(()) + } +} + +impl Deref for StateDiff { + type Target = BTreeMap; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} 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/hook.sh b/hook.sh index 978f0ca23..adb763f9d 100755 --- a/hook.sh +++ b/hook.sh @@ -7,6 +7,6 @@ echo "set -e" >> $FILE echo "cargo build --features dev" >> $FILE # Build tests echo "cargo test --no-run --features dev \\" >> $FILE -echo " -p ethash -p ethcore-util -p ethcore -p ethsync -p ethcore-rpc -p parity -p ethminer -p ethcore-dapps -p ethcore-signer" >> $FILE +echo " -p ethash -p ethcore-util -p ethcore -p ethsync -p ethcore-rpc -p parity -p ethcore-dapps -p ethcore-signer" >> $FILE echo "" >> $FILE chmod +x $FILE diff --git a/ipc/codegen/src/codegen.rs b/ipc/codegen/src/codegen.rs index 57db7d261..9dd6a7b32 100644 --- a/ipc/codegen/src/codegen.rs +++ b/ipc/codegen/src/codegen.rs @@ -392,16 +392,13 @@ fn implement_client_method_body( }); request_serialization_statements.push( - quote_stmt!(cx, let mut socket_ref = self.socket.borrow_mut())); - - request_serialization_statements.push( - quote_stmt!(cx, let mut socket = socket_ref.deref_mut())); + quote_stmt!(cx, let mut socket = self.socket.write().unwrap(); )); request_serialization_statements.push( quote_stmt!(cx, let serialized_payload = ::ipc::binary::serialize(&payload).unwrap())); request_serialization_statements.push( - quote_stmt!(cx, ::ipc::invoke($index_ident, &Some(serialized_payload), &mut socket))); + quote_stmt!(cx, ::ipc::invoke($index_ident, &Some(serialized_payload), &mut *socket))); request_serialization_statements @@ -409,17 +406,15 @@ fn implement_client_method_body( else { let mut request_serialization_statements = Vec::new(); request_serialization_statements.push( - quote_stmt!(cx, let mut socket_ref = self.socket.borrow_mut())); + quote_stmt!(cx, let mut socket = self.socket.write().unwrap(); )); request_serialization_statements.push( - quote_stmt!(cx, let mut socket = socket_ref.deref_mut())); - request_serialization_statements.push( - quote_stmt!(cx, ::ipc::invoke($index_ident, &None, &mut socket))); + quote_stmt!(cx, ::ipc::invoke($index_ident, &None, &mut *socket))); request_serialization_statements }; if let Some(ref return_ty) = dispatch.return_type_ty { let return_expr = quote_expr!(cx, - ::ipc::binary::deserialize_from::<$return_ty, _>(&mut socket).unwrap() + ::ipc::binary::deserialize_from::<$return_ty, _>(&mut *socket).unwrap() ); quote_expr!(cx, { $request @@ -525,7 +520,7 @@ fn push_client_struct(cx: &ExtCtxt, builder: &aster::AstBuilder, interface_map: let client_struct_item = quote_item!(cx, pub struct $client_short_ident $generics { - socket: ::std::cell::RefCell, + socket: ::std::sync::RwLock, phantom: $phantom, }); @@ -560,7 +555,7 @@ fn push_with_socket_client_implementation( impl $generics ::ipc::WithSocket for $client_ident $where_clause { fn init(socket: S) -> $client_ident { $client_short_ident { - socket: ::std::cell::RefCell::new(socket), + socket: ::std::sync::RwLock::new(socket), phantom: ::std::marker::PhantomData, } } @@ -594,15 +589,13 @@ fn push_client_implementation( reserved: vec![0u8; 64], }; - let mut socket_ref = self.socket.borrow_mut(); - let mut socket = socket_ref.deref_mut(); ::ipc::invoke( 0, &Some(::ipc::binary::serialize(&payload).unwrap()), - &mut socket); + &mut *self.socket.write().unwrap()); let mut result = vec![0u8; 1]; - if try!(socket.read(&mut result).map_err(|_| ::ipc::Error::HandshakeFailed)) == 1 { + if try!(self.socket.write().unwrap().read(&mut result).map_err(|_| ::ipc::Error::HandshakeFailed)) == 1 { match result[0] { 1 => Ok(()), _ => Err(::ipc::Error::RemoteServiceUnsupported), @@ -613,7 +606,7 @@ fn push_client_implementation( let socket_item = quote_impl_item!(cx, #[cfg(test)] - pub fn socket(&self) -> &::std::cell::RefCell { + pub fn socket(&self) -> &::std::sync::RwLock { &self.socket }).unwrap(); diff --git a/ipc/codegen/src/serialization.rs b/ipc/codegen/src/serialization.rs index c2e39ea33..b32c88b6d 100644 --- a/ipc/codegen/src/serialization.rs +++ b/ipc/codegen/src/serialization.rs @@ -230,6 +230,9 @@ fn binary_expr_struct( let field_amount = builder.id(&format!("{}",fields.len())); map_stmts.push(quote_stmt!(cx, let mut map = vec![0usize; $field_amount];).unwrap()); map_stmts.push(quote_stmt!(cx, let mut total = 0usize;).unwrap()); + + let mut post_write_stmts = Vec::::new(); + for (index, field) in fields.iter().enumerate() { let field_type_ident = builder.id( &::syntax::print::pprust::ty_to_string(&codegen::strip_ptr(&field.ty))); @@ -249,6 +252,7 @@ fn binary_expr_struct( }; let raw_ident = ::syntax::print::pprust::ty_to_string(&codegen::strip_ptr(&field.ty)); + let range_ident = builder.id(format!("r{}", index)); match raw_ident.as_ref() { "u8" => { write_stmts.push(quote_stmt!(cx, let next_line = offset + 1;).unwrap()); @@ -258,15 +262,17 @@ fn binary_expr_struct( write_stmts.push(quote_stmt!(cx, let size = $member_expr .len();).unwrap()); write_stmts.push(quote_stmt!(cx, let next_line = offset + size;).unwrap()); write_stmts.push(quote_stmt!(cx, length_stack.push_back(size);).unwrap()); - write_stmts.push(quote_stmt!(cx, buffer[offset..next_line].clone_from_slice($member_expr); ).unwrap()); + write_stmts.push(quote_stmt!(cx, let $range_ident = offset..next_line; ).unwrap()); + post_write_stmts.push(quote_stmt!(cx, buffer[$range_ident].clone_from_slice($member_expr); ).unwrap()); } _ => { write_stmts.push(quote_stmt!(cx, let next_line = offset + match $field_type_ident_qualified::len_params() { 0 => mem::size_of::<$field_type_ident>(), _ => { let size = $member_expr .size(); length_stack.push_back(size); size }, }).unwrap()); - write_stmts.push(quote_stmt!(cx, - if let Err(e) = $member_expr .to_bytes(&mut buffer[offset..next_line], length_stack) { return Err(e) };).unwrap()); + write_stmts.push(quote_stmt!(cx, let $range_ident = offset..next_line; ).unwrap()); + post_write_stmts.push(quote_stmt!(cx, + if let Err(e) = $member_expr .to_bytes(&mut buffer[$range_ident], length_stack) { return Err(e) };).unwrap()); } } @@ -312,7 +318,7 @@ fn binary_expr_struct( Ok(BinaryExpressions { size: total_size_expr, - write: quote_expr!(cx, { $write_stmts; Ok(()) } ), + write: quote_expr!(cx, { $write_stmts; $post_write_stmts; Ok(()) } ), read: read_expr, }) } diff --git a/ipc/nano/src/lib.rs b/ipc/nano/src/lib.rs index 964e52c68..38ff05c5b 100644 --- a/ipc/nano/src/lib.rs +++ b/ipc/nano/src/lib.rs @@ -30,6 +30,7 @@ use nanomsg::{Socket, Protocol, Error, Endpoint, PollRequest, PollFd, PollInOut} use std::ops::Deref; const POLL_TIMEOUT: isize = 100; +const CLIENT_CONNECTION_TIMEOUT: isize = 2500; /// Generic worker to handle service (binded) sockets pub struct Worker where S: IpcInterface { @@ -46,6 +47,12 @@ pub struct GuardedSocket where S: WithSocket { _endpoint: Endpoint, } +impl GuardedSocket where S: WithSocket { + pub fn service(&self) -> Arc { + self.client.clone() + } +} + impl Deref for GuardedSocket where S: WithSocket { type Target = S; @@ -63,6 +70,9 @@ pub fn init_duplex_client(socket_addr: &str) -> Result, Sock SocketError::DuplexLink })); + // 2500 ms default timeout + socket.set_receive_timeout(CLIENT_CONNECTION_TIMEOUT).unwrap(); + let endpoint = try!(socket.connect(socket_addr).map_err(|e| { warn!(target: "ipc", "Failed to bind socket to address '{}': {:?}", socket_addr, e); SocketError::DuplexLink @@ -83,6 +93,9 @@ pub fn init_client(socket_addr: &str) -> Result, SocketError SocketError::RequestLink })); + // 2500 ms default timeout + socket.set_receive_timeout(CLIENT_CONNECTION_TIMEOUT).unwrap(); + let endpoint = try!(socket.connect(socket_addr).map_err(|e| { warn!(target: "ipc", "Failed to bind socket to address '{}': {:?}", socket_addr, e); SocketError::RequestLink diff --git a/ipc/rpc/src/binary.rs b/ipc/rpc/src/binary.rs index a985a2de9..62a3c43b0 100644 --- a/ipc/rpc/src/binary.rs +++ b/ipc/rpc/src/binary.rs @@ -104,14 +104,31 @@ impl BinaryConvertable for Result) -> Result<(), BinaryConvertError> { match *self { - Ok(ref r) => { buffer[0] = 0; Ok(try!(r.to_bytes(&mut buffer[1..], length_stack))) }, - Err(ref e) => { buffer[1] = 1; Ok(try!(e.to_bytes(&mut buffer[1..], length_stack))) }, + Ok(ref r) => { + buffer[0] = 0; + if r.size() > 0 { + Ok(try!(r.to_bytes(&mut buffer[1..], length_stack))) + } + else { Ok(()) } + }, + Err(ref e) => { + buffer[0] = 1; + if e.size() > 0 { + Ok(try!(e.to_bytes(&mut buffer[1..], length_stack))) + } + else { Ok(()) } + }, } } fn from_bytes(buffer: &[u8], length_stack: &mut VecDeque) -> Result { match buffer[0] { - 0 => Ok(Ok(try!(R::from_bytes(&buffer[1..], length_stack)))), + 0 => { + match buffer.len() { + 1 => Ok(Ok(try!(R::from_empty_bytes()))), + _ => Ok(Ok(try!(R::from_bytes(&buffer[1..], length_stack)))), + } + } 1 => Ok(Err(try!(E::from_bytes(&buffer[1..], length_stack)))), _ => Err(BinaryConvertError) } @@ -154,6 +171,8 @@ impl BinaryConvertable for Vec where T: BinaryConvertable { _ => 128, }); + if buffer.len() == 0 { return Ok(result); } + loop { let next_size = match T::len_params() { 0 => mem::size_of::(), @@ -300,8 +319,8 @@ pub fn deserialize_from(r: &mut R) -> Result let mut payload = Vec::new(); try!(r.read_to_end(&mut payload).map_err(|_| BinaryConvertError)); - let mut length_stack = VecDeque::::new(); let stack_len = try!(u64::from_bytes(&payload[0..8], &mut fake_stack)) as usize; + let mut length_stack = VecDeque::::with_capacity(stack_len); if stack_len > 0 { for idx in 0..stack_len { @@ -607,7 +626,7 @@ fn deserialize_simple_err() { } #[test] -fn deserialize_opt_vec_in_out() { +fn serialize_opt_vec_in_out() { use std::io::{Cursor, SeekFrom, Seek}; let mut buff = Cursor::new(Vec::new()); @@ -619,3 +638,17 @@ fn deserialize_opt_vec_in_out() { assert!(vec.is_none()); } + +#[test] +fn serialize_err_opt_vec_in_out() { + use std::io::{Cursor, SeekFrom, Seek}; + + let mut buff = Cursor::new(Vec::new()); + let optional_vec: Result>, u32> = Ok(None); + serialize_into(&optional_vec, &mut buff).unwrap(); + + buff.seek(SeekFrom::Start(0)).unwrap(); + let vec = deserialize_from::>, u32>, _>(&mut buff).unwrap(); + + assert!(vec.is_ok()); +} 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/miner/Cargo.toml b/miner/Cargo.toml deleted file mode 100644 index a3a1c059f..000000000 --- a/miner/Cargo.toml +++ /dev/null @@ -1,24 +0,0 @@ -[package] -description = "Ethminer library" -homepage = "http://ethcore.io" -license = "GPL-3.0" -name = "ethminer" -version = "1.2.0" -authors = ["Ethcore "] -build = "build.rs" - -[build-dependencies] -rustc_version = "0.1" - -[dependencies] -ethcore-util = { path = "../util" } -ethcore = { path = "../ethcore" } -log = "0.3" -env_logger = "0.3" -rustc-serialize = "0.3" -rayon = "0.3.1" -clippy = { version = "0.0.69", optional = true} - -[features] -default = [] -dev = ["clippy"] diff --git a/parity/cli.rs b/parity/cli.rs index 95b77a00d..bb67ee5c6 100644 --- a/parity/cli.rs +++ b/parity/cli.rs @@ -96,6 +96,8 @@ API and Console Options: asked for password on startup. --dapps-pass PASSWORD Specify password for Dapps server. Use only in conjunction with --dapps-user. + --dapps-path PATH Specify directory where dapps should be installed. + [default: $HOME/.parity/dapps] --signer Enable Trusted Signer WebSocket endpoint used by System UIs. @@ -239,6 +241,7 @@ pub struct Args { pub flag_dapps_interface: String, pub flag_dapps_user: Option, pub flag_dapps_pass: Option, + pub flag_dapps_path: String, pub flag_signer: bool, pub flag_signer_port: u16, pub flag_force_sealing: bool, diff --git a/parity/configuration.rs b/parity/configuration.rs index 61a571ae1..6185d2cf7 100644 --- a/parity/configuration.rs +++ b/parity/configuration.rs @@ -40,6 +40,7 @@ pub struct Configuration { pub struct Directories { pub keys: String, pub db: String, + pub dapps: String, } impl Configuration { @@ -325,11 +326,14 @@ impl Configuration { &self.args.flag_keys_path } ); - ::std::fs::create_dir_all(&db_path).unwrap_or_else(|e| die_with_io_error("main", e)); + ::std::fs::create_dir_all(&keys_path).unwrap_or_else(|e| die_with_io_error("main", e)); + let dapps_path = Configuration::replace_home(&self.args.flag_dapps_path); + ::std::fs::create_dir_all(&dapps_path).unwrap_or_else(|e| die_with_io_error("main", e)); Directories { keys: keys_path, db: db_path, + dapps: dapps_path, } } diff --git a/parity/dapps.rs b/parity/dapps.rs index 986e3dd07..59a9ee552 100644 --- a/parity/dapps.rs +++ b/parity/dapps.rs @@ -17,14 +17,9 @@ use std::sync::Arc; use std::str::FromStr; use std::net::SocketAddr; -use ethcore::client::Client; -use ethsync::EthSync; -use ethminer::{Miner, ExternalMiner}; -use util::RotatingLogger; use util::panics::PanicHandler; -use util::keys::store::AccountService; -use util::network_settings::NetworkSettings; use die::*; +use rpc_apis; #[cfg(feature = "dapps")] pub use ethcore_dapps::Server as WebappServer; @@ -37,17 +32,12 @@ pub struct Configuration { pub port: u16, pub user: Option, pub pass: Option, + pub dapps_path: String, } pub struct Dependencies { pub panic_handler: Arc, - pub client: Arc, - pub sync: Arc, - pub secret_store: Arc, - pub miner: Arc, - pub external_miner: Arc, - pub logger: Arc, - pub settings: Arc, + pub apis: Arc, } pub fn new(configuration: Configuration, deps: Dependencies) -> Option { @@ -74,12 +64,13 @@ pub fn new(configuration: Configuration, deps: Dependencies) -> Option, ) -> ! { @@ -89,20 +80,14 @@ pub fn setup_dapps_server( #[cfg(feature = "dapps")] pub fn setup_dapps_server( deps: Dependencies, + dapps_path: String, url: &SocketAddr, auth: Option<(String, String)> ) -> WebappServer { - use ethcore_rpc::v1::*; use ethcore_dapps as dapps; - let server = dapps::ServerBuilder::new(); - server.add_delegate(Web3Client::new().to_delegate()); - server.add_delegate(NetClient::new(&deps.sync).to_delegate()); - server.add_delegate(EthClient::new(&deps.client, &deps.sync, &deps.secret_store, &deps.miner, &deps.external_miner).to_delegate()); - server.add_delegate(EthFilterClient::new(&deps.client, &deps.miner).to_delegate()); - server.add_delegate(PersonalClient::new(&deps.secret_store, &deps.client, &deps.miner).to_delegate()); - server.add_delegate(EthcoreClient::new(&deps.miner, deps.logger.clone(), deps.settings.clone()).to_delegate()); - + let server = dapps::ServerBuilder::new(dapps_path); + let server = rpc_apis::setup_rpc(server, deps.apis.clone(), rpc_apis::ApiSet::UnsafeContext); let start_result = match auth { None => { server.start_unsecure_http(url) diff --git a/parity/main.rs b/parity/main.rs index 7f16d28a5..74d14bfd6 100644 --- a/parity/main.rs +++ b/parity/main.rs @@ -27,7 +27,6 @@ extern crate rustc_serialize; extern crate ethcore_util as util; extern crate ethcore; extern crate ethsync; -extern crate ethminer; #[macro_use] extern crate log as rlog; extern crate env_logger; @@ -67,6 +66,7 @@ mod cli; mod configuration; mod migration; mod signer; +mod rpc_apis; use std::io::{Write, Read, BufReader, BufRead}; use std::ops::Deref; @@ -85,7 +85,7 @@ use ethcore::error::{Error, ImportError}; use ethcore::service::ClientService; use ethcore::spec::Spec; use ethsync::EthSync; -use ethminer::{Miner, MinerService, ExternalMiner}; +use ethcore::miner::{Miner, MinerService, ExternalMiner}; use daemonize::Daemonize; use migration::migrate; use informant::Informant; @@ -173,14 +173,6 @@ fn execute_client(conf: Configuration, spec: Spec, client_config: ClientConfig) // Secret Store let account_service = Arc::new(conf.account_service()); - // Build client - let mut service = ClientService::start( - client_config, spec, net_settings, Path::new(&conf.path()) - ).unwrap_or_else(|e| die_with_error("Client", e)); - - panic_handler.forward_from(&service); - let client = service.client(); - // Miner let miner = Miner::with_accounts(conf.args.flag_force_sealing, conf.spec(), account_service.clone()); miner.set_author(conf.author()); @@ -189,14 +181,23 @@ fn execute_client(conf: Configuration, spec: Spec, client_config: ClientConfig) miner.set_minimal_gas_price(conf.gas_price()); miner.set_transactions_limit(conf.args.flag_tx_limit); + // Build client + let mut service = ClientService::start( + client_config, spec, net_settings, Path::new(&conf.path()), miner.clone() + ).unwrap_or_else(|e| die_with_error("Client", e)); + + panic_handler.forward_from(&service); + let client = service.client(); + let external_miner = Arc::new(ExternalMiner::default()); let network_settings = Arc::new(conf.network_settings()); // Sync - let sync = EthSync::register(service.network(), sync_config, client.clone(), miner.clone()); + let sync = EthSync::register(service.network(), sync_config, client.clone()); - let dependencies = Arc::new(rpc::Dependencies { - panic_handler: panic_handler.clone(), + let deps_for_rpc_apis = Arc::new(rpc_apis::Dependencies { + signer_enabled: conf.args.flag_signer, + signer_queue: Arc::new(rpc_apis::ConfirmationsQueue::default()), client: client.clone(), sync: sync.clone(), secret_store: account_service.clone(), @@ -206,6 +207,11 @@ fn execute_client(conf: Configuration, spec: Spec, client_config: ClientConfig) settings: network_settings.clone(), }); + let dependencies = rpc::Dependencies { + panic_handler: panic_handler.clone(), + apis: deps_for_rpc_apis.clone(), + }; + // Setup http rpc let rpc_server = rpc::new_http(rpc::HttpConfiguration { enabled: network_settings.rpc_enabled, @@ -225,28 +231,19 @@ fn execute_client(conf: Configuration, spec: Spec, client_config: ClientConfig) port: conf.args.flag_dapps_port, user: conf.args.flag_dapps_user.clone(), pass: conf.args.flag_dapps_pass.clone(), + dapps_path: conf.directories().dapps, }, dapps::Dependencies { panic_handler: panic_handler.clone(), - client: client.clone(), - sync: sync.clone(), - secret_store: account_service.clone(), - miner: miner.clone(), - external_miner: external_miner.clone(), - logger: logger.clone(), - settings: network_settings.clone(), + apis: deps_for_rpc_apis.clone(), }); // Set up a signer let signer_server = signer::start(signer::Configuration { - enabled: conf.args.flag_signer, + enabled: deps_for_rpc_apis.signer_enabled, port: conf.args.flag_signer_port, }, signer::Dependencies { panic_handler: panic_handler.clone(), - client: client.clone(), - sync: sync.clone(), - secret_store: account_service.clone(), - miner: miner.clone(), - external_miner: external_miner.clone(), + apis: deps_for_rpc_apis.clone(), }); // Register IO handler @@ -295,7 +292,7 @@ fn execute_export(conf: Configuration) { // Build client let service = ClientService::start( - client_config, spec, net_settings, Path::new(&conf.path()) + client_config, spec, net_settings, Path::new(&conf.path()), Arc::new(Miner::default()), ).unwrap_or_else(|e| die_with_error("Client", e)); panic_handler.forward_from(&service); @@ -366,7 +363,7 @@ fn execute_import(conf: Configuration) { // Build client let service = ClientService::start( - client_config, spec, net_settings, Path::new(&conf.path()) + client_config, spec, net_settings, Path::new(&conf.path()), Arc::new(Miner::default()), ).unwrap_or_else(|e| die_with_error("Client", e)); panic_handler.forward_from(&service); diff --git a/parity/rpc.rs b/parity/rpc.rs index 60766263b..66f504408 100644 --- a/parity/rpc.rs +++ b/parity/rpc.rs @@ -15,19 +15,13 @@ // along with Parity. If not, see . -use std::collections::BTreeMap; use std::str::FromStr; use std::sync::Arc; use std::net::SocketAddr; -use ethcore::client::Client; -use ethsync::EthSync; -use ethminer::{Miner, ExternalMiner}; -use util::RotatingLogger; use util::panics::PanicHandler; -use util::keys::store::AccountService; -use util::network_settings::NetworkSettings; use die::*; use jsonipc; +use rpc_apis; #[cfg(feature = "rpc")] pub use ethcore_rpc::Server as RpcServer; @@ -52,16 +46,10 @@ pub struct IpcConfiguration { pub struct Dependencies { pub panic_handler: Arc, - pub client: Arc, - pub sync: Arc, - pub secret_store: Arc, - pub miner: Arc, - pub external_miner: Arc, - pub logger: Arc, - pub settings: Arc, + pub apis: Arc, } -pub fn new_http(conf: HttpConfiguration, deps: &Arc) -> Option { +pub fn new_http(conf: HttpConfiguration, deps: &Dependencies) -> Option { if !conf.enabled { return None; } @@ -78,58 +66,23 @@ pub fn new_http(conf: HttpConfiguration, deps: &Arc) -> Option) -> Option { +pub fn new_ipc(conf: IpcConfiguration, deps: &Dependencies) -> Option { if !conf.enabled { return None; } let apis = conf.apis.split(',').collect(); Some(setup_ipc_rpc_server(deps, &conf.socket_addr, apis)) } -fn setup_rpc_server(apis: Vec<&str>, deps: &Arc) -> Server { - use ethcore_rpc::v1::*; - +fn setup_rpc_server(apis: Vec<&str>, deps: &Dependencies) -> Server { + let apis = rpc_apis::from_str(apis); let server = Server::new(); - let mut modules = BTreeMap::new(); - for api in apis.into_iter() { - match api { - "web3" => { - modules.insert("web3".to_owned(), "1.0".to_owned()); - server.add_delegate(Web3Client::new().to_delegate()); - }, - "net" => { - modules.insert("net".to_owned(), "1.0".to_owned()); - server.add_delegate(NetClient::new(&deps.sync).to_delegate()); - }, - "eth" => { - modules.insert("eth".to_owned(), "1.0".to_owned()); - server.add_delegate(EthClient::new(&deps.client, &deps.sync, &deps.secret_store, &deps.miner, &deps.external_miner).to_delegate()); - server.add_delegate(EthFilterClient::new(&deps.client, &deps.miner).to_delegate()); - }, - "personal" => { - modules.insert("personal".to_owned(), "1.0".to_owned()); - server.add_delegate(PersonalClient::new(&deps.secret_store, &deps.client, &deps.miner).to_delegate()) - }, - "ethcore" => { - modules.insert("ethcore".to_owned(), "1.0".to_owned()); - server.add_delegate(EthcoreClient::new(&deps.miner, deps.logger.clone(), deps.settings.clone()).to_delegate()) - }, - "traces" => { - modules.insert("traces".to_owned(), "1.0".to_owned()); - server.add_delegate(TracesClient::new(&deps.client).to_delegate()) - }, - _ => { - die!("{}: Invalid API name to be enabled.", api); - }, - } - } - server.add_delegate(RpcClient::new(modules).to_delegate()); - server + rpc_apis::setup_rpc(server, deps.apis.clone(), rpc_apis::ApiSet::List(apis)) } #[cfg(not(feature = "rpc"))] pub fn setup_http_rpc_server( - _deps: Dependencies, + _deps: &Dependencies, _url: &SocketAddr, - _cors_domain: Option, + _cors_domain: Vec, _apis: Vec<&str>, ) -> ! { die!("Your Parity version has been compiled without JSON-RPC support.") @@ -137,27 +90,31 @@ pub fn setup_http_rpc_server( #[cfg(feature = "rpc")] pub fn setup_http_rpc_server( - dependencies: &Arc, + dependencies: &Dependencies, url: &SocketAddr, cors_domains: Vec, apis: Vec<&str>, ) -> RpcServer { let server = setup_rpc_server(apis, dependencies); let start_result = server.start_http(url, cors_domains); - let deps = dependencies.clone(); + let ph = dependencies.panic_handler.clone(); match start_result { Err(RpcServerError::IoError(err)) => die_with_io_error("RPC", err), Err(e) => die!("RPC: {:?}", e), Ok(server) => { server.set_panic_handler(move || { - deps.panic_handler.notify_all("Panic in RPC thread.".to_owned()); + ph.notify_all("Panic in RPC thread.".to_owned()); }); server }, } } - -pub fn setup_ipc_rpc_server(dependencies: &Arc, addr: &str, apis: Vec<&str>) -> jsonipc::Server { +#[cfg(not(feature = "rpc"))] +pub fn setup_ipc_rpc_server(_dependencies: &Dependencies, _addr: &str, _apis: Vec<&str>) -> ! { + die!("Your Parity version has been compiled without JSON-RPC support.") +} +#[cfg(feature = "rpc")] +pub fn setup_ipc_rpc_server(dependencies: &Dependencies, addr: &str, apis: Vec<&str>) -> jsonipc::Server { let server = setup_rpc_server(apis, dependencies); match server.start_ipc(addr) { Err(jsonipc::Error::Io(io_error)) => die_with_io_error("RPC", io_error), diff --git a/parity/rpc_apis.rs b/parity/rpc_apis.rs new file mode 100644 index 000000000..143d6b48f --- /dev/null +++ b/parity/rpc_apis.rs @@ -0,0 +1,168 @@ +// 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 . + +use std::collections::BTreeMap; +use std::str::FromStr; +use std::sync::Arc; + +use die::*; +use ethsync::EthSync; +use ethcore::miner::{Miner, ExternalMiner}; +use ethcore::client::Client; +use util::RotatingLogger; +use util::keys::store::AccountService; +use util::network_settings::NetworkSettings; + +#[cfg(feature="rpc")] +pub use ethcore_rpc::ConfirmationsQueue; +#[cfg(not(feature="rpc"))] +#[derive(Default)] +pub struct ConfirmationsQueue; + +#[cfg(feature="rpc")] +use ethcore_rpc::Extendable; + +pub enum Api { + Web3, + Net, + Eth, + Personal, + Ethcore, + Traces, + Rpc, +} + +pub enum ApiError { + UnknownApi(String) +} + +pub enum ApiSet { + SafeContext, + UnsafeContext, + List(Vec), +} + +impl FromStr for Api { + type Err = ApiError; + + fn from_str(s: &str) -> Result { + use self::Api::*; + + match s { + "web3" => Ok(Web3), + "net" => Ok(Net), + "eth" => Ok(Eth), + "personal" => Ok(Personal), + "ethcore" => Ok(Ethcore), + "traces" => Ok(Traces), + "rpc" => Ok(Rpc), + e => Err(ApiError::UnknownApi(e.into())), + } + } +} + +pub struct Dependencies { + pub signer_enabled: bool, + pub signer_queue: Arc, + pub client: Arc, + pub sync: Arc, + pub secret_store: Arc, + pub miner: Arc, + pub external_miner: Arc, + pub logger: Arc, + pub settings: Arc, +} + +fn to_modules(apis: &[Api]) -> BTreeMap { + let mut modules = BTreeMap::new(); + for api in apis { + let (name, version) = match *api { + Api::Web3 => ("web3", "1.0"), + Api::Net => ("net", "1.0"), + Api::Eth => ("eth", "1.0"), + Api::Personal => ("personal", "1.0"), + Api::Ethcore => ("ethcore", "1.0"), + Api::Traces => ("traces", "1.0"), + Api::Rpc => ("rpc", "1.0"), + }; + modules.insert(name.into(), version.into()); + } + modules +} + +pub fn from_str(apis: Vec<&str>) -> Vec { + apis.into_iter() + .map(Api::from_str) + .collect::, ApiError>>() + .unwrap_or_else(|e| match e { + ApiError::UnknownApi(s) => die!("Unknown RPC API specified: {}", s), + }) +} + +fn list_apis(apis: ApiSet, signer_enabled: bool) -> Vec { + match apis { + ApiSet::List(apis) => apis, + ApiSet::UnsafeContext if signer_enabled => { + vec![Api::Web3, Api::Net, Api::Eth, Api::Ethcore, Api::Traces, Api::Rpc] + } + _ => { + vec![Api::Web3, Api::Net, Api::Eth, Api::Personal, Api::Ethcore, Api::Traces, Api::Rpc] + } + } +} + +pub fn setup_rpc(server: T, deps: Arc, apis: ApiSet) -> T { + use ethcore_rpc::v1::*; + + let apis = list_apis(apis, deps.signer_enabled); + for api in &apis { + match *api { + Api::Web3 => { + server.add_delegate(Web3Client::new().to_delegate()); + }, + Api::Net => { + server.add_delegate(NetClient::new(&deps.sync).to_delegate()); + }, + Api::Eth => { + server.add_delegate(EthClient::new(&deps.client, &deps.sync, &deps.secret_store, &deps.miner, &deps.external_miner).to_delegate()); + server.add_delegate(EthFilterClient::new(&deps.client, &deps.miner).to_delegate()); + + if deps.signer_enabled { + server.add_delegate(EthSigningQueueClient::new(&deps.signer_queue).to_delegate()); + } else { + server.add_delegate(EthSigningUnsafeClient::new(&deps.client, &deps.secret_store, &deps.miner).to_delegate()); + } + }, + Api::Personal => { + server.add_delegate(PersonalClient::new(&deps.secret_store, &deps.client, &deps.miner).to_delegate()); + if deps.signer_enabled { + server.add_delegate(SignerClient::new(&deps.secret_store, &deps.client, &deps.miner, &deps.signer_queue).to_delegate()); + } + }, + Api::Ethcore => { + 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()) + }, + Api::Rpc => { + let modules = to_modules(&apis); + server.add_delegate(RpcClient::new(modules).to_delegate()); + } + } + } + server +} diff --git a/parity/signer.rs b/parity/signer.rs index 5e3339bcc..a7de993fb 100644 --- a/parity/signer.rs +++ b/parity/signer.rs @@ -15,12 +15,9 @@ // along with Parity. If not, see . use std::sync::Arc; -use ethcore::client::Client; -use ethsync::EthSync; -use ethminer::{Miner, ExternalMiner}; -use util::keys::store::AccountService; use util::panics::{PanicHandler, ForwardPanic}; use die::*; +use rpc_apis; #[cfg(feature = "ethcore-signer")] use ethcore_signer as signer; @@ -36,11 +33,7 @@ pub struct Configuration { pub struct Dependencies { pub panic_handler: Arc, - pub client: Arc, - pub sync: Arc, - pub secret_store: Arc, - pub miner: Arc, - pub external_miner: Arc, + pub apis: Arc, } pub fn start(conf: Configuration, deps: Dependencies) -> Option { @@ -58,13 +51,8 @@ fn do_start(conf: Configuration, deps: Dependencies) -> SignerServer { }); let start_result = { - use ethcore_rpc::v1::*; - let server = signer::ServerBuilder::new(); - server.add_delegate(Web3Client::new().to_delegate()); - server.add_delegate(NetClient::new(&deps.sync).to_delegate()); - server.add_delegate(EthClient::new(&deps.client, &deps.sync, &deps.secret_store, &deps.miner, &deps.external_miner).to_delegate()); - server.add_delegate(EthFilterClient::new(&deps.client, &deps.miner).to_delegate()); - server.add_delegate(PersonalClient::new(&deps.secret_store, &deps.client, &deps.miner).to_delegate()); + let server = signer::ServerBuilder::new(deps.apis.signer_queue.clone()); + let server = rpc_apis::setup_rpc(server, deps.apis, rpc_apis::ApiSet::SafeContext); server.start(addr) }; diff --git a/rpc/Cargo.toml b/rpc/Cargo.toml index 8db7c0491..a03bc1f6e 100644 --- a/rpc/Cargo.toml +++ b/rpc/Cargo.toml @@ -18,7 +18,6 @@ ethcore-util = { path = "../util" } ethcore = { path = "../ethcore" } ethash = { path = "../ethash" } ethsync = { path = "../sync" } -ethminer = { path = "../miner" } ethjson = { path = "../json" } ethcore-devtools = { path = "../devtools" } rustc-serialize = "0.3" @@ -34,4 +33,4 @@ syntex = "*" [features] default = ["serde_codegen"] nightly = ["serde_macros"] -dev = ["clippy", "ethcore/dev", "ethcore-util/dev", "ethsync/dev", "ethminer/dev"] +dev = ["clippy", "ethcore/dev", "ethcore-util/dev", "ethsync/dev"] diff --git a/rpc/src/lib.rs b/rpc/src/lib.rs index 24d58819c..b46a13197 100644 --- a/rpc/src/lib.rs +++ b/rpc/src/lib.rs @@ -26,10 +26,10 @@ 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; -extern crate ethminer; extern crate transient_hashmap; extern crate json_ipc_server as ipc; @@ -44,12 +44,26 @@ use self::jsonrpc_core::{IoHandler, IoDelegate}; pub use jsonrpc_http_server::{Server, RpcServerError}; pub mod v1; +pub use v1::{SigningQueue, ConfirmationsQueue}; + +/// An object that can be extended with `IoDelegates` +pub trait Extendable { + /// Add `Delegate` to this object. + fn add_delegate(&self, delegate: IoDelegate); +} /// Http server. pub struct RpcServer { handler: Arc, } +impl Extendable for RpcServer { + /// Add io delegate. + fn add_delegate(&self, delegate: IoDelegate) { + self.handler.add_delegate(delegate); + } +} + impl RpcServer { /// Construct new http server object. pub fn new() -> RpcServer { @@ -58,11 +72,6 @@ impl RpcServer { } } - /// Add io delegate. - pub fn add_delegate(&self, delegate: IoDelegate) where D: Send + Sync + 'static { - self.handler.add_delegate(delegate); - } - /// Start http server asynchronously and returns result with `Server` handle on success or an error. pub fn start_http(&self, addr: &SocketAddr, cors_domains: Vec) -> Result { let cors_domains = cors_domains.into_iter() diff --git a/rpc/src/v1/helpers/mod.rs b/rpc/src/v1/helpers/mod.rs index b1a5c05ba..2acf98bf2 100644 --- a/rpc/src/v1/helpers/mod.rs +++ b/rpc/src/v1/helpers/mod.rs @@ -16,6 +16,8 @@ mod poll_manager; mod poll_filter; +mod signing_queue; pub use self::poll_manager::PollManager; pub use self::poll_filter::PollFilter; +pub use self::signing_queue::{ConfirmationsQueue, SigningQueue}; diff --git a/rpc/src/v1/helpers/signing_queue.rs b/rpc/src/v1/helpers/signing_queue.rs new file mode 100644 index 000000000..0ded8998c --- /dev/null +++ b/rpc/src/v1/helpers/signing_queue.rs @@ -0,0 +1,367 @@ +// 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 . + +use std::thread; +use std::time::{Instant, Duration}; +use std::sync::{mpsc, Mutex, RwLock, Arc}; +use std::collections::HashMap; +use v1::types::{TransactionRequest, TransactionConfirmation}; +use util::{U256, H256}; + + +/// Possible events happening in the queue that can be listened to. +#[derive(Debug, PartialEq)] +pub enum QueueEvent { + /// Receiver should stop work upon receiving `Finish` message. + Finish, + /// Informs about new request. + NewRequest(U256), + /// Request rejected. + RequestRejected(U256), + /// Request resolved. + RequestConfirmed(U256), +} + +/// Defines possible errors returned from queue receiving method. +#[derive(Debug, PartialEq)] +pub enum QueueError { + /// Returned when method has been already used (no receiver available). + AlreadyUsed, + /// Returned when receiver encounters an error. + ReceiverError(mpsc::RecvError), +} + +/// Message Receiver type +pub type QueueEventReceiver = mpsc::Receiver; + +/// A queue of transactions awaiting to be confirmed and signed. +pub trait SigningQueue: Send + Sync { + /// Add new request to the queue. + /// Returns a `ConfirmationPromise` that can be used to await for resolution of given request. + fn add_request(&self, transaction: TransactionRequest) -> ConfirmationPromise; + + /// Removes a request from the queue. + /// Notifies possible token holders that transaction was rejected. + fn request_rejected(&self, id: U256) -> Option; + + /// Removes a request from the queue. + /// Notifies possible token holders that transaction was confirmed and given hash was assigned. + fn request_confirmed(&self, id: U256, hash: H256) -> Option; + + /// Returns a request if it is contained in the queue. + fn peek(&self, id: &U256) -> Option; + + /// Return copy of all the requests in the queue. + fn requests(&self) -> Vec; +} + +#[derive(Debug, PartialEq)] +enum ConfirmationResult { + /// The transaction has not yet been confirmed nor rejected. + Waiting, + /// The transaction has been rejected. + Rejected, + /// The transaction has been confirmed. + Confirmed(H256), +} + +/// Time you need to confirm the transaction in UI. +/// This is the amount of time token holder will wait before +/// returning `None`. +/// Unless we have a multi-threaded RPC this will lock +/// any other incoming call! +const QUEUE_TIMEOUT_DURATION_SEC : u64 = 20; + +/// A handle to submitted request. +/// Allows to block and wait for a resolution of that request. +pub struct ConfirmationToken { + result: Arc>, + handle: thread::Thread, + request: TransactionConfirmation, +} + +pub struct ConfirmationPromise { + id: U256, + result: Arc>, +} + +impl ConfirmationToken { + /// Submit solution to all listeners + fn resolve(&self, result: Option) { + let mut res = self.result.lock().unwrap(); + *res = result.map_or(ConfirmationResult::Rejected, |h| ConfirmationResult::Confirmed(h)); + // Notify listener + self.handle.unpark(); + } + + fn as_promise(&self) -> ConfirmationPromise { + ConfirmationPromise { + id: self.request.id, + result: self.result.clone(), + } + } +} + +impl ConfirmationPromise { + /// Blocks current thread and awaits for + /// resolution of the transaction (rejected / confirmed) + /// Returns `None` if transaction was rejected or timeout reached. + /// Returns `Some(hash)` if transaction was confirmed. + pub fn wait_with_timeout(&self) -> Option { + let timeout = Duration::from_secs(QUEUE_TIMEOUT_DURATION_SEC); + let deadline = Instant::now() + timeout; + + info!(target: "own_tx", "Signer: Awaiting transaction confirmation... ({:?}).", self.id); + loop { + let now = Instant::now(); + if now >= deadline { + break; + } + // Park thread (may wake up spuriously) + thread::park_timeout(deadline - now); + // Take confirmation result + let res = self.result.lock().unwrap(); + // Check the result + match *res { + ConfirmationResult::Rejected => return None, + ConfirmationResult::Confirmed(h) => return Some(h), + ConfirmationResult::Waiting => continue, + } + } + // We reached the timeout. Just return `None` + trace!(target: "own_tx", "Signer: Confirmation timeout reached... ({:?}).", self.id); + None + } +} + +/// Queue for all unconfirmed transactions. +pub struct ConfirmationsQueue { + id: Mutex, + queue: RwLock>, + sender: Mutex>, + receiver: Mutex>>, +} + +impl Default for ConfirmationsQueue { + fn default() -> Self { + let (send, recv) = mpsc::channel(); + + ConfirmationsQueue { + id: Mutex::new(U256::from(0)), + queue: RwLock::new(HashMap::new()), + sender: Mutex::new(send), + receiver: Mutex::new(Some(recv)), + } + } +} + +impl ConfirmationsQueue { + /// Blocks the thread and starts listening for notifications regarding all actions in the queue. + /// For each event, `listener` callback will be invoked. + /// This method can be used only once (only single consumer of events can exist). + pub fn start_listening(&self, listener: F) -> Result<(), QueueError> + where F: Fn(QueueEvent) -> () { + let recv = self.receiver.lock().unwrap().take(); + if let None = recv { + return Err(QueueError::AlreadyUsed); + } + let recv = recv.expect("Check for none is done earlier."); + + loop { + let message = try!(recv.recv().map_err(|e| QueueError::ReceiverError(e))); + if let QueueEvent::Finish = message { + return Ok(()); + } + + listener(message); + } + } + + /// Notifies consumer that the communcation is over. + /// No more events will be sent after this function is invoked. + pub fn finish(&self) { + self.notify(QueueEvent::Finish); + } + + /// Notifies receiver about the event happening in this queue. + fn notify(&self, message: QueueEvent) { + // We don't really care about the result + let _ = self.sender.lock().unwrap().send(message); + } + + /// Removes transaction from this queue and notifies `ConfirmationPromise` holders about the result. + /// Notifies also a receiver about that event. + fn remove(&self, id: U256, result: Option) -> Option { + let token = self.queue.write().unwrap().remove(&id); + + if let Some(token) = token { + // notify receiver about the event + self.notify(result.map_or_else( + || QueueEvent::RequestRejected(id), + |_| QueueEvent::RequestConfirmed(id) + )); + // notify token holders about resolution + token.resolve(result); + // return a result + return Some(token.request.clone()); + } + None + } +} + +impl Drop for ConfirmationsQueue { + fn drop(&mut self) { + self.finish(); + } +} + +impl SigningQueue for ConfirmationsQueue { + fn add_request(&self, transaction: TransactionRequest) -> ConfirmationPromise { + // Increment id + let id = { + let mut last_id = self.id.lock().unwrap(); + *last_id = *last_id + U256::from(1); + *last_id + }; + // Add request to queue + let res = { + let mut queue = self.queue.write().unwrap(); + queue.insert(id, ConfirmationToken { + result: Arc::new(Mutex::new(ConfirmationResult::Waiting)), + handle: thread::current(), + request: TransactionConfirmation { + id: id, + transaction: transaction, + }, + }); + debug!(target: "own_tx", "Signer: New transaction ({:?}) in confirmation queue.", id); + queue.get(&id).map(|token| token.as_promise()).expect("Token was just inserted.") + }; + // Notify listeners + self.notify(QueueEvent::NewRequest(id)); + res + + } + + fn peek(&self, id: &U256) -> Option { + self.queue.read().unwrap().get(id).map(|token| token.request.clone()) + } + + fn request_rejected(&self, id: U256) -> Option { + debug!(target: "own_tx", "Signer: Transaction rejected ({:?}).", id); + self.remove(id, None) + } + + fn request_confirmed(&self, id: U256, hash: H256) -> Option { + debug!(target: "own_tx", "Signer: Transaction confirmed ({:?}).", id); + self.remove(id, Some(hash)) + } + + fn requests(&self) -> Vec { + let queue = self.queue.read().unwrap(); + queue.values().map(|token| token.request.clone()).collect() + } +} + + +#[cfg(test)] +mod test { + use std::time::Duration; + use std::thread; + use std::sync::{Arc, Mutex}; + use util::hash::Address; + use util::numbers::{U256, H256}; + use v1::types::TransactionRequest; + use super::*; + + fn request() -> TransactionRequest { + TransactionRequest { + from: Address::from(1), + to: Some(Address::from(2)), + gas_price: None, + gas: None, + value: Some(U256::from(10_000_000)), + data: None, + nonce: None, + } + } + + #[test] + fn should_wait_for_hash() { + // given + let queue = Arc::new(ConfirmationsQueue::default()); + let request = request(); + + // when + let q = queue.clone(); + let handle = thread::spawn(move || { + let v = q.add_request(request); + v.wait_with_timeout().expect("Should return hash") + }); + + let id = U256::from(1); + while queue.peek(&id).is_none() { + // Just wait for the other thread to start + thread::sleep(Duration::from_millis(100)); + } + queue.request_confirmed(id, H256::from(1)); + + // then + assert_eq!(handle.join().expect("Thread should finish nicely"), H256::from(1)); + } + + #[test] + fn should_receive_notification() { + // given + let received = Arc::new(Mutex::new(None)); + let queue = Arc::new(ConfirmationsQueue::default()); + let request = request(); + + // when + let q = queue.clone(); + let r = received.clone(); + let handle = thread::spawn(move || { + q.start_listening(move |notification| { + let mut v = r.lock().unwrap(); + *v = Some(notification); + }).expect("Should be closed nicely.") + }); + queue.add_request(request); + queue.finish(); + + // then + handle.join().expect("Thread should finish nicely"); + let r = received.lock().unwrap().take(); + assert_eq!(r, Some(QueueEvent::NewRequest(U256::from(1)))); + } + + #[test] + fn should_add_transactions() { + // given + let queue = ConfirmationsQueue::default(); + let request = request(); + + // when + queue.add_request(request.clone()); + let all = queue.requests(); + + // then + assert_eq!(all.len(), 1); + let el = all.get(0).unwrap(); + assert_eq!(el.id, U256::from(1)); + assert_eq!(el.transaction, request); + } +} diff --git a/rpc/src/v1/impls/eth.rs b/rpc/src/v1/impls/eth.rs index a57fc333c..b0d6abcf0 100644 --- a/rpc/src/v1/impls/eth.rs +++ b/rpc/src/v1/impls/eth.rs @@ -18,17 +18,16 @@ extern crate ethash; -use std::collections::HashSet; use std::sync::{Arc, Weak, Mutex}; use std::ops::Deref; use ethsync::{SyncProvider, SyncState}; -use ethminer::{MinerService, ExternalMinerService}; +use ethcore::miner::{MinerService, ExternalMinerService}; use jsonrpc_core::*; use util::numbers::*; use util::sha3::*; use util::rlp::{encode, decode, UntrustedRlp, View}; use util::keys::store::AccountProvider; -use ethcore::client::{BlockChainClient, BlockID, TransactionID, UncleID}; +use ethcore::client::{MiningBlockChainClient, BlockID, TransactionID, UncleID}; use ethcore::block::IsBlock; use ethcore::views::*; use ethcore::ethereum::Ethash; @@ -36,15 +35,14 @@ use ethcore::transaction::{Transaction as EthTransaction, SignedTransaction, Act use ethcore::log_entry::LogEntry; use ethcore::filter::Filter as EthcoreFilter; use self::ethash::SeedHashCompute; -use v1::traits::{Eth, EthFilter}; -use v1::types::{Block, BlockTransactions, BlockNumber, Bytes, SyncStatus, SyncInfo, Transaction, TransactionRequest, CallRequest, OptionalValue, Index, Filter, Log, Receipt}; -use v1::helpers::{PollFilter, PollManager}; -use v1::impls::{dispatch_transaction, sign_and_dispatch}; +use v1::traits::Eth; +use v1::types::{Block, BlockTransactions, BlockNumber, Bytes, SyncStatus, SyncInfo, Transaction, CallRequest, OptionalValue, Index, Filter, Log, Receipt}; +use v1::impls::dispatch_transaction; use serde; /// Eth rpc implementation. pub struct EthClient where - C: BlockChainClient, + C: MiningBlockChainClient, S: SyncProvider, A: AccountProvider, M: MinerService, @@ -59,7 +57,7 @@ pub struct EthClient where } impl EthClient where - C: BlockChainClient, + C: MiningBlockChainClient, S: SyncProvider, A: AccountProvider, M: MinerService, @@ -170,6 +168,25 @@ impl EthClient where } } +pub fn pending_logs(miner: &M, filter: &EthcoreFilter) -> Vec where M: MinerService { + let receipts = miner.pending_receipts(); + + let pending_logs = receipts.into_iter() + .flat_map(|(hash, r)| r.logs.into_iter().map(|l| (hash.clone(), l)).collect::>()) + .collect::>(); + + let result = pending_logs.into_iter() + .filter(|pair| filter.matches(&pair.1)) + .map(|pair| { + let mut log = Log::from(pair.1); + log.transaction_hash = Some(pair.0); + log + }) + .collect(); + + result +} + const MAX_QUEUE_SIZE_TO_MINE_ON: usize = 4; // because uncles go back 6. fn params_len(params: &Params) -> usize { @@ -193,25 +210,6 @@ fn from_params_default_third(params: Params) -> Result<(F1, F2, BlockNum } } -fn pending_logs(miner: &M, filter: &EthcoreFilter) -> Vec where M: MinerService { - let receipts = miner.pending_receipts(); - - let pending_logs = receipts.into_iter() - .flat_map(|(hash, r)| r.logs.into_iter().map(|l| (hash.clone(), l)).collect::>()) - .collect::>(); - - let result = pending_logs.into_iter() - .filter(|pair| filter.matches(&pair.1)) - .map(|pair| { - let mut log = Log::from(pair.1); - log.transaction_hash = Some(pair.0); - log - }) - .collect(); - - result -} - // must be in range [-32099, -32000] const UNSUPPORTED_REQUEST_CODE: i64 = -32000; @@ -224,7 +222,7 @@ fn make_unsupported_err() -> Error { } impl Eth for EthClient where - C: BlockChainClient + 'static, + C: MiningBlockChainClient + 'static, S: SyncProvider + 'static, A: AccountProvider + 'static, M: MinerService + 'static, @@ -496,29 +494,12 @@ impl Eth for EthClient where }) } - fn sign(&self, params: Params) -> Result { - from_params::<(Address, H256)>(params).and_then(|(addr, msg)| { - to_value(&take_weak!(self.accounts).sign(&addr, &msg).unwrap_or(H520::zero())) - }) - } - - fn send_transaction(&self, params: Params) -> Result { - from_params::<(TransactionRequest, )>(params) - .and_then(|(request, )| { - let accounts = take_weak!(self.accounts); - match accounts.account_secret(&request.from) { - Ok(secret) => sign_and_dispatch(&self.client, &self.miner, request, secret), - Err(_) => to_value(&H256::zero()) - } - }) - } - fn send_raw_transaction(&self, params: Params) -> Result { from_params::<(Bytes, )>(params) .and_then(|(raw_transaction, )| { let raw_transaction = raw_transaction.to_vec(); match UntrustedRlp::new(&raw_transaction).as_val() { - Ok(signed_transaction) => dispatch_transaction(&*take_weak!(self.client), &*take_weak!(self.miner), signed_transaction), + Ok(signed_transaction) => to_value(&dispatch_transaction(&*take_weak!(self.client), &*take_weak!(self.miner), signed_transaction)), Err(_) => to_value(&H256::zero()), } }) @@ -530,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, Default::default()), + BlockNumber::Latest => take_weak!(self.client).call(&signed, Default::default()), _ => panic!("{:?}", block_number), }; to_value(&r.map(|e| Bytes(e.output)).unwrap_or(Bytes::new(vec![]))) @@ -543,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, Default::default()), + BlockNumber::Latest => take_weak!(self.client).call(&signed, Default::default()), _ => return Err(Error::invalid_params()), }; to_value(&r.map(|res| res.gas_used + res.refunded).unwrap_or(From::from(0))) @@ -563,186 +544,3 @@ impl Eth for EthClient where rpc_unimplemented!() } } - -/// Eth filter rpc implementation. -pub struct EthFilterClient where - C: BlockChainClient, - M: MinerService { - - client: Weak, - miner: Weak, - polls: Mutex>, -} - -impl EthFilterClient where - C: BlockChainClient, - M: MinerService { - - /// Creates new Eth filter client. - pub fn new(client: &Arc, miner: &Arc) -> Self { - EthFilterClient { - client: Arc::downgrade(client), - miner: Arc::downgrade(miner), - polls: Mutex::new(PollManager::new()), - } - } -} - -impl EthFilter for EthFilterClient where - C: BlockChainClient + 'static, - M: MinerService + 'static { - - fn new_filter(&self, params: Params) -> Result { - from_params::<(Filter,)>(params) - .and_then(|(filter,)| { - let mut polls = self.polls.lock().unwrap(); - let block_number = take_weak!(self.client).chain_info().best_block_number; - let id = polls.create_poll(PollFilter::Logs(block_number, Default::default(), filter)); - to_value(&U256::from(id)) - }) - } - - fn new_block_filter(&self, params: Params) -> Result { - match params { - Params::None => { - let mut polls = self.polls.lock().unwrap(); - let id = polls.create_poll(PollFilter::Block(take_weak!(self.client).chain_info().best_block_number)); - to_value(&U256::from(id)) - }, - _ => Err(Error::invalid_params()) - } - } - - fn new_pending_transaction_filter(&self, params: Params) -> Result { - match params { - Params::None => { - let mut polls = self.polls.lock().unwrap(); - let pending_transactions = take_weak!(self.miner).pending_transactions_hashes(); - let id = polls.create_poll(PollFilter::PendingTransaction(pending_transactions)); - - to_value(&U256::from(id)) - }, - _ => Err(Error::invalid_params()) - } - } - - fn filter_changes(&self, params: Params) -> Result { - let client = take_weak!(self.client); - from_params::<(Index,)>(params) - .and_then(|(index,)| { - let mut polls = self.polls.lock().unwrap(); - match polls.poll_mut(&index.value()) { - None => Ok(Value::Array(vec![] as Vec)), - Some(filter) => match *filter { - PollFilter::Block(ref mut block_number) => { - // + 1, cause we want to return hashes including current block hash. - let current_number = client.chain_info().best_block_number + 1; - let hashes = (*block_number..current_number).into_iter() - .map(BlockID::Number) - .filter_map(|id| client.block_hash(id)) - .collect::>(); - - *block_number = current_number; - - to_value(&hashes) - }, - PollFilter::PendingTransaction(ref mut previous_hashes) => { - // get hashes of pending transactions - let current_hashes = take_weak!(self.miner).pending_transactions_hashes(); - - let new_hashes = - { - let previous_hashes_set = previous_hashes.iter().collect::>(); - - // find all new hashes - current_hashes - .iter() - .filter(|hash| !previous_hashes_set.contains(hash)) - .cloned() - .collect::>() - }; - - // save all hashes of pending transactions - *previous_hashes = current_hashes; - - // return new hashes - to_value(&new_hashes) - }, - PollFilter::Logs(ref mut block_number, ref mut previous_logs, ref filter) => { - // retrive the current block number - let current_number = client.chain_info().best_block_number; - - // check if we need to check pending hashes - let include_pending = filter.to_block == Some(BlockNumber::Pending); - - // build appropriate filter - let mut filter: EthcoreFilter = filter.clone().into(); - filter.from_block = BlockID::Number(*block_number); - filter.to_block = BlockID::Latest; - - // retrieve logs in range from_block..min(BlockID::Latest..to_block) - let mut logs = client.logs(filter.clone()) - .into_iter() - .map(From::from) - .collect::>(); - - // additionally retrieve pending logs - if include_pending { - let pending_logs = pending_logs(take_weak!(self.miner).deref(), &filter); - - // remove logs about which client was already notified about - let new_pending_logs: Vec<_> = pending_logs.iter() - .filter(|p| !previous_logs.contains(p)) - .cloned() - .collect(); - - // save all logs retrieved by client - *previous_logs = pending_logs.into_iter().collect(); - - // append logs array with new pending logs - logs.extend(new_pending_logs); - } - - // save current block number as next from block number - *block_number = current_number; - - to_value(&logs) - } - } - } - }) - } - - fn filter_logs(&self, params: Params) -> Result { - from_params::<(Index,)>(params) - .and_then(|(index,)| { - let mut polls = self.polls.lock().unwrap(); - match polls.poll(&index.value()) { - Some(&PollFilter::Logs(ref _block_number, ref _previous_log, ref filter)) => { - let include_pending = filter.to_block == Some(BlockNumber::Pending); - let filter: EthcoreFilter = filter.clone().into(); - let mut logs = take_weak!(self.client).logs(filter.clone()) - .into_iter() - .map(From::from) - .collect::>(); - - if include_pending { - logs.extend(pending_logs(take_weak!(self.miner).deref(), &filter)); - } - - to_value(&logs) - }, - // just empty array - _ => Ok(Value::Array(vec![] as Vec)), - } - }) - } - - fn uninstall_filter(&self, params: Params) -> Result { - from_params::<(Index,)>(params) - .and_then(|(index,)| { - self.polls.lock().unwrap().remove_poll(&index.value()); - to_value(&true) - }) - } -} diff --git a/rpc/src/v1/impls/eth_filter.rs b/rpc/src/v1/impls/eth_filter.rs new file mode 100644 index 000000000..b34a4f703 --- /dev/null +++ b/rpc/src/v1/impls/eth_filter.rs @@ -0,0 +1,215 @@ +// 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 . + +//! Eth Filter RPC implementation + +use std::ops::Deref; +use std::sync::{Arc, Weak, Mutex}; +use std::collections::HashSet; +use jsonrpc_core::*; +use util::numbers::*; +use ethcore::miner::MinerService; +use ethcore::filter::Filter as EthcoreFilter; +use ethcore::client::{BlockChainClient, BlockID}; +use v1::traits::EthFilter; +use v1::types::{BlockNumber, Index, Filter, Log}; +use v1::helpers::{PollFilter, PollManager}; +use v1::impls::eth::pending_logs; + + +/// Eth filter rpc implementation. +pub struct EthFilterClient where + C: BlockChainClient, + M: MinerService { + + client: Weak, + miner: Weak, + polls: Mutex>, +} + +impl EthFilterClient where + C: BlockChainClient, + M: MinerService { + + /// Creates new Eth filter client. + pub fn new(client: &Arc, miner: &Arc) -> Self { + EthFilterClient { + client: Arc::downgrade(client), + miner: Arc::downgrade(miner), + polls: Mutex::new(PollManager::new()), + } + } +} + +impl EthFilter for EthFilterClient where + C: BlockChainClient + 'static, + M: MinerService + 'static { + + fn new_filter(&self, params: Params) -> Result { + from_params::<(Filter,)>(params) + .and_then(|(filter,)| { + let mut polls = self.polls.lock().unwrap(); + let block_number = take_weak!(self.client).chain_info().best_block_number; + let id = polls.create_poll(PollFilter::Logs(block_number, Default::default(), filter)); + to_value(&U256::from(id)) + }) + } + + fn new_block_filter(&self, params: Params) -> Result { + match params { + Params::None => { + let mut polls = self.polls.lock().unwrap(); + let id = polls.create_poll(PollFilter::Block(take_weak!(self.client).chain_info().best_block_number)); + to_value(&U256::from(id)) + }, + _ => Err(Error::invalid_params()) + } + } + + fn new_pending_transaction_filter(&self, params: Params) -> Result { + match params { + Params::None => { + let mut polls = self.polls.lock().unwrap(); + let pending_transactions = take_weak!(self.miner).pending_transactions_hashes(); + let id = polls.create_poll(PollFilter::PendingTransaction(pending_transactions)); + + to_value(&U256::from(id)) + }, + _ => Err(Error::invalid_params()) + } + } + + fn filter_changes(&self, params: Params) -> Result { + let client = take_weak!(self.client); + from_params::<(Index,)>(params) + .and_then(|(index,)| { + let mut polls = self.polls.lock().unwrap(); + match polls.poll_mut(&index.value()) { + None => Ok(Value::Array(vec![] as Vec)), + Some(filter) => match *filter { + PollFilter::Block(ref mut block_number) => { + // + 1, cause we want to return hashes including current block hash. + let current_number = client.chain_info().best_block_number + 1; + let hashes = (*block_number..current_number).into_iter() + .map(BlockID::Number) + .filter_map(|id| client.block_hash(id)) + .collect::>(); + + *block_number = current_number; + + to_value(&hashes) + }, + PollFilter::PendingTransaction(ref mut previous_hashes) => { + // get hashes of pending transactions + let current_hashes = take_weak!(self.miner).pending_transactions_hashes(); + + let new_hashes = + { + let previous_hashes_set = previous_hashes.iter().collect::>(); + + // find all new hashes + current_hashes + .iter() + .filter(|hash| !previous_hashes_set.contains(hash)) + .cloned() + .collect::>() + }; + + // save all hashes of pending transactions + *previous_hashes = current_hashes; + + // return new hashes + to_value(&new_hashes) + }, + PollFilter::Logs(ref mut block_number, ref mut previous_logs, ref filter) => { + // retrive the current block number + let current_number = client.chain_info().best_block_number; + + // check if we need to check pending hashes + let include_pending = filter.to_block == Some(BlockNumber::Pending); + + // build appropriate filter + let mut filter: EthcoreFilter = filter.clone().into(); + filter.from_block = BlockID::Number(*block_number); + filter.to_block = BlockID::Latest; + + // retrieve logs in range from_block..min(BlockID::Latest..to_block) + let mut logs = client.logs(filter.clone()) + .into_iter() + .map(From::from) + .collect::>(); + + // additionally retrieve pending logs + if include_pending { + let pending_logs = pending_logs(take_weak!(self.miner).deref(), &filter); + + // remove logs about which client was already notified about + let new_pending_logs: Vec<_> = pending_logs.iter() + .filter(|p| !previous_logs.contains(p)) + .cloned() + .collect(); + + // save all logs retrieved by client + *previous_logs = pending_logs.into_iter().collect(); + + // append logs array with new pending logs + logs.extend(new_pending_logs); + } + + // save the number of the next block as a first block from which + // we want to get logs + *block_number = current_number + 1; + + to_value(&logs) + } + } + } + }) + } + + fn filter_logs(&self, params: Params) -> Result { + from_params::<(Index,)>(params) + .and_then(|(index,)| { + let mut polls = self.polls.lock().unwrap(); + match polls.poll(&index.value()) { + Some(&PollFilter::Logs(ref _block_number, ref _previous_log, ref filter)) => { + let include_pending = filter.to_block == Some(BlockNumber::Pending); + let filter: EthcoreFilter = filter.clone().into(); + let mut logs = take_weak!(self.client).logs(filter.clone()) + .into_iter() + .map(From::from) + .collect::>(); + + if include_pending { + logs.extend(pending_logs(take_weak!(self.miner).deref(), &filter)); + } + + to_value(&logs) + }, + // just empty array + _ => Ok(Value::Array(vec![] as Vec)), + } + }) + } + + fn uninstall_filter(&self, params: Params) -> Result { + from_params::<(Index,)>(params) + .and_then(|(index,)| { + self.polls.lock().unwrap().remove_poll(&index.value()); + to_value(&true) + }) + } +} diff --git a/rpc/src/v1/impls/eth_signing.rs b/rpc/src/v1/impls/eth_signing.rs new file mode 100644 index 000000000..f8c3c343d --- /dev/null +++ b/rpc/src/v1/impls/eth_signing.rs @@ -0,0 +1,111 @@ +// 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 . + +//! Eth Signing RPC implementation. + +use std::sync::{Arc, Weak}; +use jsonrpc_core::*; +use ethcore::miner::MinerService; +use ethcore::client::MiningBlockChainClient; +use util::numbers::*; +use util::keys::store::AccountProvider; +use v1::helpers::{SigningQueue, ConfirmationsQueue}; +use v1::traits::EthSigning; +use v1::types::TransactionRequest; +use v1::impls::sign_and_dispatch; + + +/// Implementation of functions that require signing when no trusted signer is used. +pub struct EthSigningQueueClient { + queue: Weak, +} + +impl EthSigningQueueClient { + /// Creates a new signing queue client given shared signing queue. + pub fn new(queue: &Arc) -> Self { + EthSigningQueueClient { + queue: Arc::downgrade(queue), + } + } +} + +impl EthSigning for EthSigningQueueClient { + + fn sign(&self, _params: Params) -> Result { + // TODO [ToDr] Implement sign when rest of the signing queue is ready. + rpc_unimplemented!() + } + + fn send_transaction(&self, params: Params) -> Result { + from_params::<(TransactionRequest, )>(params) + .and_then(|(request, )| { + let queue = take_weak!(self.queue); + let id = queue.add_request(request); + let result = id.wait_with_timeout(); + to_value(&result.unwrap_or_else(H256::new)) + }) + } +} + +/// Implementation of functions that require signing when no trusted signer is used. +pub struct EthSigningUnsafeClient where + C: MiningBlockChainClient, + A: AccountProvider, + M: MinerService { + client: Weak, + accounts: Weak, + miner: Weak, +} + +impl EthSigningUnsafeClient where + C: MiningBlockChainClient, + A: AccountProvider, + M: MinerService { + + /// Creates new EthClient. + pub fn new(client: &Arc, accounts: &Arc, miner: &Arc) + -> Self { + EthSigningUnsafeClient { + client: Arc::downgrade(client), + miner: Arc::downgrade(miner), + accounts: Arc::downgrade(accounts), + } + } +} + +impl EthSigning for EthSigningUnsafeClient where + C: MiningBlockChainClient + 'static, + A: AccountProvider + 'static, + M: MinerService + 'static { + + fn sign(&self, params: Params) -> Result { + from_params::<(Address, H256)>(params).and_then(|(addr, msg)| { + to_value(&take_weak!(self.accounts).sign(&addr, &msg).unwrap_or(H520::zero())) + }) + } + + fn send_transaction(&self, params: Params) -> Result { + from_params::<(TransactionRequest, )>(params) + .and_then(|(request, )| { + let accounts = take_weak!(self.accounts); + match accounts.account_secret(&request.from) { + Ok(secret) => to_value(&sign_and_dispatch(&*take_weak!(self.client), &*take_weak!(self.miner), request, secret)), + Err(_) => to_value(&H256::zero()) + } + }) + } + +} diff --git a/rpc/src/v1/impls/ethcore.rs b/rpc/src/v1/impls/ethcore.rs index f5d6f1fda..69d036500 100644 --- a/rpc/src/v1/impls/ethcore.rs +++ b/rpc/src/v1/impls/ethcore.rs @@ -15,37 +15,151 @@ // 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}; use std::ops::Deref; use std::collections::BTreeMap; use jsonrpc_core::*; -use ethminer::MinerService; +use serde; +use ethcore::miner::MinerService; +use ethcore::state_diff::StateDiff; +use ethcore::account_diff::{Diff, Existance}; +use ethcore::transaction::{Transaction as EthTransaction, SignedTransaction, Action}; +use ethcore::client::{BlockChainClient, CallAnalytics}; +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) +} + +fn diff_to_object(d: &Diff) -> Value where T: serde::Serialize + Eq { + let mut ret = BTreeMap::new(); + match *d { + Diff::Same => { + ret.insert("diff".to_owned(), Value::String("=".to_owned())); + } + Diff::Born(ref x) => { + ret.insert("diff".to_owned(), Value::String("+".to_owned())); + ret.insert("+".to_owned(), to_value(x).unwrap()); + } + Diff::Died(ref x) => { + ret.insert("diff".to_owned(), Value::String("-".to_owned())); + ret.insert("-".to_owned(), to_value(x).unwrap()); + } + Diff::Changed(ref from, ref to) => { + ret.insert("diff".to_owned(), Value::String("*".to_owned())); + ret.insert("-".to_owned(), to_value(from).unwrap()); + ret.insert("+".to_owned(), to_value(to).unwrap()); + } + }; + Value::Object(ret) +} + +fn state_diff_to_object(t: &StateDiff) -> Value { + Value::Object(t.iter().map(|(address, account)| { + (address.hex(), Value::Object(map![ + "existance".to_owned() => Value::String(match account.existance() { + Existance::Born => "+", + Existance::Alive => ".", + Existance::Died => "-", + }.to_owned()), + "balance".to_owned() => diff_to_object(&account.balance), + "nonce".to_owned() => diff_to_object(&account.nonce), + "code".to_owned() => diff_to_object(&account.code), + "storage".to_owned() => Value::Object(account.storage.iter().map(|(key, val)| { + (key.hex(), diff_to_object(&val)) + }).collect::>()) + ])) + }).collect::>()) +} + +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 +249,34 @@ 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, CallAnalytics{ vm_tracing: true, state_diffing: false }); + if let Ok(executed) = r { + if let Some(vm_trace) = executed.vm_trace { + return Ok(vm_trace_to_object(&vm_trace)); + } + } + Ok(Value::Null) + }) + } + + fn state_diff_call(&self, params: Params) -> Result { + trace!(target: "jsonrpc", "state_diff_call: {:?}", params); + from_params(params) + .and_then(|(request,)| { + let signed = try!(self.sign_call(request)); + let r = take_weak!(self.client).call(&signed, CallAnalytics{ vm_tracing: false, state_diffing: true }); + if let Ok(executed) = r { + if let Some(state_diff) = executed.state_diff { + return Ok(state_diff_to_object(&state_diff)); + } + } + Ok(Value::Null) + }) + } } diff --git a/rpc/src/v1/impls/mod.rs b/rpc/src/v1/impls/mod.rs index 7ee8b8b8a..9e154a1c5 100644 --- a/rpc/src/v1/impls/mod.rs +++ b/rpc/src/v1/impls/mod.rs @@ -31,32 +31,36 @@ macro_rules! rpc_unimplemented { mod web3; mod eth; +mod eth_filter; +mod eth_signing; mod net; mod personal; +mod personal_signer; mod ethcore; mod traces; mod rpc; pub use self::web3::Web3Client; -pub use self::eth::{EthClient, EthFilterClient}; +pub use self::eth::EthClient; +pub use self::eth_filter::EthFilterClient; +pub use self::eth_signing::{EthSigningUnsafeClient, EthSigningQueueClient}; pub use self::net::NetClient; pub use self::personal::PersonalClient; +pub use self::personal_signer::SignerClient; pub use self::ethcore::EthcoreClient; pub use self::traces::TracesClient; pub use self::rpc::RpcClient; use v1::types::TransactionRequest; -use std::sync::Weak; -use ethminer::{AccountDetails, MinerService}; -use ethcore::client::BlockChainClient; +use ethcore::miner::{AccountDetails, MinerService}; +use ethcore::client::MiningBlockChainClient; use ethcore::transaction::{Action, SignedTransaction, Transaction}; use util::numbers::*; use util::rlp::encode; use util::bytes::ToPretty; -use jsonrpc_core::{Error, to_value, Value}; -fn dispatch_transaction(client: &C, miner: &M, signed_transaction: SignedTransaction) -> Result - where C: BlockChainClient, M: MinerService { +fn dispatch_transaction(client: &C, miner: &M, signed_transaction: SignedTransaction) -> H256 + where C: MiningBlockChainClient, M: MinerService { let hash = signed_transaction.hash(); let import = miner.import_own_transaction(client, signed_transaction, |a: &Address| { @@ -66,13 +70,11 @@ fn dispatch_transaction(client: &C, miner: &M, signed_transaction: SignedT } }); - to_value(&import.map(|_| hash).unwrap_or(H256::zero())) + import.map(|_| hash).unwrap_or(H256::zero()) } -fn sign_and_dispatch(client: &Weak, miner: &Weak, request: TransactionRequest, secret: H256) -> Result - where C: BlockChainClient, M: MinerService { - let client = take_weak!(client); - let miner = take_weak!(miner); +fn sign_and_dispatch(client: &C, miner: &M, request: TransactionRequest, secret: H256) -> H256 + where C: MiningBlockChainClient, M: MinerService { let signed_transaction = { Transaction { @@ -92,4 +94,4 @@ fn sign_and_dispatch(client: &Weak, miner: &Weak, request: Transacti trace!(target: "miner", "send_transaction: dispatching tx: {}", encode(&signed_transaction).to_vec().pretty()); dispatch_transaction(&*client, &*miner, signed_transaction) -} \ No newline at end of file +} diff --git a/rpc/src/v1/impls/personal.rs b/rpc/src/v1/impls/personal.rs index 19e902996..93d13aed7 100644 --- a/rpc/src/v1/impls/personal.rs +++ b/rpc/src/v1/impls/personal.rs @@ -20,21 +20,21 @@ use jsonrpc_core::*; use v1::traits::Personal; use v1::types::TransactionRequest; use v1::impls::sign_and_dispatch; -use util::keys::store::*; +use util::keys::store::AccountProvider; use util::numbers::*; -use ethcore::client::BlockChainClient; -use ethminer::MinerService; +use ethcore::client::MiningBlockChainClient; +use ethcore::miner::MinerService; /// Account management (personal) rpc implementation. pub struct PersonalClient - where A: AccountProvider, C: BlockChainClient, M: MinerService { + where A: AccountProvider, C: MiningBlockChainClient, M: MinerService { accounts: Weak, client: Weak, miner: Weak, } impl PersonalClient - where A: AccountProvider, C: BlockChainClient, M: MinerService { + where A: AccountProvider, C: MiningBlockChainClient, M: MinerService { /// Creates new PersonalClient pub fn new(store: &Arc, client: &Arc, miner: &Arc) -> Self { PersonalClient { @@ -46,7 +46,7 @@ impl PersonalClient } impl Personal for PersonalClient - where A: AccountProvider, C: BlockChainClient, M: MinerService { + where A: AccountProvider, C: MiningBlockChainClient, M: MinerService { fn accounts(&self, _: Params) -> Result { let store = take_weak!(self.accounts); match store.accounts() { @@ -83,7 +83,7 @@ impl Personal for PersonalClient .and_then(|(request, password)| { let accounts = take_weak!(self.accounts); match accounts.locked_account_secret(&request.from, &password) { - Ok(secret) => sign_and_dispatch(&self.client, &self.miner, request, secret), + Ok(secret) => to_value(&sign_and_dispatch(&*take_weak!(self.client), &*take_weak!(self.miner), request, secret)), Err(_) => to_value(&H256::zero()), } }) diff --git a/rpc/src/v1/impls/personal_signer.rs b/rpc/src/v1/impls/personal_signer.rs new file mode 100644 index 000000000..148330ced --- /dev/null +++ b/rpc/src/v1/impls/personal_signer.rs @@ -0,0 +1,101 @@ +// 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 . + +//! Transactions Confirmations (personal) rpc implementation + +use std::sync::{Arc, Weak}; +use jsonrpc_core::*; +use v1::traits::PersonalSigner; +use v1::types::TransactionModification; +use v1::impls::sign_and_dispatch; +use v1::helpers::{SigningQueue, ConfirmationsQueue}; +use util::keys::store::AccountProvider; +use util::numbers::*; +use ethcore::client::MiningBlockChainClient; +use ethcore::miner::MinerService; + +/// Transactions confirmation (personal) rpc implementation. +pub struct SignerClient + where A: AccountProvider, C: MiningBlockChainClient, M: MinerService { + queue: Weak, + accounts: Weak, + client: Weak, + miner: Weak, +} + +impl SignerClient + where A: AccountProvider, C: MiningBlockChainClient, M: MinerService { + + /// Create new instance of signer client. + pub fn new(store: &Arc, client: &Arc, miner: &Arc, queue: &Arc) -> Self { + SignerClient { + queue: Arc::downgrade(queue), + accounts: Arc::downgrade(store), + client: Arc::downgrade(client), + miner: Arc::downgrade(miner), + } + } +} + +impl PersonalSigner for SignerClient + where A: AccountProvider, C: MiningBlockChainClient, M: MinerService { + + fn transactions_to_confirm(&self, _params: Params) -> Result { + let queue = take_weak!(self.queue); + to_value(&queue.requests()) + } + + fn confirm_transaction(&self, params: Params) -> Result { + from_params::<(U256, TransactionModification, String)>(params).and_then( + |(id, modification, pass)| { + let accounts = take_weak!(self.accounts); + let queue = take_weak!(self.queue); + let client = take_weak!(self.client); + let miner = take_weak!(self.miner); + queue.peek(&id).and_then(|confirmation| { + let mut request = confirmation.transaction; + // apply modification + if let Some(gas_price) = modification.gas_price { + request.gas_price = Some(gas_price); + } + match accounts.locked_account_secret(&request.from, &pass) { + Ok(secret) => { + let hash = sign_and_dispatch(&*client, &*miner, request, secret); + queue.request_confirmed(id, hash); + Some(to_value(&hash)) + }, + Err(_) => None + } + }) + .unwrap_or_else(|| { + queue.request_rejected(id); + to_value(&H256::zero()) + }) + } + ) + } + + fn reject_transaction(&self, params: Params) -> Result { + from_params::<(U256, )>(params).and_then( + |(id, )| { + let queue = take_weak!(self.queue); + let res = queue.request_rejected(id); + to_value(&res.is_some()) + } + ) + } +} + diff --git a/rpc/src/v1/mod.rs b/rpc/src/v1/mod.rs index b1ab256c0..54628d892 100644 --- a/rpc/src/v1/mod.rs +++ b/rpc/src/v1/mod.rs @@ -25,5 +25,6 @@ pub mod traits; pub mod tests; pub mod types; -pub use self::traits::{Web3, Eth, EthFilter, Personal, Net, Ethcore, Traces, Rpc}; +pub use self::traits::{Web3, Eth, EthFilter, EthSigning, Personal, PersonalSigner, Net, Ethcore, Traces, Rpc}; pub use self::impls::*; +pub use self::helpers::{SigningQueue, ConfirmationsQueue}; diff --git a/rpc/src/v1/tests/eth.rs b/rpc/src/v1/tests/eth.rs index 30815b8a8..a7f7920ad 100644 --- a/rpc/src/v1/tests/eth.rs +++ b/rpc/src/v1/tests/eth.rs @@ -19,13 +19,13 @@ use std::collections::HashMap; use std::sync::Arc; use std::str::FromStr; +use ethcore::client::{BlockChainClient, Client, ClientConfig}; use ethcore::ids::BlockID; -use ethcore::client::{Client, BlockChainClient, ClientConfig}; use ethcore::spec::{Genesis, Spec}; use ethcore::block::Block; use ethcore::views::BlockView; use ethcore::ethereum; -use ethminer::{Miner, MinerService, ExternalMiner}; +use ethcore::miner::{MinerService, ExternalMiner, Miner}; use devtools::RandomTempPath; use util::Hashable; use util::io::IoChannel; @@ -35,8 +35,8 @@ use util::keys::{AccountProvider, TestAccount, TestAccountProvider}; use jsonrpc_core::IoHandler; use ethjson::blockchain::BlockChain; -use v1::traits::eth::Eth; -use v1::impls::EthClient; +use v1::traits::eth::{Eth, EthSigning}; +use v1::impls::{EthClient, EthSigningUnsafeClient}; use v1::tests::helpers::{TestSyncProvider, Config}; fn account_provider() -> Arc { @@ -68,8 +68,8 @@ fn make_spec(chain: &BlockChain) -> Spec { } struct EthTester { - _miner: Arc, client: Arc, + _miner: Arc, accounts: Arc, handler: IoHandler, } @@ -96,10 +96,10 @@ impl EthTester { where F: Fn() -> Spec { let dir = RandomTempPath::new(); - let client = Client::new(ClientConfig::default(), spec_provider(), dir.as_path(), IoChannel::disconnected()).unwrap(); - let sync_provider = sync_provider(); let account_provider = account_provider(); let miner_service = miner_service(spec_provider(), account_provider.clone()); + let client = Client::new(ClientConfig::default(), spec_provider(), dir.as_path(), miner_service.clone(), IoChannel::disconnected()).unwrap(); + let sync_provider = sync_provider(); let external_miner = Arc::new(ExternalMiner::default()); let eth_client = EthClient::new( @@ -109,10 +109,15 @@ impl EthTester { &miner_service, &external_miner ); + let eth_sign = EthSigningUnsafeClient::new( + &client, + &account_provider, + &miner_service + ); let handler = IoHandler::new(); - let delegate = eth_client.to_delegate(); - handler.add_delegate(delegate); + handler.add_delegate(eth_client.to_delegate()); + handler.add_delegate(eth_sign.to_delegate()); EthTester { _miner: miner_service, @@ -352,4 +357,4 @@ fn verify_transaction_counts(name: String, chain: BlockChain) { register_test!(eth_transaction_count_1, verify_transaction_counts, "BlockchainTests/bcWalletTest"); register_test!(eth_transaction_count_2, verify_transaction_counts, "BlockchainTests/bcTotalDifficultyTest"); -register_test!(eth_transaction_count_3, verify_transaction_counts, "BlockchainTests/bcGasPricerTest"); \ No newline at end of file +register_test!(eth_transaction_count_3, verify_transaction_counts, "BlockchainTests/bcGasPricerTest"); diff --git a/rpc/src/v1/tests/helpers/miner_service.rs b/rpc/src/v1/tests/helpers/miner_service.rs index 600f88508..daa5c9497 100644 --- a/rpc/src/v1/tests/helpers/miner_service.rs +++ b/rpc/src/v1/tests/helpers/miner_service.rs @@ -19,11 +19,11 @@ use util::{Address, H256, Bytes, U256, FixedHash, Uint}; use util::standard::*; use ethcore::error::{Error, ExecutionError}; -use ethcore::client::{BlockChainClient, Executed}; +use ethcore::client::{MiningBlockChainClient, Executed, CallAnalytics}; use ethcore::block::{ClosedBlock, IsBlock}; use ethcore::transaction::SignedTransaction; use ethcore::receipt::Receipt; -use ethminer::{MinerService, MinerStatus, AccountDetails, TransactionImportResult}; +use ethcore::miner::{MinerService, MinerStatus, AccountDetails, TransactionImportResult}; /// Test miner service. pub struct TestMinerService { @@ -132,7 +132,7 @@ impl MinerService for TestMinerService { } /// Imports transactions to transaction queue. - fn import_own_transaction(&self, chain: &BlockChainClient, transaction: SignedTransaction, _fetch_account: T) -> + fn import_own_transaction(&self, chain: &MiningBlockChainClient, transaction: SignedTransaction, _fetch_account: T) -> Result where T: Fn(&Address) -> AccountDetails { @@ -154,21 +154,21 @@ impl MinerService for TestMinerService { } /// Removes all transactions from the queue and restart mining operation. - fn clear_and_reset(&self, _chain: &BlockChainClient) { + fn clear_and_reset(&self, _chain: &MiningBlockChainClient) { unimplemented!(); } /// Called when blocks are imported to chain, updates transactions queue. - fn chain_new_blocks(&self, _chain: &BlockChainClient, _imported: &[H256], _invalid: &[H256], _enacted: &[H256], _retracted: &[H256]) { + fn chain_new_blocks(&self, _chain: &MiningBlockChainClient, _imported: &[H256], _invalid: &[H256], _enacted: &[H256], _retracted: &[H256]) { unimplemented!(); } /// New chain head event. Restart mining operation. - fn update_sealing(&self, _chain: &BlockChainClient) { + fn update_sealing(&self, _chain: &MiningBlockChainClient) { unimplemented!(); } - fn map_sealing_work(&self, _chain: &BlockChainClient, _f: F) -> Option where F: FnOnce(&ClosedBlock) -> T { + fn map_sealing_work(&self, _chain: &MiningBlockChainClient, _f: F) -> Option where F: FnOnce(&ClosedBlock) -> T { unimplemented!(); } @@ -194,29 +194,29 @@ impl MinerService for TestMinerService { /// Submit `seal` as a valid solution for the header of `pow_hash`. /// Will check the seal, but not actually insert the block into the chain. - fn submit_seal(&self, _chain: &BlockChainClient, _pow_hash: H256, _seal: Vec) -> Result<(), Error> { + fn submit_seal(&self, _chain: &MiningBlockChainClient, _pow_hash: H256, _seal: Vec) -> Result<(), Error> { unimplemented!(); } - fn balance(&self, _chain: &BlockChainClient, address: &Address) -> U256 { + fn balance(&self, _chain: &MiningBlockChainClient, address: &Address) -> U256 { self.latest_closed_block.lock().unwrap().as_ref().map_or_else(U256::zero, |b| b.block().fields().state.balance(address).clone()) } - fn call(&self, _chain: &BlockChainClient, _t: &SignedTransaction) -> Result { + fn call(&self, _chain: &MiningBlockChainClient, _t: &SignedTransaction, _analytics: CallAnalytics) -> Result { unimplemented!(); } - fn storage_at(&self, _chain: &BlockChainClient, address: &Address, position: &H256) -> H256 { + fn storage_at(&self, _chain: &MiningBlockChainClient, address: &Address, position: &H256) -> H256 { self.latest_closed_block.lock().unwrap().as_ref().map_or_else(H256::default, |b| b.block().fields().state.storage_at(address, position).clone()) } - fn nonce(&self, _chain: &BlockChainClient, address: &Address) -> U256 { + fn nonce(&self, _chain: &MiningBlockChainClient, address: &Address) -> U256 { // we assume all transactions are in a pending block, ignoring the // reality of gas limits. self.last_nonce(address).unwrap_or(U256::zero()) } - fn code(&self, _chain: &BlockChainClient, address: &Address) -> Option { + fn code(&self, _chain: &MiningBlockChainClient, address: &Address) -> Option { self.latest_closed_block.lock().unwrap().as_ref().map_or(None, |b| b.block().fields().state.code(address).clone()) } diff --git a/rpc/src/v1/tests/mocked/eth.rs b/rpc/src/v1/tests/mocked/eth.rs index c51d6d7da..34708e232 100644 --- a/rpc/src/v1/tests/mocked/eth.rs +++ b/rpc/src/v1/tests/mocked/eth.rs @@ -25,9 +25,9 @@ use ethcore::client::{TestBlockChainClient, EachBlockWith, Executed, Transaction use ethcore::log_entry::{LocalizedLogEntry, LogEntry}; use ethcore::receipt::LocalizedReceipt; use ethcore::transaction::{Transaction, Action}; -use ethminer::{ExternalMiner, MinerService}; +use ethcore::miner::{ExternalMiner, MinerService}; use ethsync::SyncState; -use v1::{Eth, EthClient}; +use v1::{Eth, EthClient, EthSigning, EthSigningUnsafeClient}; use v1::tests::helpers::{TestSyncProvider, Config, TestMinerService}; use rustc_serialize::hex::ToHex; @@ -72,8 +72,11 @@ impl Default for EthTester { let hashrates = Arc::new(RwLock::new(HashMap::new())); let external_miner = Arc::new(ExternalMiner::new(hashrates.clone())); let eth = EthClient::new(&client, &sync, &ap, &miner, &external_miner).to_delegate(); + let sign = EthSigningUnsafeClient::new(&client, &ap, &miner).to_delegate(); let io = IoHandler::new(); io.add_delegate(eth); + io.add_delegate(sign); + EthTester { client: client, sync: sync, @@ -362,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", @@ -427,6 +430,8 @@ fn rpc_eth_call() { contracts_created: vec![], output: vec![0x12, 0x34, 0xff], trace: None, + vm_trace: None, + state_diff: None, }); let request = r#"{ @@ -460,6 +465,8 @@ fn rpc_eth_call_default_block() { contracts_created: vec![], output: vec![0x12, 0x34, 0xff], trace: None, + vm_trace: None, + state_diff: None, }); let request = r#"{ @@ -492,6 +499,8 @@ fn rpc_eth_estimate_gas() { contracts_created: vec![], output: vec![0x12, 0x34, 0xff], trace: None, + vm_trace: None, + state_diff: None, }); let request = r#"{ @@ -525,6 +534,8 @@ fn rpc_eth_estimate_gas_default_block() { contracts_created: vec![], output: vec![0x12, 0x34, 0xff], trace: None, + vm_trace: None, + state_diff: None, }); let request = r#"{ diff --git a/rpc/src/v1/tests/mocked/eth_signing.rs b/rpc/src/v1/tests/mocked/eth_signing.rs new file mode 100644 index 000000000..6eb6e3fd6 --- /dev/null +++ b/rpc/src/v1/tests/mocked/eth_signing.rs @@ -0,0 +1,75 @@ +// 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 . + +use std::sync::Arc; +use jsonrpc_core::IoHandler; +use v1::impls::EthSigningQueueClient; +use v1::traits::EthSigning; +use v1::helpers::{ConfirmationsQueue, SigningQueue}; +use util::keys::TestAccount; + +struct EthSigningTester { + pub queue: Arc, + pub io: IoHandler, +} + +impl Default for EthSigningTester { + fn default() -> Self { + let queue = Arc::new(ConfirmationsQueue::default()); + let io = IoHandler::new(); + io.add_delegate(EthSigningQueueClient::new(&queue).to_delegate()); + + EthSigningTester { + queue: queue, + io: io, + } + } +} + +fn eth_signing() -> EthSigningTester { + EthSigningTester::default() +} + + +#[test] +fn should_add_transaction_to_queue() { + // given + let tester = eth_signing(); + let account = TestAccount::new("123"); + let address = account.address(); + assert_eq!(tester.queue.requests().len(), 0); + + // when + let request = r#"{ + "jsonrpc": "2.0", + "method": "eth_sendTransaction", + "params": [{ + "from": ""#.to_owned() + format!("0x{:?}", address).as_ref() + r#"", + "to": "0xd46e8dd67c5d32be8058bb8eb970870f07244567", + "gas": "0x76c0", + "gasPrice": "0x9184e72a000", + "value": "0x9184e72a" + }], + "id": 1 + }"#; + let response = r#"{"jsonrpc":"2.0","result":"0x0000000000000000000000000000000000000000000000000000000000000000","id":1}"#; + + + // then + assert_eq!(tester.io.handle_request(&request), Some(response.to_owned())); + assert_eq!(tester.queue.requests().len(), 1); + +} diff --git a/rpc/src/v1/tests/mocked/ethcore.rs b/rpc/src/v1/tests/mocked/ethcore.rs index cc822daef..3bed1f0f0 100644 --- a/rpc/src/v1/tests/mocked/ethcore.rs +++ b/rpc/src/v1/tests/mocked/ethcore.rs @@ -18,17 +18,23 @@ use std::sync::Arc; use std::str::FromStr; use jsonrpc_core::IoHandler; use v1::{Ethcore, EthcoreClient}; -use ethminer::MinerService; +use ethcore::client::{TestBlockChainClient}; +use ethcore::miner::MinerService; use v1::tests::helpers::TestMinerService; use util::numbers::*; 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/tests/mocked/mod.rs b/rpc/src/v1/tests/mocked/mod.rs index dc09d998d..986dbfdef 100644 --- a/rpc/src/v1/tests/mocked/mod.rs +++ b/rpc/src/v1/tests/mocked/mod.rs @@ -18,8 +18,10 @@ //! method calls properly. mod eth; +mod eth_signing; mod net; mod web3; mod personal; +mod personal_signer; mod ethcore; mod rpc; diff --git a/rpc/src/v1/tests/mocked/personal.rs b/rpc/src/v1/tests/mocked/personal.rs index 991b13cba..8bc3ab3c8 100644 --- a/rpc/src/v1/tests/mocked/personal.rs +++ b/rpc/src/v1/tests/mocked/personal.rs @@ -176,4 +176,4 @@ fn sign_and_send_transaction() { let response = r#"{"jsonrpc":"2.0","result":""#.to_owned() + format!("0x{:?}", t.hash()).as_ref() + r#"","id":1}"#; assert_eq!(tester.io.handle_request(request.as_ref()), Some(response)); -} \ No newline at end of file +} diff --git a/rpc/src/v1/tests/mocked/personal_signer.rs b/rpc/src/v1/tests/mocked/personal_signer.rs new file mode 100644 index 000000000..cd1f81d9a --- /dev/null +++ b/rpc/src/v1/tests/mocked/personal_signer.rs @@ -0,0 +1,169 @@ +// 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 . + +use std::sync::Arc; +use std::str::FromStr; +use std::collections::HashMap; +use jsonrpc_core::IoHandler; +use util::numbers::*; +use util::keys::{TestAccount, TestAccountProvider}; +use ethcore::client::TestBlockChainClient; +use ethcore::transaction::{Transaction, Action}; +use v1::{SignerClient, PersonalSigner}; +use v1::tests::helpers::TestMinerService; +use v1::helpers::{SigningQueue, ConfirmationsQueue}; +use v1::types::TransactionRequest; + + +struct PersonalSignerTester { + queue: Arc, + accounts: Arc, + io: IoHandler, + miner: Arc, + // these unused fields are necessary to keep the data alive + // as the handler has only weak pointers. + _client: Arc, +} + +fn blockchain_client() -> Arc { + let client = TestBlockChainClient::new(); + Arc::new(client) +} + +fn accounts_provider() -> Arc { + let accounts = HashMap::new(); + let ap = TestAccountProvider::new(accounts); + Arc::new(ap) +} + +fn miner_service() -> Arc { + Arc::new(TestMinerService::default()) +} + +fn signer_tester() -> PersonalSignerTester { + let queue = Arc::new(ConfirmationsQueue::default()); + let accounts = accounts_provider(); + let client = blockchain_client(); + let miner = miner_service(); + + let io = IoHandler::new(); + io.add_delegate(SignerClient::new(&accounts, &client, &miner, &queue).to_delegate()); + + PersonalSignerTester { + queue: queue, + accounts: accounts, + io: io, + miner: miner, + _client: client, + } +} + + +#[test] +fn should_return_list_of_transactions_in_queue() { + // given + let tester = signer_tester(); + tester.queue.add_request(TransactionRequest { + from: Address::from(1), + to: Some(Address::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap()), + gas_price: Some(U256::from(10_000)), + gas: Some(U256::from(10_000_000)), + value: Some(U256::from(1)), + data: None, + nonce: None, + }); + + // when + let request = r#"{"jsonrpc":"2.0","method":"personal_transactionsToConfirm","params":[],"id":1}"#; + let response = r#"{"jsonrpc":"2.0","result":[{"id":"0x01","transaction":{"data":null,"from":"0x0000000000000000000000000000000000000001","gas":"0x989680","gasPrice":"0x2710","nonce":null,"to":"0xd46e8dd67c5d32be8058bb8eb970870f07244567","value":"0x01"}}],"id":1}"#; + + // then + assert_eq!(tester.io.handle_request(&request), Some(response.to_owned())); +} + + +#[test] +fn should_reject_transaction_from_queue_without_dispatching() { + // given + let tester = signer_tester(); + tester.queue.add_request(TransactionRequest { + from: Address::from(1), + to: Some(Address::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap()), + gas_price: Some(U256::from(10_000)), + gas: Some(U256::from(10_000_000)), + value: Some(U256::from(1)), + data: None, + nonce: None, + }); + assert_eq!(tester.queue.requests().len(), 1); + + // when + let request = r#"{"jsonrpc":"2.0","method":"personal_rejectTransaction","params":["0x01"],"id":1}"#; + let response = r#"{"jsonrpc":"2.0","result":true,"id":1}"#; + + // then + assert_eq!(tester.io.handle_request(&request), Some(response.to_owned())); + assert_eq!(tester.queue.requests().len(), 0); + assert_eq!(tester.miner.imported_transactions.lock().unwrap().len(), 0); +} + +#[test] +fn should_confirm_transaction_and_dispatch() { + // given + let tester = signer_tester(); + let account = TestAccount::new("test"); + let address = account.address(); + let secret = account.secret.clone(); + let recipient = Address::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap(); + tester.accounts.accounts + .write() + .unwrap() + .insert(address, account); + tester.queue.add_request(TransactionRequest { + from: address, + to: Some(recipient), + gas_price: Some(U256::from(10_000)), + gas: Some(U256::from(10_000_000)), + value: Some(U256::from(1)), + data: None, + nonce: None, + }); + let t = Transaction { + nonce: U256::zero(), + gas_price: U256::from(0x1000), + gas: U256::from(10_000_000), + action: Action::Call(recipient), + value: U256::from(0x1), + data: vec![] + }.sign(&secret); + + assert_eq!(tester.queue.requests().len(), 1); + + // when + let request = r#"{ + "jsonrpc":"2.0", + "method":"personal_confirmTransaction", + "params":["0x01", {"gasPrice":"0x1000"}, "test"], + "id":1 + }"#; + let response = r#"{"jsonrpc":"2.0","result":""#.to_owned() + format!("0x{:?}", t.hash()).as_ref() + r#"","id":1}"#; + + // then + assert_eq!(tester.io.handle_request(&request), Some(response.to_owned())); + assert_eq!(tester.queue.requests().len(), 0); + assert_eq!(tester.miner.imported_transactions.lock().unwrap().len(), 1); +} + diff --git a/rpc/src/v1/traits/eth.rs b/rpc/src/v1/traits/eth.rs index 1577053f4..c4369ff2a 100644 --- a/rpc/src/v1/traits/eth.rs +++ b/rpc/src/v1/traits/eth.rs @@ -74,12 +74,6 @@ pub trait Eth: Sized + Send + Sync + 'static { /// Returns the code at given address at given time (block number). fn code_at(&self, _: Params) -> Result; - /// Signs the data with given address signature. - fn sign(&self, _: Params) -> Result; - - /// Sends transaction. - fn send_transaction(&self, _: Params) -> Result; - /// Sends signed transaction. fn send_raw_transaction(&self, _: Params) -> Result; @@ -150,8 +144,6 @@ pub trait Eth: Sized + Send + Sync + 'static { delegate.add_method("eth_getUncleCountByBlockHash", Eth::block_uncles_count_by_hash); delegate.add_method("eth_getUncleCountByBlockNumber", Eth::block_uncles_count_by_number); delegate.add_method("eth_getCode", Eth::code_at); - delegate.add_method("eth_sign", Eth::sign); - delegate.add_method("eth_sendTransaction", Eth::send_transaction); delegate.add_method("eth_sendRawTransaction", Eth::send_raw_transaction); delegate.add_method("eth_call", Eth::call); delegate.add_method("eth_estimateGas", Eth::estimate_gas); @@ -208,3 +200,20 @@ pub trait EthFilter: Sized + Send + Sync + 'static { delegate } } + +/// Signing methods implementation relying on unlocked accounts. +pub trait EthSigning: Sized + Send + Sync + 'static { + /// Signs the data with given address signature. + fn sign(&self, _: Params) -> Result; + + /// Sends transaction. + fn send_transaction(&self, _: Params) -> Result; + + /// Should be used to convert object to io delegate. + fn to_delegate(self) -> IoDelegate { + let mut delegate = IoDelegate::new(Arc::new(self)); + delegate.add_method("eth_sign", EthSigning::sign); + delegate.add_method("eth_sendTransaction", EthSigning::send_transaction); + delegate + } +} diff --git a/rpc/src/v1/traits/ethcore.rs b/rpc/src/v1/traits/ethcore.rs index 3646f6c5a..116a4cd05 100644 --- a/rpc/src/v1/traits/ethcore.rs +++ b/rpc/src/v1/traits/ethcore.rs @@ -72,6 +72,11 @@ 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; + + /// Executes the given call and returns the diff for it. + fn state_diff_call(&self, params: Params) -> Result; /// Should be used to convert object to io delegate. fn to_delegate(self) -> IoDelegate { @@ -95,6 +100,9 @@ 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.add_method("ethcore_stateDiffCall", Ethcore::state_diff_call); + delegate } } diff --git a/rpc/src/v1/traits/mod.rs b/rpc/src/v1/traits/mod.rs index 0728fd06a..d994ffc24 100644 --- a/rpc/src/v1/traits/mod.rs +++ b/rpc/src/v1/traits/mod.rs @@ -25,9 +25,11 @@ pub mod traces; pub mod rpc; pub use self::web3::Web3; -pub use self::eth::{Eth, EthFilter}; +pub use self::eth::{Eth, EthFilter, EthSigning}; pub use self::net::Net; -pub use self::personal::Personal; +pub use self::personal::{Personal, PersonalSigner}; pub use self::ethcore::Ethcore; pub use self::traces::Traces; pub use self::rpc::Rpc; + + diff --git a/rpc/src/v1/traits/personal.rs b/rpc/src/v1/traits/personal.rs index d66161c54..a36358766 100644 --- a/rpc/src/v1/traits/personal.rs +++ b/rpc/src/v1/traits/personal.rs @@ -43,3 +43,26 @@ pub trait Personal: Sized + Send + Sync + 'static { delegate } } + +/// Personal extension for transactions confirmations rpc interface. +pub trait PersonalSigner: Sized + Send + Sync + 'static { + + /// Returns a list of transactions to confirm. + fn transactions_to_confirm(&self, _: Params) -> Result; + + /// Confirm and send a specific transaction. + fn confirm_transaction(&self, _: Params) -> Result; + + /// Reject the transaction request. + fn reject_transaction(&self, _: Params) -> Result; + + /// Should be used to convert object to io delegate. + fn to_delegate(self) -> IoDelegate { + let mut delegate = IoDelegate::new(Arc::new(self)); + delegate.add_method("personal_transactionsToConfirm", PersonalSigner::transactions_to_confirm); + delegate.add_method("personal_confirmTransaction", PersonalSigner::confirm_transaction); + delegate.add_method("personal_rejectTransaction", PersonalSigner::reject_transaction); + 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/mod.rs.in b/rpc/src/v1/types/mod.rs.in index 824a061ef..b4e82a28b 100644 --- a/rpc/src/v1/types/mod.rs.in +++ b/rpc/src/v1/types/mod.rs.in @@ -38,7 +38,7 @@ pub use self::log::Log; pub use self::optionals::OptionalValue; pub use self::sync::{SyncStatus, SyncInfo}; pub use self::transaction::Transaction; -pub use self::transaction_request::TransactionRequest; +pub use self::transaction_request::{TransactionRequest, TransactionConfirmation, TransactionModification}; pub use self::call_request::CallRequest; pub use self::receipt::Receipt; pub use self::trace::Trace; 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/rpc/src/v1/types/transaction_request.rs b/rpc/src/v1/types/transaction_request.rs index 1b51e6b12..93d6a479b 100644 --- a/rpc/src/v1/types/transaction_request.rs +++ b/rpc/src/v1/types/transaction_request.rs @@ -21,7 +21,7 @@ use util::numbers::U256; use v1::types::bytes::Bytes; /// Transaction request coming from RPC -#[derive(Debug, Clone, Default, Eq, PartialEq, Hash, Deserialize)] +#[derive(Debug, Clone, Default, Eq, PartialEq, Hash, Serialize, Deserialize)] pub struct TransactionRequest { /// Sender pub from: Address, @@ -40,6 +40,24 @@ pub struct TransactionRequest { pub nonce: Option, } +/// Transaction confirmation waiting in a queue +#[derive(Debug, Clone, Default, Eq, PartialEq, Hash, Serialize)] +pub struct TransactionConfirmation { + /// Id of this confirmation + pub id: U256, + /// TransactionRequest + pub transaction: TransactionRequest, +} + +/// Possible modifications to the confirmed transaction sent by SystemUI +#[derive(Debug, PartialEq, Deserialize)] +pub struct TransactionModification { + /// Modified gas price + #[serde(rename="gasPrice")] + pub gas_price: Option, +} + + #[cfg(test)] mod tests { use std::str::FromStr; @@ -135,5 +153,26 @@ mod tests { nonce: None, }); } + + #[test] + fn should_deserialize_modification() { + // given + let s1 = r#"{ + "gasPrice":"0x0ba43b7400" + }"#; + let s2 = r#"{}"#; + + // when + let res1: TransactionModification = serde_json::from_str(s1).unwrap(); + let res2: TransactionModification = serde_json::from_str(s2).unwrap(); + + // then + assert_eq!(res1, TransactionModification { + gas_price: Some(U256::from_str("0ba43b7400").unwrap()), + }); + assert_eq!(res2, TransactionModification { + gas_price: None, + }); + } } diff --git a/signer/src/lib.rs b/signer/src/lib.rs index c1fe3d308..8391d42b4 100644 --- a/signer/src/lib.rs +++ b/signer/src/lib.rs @@ -30,12 +30,15 @@ //! //! ``` //! extern crate ethcore_signer; +//! extern crate ethcore_rpc; //! +//! use std::sync::Arc; //! use ethcore_signer::ServerBuilder; +//! use ethcore_rpc::ConfirmationsQueue; //! //! fn main() { -//! let builder = ServerBuilder::new(); -//! let _server = builder.start("127.0.0.1:8084".parse().unwrap()).unwrap(); +//! let queue = Arc::new(ConfirmationsQueue::default()); +//! let _server = ServerBuilder::new(queue).start("127.0.0.1:8084".parse().unwrap()); //! } //! ``` @@ -48,9 +51,7 @@ extern crate ethcore_rpc as rpc; extern crate jsonrpc_core; extern crate ws; -mod signing_queue; mod ws_server; - pub use ws_server::*; #[cfg(test)] diff --git a/signer/src/signing_queue.rs b/signer/src/signing_queue.rs deleted file mode 100644 index 611d467c2..000000000 --- a/signer/src/signing_queue.rs +++ /dev/null @@ -1,74 +0,0 @@ -// 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 . - -use std::collections::HashSet; -use rpc::v1::types::TransactionRequest; - -pub trait SigningQueue { - fn add_request(&mut self, transaction: TransactionRequest); - - fn remove_request(&mut self, id: TransactionRequest); - - fn requests(&self) -> &HashSet; -} - -impl SigningQueue for HashSet { - fn add_request(&mut self, transaction: TransactionRequest) { - self.insert(transaction); - } - - fn remove_request(&mut self, id: TransactionRequest) { - self.remove(&id); - } - - fn requests(&self) -> &HashSet { - self - } -} - - -#[cfg(test)] -mod test { - use std::collections::HashSet; - use util::hash::Address; - use util::numbers::U256; - use rpc::v1::types::TransactionRequest; - use super::*; - - #[test] - fn should_work_for_hashset() { - // given - let mut queue = HashSet::new(); - - let request = TransactionRequest { - from: Address::from(1), - to: Some(Address::from(2)), - gas_price: None, - gas: None, - value: Some(U256::from(10_000_000)), - data: None, - nonce: None, - }; - - // when - queue.add_request(request.clone()); - let all = queue.requests(); - - // then - assert_eq!(all.len(), 1); - assert!(all.contains(&request)); - } -} diff --git a/signer/src/ws_server/mod.rs b/signer/src/ws_server/mod.rs index bb10bc5c1..c987d7a87 100644 --- a/signer/src/ws_server/mod.rs +++ b/signer/src/ws_server/mod.rs @@ -25,6 +25,7 @@ use std::sync::Arc; use std::net::SocketAddr; use util::panics::{PanicHandler, OnPanicListener, MayPanic}; use jsonrpc_core::{IoHandler, IoDelegate}; +use rpc::{Extendable, ConfirmationsQueue}; mod session; @@ -48,46 +49,44 @@ impl From for ServerError { /// Builder for `WebSockets` server pub struct ServerBuilder { + queue: Arc, handler: Arc, } -impl Default for ServerBuilder { - fn default() -> Self { - ServerBuilder::new() +impl Extendable for ServerBuilder { + fn add_delegate(&self, delegate: IoDelegate) { + self.handler.add_delegate(delegate); } } impl ServerBuilder { /// Creates new `ServerBuilder` - pub fn new() -> Self { + pub fn new(queue: Arc) -> Self { ServerBuilder { - handler: Arc::new(IoHandler::new()) + queue: queue, + handler: Arc::new(IoHandler::new()), } } - /// Adds rpc delegate - pub fn add_delegate(&self, delegate: IoDelegate) where D: Send + Sync + 'static { - self.handler.add_delegate(delegate); - } - /// Starts a new `WebSocket` server in separate thread. /// Returns a `Server` handle which closes the server when droped. pub fn start(self, addr: SocketAddr) -> Result { - Server::start(addr, self.handler) + Server::start(addr, self.handler, self.queue) } } /// `WebSockets` server implementation. pub struct Server { handle: Option>>, - broadcaster: ws::Sender, + broadcaster_handle: Option>, + queue: Arc, panic_handler: Arc, } impl Server { /// Starts a new `WebSocket` server in separate thread. /// Returns a `Server` handle which closes the server when droped. - pub fn start(addr: SocketAddr, handler: Arc) -> Result { + fn start(addr: SocketAddr, handler: Arc, queue: Arc) -> Result { let config = { let mut config = ws::Settings::default(); config.max_connections = 5; @@ -101,6 +100,7 @@ impl Server { let panic_handler = PanicHandler::new_in_arc(); let ph = panic_handler.clone(); let broadcaster = ws.broadcaster(); + // Spawn a thread with event loop let handle = thread::spawn(move || { ph.catch_panic(move || { @@ -108,10 +108,24 @@ impl Server { }).unwrap() }); + // Spawn a thread for broadcasting + let ph = panic_handler.clone(); + let q = queue.clone(); + let broadcaster_handle = thread::spawn(move || { + ph.catch_panic(move || { + q.start_listening(|_message| { + // TODO [ToDr] Some better structure here for messages. + broadcaster.send("new_message").unwrap(); + }).expect("It's the only place we are running start_listening. It shouldn't fail."); + broadcaster.shutdown().expect("Broadcaster should close gently.") + }).unwrap() + }); + // Return a handle Ok(Server { handle: Some(handle), - broadcaster: broadcaster, + broadcaster_handle: Some(broadcaster_handle), + queue: queue, panic_handler: panic_handler, }) } @@ -125,7 +139,8 @@ impl MayPanic for Server { impl Drop for Server { fn drop(&mut self) { - self.broadcaster.shutdown().expect("WsServer should close nicely."); + self.queue.finish(); + self.broadcaster_handle.take().unwrap().join().unwrap(); self.handle.take().unwrap().join().unwrap(); } } diff --git a/sync/Cargo.toml b/sync/Cargo.toml index 7940aaa2d..1b80e23fc 100644 --- a/sync/Cargo.toml +++ b/sync/Cargo.toml @@ -11,7 +11,6 @@ authors = ["Ethcore , /// Network ID network_id: U256, - /// Miner - miner: Arc, } type RlpResponseResult = Result, PacketDecodeError>; impl ChainSync { /// Create a new instance of syncing strategy. - pub fn new(config: SyncConfig, miner: Arc, chain: &BlockChainClient) -> ChainSync { + pub fn new(config: SyncConfig, chain: &BlockChainClient) -> ChainSync { let chain = chain.chain_info(); let mut sync = ChainSync { state: SyncState::ChainHead, @@ -265,7 +262,6 @@ impl ChainSync { imported_this_round: None, _max_download_ahead_blocks: max(MAX_HEADERS_TO_REQUEST, config.max_download_ahead_blocks), network_id: config.network_id, - miner: miner, }; sync.reset(); sync @@ -898,12 +894,7 @@ impl ChainSync { let tx: SignedTransaction = try!(r.val_at(i)); transactions.push(tx); } - let chain = io.chain(); - let fetch_account = |a: &Address| AccountDetails { - nonce: chain.latest_nonce(a), - balance: chain.latest_balance(a), - }; - let _ = self.miner.import_transactions(transactions, fetch_account); + let _ = io.chain().import_transactions(transactions); Ok(()) } @@ -1226,7 +1217,7 @@ impl ChainSync { return 0; } - let mut transactions = self.miner.all_transactions(); + let mut transactions = io.chain().all_transactions(); if transactions.is_empty() { return 0; } @@ -1276,11 +1267,9 @@ impl ChainSync { self.check_resume(io); } - /// called when block is imported to chain, updates transactions queue and propagates the blocks - pub fn chain_new_blocks(&mut self, io: &mut SyncIo, imported: &[H256], invalid: &[H256], enacted: &[H256], retracted: &[H256]) { + /// called when block is imported to chain, updates transactions queue and propagates the blocks + pub fn chain_new_blocks(&mut self, io: &mut SyncIo, _imported: &[H256], invalid: &[H256], _enacted: &[H256], _retracted: &[H256]) { if io.is_chain_queue_empty() { - // Notify miner - self.miner.chain_new_blocks(io.chain(), imported, invalid, enacted, retracted); // Propagate latests blocks self.propagate_latest_blocks(io); } @@ -1289,10 +1278,6 @@ impl ChainSync { self.restart_on_bad_block(io); } } - - pub fn chain_new_head(&mut self, io: &mut SyncIo) { - self.miner.update_sealing(io.chain()); - } } #[cfg(test)] @@ -1305,8 +1290,7 @@ mod tests { use ethcore::views::BlockView; use ethcore::header::*; use ethcore::client::*; - use ethcore::spec::Spec; - use ethminer::{Miner, MinerService}; + use ethcore::miner::MinerService; fn get_dummy_block(order: u32, parent_hash: H256) -> Bytes { let mut header = Header::new(); @@ -1480,7 +1464,7 @@ mod tests { } fn dummy_sync_with_peer(peer_latest_hash: H256, client: &BlockChainClient) -> ChainSync { - let mut sync = ChainSync::new(SyncConfig::default(), Miner::new(false, Spec::new_test()), client); + let mut sync = ChainSync::new(SyncConfig::default(), client); sync.peers.insert(0, PeerInfo { protocol_version: 0, @@ -1711,9 +1695,10 @@ mod tests { { let mut queue = VecDeque::new(); let mut io = TestIo::new(&mut client, &mut queue, None); + io.chain.miner.chain_new_blocks(io.chain, &[], &[], &[], &good_blocks); sync.chain_new_blocks(&mut io, &[], &[], &[], &good_blocks); - assert_eq!(sync.miner.status().transactions_in_future_queue, 0); - assert_eq!(sync.miner.status().transactions_in_pending_queue, 1); + assert_eq!(io.chain.miner.status().transactions_in_future_queue, 0); + assert_eq!(io.chain.miner.status().transactions_in_pending_queue, 1); } // We need to update nonce status (because we say that the block has been imported) for h in &[good_blocks[0]] { @@ -1724,11 +1709,12 @@ mod tests { { let mut queue = VecDeque::new(); let mut io = TestIo::new(&mut client, &mut queue, None); + io.chain.miner.chain_new_blocks(io.chain, &[], &[], &good_blocks, &retracted_blocks); sync.chain_new_blocks(&mut io, &[], &[], &good_blocks, &retracted_blocks); } // then - let status = sync.miner.status(); + let status = client.miner.status(); assert_eq!(status.transactions_in_pending_queue, 1); assert_eq!(status.transactions_in_future_queue, 0); } @@ -1750,12 +1736,12 @@ mod tests { // when sync.chain_new_blocks(&mut io, &[], &[], &[], &good_blocks); - assert_eq!(sync.miner.status().transactions_in_future_queue, 0); - assert_eq!(sync.miner.status().transactions_in_pending_queue, 0); + assert_eq!(io.chain.miner.status().transactions_in_future_queue, 0); + assert_eq!(io.chain.miner.status().transactions_in_pending_queue, 0); sync.chain_new_blocks(&mut io, &[], &[], &good_blocks, &retracted_blocks); // then - let status = sync.miner.status(); + let status = io.chain.miner.status(); assert_eq!(status.transactions_in_pending_queue, 0); assert_eq!(status.transactions_in_future_queue, 0); } diff --git a/sync/src/io.rs b/sync/src/io.rs index 84697a021..53a546e1c 100644 --- a/sync/src/io.rs +++ b/sync/src/io.rs @@ -14,10 +14,10 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -use ethcore::client::BlockChainClient; use util::{NetworkContext, PeerId, PacketId,}; use util::error::UtilError; use ethcore::service::SyncMessage; +use ethcore::client::BlockChainClient; /// IO interface for the syning handler. /// Provides peer connection management and an interface to the blockchain client. diff --git a/sync/src/lib.rs b/sync/src/lib.rs index c8dc93db6..9f69bb7da 100644 --- a/sync/src/lib.rs +++ b/sync/src/lib.rs @@ -32,21 +32,20 @@ //! extern crate ethcore_util as util; //! extern crate ethcore; //! extern crate ethsync; -//! extern crate ethminer; //! use std::env; //! use std::sync::Arc; //! use util::network::{NetworkService, NetworkConfiguration}; //! use ethcore::client::{Client, ClientConfig}; //! use ethsync::{EthSync, SyncConfig}; -//! use ethminer::Miner; //! use ethcore::ethereum; +//! use ethcore::miner::Miner; //! //! fn main() { //! let mut service = NetworkService::start(NetworkConfiguration::new()).unwrap(); //! let dir = env::temp_dir(); -//! let client = Client::new(ClientConfig::default(), ethereum::new_frontier(), &dir, service.io().channel()).unwrap(); +//! let client = Client::new(ClientConfig::default(), ethereum::new_frontier(), &dir, Arc::new(Miner::default()), service.io().channel()).unwrap(); //! let miner = Miner::new(false, ethereum::new_frontier()); -//! EthSync::register(&mut service, SyncConfig::default(), client, miner); +//! EthSync::register(&mut service, SyncConfig::default(), client); //! } //! ``` @@ -55,7 +54,6 @@ extern crate log; #[macro_use] extern crate ethcore_util as util; extern crate ethcore; -extern crate ethminer; extern crate env_logger; extern crate time; extern crate rand; @@ -69,7 +67,6 @@ use util::TimerToken; use util::{U256, ONE_U256}; use ethcore::client::Client; use ethcore::service::SyncMessage; -use ethminer::Miner; use io::NetSyncIo; use chain::ChainSync; @@ -115,8 +112,8 @@ pub use self::chain::{SyncStatus, SyncState}; impl EthSync { /// Creates and register protocol with the network service - pub fn register(service: &mut NetworkService, config: SyncConfig, chain: Arc, miner: Arc) -> Arc { - let sync = ChainSync::new(config, miner, chain.deref()); + pub fn register(service: &mut NetworkService, config: SyncConfig, chain: Arc) -> Arc { + let sync = ChainSync::new(config, chain.deref()); let sync = Arc::new(EthSync { chain: chain, sync: RwLock::new(sync), @@ -171,10 +168,6 @@ impl NetworkProtocolHandler for EthSync { let mut sync_io = NetSyncIo::new(io, self.chain.deref()); self.sync.write().unwrap().chain_new_blocks(&mut sync_io, imported, invalid, enacted, retracted); }, - SyncMessage::NewChainHead => { - let mut sync_io = NetSyncIo::new(io, self.chain.deref()); - self.sync.write().unwrap().chain_new_head(&mut sync_io); - }, _ => {/* Ignore other messages */}, } } diff --git a/sync/src/tests/helpers.rs b/sync/src/tests/helpers.rs index d1ffde0f0..a9163b52e 100644 --- a/sync/src/tests/helpers.rs +++ b/sync/src/tests/helpers.rs @@ -16,10 +16,8 @@ use util::*; use ethcore::client::{TestBlockChainClient, BlockChainClient}; -use ethcore::spec::Spec; use io::SyncIo; use chain::ChainSync; -use ethminer::Miner; use ::SyncConfig; pub struct TestIo<'p> { @@ -93,7 +91,7 @@ impl TestNet { }; for _ in 0..n { let chain = TestBlockChainClient::new(); - let sync = ChainSync::new(SyncConfig::default(), Miner::new(false, Spec::new_test()), &chain); + let sync = ChainSync::new(SyncConfig::default(), &chain); net.peers.push(TestPeer { sync: sync, chain: chain, diff --git a/test.sh b/test.sh index e70718afc..d89740aba 100755 --- a/test.sh +++ b/test.sh @@ -10,5 +10,4 @@ cargo test --features ethcore/json-tests $1 \ -p ethcore-signer \ -p ethcore-dapps \ -p parity \ - -p ethminer \ -p bigint 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/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/misc.rs b/util/src/misc.rs index 159381603..8d22e04d7 100644 --- a/util/src/misc.rs +++ b/util/src/misc.rs @@ -24,33 +24,6 @@ use target_info::Target; include!(concat!(env!("OUT_DIR"), "/version.rs")); include!(concat!(env!("OUT_DIR"), "/rustc_version.rs")); -#[derive(Debug,Clone,PartialEq,Eq)] -/// Diff type for specifying a change (or not). -pub enum Diff where T: Eq { - /// Both sides are the same. - Same, - /// Left (pre, source) side doesn't include value, right side (post, destination) does. - Born(T), - /// Both sides include data; it chaged value between them. - Changed(T, T), - /// Left (pre, source) side does include value, right side (post, destination) does not. - Died(T), -} - -impl Diff where T: Eq { - /// Construct new object with given `pre` and `post`. - pub fn new(pre: T, post: T) -> Self { if pre == post { Diff::Same } else { Diff::Changed(pre, post) } } - - /// Get the before value, if there is one. - pub fn pre(&self) -> Option<&T> { match *self { Diff::Died(ref x) | Diff::Changed(ref x, _) => Some(x), _ => None } } - - /// Get the after value, if there is one. - pub fn post(&self) -> Option<&T> { match *self { Diff::Born(ref x) | Diff::Changed(_, ref x) => Some(x), _ => None } } - - /// Determine whether there was a change or not. - pub fn is_same(&self) -> bool { match *self { Diff::Same => true, _ => false }} -} - #[derive(PartialEq,Eq,Clone,Copy)] /// Boolean type for clean/dirty status. pub enum Filth { 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(()) } } 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) }