diff --git a/Cargo.lock b/Cargo.lock index 61b78d52b..36271322d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -253,6 +253,15 @@ dependencies = [ "unicode-normalization 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "coco" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "either 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "scopeguard 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "common-types" version = "0.1.0" @@ -343,14 +352,6 @@ dependencies = [ "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "deque" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "difference" version = "1.0.0" @@ -476,6 +477,7 @@ dependencies = [ "num 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)", "num_cpus 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", + "rayon 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", "rlp 0.2.0", "rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-hex 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -667,7 +669,7 @@ dependencies = [ "ethcrypto 0.1.0", "ethkey 0.2.0", "futures 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-cpupool 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-cpupool 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "hyper 0.10.5 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", "native-contracts 0.1.0", @@ -900,7 +902,7 @@ name = "fetch" version = "0.1.0" dependencies = [ "futures 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-cpupool 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-cpupool 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", "mime 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "parking_lot 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -933,10 +935,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "futures-cpupool" -version = "0.1.2" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "crossbeam 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", "num_cpus 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -946,7 +947,7 @@ name = "gcc" version = "0.3.51" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "rayon 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rayon 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1541,6 +1542,21 @@ dependencies = [ "ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "node-health" +version = "0.1.0" +dependencies = [ + "futures 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-cpupool 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", + "ntp 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "parity-reactor 0.1.0", + "parking_lot 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "nodrop" version = "0.1.9" @@ -1727,10 +1743,11 @@ dependencies = [ "ethsync 1.7.0", "fdlimit 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-cpupool 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-cpupool 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "isatty 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "jsonrpc-core 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)", "log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", + "node-health 0.1.0", "num_cpus 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "number_prefix 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", "parity-dapps 1.7.0", @@ -1770,14 +1787,13 @@ dependencies = [ "ethcore-util 1.7.2", "fetch 0.1.0", "futures 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-cpupool 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "jsonrpc-core 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)", "jsonrpc-http-server 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)", "linked-hash-map 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", "mime 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "mime_guess 1.6.1 (registry+https://github.com/rust-lang/crates.io-index)", - "ntp 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "node-health 0.1.0", "parity-dapps-glue 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)", "parity-hash-fetch 1.7.0", "parity-reactor 0.1.0", @@ -1882,7 +1898,7 @@ dependencies = [ "evm 0.1.0", "fetch 0.1.0", "futures 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-cpupool 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-cpupool 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "jsonrpc-core 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)", "jsonrpc-http-server 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)", "jsonrpc-ipc-server 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)", @@ -1892,6 +1908,7 @@ dependencies = [ "jsonrpc-ws-server 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)", "log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", "multihash 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "node-health 0.1.0", "order-stat 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", "parity-reactor 0.1.0", "parity-updater 1.7.0", @@ -2188,18 +2205,27 @@ dependencies = [ [[package]] name = "rayon" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "rayon-core 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rayon-core 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rayon" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rayon-core 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "rayon-core" -version = "1.0.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "deque 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "coco 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", "num_cpus 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2251,7 +2277,7 @@ dependencies = [ "gcc 0.3.51 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", - "rayon 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rayon 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", "untrusted 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -2393,6 +2419,11 @@ name = "scoped-tls" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "scopeguard" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "secur32-sys" version = "0.2.0" @@ -3094,6 +3125,7 @@ dependencies = [ "checksum clap 2.24.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6b8f69e518f967224e628896b54e41ff6acfb4dcfefc5076325c36525dac900f" "checksum clippy 0.0.103 (registry+https://github.com/rust-lang/crates.io-index)" = "5b4fabf979ddf6419a313c1c0ada4a5b95cfd2049c56e8418d622d27b4b6ff32" "checksum clippy_lints 0.0.103 (registry+https://github.com/rust-lang/crates.io-index)" = "ce96ec05bfe018a0d5d43da115e54850ea2217981ff0f2e462780ab9d594651a" +"checksum coco 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c06169f5beb7e31c7c67ebf5540b8b472d23e3eade3b2ec7d1f5b504a85f91bd" "checksum conv 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "78ff10625fd0ac447827aa30ea8b861fead473bb60aeb73af6c1c58caf0d1299" "checksum cookie 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d53b80dde876f47f03cda35303e368a79b91c70b0d65ecba5fd5280944a08591" "checksum core-foundation 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "20a6d0448d3a99d977ae4a2aa5a98d886a923e863e81ad9ff814645b6feb3bbd" @@ -3104,7 +3136,6 @@ dependencies = [ "checksum custom_derive 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "ef8ae57c4978a2acd8b869ce6b9ca1dfe817bff704c220209fdef2c0b75a01b9" "checksum daemonize 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "271ec51b7e0bee92f0d04601422c73eb76ececf197026711c97ad25038a010cf" "checksum dbghelp-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "97590ba53bcb8ac28279161ca943a924d1fd4a8fb3fa63302591647c4fc5b850" -"checksum deque 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1614659040e711785ed8ea24219140654da1729f3ec8a47a9719d041112fe7bf" "checksum difference 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b3304d19798a8e067e48d8e69b2c37f0b5e9b4e462504ad9e27e9f3fce02bba8" "checksum docopt 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3b5b93718f8b3e5544fcc914c43de828ca6c6ace23e0332c6080a2977b49787a" "checksum dtoa 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5edd69c67b2f8e0911629b7e6b8a34cb3956613cd7c6e6414966dee349c2db4f" @@ -3119,7 +3150,7 @@ dependencies = [ "checksum fnv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6cc484842f1e2884faf56f529f960cc12ad8c71ce96cc7abba0a067c98fee344" "checksum foreign-types 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3e4056b9bd47f8ac5ba12be771f77a0dae796d1bbaaf5fd0b9c2d38b69b8a29d" "checksum futures 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8e51e7f9c150ba7fd4cee9df8bf6ea3dea5b63b68955ddad19ccd35b71dcfb4d" -"checksum futures-cpupool 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bb982bb25cd8fa5da6a8eb3a460354c984ff1113da82bcb4f0b0862b5795db82" +"checksum futures-cpupool 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "a283c84501e92cade5ea673a2a7ca44f71f209ccdd302a3e0896f50083d2c5ff" "checksum gcc 0.3.51 (registry+https://github.com/rust-lang/crates.io-index)" = "120d07f202dcc3f72859422563522b66fe6463a4c513df062874daad05f85f0a" "checksum gdi32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0912515a8ff24ba900422ecda800b52f4016a56251922d397c576bf92c690518" "checksum getopts 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "d9047cfbd08a437050b363d35ef160452c5fe8ea5187ae0a624708c91581d685" @@ -3225,8 +3256,9 @@ dependencies = [ "checksum quine-mc_cluskey 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a6683b0e23d80813b1a535841f0048c1537d3f86d63c999e8373b39a9b0eb74a" "checksum quote 0.3.10 (registry+https://github.com/rust-lang/crates.io-index)" = "6732e32663c9c271bfc7c1823486b471f18c47a2dbf87c066897b7b51afc83be" "checksum rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)" = "2791d88c6defac799c3f20d74f094ca33b9332612d9aef9078519c82e4fe04a5" -"checksum rayon 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8c83adcb08e5b922e804fe1918142b422602ef11f2fd670b0b52218cb5984a20" -"checksum rayon-core 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "767d91bacddf07d442fe39257bf04fd95897d1c47c545d009f6beb03efd038f8" +"checksum rayon 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a77c51c07654ddd93f6cb543c7a849863b03abc7e82591afda6dc8ad4ac3ac4a" +"checksum rayon 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b614fe08b6665cb9a231d07ac1364b0ef3cb3698f1239ee0c4c3a88a524f54c8" +"checksum rayon-core 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7febc28567082c345f10cddc3612c6ea020fc3297a1977d472cf9fdb73e6e493" "checksum regex 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4278c17d0f6d62dfef0ab00028feb45bd7d2102843f80763474eeb1be8a10c01" "checksum regex-syntax 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "841591b1e05609a643e3b4d0045fce04f701daba7151ddcd3ad47b080693d5a9" "checksum regex-syntax 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9191b1f57603095f105d317e375d19b1c9c5c3185ea9633a99a6dcbed04457" @@ -3245,6 +3277,7 @@ dependencies = [ "checksum rustc_version 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1e114e275f7c9b5d50bb52b28f9aac1921209f02aa6077c8b255e21eefaf8ffa" "checksum schannel 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "4e45ac5e9e4698c1c138d2972bedcd90b81fe1efeba805449d2bdd54512de5f9" "checksum scoped-tls 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f417c22df063e9450888a7561788e9bd46d3bb3c1466435b4eccb903807f147d" +"checksum scopeguard 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c79eb2c3ac4bc2507cda80e7f3ac5b88bd8eae4c0914d5663e6a8933994be918" "checksum secur32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3f412dfa83308d893101dd59c10d6fda8283465976c28c287c5c855bf8d216bc" "checksum security-framework 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "42ddf098d78d0b64564b23ee6345d07573e7d10e52ad86875d89ddf5f8378a02" "checksum security-framework-sys 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "5bacdada57ea62022500c457c8571c17dfb5e6240b7c8eac5916ffa8c7138a55" diff --git a/Cargo.toml b/Cargo.toml index 1d0db07c1..984e0649d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,6 +42,7 @@ ethcore-light = { path = "ethcore/light" } ethcore-logger = { path = "logger" } ethcore-stratum = { path = "stratum" } ethkey = { path = "ethkey" } +node-health = { path = "dapps/node-health" } rlp = { path = "util/rlp" } rpc-cli = { path = "rpc_cli" } parity-hash-fetch = { path = "hash-fetch" } @@ -106,4 +107,4 @@ lto = false panic = "abort" [workspace] -members = ["ethstore/cli", "ethkey/cli", "evmbin"] +members = ["ethstore/cli", "ethkey/cli", "evmbin", "dapps/node-health"] diff --git a/dapps/Cargo.toml b/dapps/Cargo.toml index a0d63b6e3..069d4069a 100644 --- a/dapps/Cargo.toml +++ b/dapps/Cargo.toml @@ -9,15 +9,12 @@ authors = ["Parity Technologies "] [dependencies] base32 = "0.3" -env_logger = "0.4" futures = "0.1" -futures-cpupool = "0.1" linked-hash-map = "0.3" log = "0.3" parity-dapps-glue = "1.7" mime = "0.2" mime_guess = "1.6.1" -ntp = "0.2.0" rand = "0.3" rustc-hex = "1.0" serde = "1.0" @@ -31,15 +28,19 @@ zip = { version = "0.1", default-features = false } jsonrpc-core = { git = "https://github.com/paritytech/jsonrpc.git", branch = "parity-1.7" } jsonrpc-http-server = { git = "https://github.com/paritytech/jsonrpc.git", branch = "parity-1.7" } -ethcore-devtools = { path = "../devtools" } ethcore-util = { path = "../util" } fetch = { path = "../util/fetch" } +node-health = { path = "./node-health" } parity-hash-fetch = { path = "../hash-fetch" } parity-reactor = { path = "../util/reactor" } parity-ui = { path = "./ui" } clippy = { version = "0.0.103", optional = true} +[dev-dependencies] +env_logger = "0.4" +ethcore-devtools = { path = "../devtools" } + [features] dev = ["clippy", "ethcore-util/dev"] diff --git a/dapps/node-health/Cargo.toml b/dapps/node-health/Cargo.toml new file mode 100644 index 000000000..ff13e8593 --- /dev/null +++ b/dapps/node-health/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "node-health" +description = "Node's health status" +version = "0.1.0" +license = "GPL-3.0" +authors = ["Parity Technologies "] + +[dependencies] +futures = "0.1" +futures-cpupool = "0.1" +log = "0.3" +ntp = "0.2.0" +parking_lot = "0.4" +serde = "1.0" +serde_derive = "1.0" +time = "0.1.35" + +parity-reactor = { path = "../../util/reactor" } diff --git a/dapps/node-health/src/health.rs b/dapps/node-health/src/health.rs new file mode 100644 index 000000000..3b3563d6b --- /dev/null +++ b/dapps/node-health/src/health.rs @@ -0,0 +1,122 @@ +// Copyright 2015-2017 Parity Technologies (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 . + +//! Reporting node's health. + +use std::sync::Arc; +use std::time; +use futures::{Future, BoxFuture}; +use futures::sync::oneshot; +use types::{HealthInfo, HealthStatus, Health}; +use time::{TimeChecker, MAX_DRIFT}; +use parity_reactor::Remote; +use parking_lot::Mutex; +use {SyncStatus}; + +const TIMEOUT_SECS: u64 = 5; +const PROOF: &str = "Only one closure is invoked."; + +/// A struct enabling you to query for node's health. +#[derive(Debug, Clone)] +pub struct NodeHealth { + sync_status: Arc, + time: TimeChecker, + remote: Remote, +} + +impl NodeHealth { + /// Creates new `NodeHealth`. + pub fn new(sync_status: Arc, time: TimeChecker, remote: Remote) -> Self { + NodeHealth { sync_status, time, remote, } + } + + /// Query latest health report. + pub fn health(&self) -> BoxFuture { + trace!(target: "dapps", "Checking node health."); + // Check timediff + let sync_status = self.sync_status.clone(); + let time = self.time.time_drift(); + let (tx, rx) = oneshot::channel(); + let tx = Arc::new(Mutex::new(Some(tx))); + let tx2 = tx.clone(); + self.remote.spawn_with_timeout( + move || time.then(move |result| { + let _ = tx.lock().take().expect(PROOF).send(Ok(result)); + Ok(()) + }), + time::Duration::from_secs(TIMEOUT_SECS), + move || { + let _ = tx2.lock().take().expect(PROOF).send(Err(())); + }, + ); + + rx.map_err(|err| { + warn!(target: "dapps", "Health request cancelled: {:?}", err); + }).and_then(move |time| { + // Check peers + let peers = { + let (connected, max) = sync_status.peers(); + let (status, message) = match connected { + 0 => { + (HealthStatus::Bad, "You are not connected to any peers. There is most likely some network issue. Fix connectivity.".into()) + }, + 1 => (HealthStatus::NeedsAttention, "You are connected to only one peer. Your node might not be reliable. Check your network connection.".into()), + _ => (HealthStatus::Ok, "".into()), + }; + HealthInfo { status, message, details: (connected, max) } + }; + + // Check sync + let sync = { + let is_syncing = sync_status.is_major_importing(); + let (status, message) = if is_syncing { + (HealthStatus::NeedsAttention, "Your node is still syncing, the values you see might be outdated. Wait until it's fully synced.".into()) + } else { + (HealthStatus::Ok, "".into()) + }; + HealthInfo { status, message, details: is_syncing } + }; + + // Check time + let time = { + let (status, message, details) = match time { + Ok(Ok(diff)) if diff < MAX_DRIFT && diff > -MAX_DRIFT => { + (HealthStatus::Ok, "".into(), diff) + }, + Ok(Ok(diff)) => { + (HealthStatus::Bad, format!( + "Your clock is not in sync. Detected difference is too big for the protocol to work: {}ms. Synchronize your clock.", + diff, + ), diff) + }, + Ok(Err(err)) => { + (HealthStatus::NeedsAttention, format!( + "Unable to reach time API: {}. Make sure that your clock is synchronized.", + err, + ), 0) + }, + Err(_) => { + (HealthStatus::NeedsAttention, "Time API request timed out. Make sure that the clock is synchronized.".into(), 0) + }, + }; + + HealthInfo { status, message, details, } + }; + + Ok(Health { peers, sync, time}) + }).boxed() + } +} diff --git a/dapps/node-health/src/lib.rs b/dapps/node-health/src/lib.rs new file mode 100644 index 000000000..b0eb133ee --- /dev/null +++ b/dapps/node-health/src/lib.rs @@ -0,0 +1,49 @@ +// Copyright 2015-2017 Parity Technologies (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 . + +//! Node Health status reporting. + +#![warn(missing_docs)] + +extern crate futures; +extern crate futures_cpupool; +extern crate ntp; +extern crate time as time_crate; +extern crate parity_reactor; +extern crate parking_lot; + +#[macro_use] +extern crate log; +#[macro_use] +extern crate serde_derive; + +mod health; +mod time; +mod types; + +pub use futures_cpupool::CpuPool; +pub use health::NodeHealth; +pub use types::{Health, HealthInfo, HealthStatus}; +pub use time::{TimeChecker, Error}; + +/// Indicates sync status +pub trait SyncStatus: ::std::fmt::Debug + Send + Sync { + /// Returns true if there is a major sync happening. + fn is_major_importing(&self) -> bool; + + /// Returns number of connected and ideal peers. + fn peers(&self) -> (usize, usize); +} diff --git a/dapps/src/api/time.rs b/dapps/node-health/src/time.rs similarity index 98% rename from dapps/src/api/time.rs rename to dapps/node-health/src/time.rs index 8ce79f6ad..588af6659 100644 --- a/dapps/src/api/time.rs +++ b/dapps/node-health/src/time.rs @@ -33,6 +33,7 @@ use std::io; use std::{fmt, mem, time}; +use std::sync::Arc; use std::sync::atomic::{self, AtomicUsize}; use std::collections::VecDeque; @@ -40,8 +41,8 @@ use futures::{self, Future, BoxFuture}; use futures::future::{self, IntoFuture}; use futures_cpupool::{CpuPool, CpuFuture}; use ntp; -use time::{Duration, Timespec}; -use util::{Arc, RwLock}; +use parking_lot::RwLock; +use time_crate::{Duration, Timespec}; /// Time checker error. #[derive(Debug, Clone, PartialEq)] @@ -163,7 +164,7 @@ impl Ntp for SimpleNtp { match ntp::request(&server.address) { Ok(packet) => { - let dest_time = ::time::now_utc().to_timespec(); + let dest_time = ::time_crate::now_utc().to_timespec(); let orig_time = Timespec::from(packet.orig_time); let recv_time = Timespec::from(packet.recv_time); let transmit_time = Timespec::from(packet.transmit_time); @@ -292,7 +293,7 @@ mod tests { use time::Duration; use futures::{future, Future}; use super::{Ntp, TimeChecker, Error}; - use util::RwLock; + use parking_lot::RwLock; #[derive(Clone)] struct FakeNtp(RefCell>, Cell); diff --git a/dapps/node-health/src/types.rs b/dapps/node-health/src/types.rs new file mode 100644 index 000000000..ae883a626 --- /dev/null +++ b/dapps/node-health/src/types.rs @@ -0,0 +1,57 @@ +// Copyright 2015-2017 Parity Technologies (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 . + +//! Base health types. + +/// Health API endpoint status. +#[derive(Debug, PartialEq, Serialize)] +pub enum HealthStatus { + /// Everything's OK. + #[serde(rename = "ok")] + Ok, + /// Node health need attention + /// (the issue is not critical, but may need investigation) + #[serde(rename = "needsAttention")] + NeedsAttention, + /// There is something bad detected with the node. + #[serde(rename = "bad")] + Bad, +} + +/// Represents a single check in node health. +/// Cointains the status of that check and apropriate message and details. +#[derive(Debug, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct HealthInfo { + /// Check status. + pub status: HealthStatus, + /// Human-readable message. + pub message: String, + /// Technical details of the check. + pub details: T, +} + +/// Node Health status. +#[derive(Debug, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct Health { + /// Status of peers. + pub peers: HealthInfo<(usize, usize)>, + /// Sync status. + pub sync: HealthInfo, + /// Time diff info. + pub time: HealthInfo, +} diff --git a/dapps/src/api/api.rs b/dapps/src/api/api.rs index 3452669b6..03ba859f8 100644 --- a/dapps/src/api/api.rs +++ b/dapps/src/api/api.rs @@ -21,32 +21,28 @@ use hyper::method::Method; use hyper::status::StatusCode; use api::{response, types}; -use api::time::{TimeChecker, MAX_DRIFT}; use apps::fetcher::Fetcher; use handlers::{self, extract_url}; use endpoint::{Endpoint, Handler, EndpointPath}; +use node_health::{NodeHealth, HealthStatus, Health}; use parity_reactor::Remote; -use {SyncStatus}; #[derive(Clone)] pub struct RestApi { fetcher: Arc, - sync_status: Arc, - time: TimeChecker, + health: NodeHealth, remote: Remote, } impl RestApi { pub fn new( fetcher: Arc, - sync_status: Arc, - time: TimeChecker, + health: NodeHealth, remote: Remote, ) -> Box { Box::new(RestApi { fetcher, - sync_status, - time, + health, remote, }) } @@ -90,74 +86,23 @@ impl RestApiRouter { } fn health(&self, control: Control) -> Box { - use self::types::{HealthInfo, HealthStatus, Health}; - - trace!(target: "dapps", "Checking node health."); - // Check timediff - let sync_status = self.api.sync_status.clone(); - let map = move |time| { - // Check peers - let peers = { - let (connected, max) = sync_status.peers(); - let (status, message) = match connected { - 0 => { - (HealthStatus::Bad, "You are not connected to any peers. There is most likely some network issue. Fix connectivity.".into()) - }, - 1 => (HealthStatus::NeedsAttention, "You are connected to only one peer. Your node might not be reliable. Check your network connection.".into()), - _ => (HealthStatus::Ok, "".into()), - }; - HealthInfo { status, message, details: (connected, max) } + let map = move |health: Result, ()>| { + let status = match health { + Ok(Ok(ref health)) => { + if [&health.peers.status, &health.sync.status].iter().any(|x| *x != &HealthStatus::Ok) { + StatusCode::PreconditionFailed // HTTP 412 + } else { + StatusCode::Ok // HTTP 200 + } + }, + _ => StatusCode::ServiceUnavailable, // HTTP 503 }; - // Check sync - let sync = { - let is_syncing = sync_status.is_major_importing(); - let (status, message) = if is_syncing { - (HealthStatus::NeedsAttention, "Your node is still syncing, the values you see might be outdated. Wait until it's fully synced.".into()) - } else { - (HealthStatus::Ok, "".into()) - }; - HealthInfo { status, message, details: is_syncing } - }; - - // Check time - let time = { - let (status, message, details) = match time { - Ok(Ok(diff)) if diff < MAX_DRIFT && diff > -MAX_DRIFT => { - (HealthStatus::Ok, "".into(), diff) - }, - Ok(Ok(diff)) => { - (HealthStatus::Bad, format!( - "Your clock is not in sync. Detected difference is too big for the protocol to work: {}ms. Synchronize your clock.", - diff, - ), diff) - }, - Ok(Err(err)) => { - (HealthStatus::NeedsAttention, format!( - "Unable to reach time API: {}. Make sure that your clock is synchronized.", - err, - ), 0) - }, - Err(_) => { - (HealthStatus::NeedsAttention, "Time API request timed out. Make sure that the clock is synchronized.".into(), 0) - }, - }; - - HealthInfo { status, message, details, } - }; - - let status = if [&peers.status, &sync.status, &time.status].iter().any(|x| *x != &HealthStatus::Ok) { - StatusCode::PreconditionFailed // HTTP 412 - } else { - StatusCode::Ok // HTTP 200 - }; - - response::as_json(status, &Health { peers, sync, time }) + response::as_json(status, &health) }; - - let time = self.api.time.time_drift(); + let health = self.api.health.health(); let remote = self.api.remote.clone(); - Box::new(handlers::AsyncHandler::new(time, map, remote, control)) + Box::new(handlers::AsyncHandler::new(health, map, remote, control)) } } diff --git a/dapps/src/api/mod.rs b/dapps/src/api/mod.rs index 59c634399..4ffb9f791 100644 --- a/dapps/src/api/mod.rs +++ b/dapps/src/api/mod.rs @@ -18,8 +18,6 @@ mod api; mod response; -mod time; mod types; pub use self::api::RestApi; -pub use self::time::TimeChecker; diff --git a/dapps/src/api/types.rs b/dapps/src/api/types.rs index a61964143..6beca3b58 100644 --- a/dapps/src/api/types.rs +++ b/dapps/src/api/types.rs @@ -25,43 +25,3 @@ pub struct ApiError { /// More technical error details. pub detail: String, } - -/// Health API endpoint status. -#[derive(Debug, PartialEq, Serialize)] -pub enum HealthStatus { - /// Everything's OK. - #[serde(rename = "ok")] - Ok, - /// Node health need attention - /// (the issue is not critical, but may need investigation) - #[serde(rename = "needsAttention")] - NeedsAttention, - /// There is something bad detected with the node. - #[serde(rename = "bad")] - Bad -} - -/// Represents a single check in node health. -/// Cointains the status of that check and apropriate message and details. -#[derive(Debug, PartialEq, Serialize)] -#[serde(deny_unknown_fields)] -pub struct HealthInfo { - /// Check status. - pub status: HealthStatus, - /// Human-readable message. - pub message: String, - /// Technical details of the check. - pub details: T, -} - -/// Node Health status. -#[derive(Debug, PartialEq, Serialize)] -#[serde(deny_unknown_fields)] -pub struct Health { - /// Status of peers. - pub peers: HealthInfo<(usize, usize)>, - /// Sync status. - pub sync: HealthInfo, - /// Time diff info. - pub time: HealthInfo, -} diff --git a/dapps/src/apps/fetcher/mod.rs b/dapps/src/apps/fetcher/mod.rs index 1fdf2f697..5b91da1a3 100644 --- a/dapps/src/apps/fetcher/mod.rs +++ b/dapps/src/apps/fetcher/mod.rs @@ -281,6 +281,7 @@ mod tests { } } + #[derive(Debug)] struct FakeSync(bool); impl SyncStatus for FakeSync { fn is_major_importing(&self) -> bool { self.0 } diff --git a/dapps/src/lib.rs b/dapps/src/lib.rs index daa415edb..d27341ab1 100644 --- a/dapps/src/lib.rs +++ b/dapps/src/lib.rs @@ -21,10 +21,8 @@ extern crate base32; extern crate futures; -extern crate futures_cpupool; extern crate linked_hash_map; extern crate mime_guess; -extern crate ntp; extern crate rand; extern crate rustc_hex; extern crate serde; @@ -39,6 +37,7 @@ extern crate jsonrpc_http_server; extern crate ethcore_util as util; extern crate fetch; +extern crate node_health; extern crate parity_dapps_glue as parity_dapps; extern crate parity_hash_fetch as hash_fetch; extern crate parity_reactor; @@ -56,7 +55,6 @@ extern crate ethcore_devtools as devtools; #[cfg(test)] extern crate env_logger; - mod endpoint; mod apps; mod page; @@ -76,19 +74,12 @@ use std::collections::HashMap; use jsonrpc_http_server::{self as http, hyper, Origin}; use fetch::Fetch; -use futures_cpupool::CpuPool; +use node_health::NodeHealth; use parity_reactor::Remote; pub use hash_fetch::urlhint::ContractClient; +pub use node_health::SyncStatus; -/// Indicates sync status -pub trait SyncStatus: Send + Sync { - /// Returns true if there is a major sync happening. - fn is_major_importing(&self) -> bool; - - /// Returns number of connected and ideal peers. - fn peers(&self) -> (usize, usize); -} /// Validates Web Proxy tokens pub trait WebProxyTokens: Send + Sync { @@ -130,8 +121,7 @@ impl Middleware { /// Creates new middleware for UI server. pub fn ui( - ntp_servers: &[String], - pool: CpuPool, + health: NodeHealth, remote: Remote, dapps_domain: &str, registrar: Arc, @@ -146,11 +136,9 @@ impl Middleware { ).embeddable_on(None).allow_dapps(false)); let special = { let mut special = special_endpoints( - ntp_servers, - pool, + health, content_fetcher.clone(), remote.clone(), - sync_status.clone(), ); special.insert(router::SpecialEndpoint::Home, Some(apps::ui())); special @@ -171,8 +159,7 @@ impl Middleware { /// Creates new Dapps server middleware. pub fn dapps( - ntp_servers: &[String], - pool: CpuPool, + health: NodeHealth, remote: Remote, ui_address: Option<(String, u16)>, extra_embed_on: Vec<(String, u16)>, @@ -204,11 +191,9 @@ impl Middleware { let special = { let mut special = special_endpoints( - ntp_servers, - pool, + health, content_fetcher.clone(), remote.clone(), - sync_status, ); special.insert( router::SpecialEndpoint::Home, @@ -238,20 +223,17 @@ impl http::RequestMiddleware for Middleware { } } -fn special_endpoints>( - ntp_servers: &[T], - pool: CpuPool, +fn special_endpoints( + health: NodeHealth, content_fetcher: Arc, remote: Remote, - sync_status: Arc, ) -> HashMap>> { let mut special = HashMap::new(); special.insert(router::SpecialEndpoint::Rpc, None); special.insert(router::SpecialEndpoint::Utils, Some(apps::utils())); special.insert(router::SpecialEndpoint::Api, Some(api::RestApi::new( content_fetcher, - sync_status, - api::TimeChecker::new(ntp_servers, pool), + health, remote, ))); special diff --git a/dapps/src/tests/helpers/mod.rs b/dapps/src/tests/helpers/mod.rs index 30d3ba8f9..6f4652351 100644 --- a/dapps/src/tests/helpers/mod.rs +++ b/dapps/src/tests/helpers/mod.rs @@ -26,7 +26,7 @@ use jsonrpc_http_server::{self as http, Host, DomainsValidation}; use devtools::http_client; use hash_fetch::urlhint::ContractClient; use fetch::{Fetch, Client as FetchClient}; -use futures_cpupool::CpuPool; +use node_health::{NodeHealth, TimeChecker, CpuPool}; use parity_reactor::Remote; use {Middleware, SyncStatus, WebProxyTokens}; @@ -39,6 +39,7 @@ use self::fetch::FakeFetch; const SIGNER_PORT: u16 = 18180; +#[derive(Debug)] struct FakeSync(bool); impl SyncStatus for FakeSync { fn is_major_importing(&self) -> bool { self.0 } @@ -254,9 +255,13 @@ impl Server { remote: Remote, fetch: F, ) -> Result { + let health = NodeHealth::new( + sync_status.clone(), + TimeChecker::new::(&[], CpuPool::new(1)), + remote.clone(), + ); let middleware = Middleware::dapps( - &["0.pool.ntp.org:123".into(), "1.pool.ntp.org:123".into()], - CpuPool::new(4), + health, remote, signer_address, vec![], diff --git a/ethcore/Cargo.toml b/ethcore/Cargo.toml index 2ec89c005..c2029ac28 100644 --- a/ethcore/Cargo.toml +++ b/ethcore/Cargo.toml @@ -44,6 +44,7 @@ lru-cache = "0.1.0" native-contracts = { path = "native_contracts" } num = "0.1" num_cpus = "1.2" +rayon = "0.8" rand = "0.3" rlp = { path = "../util/rlp" } rust-crypto = "0.2.34" diff --git a/ethcore/evm/src/interpreter/memory.rs b/ethcore/evm/src/interpreter/memory.rs index 017b5777d..6894baaf2 100644 --- a/ethcore/evm/src/interpreter/memory.rs +++ b/ethcore/evm/src/interpreter/memory.rs @@ -44,7 +44,7 @@ pub trait Memory { } /// Checks whether offset and size is valid memory range -fn is_valid_range(off: usize, size: usize) -> bool { +pub fn is_valid_range(off: usize, size: usize) -> bool { // When size is zero we haven't actually expanded the memory let overflow = off.overflowing_add(size).1; size > 0 && !overflow diff --git a/ethcore/evm/src/interpreter/mod.rs b/ethcore/evm/src/interpreter/mod.rs index 30b431912..633fe70d4 100644 --- a/ethcore/evm/src/interpreter/mod.rs +++ b/ethcore/evm/src/interpreter/mod.rs @@ -162,7 +162,12 @@ impl evm::Evm for Interpreter { } if do_trace { - ext.trace_executed(gasometer.current_gas.as_u256(), stack.peek_top(info.ret), mem_written.map(|(o, s)| (o, &(self.mem[o..(o + s)]))), store_written); + ext.trace_executed( + gasometer.current_gas.as_u256(), + stack.peek_top(info.ret), + mem_written.map(|(o, s)| (o, &(self.mem[o..o+s]))), + store_written, + ); } // Advance @@ -246,14 +251,20 @@ impl Interpreter { 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 | instructions::RETURNDATACOPY => 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)), + let read = |pos| stack.peek(pos).low_u64() as usize; + let written = match instruction { + instructions::MSTORE | instructions::MLOAD => Some((read(0), 32)), + instructions::MSTORE8 => Some((read(0), 1)), + instructions::CALLDATACOPY | instructions::CODECOPY | instructions::RETURNDATACOPY => Some((read(0), read(2))), + instructions::EXTCODECOPY => Some((read(1), read(3))), + instructions::CALL | instructions::CALLCODE => Some((read(5), read(6))), + instructions::DELEGATECALL | instructions::STATICCALL => Some((read(4), read(5))), _ => None, + }; + + match written { + Some((offset, size)) if !memory::is_valid_range(offset, size) => None, + written => written, } } @@ -856,3 +867,36 @@ fn address_to_u256(value: Address) -> U256 { U256::from(&*H256::from(value)) } + +#[cfg(test)] +mod tests { + use std::sync::Arc; + use rustc_hex::FromHex; + use vmtype::VMType; + use factory::Factory; + use vm::{self, ActionParams, ActionValue}; + use vm::tests::{FakeExt, test_finalize}; + + #[test] + fn should_not_fail_on_tracing_mem() { + let code = "7feeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff006000527faaffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffaa6020526000620f120660406000601773945304eb96065b2a98b57a48a06ae28d285a71b56101f4f1600055".from_hex().unwrap(); + + let mut params = ActionParams::default(); + params.address = 5.into(); + params.gas = 300_000.into(); + params.gas_price = 1.into(); + params.value = ActionValue::Transfer(100_000.into()); + params.code = Some(Arc::new(code)); + let mut ext = FakeExt::new(); + ext.balances.insert(5.into(), 1_000_000_000.into()); + ext.tracing = true; + + let gas_left = { + let mut vm = Factory::new(VMType::Interpreter, 1).create(params.gas); + test_finalize(vm.exec(params, &mut ext)).unwrap() + }; + + assert_eq!(ext.calls.len(), 1); + assert_eq!(gas_left, 248_212.into()); + } +} diff --git a/ethcore/src/blockchain/blockchain.rs b/ethcore/src/blockchain/blockchain.rs index 4412df567..b7d7dd188 100644 --- a/ethcore/src/blockchain/blockchain.rs +++ b/ethcore/src/blockchain/blockchain.rs @@ -36,6 +36,7 @@ use db::{self, Writable, Readable, CacheUpdatePolicy}; use cache_manager::CacheManager; use encoded; use engines::epoch::{Transition as EpochTransition, PendingTransition as PendingEpochTransition}; +use rayon::prelude::*; const LOG_BLOOMS_LEVELS: usize = 3; const LOG_BLOOMS_ELEMENTS_PER_INDEX: usize = 16; @@ -143,7 +144,7 @@ pub trait BlockProvider { /// Returns logs matching given filter. fn logs(&self, blocks: Vec, matches: F, limit: Option) -> Vec - where F: Fn(&LogEntry) -> bool, Self: Sized; + where F: Fn(&LogEntry) -> bool + Send + Sync, Self: Sized; } macro_rules! otry { @@ -354,50 +355,56 @@ impl BlockProvider for BlockChain { } fn logs(&self, mut blocks: Vec, matches: F, limit: Option) -> Vec - where F: Fn(&LogEntry) -> bool, Self: Sized { + where F: Fn(&LogEntry) -> bool + Send + Sync, Self: Sized { // sort in reverse order blocks.sort_by(|a, b| b.cmp(a)); - let mut log_index = 0; - let mut logs = blocks.into_iter() - .filter_map(|number| self.block_hash(number).map(|hash| (number, hash))) - .filter_map(|(number, hash)| self.block_receipts(&hash).map(|r| (number, hash, r.receipts))) - .filter_map(|(number, hash, receipts)| self.block_body(&hash).map(|ref b| (number, hash, receipts, b.transaction_hashes()))) - .flat_map(|(number, hash, mut receipts, mut hashes)| { - if receipts.len() != hashes.len() { - warn!("Block {} ({}) has different number of receipts ({}) to transactions ({}). Database corrupt?", number, hash, receipts.len(), hashes.len()); - assert!(false); - } - log_index = receipts.iter().fold(0, |sum, receipt| sum + receipt.logs.len()); + let mut logs = blocks + .chunks(128) + .flat_map(move |blocks_chunk| { + blocks_chunk.into_par_iter() + .filter_map(|number| self.block_hash(*number).map(|hash| (*number, hash))) + .filter_map(|(number, hash)| self.block_receipts(&hash).map(|r| (number, hash, r.receipts))) + .filter_map(|(number, hash, receipts)| self.block_body(&hash).map(|ref b| (number, hash, receipts, b.transaction_hashes()))) + .flat_map(|(number, hash, mut receipts, mut hashes)| { + if receipts.len() != hashes.len() { + warn!("Block {} ({}) has different number of receipts ({}) to transactions ({}). Database corrupt?", number, hash, receipts.len(), hashes.len()); + assert!(false); + } + let mut log_index = receipts.iter().fold(0, |sum, receipt| sum + receipt.logs.len()); - let receipts_len = receipts.len(); - hashes.reverse(); - receipts.reverse(); - receipts.into_iter() - .map(|receipt| receipt.logs) - .zip(hashes) - .enumerate() - .flat_map(move |(index, (mut logs, tx_hash))| { - let current_log_index = log_index; - let no_of_logs = logs.len(); - log_index -= no_of_logs; - - logs.reverse(); - logs.into_iter() + let receipts_len = receipts.len(); + hashes.reverse(); + receipts.reverse(); + receipts.into_iter() + .map(|receipt| receipt.logs) + .zip(hashes) .enumerate() - .map(move |(i, log)| LocalizedLogEntry { - entry: log, - block_hash: hash, - block_number: number, - transaction_hash: tx_hash, - // iterating in reverse order - transaction_index: receipts_len - index - 1, - transaction_log_index: no_of_logs - i - 1, - log_index: current_log_index - i - 1, + .flat_map(move |(index, (mut logs, tx_hash))| { + let current_log_index = log_index; + let no_of_logs = logs.len(); + log_index -= no_of_logs; + + logs.reverse(); + logs.into_iter() + .enumerate() + .map(move |(i, log)| LocalizedLogEntry { + entry: log, + block_hash: hash, + block_number: number, + transaction_hash: tx_hash, + // iterating in reverse order + transaction_index: receipts_len - index - 1, + transaction_log_index: no_of_logs - i - 1, + log_index: current_log_index - i - 1, + }) }) + .filter(|log_entry| matches(&log_entry.entry)) + .take(limit.unwrap_or(::std::usize::MAX)) + .collect::>() }) + .collect::>() }) - .filter(|log_entry| matches(&log_entry.entry)) .take(limit.unwrap_or(::std::usize::MAX)) .collect::>(); logs.reverse(); diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index 48cf481f1..b0ffe8bef 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -24,7 +24,7 @@ use time::precise_time_ns; // util use util::{Bytes, PerfTimer, Itertools, Mutex, RwLock, MutexGuard, Hashable}; use util::{journaldb, DBValue, TrieFactory, Trie}; -use util::{U256, H256, Address, H2048}; +use util::{U256, H256, Address}; use util::trie::TrieSpec; use util::kvdb::*; @@ -748,7 +748,7 @@ impl Client { self.factories.clone(), ).expect("state known to be available for just-imported block; qed"); - let options = TransactOptions { tracing: false, vm_tracing: false, check_nonce: false }; + let options = TransactOptions::with_no_tracing().dont_check_nonce().save_output_from_contract(); let res = Executive::new(&mut state, &env_info, &*self.engine) .transact(&transaction, options); @@ -912,7 +912,7 @@ impl Client { _ => {}, } - let block_number = match self.block_number(id.clone()) { + let block_number = match self.block_number(id) { Some(num) => num, None => return None, }; @@ -1111,6 +1111,15 @@ impl Client { data: data, }.fake_sign(from) } + + fn block_number_ref(&self, id: &BlockId) -> Option { + match *id { + BlockId::Number(number) => Some(number), + BlockId::Hash(ref hash) => self.chain.read().block_number(hash), + BlockId::Earliest => Some(0), + BlockId::Latest | BlockId::Pending => Some(self.chain.read().best_block_number()), + } + } } impl snapshot::DatabaseRestore for Client { @@ -1143,7 +1152,9 @@ impl BlockChainClient for Client { let mut state = self.state_at(block).ok_or(CallError::StatePruned)?; let original_state = if analytics.state_diffing { Some(state.clone()) } else { None }; - let options = TransactOptions { tracing: analytics.transaction_tracing, vm_tracing: analytics.vm_tracing, check_nonce: false }; + let options = TransactOptions::new(analytics.transaction_tracing, analytics.vm_tracing) + .dont_check_nonce() + .save_output_from_contract(); let mut ret = Executive::new(&mut state, &env_info, &*self.engine).transact_virtual(t, options)?; // TODO gav move this into Executive. @@ -1166,7 +1177,7 @@ impl BlockChainClient for Client { // that's just a copy of the state. let original_state = self.state_at(block).ok_or(CallError::StatePruned)?; let sender = t.sender(); - let options = TransactOptions { tracing: true, vm_tracing: false, check_nonce: false }; + let options = TransactOptions::with_tracing().dont_check_nonce(); let cond = |gas| { let mut tx = t.as_unsigned().clone(); @@ -1231,7 +1242,9 @@ impl BlockChainClient for Client { return Err(CallError::TransactionNotFound); } - let options = TransactOptions { tracing: analytics.transaction_tracing, vm_tracing: analytics.vm_tracing, check_nonce: false }; + let options = TransactOptions::new(analytics.transaction_tracing, analytics.vm_tracing) + .dont_check_nonce() + .save_output_from_contract(); const PROOF: &'static str = "Transactions fetched from blockchain; blockchain transactions are valid; qed"; let rest = txs.split_off(address.index); for t in txs { @@ -1308,12 +1321,7 @@ impl BlockChainClient for Client { } fn block_number(&self, id: BlockId) -> Option { - match id { - BlockId::Number(number) => Some(number), - BlockId::Hash(ref hash) => self.chain.read().block_number(hash), - BlockId::Earliest => Some(0), - BlockId::Latest | BlockId::Pending => Some(self.chain.read().best_block_number()), - } + self.block_number_ref(&id) } fn block_body(&self, id: BlockId) -> Option { @@ -1566,16 +1574,17 @@ impl BlockChainClient for Client { self.engine.additional_params().into_iter().collect() } - fn blocks_with_bloom(&self, bloom: &H2048, from_block: BlockId, to_block: BlockId) -> Option> { - match (self.block_number(from_block), self.block_number(to_block)) { - (Some(from), Some(to)) => Some(self.chain.read().blocks_with_bloom(bloom, from, to)), - _ => None - } - } - fn logs(&self, filter: Filter) -> Vec { + let (from, to) = match (self.block_number_ref(&filter.from_block), self.block_number_ref(&filter.to_block)) { + (Some(from), Some(to)) => (from, to), + _ => return Vec::new(), + }; + + let chain = self.chain.read(); let blocks = filter.bloom_possibilities().iter() - .filter_map(|bloom| self.blocks_with_bloom(bloom, filter.from_block.clone(), filter.to_block.clone())) + .map(move |bloom| { + chain.blocks_with_bloom(bloom, from, to) + }) .flat_map(|m| m) // remove duplicate elements .collect::>() @@ -1894,7 +1903,7 @@ impl ProvingBlockChainClient for Client { let backend = state::backend::Proving::new(jdb.as_hashdb_mut()); let mut state = state.replace_backend(backend); - let options = TransactOptions { tracing: false, vm_tracing: false, check_nonce: false }; + let options = TransactOptions::with_no_tracing().dont_check_nonce().save_output_from_contract(); let res = Executive::new(&mut state, &env_info, &*self.engine).transact(&transaction, options); match res { diff --git a/ethcore/src/client/test_client.rs b/ethcore/src/client/test_client.rs index 5dfdf5c25..b0d2301ac 100644 --- a/ethcore/src/client/test_client.rs +++ b/ethcore/src/client/test_client.rs @@ -486,10 +486,6 @@ impl BlockChainClient for TestBlockChainClient { self.receipts.read().get(&id).cloned() } - fn blocks_with_bloom(&self, _bloom: &H2048, _from_block: BlockId, _to_block: BlockId) -> Option> { - unimplemented!(); - } - fn logs(&self, filter: Filter) -> Vec { let mut logs = self.logs.read().clone(); let len = logs.len(); diff --git a/ethcore/src/client/traits.rs b/ethcore/src/client/traits.rs index aca369b21..2e558788f 100644 --- a/ethcore/src/client/traits.rs +++ b/ethcore/src/client/traits.rs @@ -33,7 +33,7 @@ use trace::LocalizedTrace; use transaction::{LocalizedTransaction, PendingTransaction, SignedTransaction}; use verification::queue::QueueInfo as BlockQueueInfo; -use util::{U256, Address, H256, H2048, Bytes, Itertools}; +use util::{U256, Address, H256, Bytes, Itertools}; use util::hashdb::DBValue; use types::ids::*; @@ -175,9 +175,6 @@ pub trait BlockChainClient : Sync + Send { /// Get the best block header. fn best_block_header(&self) -> encoded::Header; - /// Returns numbers of blocks containing given bloom. - fn blocks_with_bloom(&self, bloom: &H2048, from_block: BlockId, to_block: BlockId) -> Option>; - /// Returns logs matching given filter. fn logs(&self, filter: Filter) -> Vec; diff --git a/ethcore/src/engines/authority_round/mod.rs b/ethcore/src/engines/authority_round/mod.rs index 8f0170526..83eadc422 100644 --- a/ethcore/src/engines/authority_round/mod.rs +++ b/ethcore/src/engines/authority_round/mod.rs @@ -538,7 +538,9 @@ impl Engine for AuthorityRound { let parent_hash = block.fields().header.parent_hash().clone(); ::engines::common::push_last_hash(block, last_hashes.clone(), self, &parent_hash)?; - if !epoch_begin { return Ok(()) } + // with immediate transitions, we don't use the epoch mechanism anyway. + // the genesis is always considered an epoch, but we ignore it intentionally. + if self.immediate_transitions || !epoch_begin { return Ok(()) } // genesis is never a new block, but might as well check. let header = block.fields().header.clone(); diff --git a/ethcore/src/executive.rs b/ethcore/src/executive.rs index af95af20b..4f63db9b9 100644 --- a/ethcore/src/executive.rs +++ b/ethcore/src/executive.rs @@ -72,9 +72,75 @@ pub struct TransactOptions { pub vm_tracing: bool, /// Check transaction nonce before execution. pub check_nonce: bool, + /// Records the output from init contract calls. + pub output_from_init_contract: bool, } -pub fn executor(engine: &E, vm_factory: &Factory, params: &ActionParams) +impl TransactOptions { + /// Create new `TransactOptions` with given tracer and VM tracer. + pub fn new(tracing: bool, vm_tracing: bool) -> Self { + TransactOptions { + tracing, + vm_tracing, + check_nonce: true, + output_from_init_contract: false, + } + } + + /// Disables the nonce check + pub fn dont_check_nonce(mut self) -> Self { + self.check_nonce = false; + self + } + + /// Saves the output from contract creation. + pub fn save_output_from_contract(mut self) -> Self { + self.output_from_init_contract = true; + self + } + + /// Creates new `TransactOptions` with default tracing and VM tracing. + pub fn with_tracing_and_vm_tracing() -> Self { + TransactOptions { + tracing: true, + vm_tracing: true, + check_nonce: true, + output_from_init_contract: false, + } + } + + /// Creates new `TransactOptions` with default tracing and no VM tracing. + pub fn with_tracing() -> Self { + TransactOptions { + tracing: true, + vm_tracing: false, + check_nonce: true, + output_from_init_contract: false, + } + } + + /// Creates new `TransactOptions` with no tracing and default VM tracing. + pub fn with_vm_tracing() -> Self { + TransactOptions { + tracing: false, + vm_tracing: true, + check_nonce: true, + output_from_init_contract: false, + } + } + + /// Creates new `TransactOptions` without any tracing. + pub fn with_no_tracing() -> Self { + TransactOptions { + tracing: false, + vm_tracing: false, + check_nonce: true, + output_from_init_contract: false, + } + } +} + +pub fn executor(engine: &E, vm_factory: &Factory, params: &ActionParams) -> Box where E: Engine + ?Sized { if engine.supports_wasm() && params.code.as_ref().map_or(false, |code| code.len() > 4 && &code[0..4] == WASM_MAGIC_NUMBER) { @@ -137,14 +203,15 @@ impl<'a, B: 'a + StateBackend, E: Engine + ?Sized> Executive<'a, B, E> { /// This function should be used to execute transaction. pub fn transact(&'a mut self, t: &SignedTransaction, options: TransactOptions) -> Result { let check = options.check_nonce; + let output = options.output_from_init_contract; match options.tracing { true => match options.vm_tracing { - true => self.transact_with_tracer(t, check, ExecutiveTracer::default(), ExecutiveVMTracer::toplevel()), - false => self.transact_with_tracer(t, check, ExecutiveTracer::default(), NoopVMTracer), + true => self.transact_with_tracer(t, check, output, ExecutiveTracer::default(), ExecutiveVMTracer::toplevel()), + false => self.transact_with_tracer(t, check, output, ExecutiveTracer::default(), NoopVMTracer), }, false => match options.vm_tracing { - true => self.transact_with_tracer(t, check, NoopTracer, ExecutiveVMTracer::toplevel()), - false => self.transact_with_tracer(t, check, NoopTracer, NoopVMTracer), + true => self.transact_with_tracer(t, check, output, NoopTracer, ExecutiveVMTracer::toplevel()), + false => self.transact_with_tracer(t, check, output, NoopTracer, NoopVMTracer), }, } } @@ -169,6 +236,7 @@ impl<'a, B: 'a + StateBackend, E: Engine + ?Sized> Executive<'a, B, E> { &'a mut self, t: &SignedTransaction, check_nonce: bool, + output_from_create: bool, mut tracer: T, mut vm_tracer: V ) -> Result where T: Tracer, V: VMTracer { @@ -237,7 +305,8 @@ impl<'a, B: 'a + StateBackend, E: Engine + ?Sized> Executive<'a, B, E> { data: None, call_type: CallType::None, }; - (self.create(params, &mut substate, &mut tracer, &mut vm_tracer), vec![]) + let mut out = if output_from_create { Some(vec![]) } else { None }; + (self.create(params, &mut substate, &mut out, &mut tracer, &mut vm_tracer), out.unwrap_or_else(Vec::new)) }, Action::Call(ref address) => { let params = ActionParams { @@ -430,6 +499,7 @@ impl<'a, B: 'a + StateBackend, E: Engine + ?Sized> Executive<'a, B, E> { &mut self, params: ActionParams, substate: &mut Substate, + output: &mut Option, tracer: &mut T, vm_tracer: &mut V, ) -> evm::Result<(U256, ReturnData)> where T: Tracer, V: VMTracer { @@ -471,7 +541,7 @@ impl<'a, B: 'a + StateBackend, E: Engine + ?Sized> Executive<'a, B, E> { let mut subvmtracer = vm_tracer.prepare_subtrace(params.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, &mut subvmtracer) + self.exec_vm(params, &mut unconfirmed_substate, OutputPolicy::InitContract(output.as_mut().or(trace_output.as_mut())), &mut subtracer, &mut subvmtracer) }; vm_tracer.done_subtrace(subvmtracer); @@ -480,7 +550,7 @@ impl<'a, B: 'a + StateBackend, E: Engine + ?Sized> Executive<'a, B, E> { Ok(ref res) => tracer.trace_create( trace_info, gas - res.gas_left, - trace_output, + trace_output.map(|data| output.as_ref().map(|out| out.to_vec()).unwrap_or(data)), created, subtracer.traces() ), @@ -641,7 +711,7 @@ mod tests { let (gas_left, _) = { let mut ex = Executive::new(&mut state, &info, &engine); - ex.create(params, &mut substate, &mut NoopTracer, &mut NoopVMTracer).unwrap() + ex.create(params, &mut substate, &mut None, &mut NoopTracer, &mut NoopVMTracer).unwrap() }; assert_eq!(gas_left, U256::from(79_975)); @@ -699,7 +769,7 @@ mod tests { let (gas_left, _) = { let mut ex = Executive::new(&mut state, &info, &engine); - ex.create(params, &mut substate, &mut NoopTracer, &mut NoopVMTracer).unwrap() + ex.create(params, &mut substate, &mut None, &mut NoopTracer, &mut NoopVMTracer).unwrap() }; assert_eq!(gas_left, U256::from(62_976)); @@ -866,7 +936,7 @@ mod tests { let (gas_left, _) = { let mut ex = Executive::new(&mut state, &info, &engine); - ex.create(params.clone(), &mut substate, &mut tracer, &mut vm_tracer).unwrap() + ex.create(params.clone(), &mut substate, &mut None, &mut tracer, &mut vm_tracer).unwrap() }; assert_eq!(gas_left, U256::from(96_776)); @@ -951,7 +1021,7 @@ mod tests { let (gas_left, _) = { let mut ex = Executive::new(&mut state, &info, &engine); - ex.create(params, &mut substate, &mut NoopTracer, &mut NoopVMTracer).unwrap() + ex.create(params, &mut substate, &mut None, &mut NoopTracer, &mut NoopVMTracer).unwrap() }; assert_eq!(gas_left, U256::from(62_976)); @@ -1002,7 +1072,7 @@ mod tests { { let mut ex = Executive::new(&mut state, &info, &engine); - ex.create(params, &mut substate, &mut NoopTracer, &mut NoopVMTracer).unwrap(); + ex.create(params, &mut substate, &mut None, &mut NoopTracer, &mut NoopVMTracer).unwrap(); } assert_eq!(substate.contracts_created.len(), 1); @@ -1138,7 +1208,7 @@ mod tests { let executed = { let mut ex = Executive::new(&mut state, &info, &engine); - let opts = TransactOptions { check_nonce: true, tracing: false, vm_tracing: false }; + let opts = TransactOptions { check_nonce: true, tracing: false, vm_tracing: false, output_from_init_contract: false }; ex.transact(&t, opts).unwrap() }; @@ -1175,7 +1245,7 @@ mod tests { let res = { let mut ex = Executive::new(&mut state, &info, &engine); - let opts = TransactOptions { check_nonce: true, tracing: false, vm_tracing: false }; + let opts = TransactOptions { check_nonce: true, tracing: false, vm_tracing: false, output_from_init_contract: false }; ex.transact(&t, opts) }; @@ -1208,7 +1278,7 @@ mod tests { let res = { let mut ex = Executive::new(&mut state, &info, &engine); - let opts = TransactOptions { check_nonce: true, tracing: false, vm_tracing: false }; + let opts = TransactOptions { check_nonce: true, tracing: false, vm_tracing: false, output_from_init_contract: false }; ex.transact(&t, opts) }; @@ -1241,7 +1311,7 @@ mod tests { let res = { let mut ex = Executive::new(&mut state, &info, &engine); - let opts = TransactOptions { check_nonce: true, tracing: false, vm_tracing: false }; + let opts = TransactOptions { check_nonce: true, tracing: false, vm_tracing: false, output_from_init_contract: false }; ex.transact(&t, opts) }; @@ -1275,7 +1345,7 @@ mod tests { let result = { let mut ex = Executive::new(&mut state, &info, &engine); - ex.create(params, &mut substate, &mut NoopTracer, &mut NoopVMTracer) + ex.create(params, &mut substate, &mut None, &mut NoopTracer, &mut NoopVMTracer) }; match result { diff --git a/ethcore/src/externalities.rs b/ethcore/src/externalities.rs index 39c8673a9..5841e1700 100644 --- a/ethcore/src/externalities.rs +++ b/ethcore/src/externalities.rs @@ -219,7 +219,7 @@ impl<'a, T: 'a, V: 'a, B: 'a, E: 'a> Ext for Externalities<'a, T, V, B, E> let mut ex = Executive::from_parent(self.state, self.env_info, self.engine, self.depth, self.static_flag); // TODO: handle internal error separately - match ex.create(params, self.substate, self.tracer, self.vm_tracer) { + match ex.create(params, self.substate, &mut None, self.tracer, self.vm_tracer) { Ok((gas_left, _)) => { self.substate.contracts_created.push(address.clone()); ContractCreateResult::Created(address, gas_left) diff --git a/ethcore/src/lib.rs b/ethcore/src/lib.rs index af7922c20..f37d44429 100644 --- a/ethcore/src/lib.rs +++ b/ethcore/src/lib.rs @@ -99,6 +99,7 @@ extern crate native_contracts; extern crate num_cpus; extern crate num; extern crate rand; +extern crate rayon; extern crate rlp; extern crate rustc_hex; extern crate rustc_serialize; diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index b8621a741..fbdab0ea5 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -742,7 +742,7 @@ impl MinerService for Miner { state.add_balance(&sender, &(needed_balance - balance), CleanupMode::NoEmpty) .map_err(ExecutionError::from)?; } - let options = TransactOptions { tracing: analytics.transaction_tracing, vm_tracing: analytics.vm_tracing, check_nonce: false }; + let options = TransactOptions { tracing: analytics.transaction_tracing, vm_tracing: analytics.vm_tracing, check_nonce: false, output_from_init_contract: true }; let mut ret = Executive::new(&mut state, &env_info, &*self.engine).transact(t, options)?; // TODO gav move this into Executive. diff --git a/ethcore/src/miner/transaction_queue.rs b/ethcore/src/miner/transaction_queue.rs index 542b42b93..6ce8c7e0c 100644 --- a/ethcore/src/miner/transaction_queue.rs +++ b/ethcore/src/miner/transaction_queue.rs @@ -506,8 +506,6 @@ pub struct AccountDetails { pub balance: U256, } -/// Transactions with `gas > (gas_limit + gas_limit * Factor(in percents))` are not imported to the queue. -const GAS_LIMIT_HYSTERESIS: usize = 200; // (100/GAS_LIMIT_HYSTERESIS) % /// Transaction with the same (sender, nonce) can be replaced only if /// `new_gas_price > old_gas_price + old_gas_price >> SHIFT` const GAS_PRICE_BUMP_SHIFT: usize = 3; // 2 = 25%, 3 = 12.5%, 4 = 6.25% @@ -570,8 +568,8 @@ pub struct TransactionQueue { minimal_gas_price: U256, /// The maximum amount of gas any individual transaction may use. tx_gas_limit: U256, - /// Current gas limit (block gas limit * factor). Transactions above the limit will not be accepted (default to !0) - total_gas_limit: U256, + /// Current gas limit (block gas limit). Transactions above the limit will not be accepted (default to !0) + block_gas_limit: U256, /// Maximal time transaction may occupy the queue. /// When we reach `max_time_in_queue / 2^3` we re-validate /// account balance. @@ -631,7 +629,7 @@ impl TransactionQueue { TransactionQueue { strategy, minimal_gas_price: U256::zero(), - total_gas_limit: !U256::zero(), + block_gas_limit: !U256::zero(), tx_gas_limit, max_time_in_queue: DEFAULT_QUEUING_PERIOD, current, @@ -674,16 +672,10 @@ impl TransactionQueue { self.current.gas_price_entry_limit() } - /// Sets new gas limit. Transactions with gas slightly (`GAS_LIMIT_HYSTERESIS`) above the limit won't be imported. + /// Sets new gas limit. Transactions with gas over the limit will not be accepted. /// Any transaction already imported to the queue is not affected. pub fn set_gas_limit(&mut self, gas_limit: U256) { - let extra = gas_limit / U256::from(GAS_LIMIT_HYSTERESIS); - - let total_gas_limit = match gas_limit.overflowing_add(extra) { - (_, true) => !U256::zero(), - (val, false) => val, - }; - self.total_gas_limit = total_gas_limit; + self.block_gas_limit = gas_limit; } /// Sets new total gas limit. @@ -819,13 +811,13 @@ impl TransactionQueue { })); } - let gas_limit = cmp::min(self.tx_gas_limit, self.total_gas_limit); + let gas_limit = cmp::min(self.tx_gas_limit, self.block_gas_limit); if tx.gas > gas_limit { trace!(target: "txqueue", "Dropping transaction above gas limit: {:?} ({} > min({}, {}))", tx.hash(), tx.gas, - self.total_gas_limit, + self.block_gas_limit, self.tx_gas_limit ); return Err(Error::Transaction(TransactionError::GasLimitExceeded { @@ -1922,13 +1914,13 @@ pub mod test { // given let mut txq = TransactionQueue::default(); txq.set_gas_limit(U256::zero()); - assert_eq!(txq.total_gas_limit, U256::zero()); + assert_eq!(txq.block_gas_limit, U256::zero()); // when txq.set_gas_limit(!U256::zero()); // then - assert_eq!(txq.total_gas_limit, !U256::zero()); + assert_eq!(txq.block_gas_limit, !U256::zero()); } #[test] @@ -1945,7 +1937,7 @@ pub mod test { // then assert_eq!(unwrap_tx_err(res), TransactionError::GasLimitExceeded { - limit: U256::from(50_250), // Should be 100.5% of set_gas_limit + limit: U256::from(50_000), got: gas, }); let stats = txq.status(); diff --git a/ethcore/src/spec/spec.rs b/ethcore/src/spec/spec.rs index 7d3900f2c..8b6476436 100644 --- a/ethcore/src/spec/spec.rs +++ b/ethcore/src/spec/spec.rs @@ -311,11 +311,10 @@ impl Spec { }; let mut substate = Substate::new(); - state.kill_account(&address); { let mut exec = Executive::new(&mut state, &env_info, self.engine.as_ref()); - if let Err(e) = exec.create(params, &mut substate, &mut NoopTracer, &mut NoopVMTracer) { + if let Err(e) = exec.create(params, &mut substate, &mut None, &mut NoopTracer, &mut NoopVMTracer) { warn!(target: "spec", "Genesis constructor execution at {} failed: {}.", address, e); } } @@ -525,6 +524,9 @@ mod tests { let db = spec.ensure_db_good(get_temp_state_db(), &Default::default()).unwrap(); let state = State::from_existing(db.boxed_clone(), spec.state_root(), spec.engine.account_start_nonce(0), Default::default()).unwrap(); let expected = H256::from_str("0000000000000000000000000000000000000000000000000000000000000001").unwrap(); - assert_eq!(state.storage_at(&Address::from_str("0000000000000000000000000000000000000005").unwrap(), &H256::zero()).unwrap(), expected); + let address = Address::from_str("0000000000000000000000000000000000000005").unwrap(); + + assert_eq!(state.storage_at(&address, &H256::zero()).unwrap(), expected); + assert_eq!(state.balance(&address).unwrap(), 1.into()); } } diff --git a/ethcore/src/state/mod.rs b/ethcore/src/state/mod.rs index 354da2cc3..d1a1cb583 100644 --- a/ethcore/src/state/mod.rs +++ b/ethcore/src/state/mod.rs @@ -642,7 +642,7 @@ impl State { fn execute(&mut self, env_info: &EnvInfo, engine: &Engine, t: &SignedTransaction, tracing: bool, virt: bool) -> Result { - let options = TransactOptions { tracing: tracing, vm_tracing: false, check_nonce: true }; + let options = TransactOptions { tracing: tracing, vm_tracing: false, check_nonce: true, output_from_init_contract: true }; let mut e = Executive::new(self, env_info, engine); match virt { diff --git a/ethcore/src/verification/verification.rs b/ethcore/src/verification/verification.rs index 823d2ef70..0ff9612fa 100644 --- a/ethcore/src/verification/verification.rs +++ b/ethcore/src/verification/verification.rs @@ -380,14 +380,13 @@ mod tests { self.numbers.get(&index).cloned() } - fn blocks_with_bloom(&self, _bloom: &H2048, _from_block: BlockNumber, _to_block: BlockNumber) -> Vec { - unimplemented!() - } - fn block_receipts(&self, _hash: &H256) -> Option { unimplemented!() } + fn blocks_with_bloom(&self, _bloom: &H2048, _from_block: BlockNumber, _to_block: BlockNumber) -> Vec { + unimplemented!() + } fn logs(&self, _blocks: Vec, _matches: F, _limit: Option) -> Vec where F: Fn(&LogEntry) -> bool, Self: Sized { diff --git a/js/src/api/api.js b/js/src/api/api.js index 220c3be29..a1a7dbbb5 100644 --- a/js/src/api/api.js +++ b/js/src/api/api.js @@ -71,10 +71,15 @@ export default class Api extends EventEmitter { } } + get isPubSub () { + return !!this._pubsub; + } + get pubsub () { - if (!this._pubsub) { + if (!this.isPubSub) { throw Error('Pubsub is only available with a subscribing-supported transport injected!'); } + return this._pubsub; } diff --git a/js/src/api/pubsub/eth/eth.js b/js/src/api/pubsub/eth/eth.js index 0bbc85bec..044473ec6 100644 --- a/js/src/api/pubsub/eth/eth.js +++ b/js/src/api/pubsub/eth/eth.js @@ -25,7 +25,7 @@ export default class Eth extends PubsubBase { } newHeads (callback) { - return this.addListener('eth', 'newHeads', callback); + return this.addListener('eth', 'newHeads', callback, null); } logs (callback) { diff --git a/js/src/api/pubsub/parity/parity.js b/js/src/api/pubsub/parity/parity.js index bf18effa1..5551a9c3b 100644 --- a/js/src/api/pubsub/parity/parity.js +++ b/js/src/api/pubsub/parity/parity.js @@ -266,7 +266,7 @@ export default class Parity extends PubsubBase { // parity accounts API (only secure API or configured to be exposed) allAccountsInfo (callback) { - return this._addListener(this._api, 'parity_allAccountsInfo', (error, data) => { + return this.addListener(this._api, 'parity_allAccountsInfo', (error, data) => { error ? callback(error) : callback(null, outAccountInfo(data)); @@ -274,7 +274,7 @@ export default class Parity extends PubsubBase { } getDappAddresses (callback, dappId) { - return this._addListener(this._api, 'parity_getDappAddresses', (error, data) => { + return this.addListener(this._api, 'parity_getDappAddresses', (error, data) => { error ? callback(error) : callback(null, outAddresses(data)); @@ -282,7 +282,7 @@ export default class Parity extends PubsubBase { } getDappDefaultAddress (callback, dappId) { - return this._addListener(this._api, 'parity_getDappDefaultAddress', (error, data) => { + return this.addListener(this._api, 'parity_getDappDefaultAddress', (error, data) => { error ? callback(error) : callback(null, outAddress(data)); @@ -290,7 +290,7 @@ export default class Parity extends PubsubBase { } getNewDappsAddresses (callback) { - return this._addListener(this._api, 'parity_getDappDefaultAddress', (error, addresses) => { + return this.addListener(this._api, 'parity_getDappDefaultAddress', (error, addresses) => { error ? callback(error) : callback(null, addresses ? addresses.map(outAddress) : null); @@ -298,7 +298,7 @@ export default class Parity extends PubsubBase { } getNewDappsDefaultAddress (callback) { - return this._addListener(this._api, 'parity_getNewDappsDefaultAddress', (error, data) => { + return this.addListener(this._api, 'parity_getNewDappsDefaultAddress', (error, data) => { error ? callback(error) : callback(null, outAddress(data)); @@ -306,7 +306,7 @@ export default class Parity extends PubsubBase { } listRecentDapps (callback) { - return this._addListener(this._api, 'parity_listRecentDapps', (error, data) => { + return this.addListener(this._api, 'parity_listRecentDapps', (error, data) => { error ? callback(error) : callback(null, outRecentDapps(data)); @@ -314,7 +314,7 @@ export default class Parity extends PubsubBase { } listGethAccounts (callback) { - return this._addListener(this._api, 'parity_listGethAccounts', (error, data) => { + return this.addListener(this._api, 'parity_listGethAccounts', (error, data) => { error ? callback(error) : callback(null, outAddresses(data)); @@ -322,15 +322,15 @@ export default class Parity extends PubsubBase { } listVaults (callback) { - return this._addListener(this._api, 'parity_listVaults', callback); + return this.addListener(this._api, 'parity_listVaults', callback); } listOpenedVaults (callback) { - return this._addListener(this._api, 'parity_listOpenedVaults', callback); + return this.addListener(this._api, 'parity_listOpenedVaults', callback); } getVaultMeta (callback, vaultName) { - return this._addListener(this._api, 'parity_getVaultMeta', (error, data) => { + return this.addListener(this._api, 'parity_getVaultMeta', (error, data) => { error ? callback(error) : callback(null, outVaultMeta(data)); @@ -338,7 +338,7 @@ export default class Parity extends PubsubBase { } deriveAddressHash (callback, address, password, hash, shouldSave) { - return this._addListener(this._api, 'parity_deriveAddressHash', (error, data) => { + return this.addListener(this._api, 'parity_deriveAddressHash', (error, data) => { error ? callback(error) : callback(null, outAddress(data)); @@ -346,10 +346,18 @@ export default class Parity extends PubsubBase { } deriveAddressIndex (callback, address, password, index, shouldSave) { - return this._addListener(this._api, 'parity_deriveAddressIndex', (error, data) => { + return this.addListener(this._api, 'parity_deriveAddressIndex', (error, data) => { error ? callback(error) : callback(null, outAddress(data)); }, [inAddress(address), password, inDeriveIndex(index), !!shouldSave]); } + + nodeHealth (callback) { + return this.addListener(this._api, 'parity_nodeHealth', (error, data) => { + error + ? callback(error) + : callback(null, data); + }); + } } diff --git a/js/src/api/pubsub/pubsub.js b/js/src/api/pubsub/pubsub.js index edbc201ae..4420967ee 100644 --- a/js/src/api/pubsub/pubsub.js +++ b/js/src/api/pubsub/pubsub.js @@ -16,6 +16,7 @@ import Eth from './eth'; import Parity from './parity'; +import Signer from './signer'; import Net from './net'; import { isFunction } from '../util/types'; @@ -29,6 +30,7 @@ export default class Pubsub { this._eth = new Eth(transport); this._net = new Net(transport); this._parity = new Parity(transport); + this._signer = new Signer(transport); } get net () { @@ -43,8 +45,35 @@ export default class Pubsub { return this._parity; } + get signer () { + return this._signer; + } + unsubscribe (subscriptionIds) { // subscriptions are namespace independent. Thus we can simply removeListener from any. return this._parity.removeListener(subscriptionIds); } + + subscribeAndGetResult (f, callback) { + return new Promise((resolve, reject) => { + let isFirst = true; + let onSubscription = (error, data) => { + const p1 = error ? Promise.reject(error) : Promise.resolve(data); + const p2 = p1.then(callback); + + if (isFirst) { + isFirst = false; + p2 + .then(resolve) + .catch(reject); + } + }; + + try { + f.call(this, onSubscription).catch(reject); + } catch (err) { + reject(err); + } + }); + } } diff --git a/js/src/api/pubsub/pubsubBase.js b/js/src/api/pubsub/pubsubBase.js index fcc7525d5..c50f45775 100644 --- a/js/src/api/pubsub/pubsubBase.js +++ b/js/src/api/pubsub/pubsubBase.js @@ -20,11 +20,12 @@ export default class PubsubBase { this._transport = transport; } - addListener (module, eventName, callback, eventParams) { - return eventParams - ? this._transport.subscribe(module, callback, eventName, eventParams) - : this._transport.subscribe(module, callback, eventName, []); - // this._transport.subscribe(module, callback, eventName); After Patch from tomac is merged to master! => eth_subscribe does not support empty array as params + addListener (module, eventName, callback, eventParams = []) { + if (eventName) { + return this._transport.subscribe(module, callback, eventParams ? [eventName, eventParams] : [eventName]); + } + + return this._transport.subscribe(module, callback, eventParams); } removeListener (subscriptionIds) { diff --git a/js/src/api/pubsub/signer/index.js b/js/src/api/pubsub/signer/index.js new file mode 100644 index 000000000..caa410722 --- /dev/null +++ b/js/src/api/pubsub/signer/index.js @@ -0,0 +1,16 @@ +// Copyright 2015-2017 Parity Technologies (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 . +export default from './signer'; diff --git a/js/src/api/pubsub/signer/signer.js b/js/src/api/pubsub/signer/signer.js new file mode 100644 index 000000000..266da6b8a --- /dev/null +++ b/js/src/api/pubsub/signer/signer.js @@ -0,0 +1,37 @@ +// Copyright 2015-2017 Parity Technologies (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 . +import PubsubBase from '../pubsubBase'; + +import { outSignerRequest } from '../../format/output'; + +export default class Net extends PubsubBase { + constructor (transport) { + super(transport); + this._api = { + subscribe: 'signer_subscribePending', + unsubscribe: 'signer_unsubscribePending', + subscription: 'signer_pending' + }; + } + + pendingRequests (callback) { + return this.addListener(this._api, null, (error, data) => { + error + ? callback(error) + : callback(null, data.map(outSignerRequest)); + }); + } +} diff --git a/js/src/api/rpc/parity/parity.js b/js/src/api/rpc/parity/parity.js index 4fdaf5b1b..98583123f 100644 --- a/js/src/api/rpc/parity/parity.js +++ b/js/src/api/rpc/parity/parity.js @@ -44,6 +44,15 @@ export default class Parity { .execute('parity_addReservedPeer', enode); } + call (requests, blockNumber = 'latest') { + return this._transport + .execute( + 'parity_call', + requests.map((options) => inOptions(options)), + inBlockNumber(blockNumber) + ); + } + chainStatus () { return this._transport .execute('parity_chainStatus') @@ -380,6 +389,11 @@ export default class Parity { .then(outNumber); } + nodeHealth () { + return this._transport + .execute('parity_nodeHealth'); + } + nodeName () { return this._transport .execute('parity_nodeName'); diff --git a/js/src/api/subscriptions/eth.js b/js/src/api/subscriptions/eth.js index 8e56f335f..bbab95672 100644 --- a/js/src/api/subscriptions/eth.js +++ b/js/src/api/subscriptions/eth.js @@ -24,6 +24,13 @@ export default class Eth { this._lastBlock = new BigNumber(-1); this._pollTimerId = null; + + // Try to restart subscription if transport is closed + this._api.transport.on('close', () => { + if (this.isStarted) { + this.start(); + } + }); } get isStarted () { @@ -33,31 +40,56 @@ export default class Eth { start () { this._started = true; - return this._blockNumber(); + if (this._api.isPubSub) { + return Promise.all([ + this._pollBlockNumber(false), + this._api.pubsub + .subscribeAndGetResult( + callback => this._api.pubsub.eth.newHeads(callback), + () => { + return this._api.eth + .blockNumber() + .then(blockNumber => { + this.updateBlock(blockNumber); + return blockNumber; + }); + } + ) + ]); + } + + // fallback to polling + return this._pollBlockNumber(true); } - _blockNumber = () => { - const nextTimeout = (timeout = 1000) => { - this._pollTimerId = setTimeout(() => { - this._blockNumber(); - }, timeout); + _pollBlockNumber = (doTimeout) => { + const nextTimeout = (timeout = 1000, forceTimeout = doTimeout) => { + if (forceTimeout) { + this._pollTimerId = setTimeout(() => { + this._pollBlockNumber(doTimeout); + }, timeout); + } }; if (!this._api.transport.isConnected) { - nextTimeout(500); + nextTimeout(500, true); return; } return this._api.eth .blockNumber() .then((blockNumber) => { - if (!blockNumber.eq(this._lastBlock)) { - this._lastBlock = blockNumber; - this._updateSubscriptions('eth_blockNumber', null, blockNumber); - } + this.updateBlock(blockNumber); nextTimeout(); }) .catch(() => nextTimeout()); } + + updateBlock (blockNumber) { + if (!blockNumber.eq(this._lastBlock)) { + this._lastBlock = blockNumber; + this._updateSubscriptions('eth_blockNumber', null, blockNumber); + } + } } diff --git a/js/src/api/subscriptions/eth.spec.js b/js/src/api/subscriptions/eth.spec.js index 3f5ee81d6..2893a14dc 100644 --- a/js/src/api/subscriptions/eth.spec.js +++ b/js/src/api/subscriptions/eth.spec.js @@ -29,7 +29,8 @@ function stubApi (blockNumber) { return { _calls, transport: { - isConnected: true + isConnected: true, + on: () => {} }, eth: { blockNumber: () => { diff --git a/js/src/api/subscriptions/manager.spec.js b/js/src/api/subscriptions/manager.spec.js index df708a36d..05d645e6b 100644 --- a/js/src/api/subscriptions/manager.spec.js +++ b/js/src/api/subscriptions/manager.spec.js @@ -23,7 +23,8 @@ function newStub () { const manager = new Manager({ transport: { - isConnected: true + isConnected: true, + on: sinon.stub() } }); diff --git a/js/src/api/subscriptions/personal.js b/js/src/api/subscriptions/personal.js index fa7ae823c..8b2b826d0 100644 --- a/js/src/api/subscriptions/personal.js +++ b/js/src/api/subscriptions/personal.js @@ -23,6 +23,13 @@ export default class Personal { this._lastDefaultAccount = '0x0'; this._pollTimerId = null; + + // Try to restart subscription if transport is closed + this._api.transport.on('close', () => { + if (this.isStarted) { + this.start(); + } + }); } get isStarted () { @@ -32,20 +39,42 @@ export default class Personal { start () { this._started = true; + let defaultAccount = null; + + if (this._api.isPubSub) { + defaultAccount = this._api.pubsub + .subscribeAndGetResult( + callback => this._api.pubsub.parity.defaultAccount(callback), + (defaultAccount) => { + this.updateDefaultAccount(defaultAccount); + return defaultAccount; + } + ); + } else { + defaultAccount = this._defaultAccount(); + } + return Promise.all([ - this._defaultAccount(), + defaultAccount, this._listAccounts(), this._accountsInfo(), this._loggingSubscribe() ]); } + updateDefaultAccount (defaultAccount) { + if (this._lastDefaultAccount !== defaultAccount) { + this._lastDefaultAccount = defaultAccount; + this._updateSubscriptions('parity_defaultAccount', null, defaultAccount); + } + } + // FIXME: Because of the different API instances, the "wait for valid changes" approach // doesn't work. Since the defaultAccount is critical to operation, we poll in exactly // same way we do in ../eth (ala eth_blockNumber) and update. This should be moved // to pub-sub as it becomes available _defaultAccount = (timerDisabled = false) => { - const nextTimeout = (timeout = 1000) => { + const nextTimeout = (timeout = 3000) => { if (!timerDisabled) { this._pollTimerId = setTimeout(() => { this._defaultAccount(); @@ -61,11 +90,7 @@ export default class Personal { return this._api.parity .defaultAccount() .then((defaultAccount) => { - if (this._lastDefaultAccount !== defaultAccount) { - this._lastDefaultAccount = defaultAccount; - this._updateSubscriptions('parity_defaultAccount', null, defaultAccount); - } - + this.updateDefaultAccount(defaultAccount); nextTimeout(); }) .catch(() => nextTimeout()); diff --git a/js/src/api/subscriptions/personal.spec.js b/js/src/api/subscriptions/personal.spec.js index ac046d250..3e950579c 100644 --- a/js/src/api/subscriptions/personal.spec.js +++ b/js/src/api/subscriptions/personal.spec.js @@ -37,7 +37,8 @@ function stubApi (accounts, info) { return { _calls, transport: { - isConnected: true + isConnected: true, + on: sinon.stub() }, parity: { accountsInfo: () => { diff --git a/js/src/api/subscriptions/signer.js b/js/src/api/subscriptions/signer.js index 2215ed7f3..a0c202c1b 100644 --- a/js/src/api/subscriptions/signer.js +++ b/js/src/api/subscriptions/signer.js @@ -22,6 +22,13 @@ export default class Signer { this._api = api; this._updateSubscriptions = updateSubscriptions; this._started = false; + + // Try to restart subscription if transport is closed + this._api.transport.on('close', () => { + if (this.isStarted) { + this.start(); + } + }); } get isStarted () { @@ -31,30 +38,50 @@ export default class Signer { start () { this._started = true; + if (this._api.isPubSub) { + const subscription = this._api.pubsub + .subscribeAndGetResult( + callback => this._api.pubsub.signer.pendingRequests(callback), + requests => { + this.updateSubscriptions(requests); + return requests; + } + ); + + return Promise.all([ + this._listRequests(false), + subscription + ]); + } + return Promise.all([ this._listRequests(true), this._loggingSubscribe() ]); } + updateSubscriptions (requests) { + return this._updateSubscriptions('signer_requestsToConfirm', null, requests); + } + _listRequests = (doTimeout) => { - const nextTimeout = (timeout = 1000) => { - if (doTimeout) { + const nextTimeout = (timeout = 1000, forceTimeout = doTimeout) => { + if (forceTimeout) { setTimeout(() => { - this._listRequests(true); + this._listRequests(doTimeout); }, timeout); } }; if (!this._api.transport.isConnected) { - nextTimeout(500); + nextTimeout(500, true); return; } return this._api.signer .requestsToConfirm() .then((requests) => { - this._updateSubscriptions('signer_requestsToConfirm', null, requests); + this.updateSubscriptions(requests); nextTimeout(); }) .catch(() => nextTimeout()); diff --git a/js/src/api/transport/jsonRpcBase.js b/js/src/api/transport/jsonRpcBase.js index 819e1f496..1b96347d3 100644 --- a/js/src/api/transport/jsonRpcBase.js +++ b/js/src/api/transport/jsonRpcBase.js @@ -15,7 +15,11 @@ // along with Parity. If not, see . import EventEmitter from 'eventemitter3'; + import { Logging } from '../subscriptions'; +import logger from './logger'; + +const LOGGER_ENABLED = process.env.NODE_ENV !== 'production'; export default class JsonRpcBase extends EventEmitter { constructor () { @@ -75,6 +79,14 @@ export default class JsonRpcBase extends EventEmitter { } execute (method, ...params) { + let start; + let logId; + + if (LOGGER_ENABLED) { + start = Date.now(); + logId = logger.log({ method, params }); + } + return this._middlewareList.then((middlewareList) => { for (const middleware of middlewareList) { const res = middleware.handle(method, params); @@ -93,7 +105,18 @@ export default class JsonRpcBase extends EventEmitter { } } - return this._execute(method, params); + const result = this._execute(method, params); + + if (!LOGGER_ENABLED) { + return result; + } + + return result + .then((result) => { + logger.set(logId, { result, time: Date.now() - start }); + + return result; + }); }); } diff --git a/js/src/api/transport/logger.js b/js/src/api/transport/logger.js new file mode 100644 index 000000000..930c4a34e --- /dev/null +++ b/js/src/api/transport/logger.js @@ -0,0 +1,150 @@ +// Copyright 2015-2017 Parity Technologies (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 . + +import MethodDecodingStore from '~/ui/MethodDecoding/methodDecodingStore'; + +const LOGGER_ENABLED = process.env.NODE_ENV !== 'production'; + +let logger; + +if (LOGGER_ENABLED) { + class Logger { + _logs = {}; + _id = 0; + + log ({ method, params }) { + const logId = this._id++; + + this._logs[logId] = { method, params, date: Date.now() }; + return logId; + } + + set (logId, data) { + this._logs[logId] = { + ...this._logs[logId], + ...data + }; + } + + static sorter (logA, logB) { + return logA.date - logB.date; + } + + get calls () { + const calls = this.methods['eth_call'] || []; + const decoding = MethodDecodingStore.get(window.secureApi); + const contracts = {}; + + const progress = Math.round(calls.length / 20); + + return calls + .reduce((promise, call, index) => { + const { data, to } = call.params[0]; + + contracts[to] = contracts[to] || []; + + return promise + .then(() => decoding.lookup(null, { data, to })) + .then((lookup) => { + if (!lookup.name) { + contracts[to].push(data); + return; + } + + const inputs = lookup.inputs.map((input) => { + if (/bytes/.test(input.type)) { + return '0x' + input.value.map((v) => v.toString(16).padStart(2, 0)).join(''); + } + + return input.value; + }); + + const called = `${lookup.name}(${inputs.join(', ')})`; + + contracts[to].push(called); + + if (index % progress === 0) { + console.warn(`progress: ${Math.round(100 * index / calls.length)}%`); + } + }); + }, Promise.resolve()) + .then(() => { + return Object.keys(contracts) + .map((address) => { + const count = contracts[address].length; + + return { + count, + calls: contracts[address], + to: address + }; + }) + .sort((cA, cB) => cB.count - cA.count); + }); + } + + get logs () { + return Object.values(this._logs).sort(Logger.sorter); + } + + get methods () { + return this.logs.reduce((methods, log) => { + methods[log.method] = methods[log.method] || []; + methods[log.method].push(log); + return methods; + }, {}); + } + + get stats () { + const logs = this.logs; + const methods = this.methods; + + const start = logs[0].date; + const end = logs[logs.length - 1].date; + + // Duration in seconds + const duration = (end - start) / 1000; + const speed = logs.length / duration; + + const sortedMethods = Object.keys(methods) + .map((method) => { + const methodLogs = methods[method].sort(Logger.sorter); + const methodSpeed = methodLogs.length / duration; + + return { + speed: methodSpeed, + count: methodLogs.length, + logs: methodLogs, + method + }; + }) + .sort((mA, mB) => mB.count - mA.count); + + return { + methods: sortedMethods, + speed + }; + } + } + + logger = new Logger(); + + if (window) { + window._logger = logger; + } +} + +export default logger; diff --git a/js/src/api/transport/ws/ws.js b/js/src/api/transport/ws/ws.js index 3c642d5f8..63abecb83 100644 --- a/js/src/api/transport/ws/ws.js +++ b/js/src/api/transport/ws/ws.js @@ -29,7 +29,7 @@ export default class Ws extends JsonRpcBase { this._url = url; this._token = token; this._messages = {}; - this._subscriptions = { 'eth_subscription': [], 'parity_subscription': [] }; + this._subscriptions = {}; this._sessionHash = null; this._connecting = false; @@ -209,6 +209,7 @@ export default class Ws extends JsonRpcBase { // initial pubsub ACK if (id && msg.subscription) { // save subscription to map subId -> messageId + this._subscriptions[msg.subscription] = this._subscriptions[msg.subscription] || {}; this._subscriptions[msg.subscription][res] = id; // resolve promise with messageId because subId's can collide (eth/parity) msg.resolve(id); @@ -223,7 +224,7 @@ export default class Ws extends JsonRpcBase { } // pubsub format - if (method.includes('subscription')) { + if (this._subscriptions[method]) { const messageId = this._messages[this._subscriptions[method][params.subscription]]; if (messageId) { @@ -302,6 +303,16 @@ export default class Ws extends JsonRpcBase { } _methodsFromApi (api) { + if (api.subscription) { + const { subscribe, unsubscribe, subscription } = api; + + return { + method: subscribe, + uMethod: unsubscribe, + subscription + }; + } + const method = `${api}_subscribe`; const uMethod = `${api}_unsubscribe`; const subscription = `${api}_subscription`; @@ -309,7 +320,7 @@ export default class Ws extends JsonRpcBase { return { method, uMethod, subscription }; } - subscribe (api, callback, ...params) { + subscribe (api, callback, params) { return new Promise((resolve, reject) => { const id = this.id; const { method, uMethod, subscription } = this._methodsFromApi(api); diff --git a/js/src/index.js b/js/src/index.js index 7e85dd51f..24cf38286 100644 --- a/js/src/index.js +++ b/js/src/index.js @@ -36,7 +36,6 @@ import muiTheme from '~/ui/Theme'; import MainApplication from './main'; import { loadSender, patchApi } from '~/util/tx'; -import { setApi } from '~/redux/providers/apiActions'; import './environment'; @@ -69,9 +68,6 @@ ContractInstances.create(api); const store = initStore(api, hashHistory); -store.dispatch({ type: 'initAll', api }); -store.dispatch(setApi(api)); - window.secureApi = api; ReactDOM.render( diff --git a/js/src/mobx/hardwareStore.js b/js/src/mobx/hardwareStore.js index 46bf3fa58..5a3960c2f 100644 --- a/js/src/mobx/hardwareStore.js +++ b/js/src/mobx/hardwareStore.js @@ -31,6 +31,10 @@ export default class HardwareStore { this._pollId = null; this._pollScan(); + this._subscribeParity(); + this._api.transport.on('close', () => { + this._subscribeParity(); + }); } isConnected (address) { @@ -78,26 +82,30 @@ export default class HardwareStore { }); } - scanParity () { - return this._api.parity - .hardwareAccountsInfo() - .then((hwInfo) => { - Object - .keys(hwInfo) - .forEach((address) => { - const info = hwInfo[address]; + _subscribeParity () { + const onError = error => { + console.warn('HardwareStore::scanParity', error); - info.address = address; - info.via = 'parity'; - }); + return {}; + }; - return hwInfo; - }) - .catch((error) => { - console.warn('HardwareStore::scanParity', error); + return this._api.pubsub + .subscribeAndGetResult( + callback => this._api.pubsub.parity.hardwareAccountsInfo(callback), + hwInfo => { + Object + .keys(hwInfo) + .forEach((address) => { + const info = hwInfo[address]; - return {}; - }); + info.address = address; + info.via = 'parity'; + }); + this.setWallets(hwInfo); + return hwInfo; + }, + onError + ).catch(onError); } scan () { @@ -107,14 +115,10 @@ export default class HardwareStore { // is done, different results will be retrieved via Parity vs. the browser APIs // (latter is Chrome-only, needs the browser app enabled on a Ledger, former is // not intended as a network call, i.e. hw wallet is with the user) - return Promise - .all([ - this.scanParity(), - this.scanLedger() - ]) - .then(([hwAccounts, ledgerAccounts]) => { + return this.scanLedger() + .then((ledgerAccounts) => { transaction(() => { - this.setWallets(Object.assign({}, hwAccounts, ledgerAccounts)); + this.setWallets(Object.assign({}, ledgerAccounts)); this.setScanning(false); }); }); diff --git a/js/src/mobx/hardwareStore.spec.js b/js/src/mobx/hardwareStore.spec.js index 784fc3f10..c620294f8 100644 --- a/js/src/mobx/hardwareStore.spec.js +++ b/js/src/mobx/hardwareStore.spec.js @@ -31,6 +31,12 @@ let store; function createApi () { api = { + transport: { + on: sinon.stub() + }, + pubsub: { + subscribeAndGetResult: sinon.stub().returns(Promise.reject(new Error('not connected'))) + }, parity: { hardwareAccountsInfo: sinon.stub().resolves({ ADDRESS: WALLET }), setAccountMeta: sinon.stub().resolves(true), @@ -195,22 +201,11 @@ describe('mobx/HardwareStore', () => { }); }); - describe('scanParity', () => { - beforeEach(() => { - return store.scanParity(); - }); - - it('calls parity_hardwareAccountsInfo', () => { - expect(api.parity.hardwareAccountsInfo).to.have.been.called; - }); - }); - describe('scan', () => { beforeEach(() => { sinon.spy(store, 'setScanning'); sinon.spy(store, 'setWallets'); sinon.spy(store, 'scanLedger'); - sinon.spy(store, 'scanParity'); return store.scan(); }); @@ -219,17 +214,12 @@ describe('mobx/HardwareStore', () => { store.setScanning.restore(); store.setWallets.restore(); store.scanLedger.restore(); - store.scanParity.restore(); }); it('calls scanLedger', () => { expect(store.scanLedger).to.have.been.called; }); - it('calls scanParity', () => { - expect(store.scanParity).to.have.been.called; - }); - it('sets and resets the scanning state', () => { expect(store.setScanning).to.have.been.calledWith(true); expect(store.setScanning).to.have.been.calledWith(false); diff --git a/js/src/modals/Transfer/store.js b/js/src/modals/Transfer/store.js index eaccf4f40..71458c85d 100644 --- a/js/src/modals/Transfer/store.js +++ b/js/src/modals/Transfer/store.js @@ -133,8 +133,8 @@ export default class TransferStore { } @action handleClose = () => { - this.stage = 0; this.onClose(); + this.stage = 0; } @action onUpdateDetails = (type, value) => { @@ -169,7 +169,6 @@ export default class TransferStore { } @action onSend = () => { - this.onNext(); this.sending = true; this diff --git a/js/src/modals/Transfer/transfer.js b/js/src/modals/Transfer/transfer.js index ab769ff02..fd2625ee7 100644 --- a/js/src/modals/Transfer/transfer.js +++ b/js/src/modals/Transfer/transfer.js @@ -192,7 +192,7 @@ class Transfer extends Component { renderDialogActions () { const { account } = this.props; - const { extras, sending, stage } = this.store; + const { extras, sending, stage, isValid } = this.store; const cancelBtn = (