diff --git a/.gitmodules b/.gitmodules index 06b71f6ae..b49256b4d 100644 --- a/.gitmodules +++ b/.gitmodules @@ -2,3 +2,6 @@ path = ethcore/res/ethereum/tests url = https://github.com/ethereum/tests.git branch = develop +[submodule "ethcore/res/wasm-tests"] + path = ethcore/res/wasm-tests + url = https://github.com/paritytech/wasm-tests diff --git a/Cargo.lock b/Cargo.lock index b346a7cad..fe59b17b9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -57,6 +57,39 @@ dependencies = [ "syntex_syntax 0.58.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "atty" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "backtrace" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "backtrace-sys 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "dbghelp-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-demangle 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "backtrace-sys" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "gcc 0.3.51 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "base-x" version = "0.2.2" @@ -119,6 +152,11 @@ name = "bitflags" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "bitflags" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "bitflags" version = "0.9.1" @@ -144,6 +182,11 @@ dependencies = [ "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "byteorder" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "byteorder" version = "1.0.0" @@ -173,6 +216,21 @@ dependencies = [ "multihash 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "clap" +version = "2.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "atty 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", + "strsim 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "term_size 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-segmentation 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "vec_map 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "clippy" version = "0.0.103" @@ -195,6 +253,14 @@ dependencies = [ "unicode-normalization 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "conv" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "custom_derive 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "cookie" version = "0.3.1" @@ -245,6 +311,11 @@ dependencies = [ "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "custom_derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "daemonize" version = "0.2.2" @@ -253,6 +324,15 @@ dependencies = [ "libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "dbghelp-sys" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "deque" version = "0.3.1" @@ -305,6 +385,14 @@ dependencies = [ "regex 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "error-chain" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "backtrace 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "eth-secp256k1" version = "0.5.6" @@ -376,6 +464,7 @@ dependencies = [ "native-contracts 0.1.0", "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)", + "parity-wasm 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.3.14 (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)", @@ -385,6 +474,7 @@ dependencies = [ "stats 0.1.0", "time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", "transient-hashmap 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-utils 0.1.0 (git+https://github.com/paritytech/wasm-utils)", ] [[package]] @@ -1029,7 +1119,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "jsonrpc-core" version = "7.0.0" -source = "git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7#d12476f42ee672fa9d023f66fcfa5981d9aaba3a" +source = "git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7#5e79be8a098cdda221713992f4a46b41a1d4d8f0" dependencies = [ "futures 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1041,7 +1131,7 @@ dependencies = [ [[package]] name = "jsonrpc-http-server" version = "7.0.0" -source = "git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7#d12476f42ee672fa9d023f66fcfa5981d9aaba3a" +source = "git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7#5e79be8a098cdda221713992f4a46b41a1d4d8f0" dependencies = [ "hyper 0.10.0-a.0 (git+https://github.com/paritytech/hyper)", "jsonrpc-core 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)", @@ -1054,7 +1144,7 @@ dependencies = [ [[package]] name = "jsonrpc-ipc-server" version = "7.0.0" -source = "git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7#d12476f42ee672fa9d023f66fcfa5981d9aaba3a" +source = "git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7#5e79be8a098cdda221713992f4a46b41a1d4d8f0" dependencies = [ "bytes 0.4.4 (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)", @@ -1067,7 +1157,7 @@ dependencies = [ [[package]] name = "jsonrpc-macros" version = "7.0.0" -source = "git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7#d12476f42ee672fa9d023f66fcfa5981d9aaba3a" +source = "git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7#5e79be8a098cdda221713992f4a46b41a1d4d8f0" dependencies = [ "jsonrpc-core 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)", "jsonrpc-pubsub 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)", @@ -1077,7 +1167,7 @@ dependencies = [ [[package]] name = "jsonrpc-minihttp-server" version = "7.0.0" -source = "git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7#d12476f42ee672fa9d023f66fcfa5981d9aaba3a" +source = "git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7#5e79be8a098cdda221713992f4a46b41a1d4d8f0" dependencies = [ "bytes 0.4.4 (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)", @@ -1092,7 +1182,7 @@ dependencies = [ [[package]] name = "jsonrpc-pubsub" version = "7.0.0" -source = "git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7#d12476f42ee672fa9d023f66fcfa5981d9aaba3a" +source = "git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7#5e79be8a098cdda221713992f4a46b41a1d4d8f0" dependencies = [ "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)", @@ -1102,8 +1192,9 @@ dependencies = [ [[package]] name = "jsonrpc-server-utils" version = "7.0.0" -source = "git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7#d12476f42ee672fa9d023f66fcfa5981d9aaba3a" +source = "git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7#5e79be8a098cdda221713992f4a46b41a1d4d8f0" dependencies = [ + "bytes 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", "globset 0.1.4 (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)", @@ -1114,7 +1205,7 @@ dependencies = [ [[package]] name = "jsonrpc-tcp-server" version = "7.0.0" -source = "git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7#d12476f42ee672fa9d023f66fcfa5981d9aaba3a" +source = "git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7#5e79be8a098cdda221713992f4a46b41a1d4d8f0" dependencies = [ "bytes 0.4.4 (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)", @@ -1128,11 +1219,13 @@ dependencies = [ [[package]] name = "jsonrpc-ws-server" version = "7.0.0" -source = "git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7#d12476f42ee672fa9d023f66fcfa5981d9aaba3a" +source = "git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7#5e79be8a098cdda221713992f4a46b41a1d4d8f0" dependencies = [ "jsonrpc-core 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)", "jsonrpc-server-utils 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)", + "parking_lot 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "slab 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "ws 0.7.1 (git+https://github.com/tomusdrw/ws-rs)", ] @@ -1432,6 +1525,19 @@ name = "nom" version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "ntp" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byteorder 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", + "conv 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "custom_derive 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "error-chain 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "num" version = "0.1.32" @@ -1592,6 +1698,7 @@ 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)", "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)", @@ -1634,12 +1741,14 @@ dependencies = [ "ethcore-util 1.7.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)", "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)", "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", @@ -1743,6 +1852,7 @@ dependencies = [ "ethsync 1.7.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)", "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)", @@ -1848,6 +1958,16 @@ dependencies = [ "target_info 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "parity-wasm" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", + "parking_lot 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "parity-wordlist" version = "1.0.1" @@ -2193,6 +2313,11 @@ dependencies = [ "time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "rustc-demangle" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "rustc-hex" version = "1.0.0" @@ -2523,6 +2648,16 @@ dependencies = [ "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "term_size" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "termios" version = "0.2.2" @@ -2743,6 +2878,11 @@ name = "unicode-segmentation" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "unicode-width" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "unicode-xid" version = "0.0.4" @@ -2784,6 +2924,11 @@ name = "utf8-ranges" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "vec_map" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "vecio" version = "0.1.0" @@ -2807,6 +2952,18 @@ name = "void" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "wasm-utils" +version = "0.1.0" +source = "git+https://github.com/paritytech/wasm-utils#fee06b6d5826c2dc1fc1aa183b0c2c75e3e140c3" +dependencies = [ + "clap 2.24.2 (registry+https://github.com/rust-lang/crates.io-index)", + "env_logger 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", + "parity-wasm 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "winapi" version = "0.2.8" @@ -2882,6 +3039,9 @@ dependencies = [ "checksum app_dirs 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b7d1c0d48a81bbb13043847f957971f4d87c81542d80ece5e84ba3cba4058fd4" "checksum arrayvec 0.3.20 (registry+https://github.com/rust-lang/crates.io-index)" = "d89f1b0e242270b5b797778af0c8d182a1a2ccac5d8d6fadf414223cc0fab096" "checksum aster 0.41.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4ccfdf7355d9db158df68f976ed030ab0f6578af811f5a7bb6dcf221ec24e0e0" +"checksum atty 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d912da0db7fa85514874458ca3651fe2cddace8d0b0505571dbdcd41ab490159" +"checksum backtrace 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "346d7644f0b5f9bc73082d3b2236b69a05fd35cce0cfa3724e184e6a5c9e2a2f" +"checksum backtrace-sys 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "3a0d842ea781ce92be2bf78a9b38883948542749640b8378b3b2f03d1fd9f1ff" "checksum base-x 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2f59103b47307f76e03bef1633aec7fa9e29bfb5aa6daf5a334f94233c71f6c1" "checksum base32 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1b9605ba46d61df0410d8ac686b0007add8172eba90e8e909c347856fe794d8c" "checksum bigint 3.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d0673c930652d3d4d6dcd5c45b5db4fa5f8f33994d7323618c43c083b223e8c" @@ -2891,23 +3051,29 @@ dependencies = [ "checksum bit-vec 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "5b97c2c8e8bbb4251754f559df8af22fb264853c7d009084a576cdf12565089d" "checksum bitflags 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4f67931368edf3a9a51d29886d245f1c3db2f1ef0dcc9e35ff70341b78c10d23" "checksum bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d" +"checksum bitflags 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1370e9fc2a6ae53aea8b7a5110edbd08836ed87c88736dfabccade1c2b44bff4" "checksum bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4efd02e230a02e18f92fc2735f44597385ed02ad8f831e7c1c1156ee5e1ab3a5" "checksum blastfig 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "09640e0509d97d5cdff03a9f5daf087a8e04c735c3b113a75139634a19cfc7b2" "checksum bloomchain 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3f421095d2a76fc24cd3fb3f912b90df06be7689912b1bdb423caefae59c258d" "checksum bn 0.4.4 (git+https://github.com/paritytech/bn)" = "" +"checksum byteorder 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "0fc10e8cc6b2580fda3f36eb6dc5316657f812a3df879a44a66fc9f0fdbc4855" "checksum byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c40977b0ee6b9885c9013cd41d9feffdd22deb3bb4dc3a71d901cc7a77de18c8" "checksum bytes 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8b24f16593f445422331a5eed46b72f7f171f910fead4f2ea8f17e727e9c5c14" "checksum cfg-if 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "de1e760d7b6535af4241fca8bd8adf68e2e7edacc6b29f5d399050c5e48cf88c" "checksum cid 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "34aa7da06f10541fbca6850719cdaa8fa03060a5d2fb33840f149cf8133a00c7" +"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 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" "checksum core-foundation-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "05eed248dc504a5391c63794fe4fb64f46f071280afaa1b73308f3c0ce4574c5" "checksum crossbeam 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "0c5ea215664ca264da8a9d9c3be80d2eaf30923c259d03e870388eb927508f97" "checksum crypt32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e34988f7e069e0b2f3bfc064295161e489b2d4e04a2e4248fb94360cdf00b4ec" "checksum ctrlc 1.1.1 (git+https://github.com/paritytech/rust-ctrlc.git)" = "" +"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" @@ -2915,6 +3081,7 @@ dependencies = [ "checksum either 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3d2b503c86dad62aaf414ecf2b8c527439abedb3f8d812537f0b12bfd6f32a91" "checksum elastic-array 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "258ff6a9a94f648d0379dbd79110e057edbb53eb85cc237e33eadf8e5a30df85" "checksum env_logger 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e3856f1697098606fc6cb97a93de88ca3f3bc35bb878c725920e6e82ecf05e83" +"checksum error-chain 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bd5c82c815138e278b8dcdeffc49f27ea6ffb528403e9dea4194f2e3dd40b143" "checksum eth-secp256k1 0.5.6 (git+https://github.com/paritytech/rust-secp256k1)" = "" "checksum ethabi 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0c3d62319ee0f35abf20afe8859dd2668195912614346447bb2dee9fb8da7c62" "checksum fdlimit 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b1ee15a7050e5580b3712877157068ea713b245b080ff302ae2ca973cfcd9baa" @@ -2985,6 +3152,7 @@ dependencies = [ "checksum net2 0.2.29 (registry+https://github.com/rust-lang/crates.io-index)" = "bc01404e7568680f1259aa5729539f221cb1e6d047a0d9053cab4be8a73b5d67" "checksum nodrop 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "52cd74cd09beba596430cc6e3091b74007169a56246e1262f0ba451ea95117b2" "checksum nom 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6caab12c5f97aa316cb249725aa32115118e1522b445e26c257dd77cad5ffd4e" +"checksum ntp 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d23f30ae7da76e2c6c2f5de53f298aa9a3911d3955ab2c349eb944caedceb088" "checksum num 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)" = "c04bd954dbf96f76bab6e5bd6cef6f1ce1262d15268ce4f926d2b5b778fa7af2" "checksum num-bigint 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)" = "41655c8d667be847a0b72fe0888857a7b3f052f691cf40852be5fcf87b274a65" "checksum num-complex 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)" = "ccac67baf893ac97474f8d70eff7761dabb1f6c66e71f8f1c67a6859218db810" @@ -3003,6 +3171,7 @@ dependencies = [ "checksum parity-dapps-glue 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e1d06f6ee0fda786df3784a96ee3f0629f529b91cbfb7d142f6410e6bcd1ce2c" "checksum parity-tokio-ipc 0.1.5 (git+https://github.com/nikvolf/parity-tokio-ipc)" = "" "checksum parity-ui-precompiled 1.4.0 (git+https://github.com/paritytech/js-precompiled.git)" = "" +"checksum parity-wasm 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)" = "51104c8b8da5cd0ebe0ab765dfab37bc1927b4a01a3d870b0fe09d9ee65e35ea" "checksum parity-wordlist 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "52142d717754f7ff7ef0fc8da1bdce4f302dd576fb9bf8b727d6a5fdef33348d" "checksum parking_lot 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aebb68eebde2c99f89592d925288600fde220177e46b5c9a91ca218d245aeedf" "checksum parking_lot_core 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fb1b97670a2ffadce7c397fb80a3d687c4f3060140b885621ef1653d0e5d5068" @@ -3039,6 +3208,7 @@ dependencies = [ "checksum rpassword 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5d3a99497c5c544e629cc8b359ae5ede321eba5fa8e5a8078f3ced727a976c3f" "checksum rpassword 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ab6e42be826e215f30ff830904f8f4a0933c6e2ae890e1af8b408f5bae60081e" "checksum rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)" = "f76d05d3993fd5f4af9434e8e436db163a12a9d40e1a58a726f27a01dfd12a2a" +"checksum rustc-demangle 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "3058a43ada2c2d0b92b3ae38007a2d0fa5e9db971be260e0171408a4ff471c95" "checksum rustc-hex 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0ceb8ce7a5e520de349e1fa172baeba4a9e8d5ef06c47471863530bc4972ee1e" "checksum rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)" = "6159e4e6e559c81bd706afe9c8fd68f547d3e851ce12e76b1de7914bab61691b" "checksum rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "c5f5376ea5e30ce23c03eb77cbe4962b988deead10910c372b226388b594c084" @@ -3080,6 +3250,7 @@ dependencies = [ "checksum target_info 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c63f48baada5c52e65a29eef93ab4f8982681b67f9e8d29c7b05abcfec2b9ffe" "checksum tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "87974a6f5c1dfb344d733055601650059a3363de2a6104819293baff662132d6" "checksum term 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "d168af3930b369cfe245132550579d47dfd873d69470755a19c2c6568dbbd989" +"checksum term_size 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2b6b55df3198cc93372e85dd2ed817f0e38ce8cc0f22eb32391bfad9c4bf209" "checksum termios 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d5d9cf598a6d7ce700a4e6a9199da127e6819a61e64b68609683cc9a01b5683a" "checksum thread-id 3.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4437c97558c70d129e40629a5b385b3fb1ffac301e63941335e4d354081ec14a" "checksum thread_local 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c85048c6260d17cf486ceae3282d9fb6b90be220bf5b28c400f5485ffc29f0c7" @@ -3103,15 +3274,18 @@ dependencies = [ "checksum unicode-bidi 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c1f7ceb96afdfeedee42bade65a0d585a6a0106f681b6749c8ff4daa8df30b3f" "checksum unicode-normalization 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "26643a2f83bac55f1976fb716c10234485f9202dcd65cfbdf9da49867b271172" "checksum unicode-segmentation 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "18127285758f0e2c6cf325bb3f3d138a12fee27de4f23e146cd6a179f26c2cf3" +"checksum unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "bf3a113775714a22dcb774d8ea3655c53a32debae63a063acc00a91cc586245f" "checksum unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc" "checksum unreachable 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1f2ae5ddb18e1c92664717616dd9549dde73f539f01bd7b77c2edb2446bdff91" "checksum untrusted 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6b65243989ef6aacd9c0d6bd2b822765c3361d8ed352185a6f3a41f3a718c673" "checksum url 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "afe9ec54bc4db14bc8744b7fed060d785ac756791450959b2248443319d5b119" "checksum user32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4ef4711d107b21b410a3a974b1204d9accc8b10dad75d8324b5d755de1617d47" "checksum utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "662fab6525a98beff2921d7f61a39e7d59e0b425ebc7d0d9e66d316e55124122" +"checksum vec_map 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "887b5b631c2ad01628bbbaa7dd4c869f80d3186688f8d0b6f58774fbe324988c" "checksum vecio 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0795a11576d29ae80525a3fda315bf7b534f8feb9d34101e5fe63fb95bb2fd24" "checksum vergen 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "56b639f935488eb40f06d17c3e3bcc3054f6f75d264e187b1107c8d1cba8d31c" "checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" +"checksum wasm-utils 0.1.0 (git+https://github.com/paritytech/wasm-utils)" = "" "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" "checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" "checksum ws 0.7.1 (git+https://github.com/tomusdrw/ws-rs)" = "" diff --git a/Cargo.toml b/Cargo.toml index 429ea0dd4..93341b564 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,7 @@ serde_json = "1.0" serde_derive = "1.0" app_dirs = "1.1.1" futures = "0.1" +futures-cpupool = "0.1" fdlimit = "0.1" ws2_32-sys = "0.2" ctrlc = { git = "https://github.com/paritytech/rust-ctrlc.git" } diff --git a/dapps/Cargo.toml b/dapps/Cargo.toml index f34335d2f..a0d63b6e3 100644 --- a/dapps/Cargo.toml +++ b/dapps/Cargo.toml @@ -11,11 +11,13 @@ authors = ["Parity Technologies "] 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" diff --git a/dapps/src/api/api.rs b/dapps/src/api/api.rs index d377ebe57..7d38e288f 100644 --- a/dapps/src/api/api.rs +++ b/dapps/src/api/api.rs @@ -18,23 +18,36 @@ use std::sync::Arc; use hyper::{server, net, Decoder, Encoder, Next, Control}; use hyper::method::Method; +use hyper::status::StatusCode; -use api::types::ApiError; -use api::response; +use api::{response, types}; +use api::time::TimeChecker; use apps::fetcher::Fetcher; - -use handlers::extract_url; +use handlers::{self, extract_url}; use endpoint::{Endpoint, Handler, EndpointPath}; +use parity_reactor::Remote; +use {SyncStatus}; #[derive(Clone)] pub struct RestApi { fetcher: Arc, + sync_status: Arc, + time: TimeChecker, + remote: Remote, } impl RestApi { - pub fn new(fetcher: Arc) -> Box { + pub fn new( + fetcher: Arc, + sync_status: Arc, + time: TimeChecker, + remote: Remote, + ) -> Box { Box::new(RestApi { - fetcher: fetcher, + fetcher, + sync_status, + time, + remote, }) } } @@ -58,11 +71,11 @@ impl RestApiRouter { path: Some(path), control: Some(control), api: api, - handler: response::as_json_error(&ApiError { + handler: Box::new(response::as_json_error(StatusCode::NotFound, &types::ApiError { code: "404".into(), title: "Not Found".into(), detail: "Resource you requested has not been found.".into(), - }), + })), } } @@ -75,6 +88,78 @@ impl RestApiRouter { _ => None } } + + 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) } + }; + + // 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 = { + const MAX_DRIFT: i64 = 500; + 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 }) + }; + + let time = self.api.time.time_drift(); + let remote = self.api.remote.clone(); + Box::new(handlers::AsyncHandler::new(time, map, remote, control)) + } } impl server::Handler for RestApiRouter { @@ -103,6 +188,7 @@ impl server::Handler for RestApiRouter { let handler = endpoint.and_then(|v| match v { "ping" => Some(response::ping()), + "health" => Some(self.health(control)), "content" => self.resolve_content(hash, path, control), _ => None }); diff --git a/dapps/src/api/mod.rs b/dapps/src/api/mod.rs index 4ffb9f791..59c634399 100644 --- a/dapps/src/api/mod.rs +++ b/dapps/src/api/mod.rs @@ -18,6 +18,8 @@ mod api; mod response; +mod time; mod types; pub use self::api::RestApi; +pub use self::time::TimeChecker; diff --git a/dapps/src/api/response.rs b/dapps/src/api/response.rs index 2da2d0c14..6ecc2df60 100644 --- a/dapps/src/api/response.rs +++ b/dapps/src/api/response.rs @@ -16,6 +16,8 @@ use serde::Serialize; use serde_json; +use hyper::status::StatusCode; + use endpoint::Handler; use handlers::{ContentHandler, EchoHandler}; @@ -23,10 +25,16 @@ pub fn empty() -> Box { Box::new(ContentHandler::ok("".into(), mime!(Text/Plain))) } -pub fn as_json_error(val: &T) -> Box { +pub fn as_json(status: StatusCode, val: &T) -> ContentHandler { let json = serde_json::to_string(val) .expect("serialization to string is infallible; qed"); - Box::new(ContentHandler::not_found(json, mime!(Application/Json))) + ContentHandler::new(status, json, mime!(Application/Json)) +} + +pub fn as_json_error(status: StatusCode, val: &T) -> ContentHandler { + let json = serde_json::to_string(val) + .expect("serialization to string is infallible; qed"); + ContentHandler::new(status, json, mime!(Application/Json)) } pub fn ping() -> Box { diff --git a/dapps/src/api/time.rs b/dapps/src/api/time.rs new file mode 100644 index 000000000..084890dc9 --- /dev/null +++ b/dapps/src/api/time.rs @@ -0,0 +1,264 @@ +// 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 . + +//! Periodically checks node's time drift using [SNTP](https://tools.ietf.org/html/rfc1769). +//! +//! An NTP packet is sent to the server with a local timestamp, the server then completes the packet, yielding the +//! following timestamps: +//! +//! Timestamp Name ID When Generated +//! ------------------------------------------------------------ +//! Originate Timestamp T1 time request sent by client +//! Receive Timestamp T2 time request received at server +//! Transmit Timestamp T3 time reply sent by server +//! Destination Timestamp T4 time reply received at client +//! +//! The drift is defined as: +//! +//! drift = ((T2 - T1) + (T3 - T4)) / 2. +//! + +use std::io; +use std::{fmt, mem, time}; + +use std::collections::VecDeque; +use futures::{self, Future, BoxFuture}; +use futures_cpupool::CpuPool; +use ntp; +use time::{Duration, Timespec}; +use util::{Arc, RwLock}; + +/// Time checker error. +#[derive(Debug, Clone, PartialEq)] +pub enum Error { + /// There was an error when trying to reach the NTP server. + Ntp(String), + /// IO error when reading NTP response. + Io(String), +} + +impl fmt::Display for Error { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + use self::Error::*; + + match *self { + Ntp(ref err) => write!(fmt, "NTP error: {}", err), + Io(ref err) => write!(fmt, "Connection Error: {}", err), + } + } +} + +impl From for Error { + fn from(err: io::Error) -> Self { Error::Io(format!("{}", err)) } +} + +impl From for Error { + fn from(err: ntp::errors::Error) -> Self { Error::Ntp(format!("{}", err)) } +} + +/// NTP time drift checker. +pub trait Ntp { + /// Returns the current time drift. + fn drift(&self) -> BoxFuture; +} + +/// NTP client using the SNTP algorithm for calculating drift. +#[derive(Clone)] +pub struct SimpleNtp { + address: Arc, + pool: CpuPool, +} + +impl fmt::Debug for SimpleNtp { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "Ntp {{ address: {} }}", self.address) + } +} + +impl SimpleNtp { + fn new(address: &str, pool: CpuPool) -> SimpleNtp { + SimpleNtp { + address: Arc::new(address.to_owned()), + pool: pool, + } + } +} + +impl Ntp for SimpleNtp { + fn drift(&self) -> BoxFuture { + let address = self.address.clone(); + self.pool.spawn_fn(move || { + let packet = ntp::request(&*address)?; + let dest_time = ::time::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); + + let drift = ((recv_time - orig_time) + (transmit_time - dest_time)) / 2; + + Ok(drift) + }).boxed() + } +} + +const MAX_RESULTS: usize = 4; +const UPDATE_TIMEOUT_OK_SECS: u64 = 30; +const UPDATE_TIMEOUT_ERR_SECS: u64 = 2; + +#[derive(Debug, Clone)] +/// A time checker. +pub struct TimeChecker { + ntp: N, + last_result: Arc>)>>, +} + +impl TimeChecker { + /// Creates new time checker given the NTP server address. + pub fn new(ntp_address: String, pool: CpuPool) -> Self { + let last_result = Arc::new(RwLock::new( + // Assume everything is ok at the very beginning. + (time::Instant::now(), vec![Ok(0)].into()) + )); + + let ntp = SimpleNtp::new(&ntp_address, pool); + + TimeChecker { + ntp, + last_result, + } + } +} + +impl TimeChecker { + /// Updates the time + pub fn update(&self) -> BoxFuture { + let last_result = self.last_result.clone(); + self.ntp.drift().then(move |res| { + let mut results = mem::replace(&mut last_result.write().1, VecDeque::new()); + let valid_till = time::Instant::now() + time::Duration::from_secs( + if res.is_ok() && results.len() == MAX_RESULTS { + UPDATE_TIMEOUT_OK_SECS + } else { + UPDATE_TIMEOUT_ERR_SECS + } + ); + + // Push the result. + results.push_back(res.map(|d| d.num_milliseconds())); + while results.len() > MAX_RESULTS { + results.pop_front(); + } + + // Select a response and update last result. + let res = select_result(results.iter()); + *last_result.write() = (valid_till, results); + res + }).boxed() + } + + /// Returns a current time drift or error if last request to NTP server failed. + pub fn time_drift(&self) -> BoxFuture { + // return cached result + { + let res = self.last_result.read(); + if res.0 > time::Instant::now() { + return futures::done(select_result(res.1.iter())).boxed(); + } + } + // or update and return result + self.update() + } +} + +fn select_result<'a, T: Iterator>>(results: T) -> Result { + let mut min = None; + for res in results { + min = Some(match (min.take(), res) { + (Some(Ok(min)), &Ok(ref new)) => Ok(::std::cmp::min(min, *new)), + (Some(Ok(old)), &Err(_)) => Ok(old), + (_, ref new) => (*new).clone(), + }) + } + + min.unwrap_or_else(|| Err(Error::Ntp("NTP server unavailable.".into()))) +} + +#[cfg(test)] +mod tests { + use std::sync::Arc; + use std::cell::{Cell, RefCell}; + use std::time::Instant; + use time::Duration; + use futures::{self, BoxFuture, Future}; + use super::{Ntp, TimeChecker, Error}; + use util::RwLock; + + #[derive(Clone)] + struct FakeNtp(RefCell>, Cell); + impl FakeNtp { + fn new() -> FakeNtp { + FakeNtp( + RefCell::new(vec![Duration::milliseconds(150)]), + Cell::new(0)) + } + } + + impl Ntp for FakeNtp { + fn drift(&self) -> BoxFuture { + self.1.set(self.1.get() + 1); + futures::future::ok(self.0.borrow_mut().pop().expect("Unexpected call to drift().")).boxed() + } + } + + fn time_checker() -> TimeChecker { + let last_result = Arc::new(RwLock::new( + (Instant::now(), vec![Err(Error::Ntp("NTP server unavailable.".into()))].into()) + )); + + TimeChecker { + ntp: FakeNtp::new(), + last_result: last_result, + } + } + + #[test] + fn should_fetch_time_on_start() { + // given + let time = time_checker(); + + // when + let diff = time.time_drift().wait().unwrap(); + + // then + assert_eq!(diff, 150); + assert_eq!(time.ntp.1.get(), 1); + } + + #[test] + fn should_not_fetch_twice_if_timeout_has_not_passed() { + // given + let time = time_checker(); + + // when + let diff1 = time.time_drift().wait().unwrap(); + let diff2 = time.time_drift().wait().unwrap(); + + // then + assert_eq!(diff1, 150); + assert_eq!(diff2, 150); + assert_eq!(time.ntp.1.get(), 1); + } +} diff --git a/dapps/src/api/types.rs b/dapps/src/api/types.rs index 549186955..a61964143 100644 --- a/dapps/src/api/types.rs +++ b/dapps/src/api/types.rs @@ -14,11 +14,54 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +/// A structure representing any error in REST API. #[derive(Debug, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct ApiError { + /// Error code. pub code: String, + /// Human-readable error summary. pub title: String, + /// 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 c0a33fe00..1fdf2f697 100644 --- a/dapps/src/apps/fetcher/mod.rs +++ b/dapps/src/apps/fetcher/mod.rs @@ -48,7 +48,7 @@ pub trait Fetcher: Send + Sync + 'static { } pub struct ContentFetcher { - dapps_path: PathBuf, + cache_path: PathBuf, resolver: R, cache: Arc>, sync: Arc, @@ -61,7 +61,7 @@ pub struct ContentFetcher Drop for ContentFetcher { fn drop(&mut self) { // Clear cache path - let _ = fs::remove_dir_all(&self.dapps_path); + let _ = fs::remove_dir_all(&self.cache_path); } } @@ -73,11 +73,11 @@ impl ContentFetcher { remote: Remote, fetch: F, ) -> Self { - let mut dapps_path = env::temp_dir(); - dapps_path.push(random_filename()); + let mut cache_path = env::temp_dir(); + cache_path.push(random_filename()); ContentFetcher { - dapps_path: dapps_path, + cache_path: cache_path, resolver: resolver, sync: sync_status, cache: Arc::new(Mutex::new(ContentCache::default())), @@ -200,7 +200,7 @@ impl Fetcher for ContentFetcher { control, installers::Dapp::new( content_id.clone(), - self.dapps_path.clone(), + self.cache_path.clone(), Box::new(on_done), self.embeddable_on.clone(), ), @@ -219,7 +219,7 @@ impl Fetcher for ContentFetcher { installers::Content::new( content_id.clone(), content.mime, - self.dapps_path.clone(), + self.cache_path.clone(), Box::new(on_done), ), self.embeddable_on.clone(), @@ -271,6 +271,7 @@ mod tests { use endpoint::EndpointInfo; use page::LocalPageEndpoint; use super::{ContentFetcher, Fetcher}; + use {SyncStatus}; #[derive(Clone)] struct FakeResolver; @@ -280,11 +281,17 @@ mod tests { } } + struct FakeSync(bool); + impl SyncStatus for FakeSync { + fn is_major_importing(&self) -> bool { self.0 } + fn peers(&self) -> (usize, usize) { (0, 5) } + } + #[test] fn should_true_if_contains_the_app() { // given let path = env::temp_dir(); - let fetcher = ContentFetcher::new(FakeResolver, Arc::new(|| false), Remote::new_sync(), Client::new().unwrap()) + let fetcher = ContentFetcher::new(FakeResolver, Arc::new(FakeSync(false)), Remote::new_sync(), Client::new().unwrap()) .allow_dapps(true); let handler = LocalPageEndpoint::new(path, EndpointInfo { name: "fake".into(), diff --git a/dapps/src/apps/mod.rs b/dapps/src/apps/mod.rs index c995d0a7e..376b8a36f 100644 --- a/dapps/src/apps/mod.rs +++ b/dapps/src/apps/mod.rs @@ -26,7 +26,7 @@ use fetch::Fetch; use parity_dapps::WebApp; use parity_reactor::Remote; use parity_ui; -use {WebProxyTokens, ParentFrameSettings, as_embeddable}; +use {WebProxyTokens, ParentFrameSettings}; mod app; mod cache; @@ -52,20 +52,19 @@ pub fn ui() -> Box { Box::new(PageEndpoint::with_fallback_to_index(parity_ui::App::default())) } -pub fn ui_redirection(ui_address: Option<(String, u16)>, dapps_domain: String) -> Box { - Box::new(ui::Redirection::new(as_embeddable(ui_address, dapps_domain))) +pub fn ui_redirection(embeddable: Option) -> Box { + Box::new(ui::Redirection::new(embeddable)) } pub fn all_endpoints( dapps_path: PathBuf, extra_dapps: Vec, - dapps_domain: String, - ui_address: Option<(String, u16)>, + dapps_domain: &str, + embeddable: Option, web_proxy_tokens: Arc, remote: Remote, fetch: F, ) -> Endpoints { - let embeddable = as_embeddable(ui_address.clone(), dapps_domain.clone()); // fetch fs dapps at first to avoid overwriting builtins let mut pages = fs::local_endpoints(dapps_path, embeddable.clone()); for path in extra_dapps { @@ -78,7 +77,7 @@ pub fn all_endpoints( // NOTE [ToDr] Dapps will be currently embeded on 8180 insert::(&mut pages, "ui", Embeddable::Yes(embeddable.clone())); - pages.insert("proxy".into(), ProxyPac::boxed(ui_address.clone(), dapps_domain)); + pages.insert("proxy".into(), ProxyPac::boxed(embeddable.clone(), dapps_domain.to_owned())); pages.insert(WEB_PATH.into(), Web::boxed(embeddable.clone(), web_proxy_tokens.clone(), remote.clone(), fetch.clone())); Arc::new(pages) diff --git a/dapps/src/handlers/async.rs b/dapps/src/handlers/async.rs new file mode 100644 index 000000000..d68c55cce --- /dev/null +++ b/dapps/src/handlers/async.rs @@ -0,0 +1,112 @@ +// 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 . + +//! Async Content Handler +//! Temporary solution until we switch to future-based server. +//! Wraps a future and converts it to hyper::server::Handler; + +use std::{mem, time}; +use std::sync::mpsc; +use futures::Future; +use hyper::{server, Decoder, Encoder, Next, Control}; +use hyper::net::HttpStream; + +use handlers::ContentHandler; +use parity_reactor::Remote; + +const TIMEOUT_SECS: u64 = 15; + +enum State { + Initial(F, M, Remote, Control), + Waiting(mpsc::Receiver>, M), + Done(ContentHandler), + Invalid, +} + +pub struct AsyncHandler { + state: State, +} + +impl AsyncHandler { + pub fn new(future: F, map: M, remote: Remote, control: Control) -> Self { + AsyncHandler { + state: State::Initial(future, map, remote, control), + } + } +} + +impl server::Handler for AsyncHandler, M> where + F: Future + Send + 'static, + M: FnOnce(Result, ()>) -> ContentHandler, + T: Send + 'static, + E: Send + 'static, +{ + fn on_request(&mut self, _request: server::Request) -> Next { + if let State::Initial(future, map, remote, control) = mem::replace(&mut self.state, State::Invalid) { + let (tx, rx) = mpsc::sync_channel(1); + let control2 = control.clone(); + let tx2 = tx.clone(); + remote.spawn_with_timeout(move || future.then(move |result| { + // Send a result (ignore errors if the connection was dropped) + let _ = tx.send(Ok(result)); + // Resume handler + let _ = control.ready(Next::read()); + + Ok(()) + }), time::Duration::from_secs(TIMEOUT_SECS), move || { + // Notify about error + let _ = tx2.send(Err(())); + // Resume handler + let _ = control2.ready(Next::read()); + }); + + self.state = State::Waiting(rx, map); + } + + Next::wait() + } + + fn on_request_readable(&mut self, _decoder: &mut Decoder) -> Next { + if let State::Waiting(rx, map) = mem::replace(&mut self.state, State::Invalid) { + match rx.try_recv() { + Ok(result) => { + self.state = State::Done(map(result)); + }, + Err(err) => { + warn!("Resuming handler in incorrect state: {:?}", err); + } + } + } + + Next::write() + } + + fn on_response(&mut self, res: &mut server::Response) -> Next { + if let State::Done(ref mut handler) = self.state { + handler.on_response(res) + } else { + Next::end() + } + } + + fn on_response_writable(&mut self, encoder: &mut Encoder) -> Next { + if let State::Done(ref mut handler) = self.state { + handler.on_response_writable(encoder) + } else { + Next::end() + } + } +} diff --git a/dapps/src/handlers/content.rs b/dapps/src/handlers/content.rs index d2e1b9650..300f4b61a 100644 --- a/dapps/src/handlers/content.rs +++ b/dapps/src/handlers/content.rs @@ -40,10 +40,6 @@ impl ContentHandler { Self::new(StatusCode::Ok, content, mimetype) } - pub fn not_found(content: String, mimetype: Mime) -> Self { - Self::new(StatusCode::NotFound, content, mimetype) - } - pub fn html(code: StatusCode, content: String, embeddable_on: Embeddable) -> Self { Self::new_embeddable(code, content, mime!(Text/Html), embeddable_on) } diff --git a/dapps/src/handlers/mod.rs b/dapps/src/handlers/mod.rs index 61e7b9766..bb640ed17 100644 --- a/dapps/src/handlers/mod.rs +++ b/dapps/src/handlers/mod.rs @@ -16,18 +16,23 @@ //! Hyper handlers implementations. +mod async; mod content; mod echo; mod fetch; mod redirect; mod streaming; +pub use self::async::AsyncHandler; pub use self::content::ContentHandler; pub use self::echo::EchoHandler; pub use self::fetch::{ContentFetcherHandler, ContentValidator, FetchControl, ValidatorResponse}; pub use self::redirect::Redirection; pub use self::streaming::StreamingHandler; +use std::iter; +use util::Itertools; + use url::Url; use hyper::{server, header, net, uri}; use {apps, address, Embeddable}; @@ -78,21 +83,21 @@ pub fn add_security_headers(headers: &mut header::Headers, embeddable_on: Embedd let proxy = format!("{}.{}", apps::HOME_PAGE, embed.dapps_domain); let domain = format!("*.{}:{}", embed.dapps_domain, embed.port); - if embed.host == "127.0.0.1" { + let mut ancestors = vec![std, domain, proxy] + .into_iter() + .chain(embed.extra_embed_on + .iter() + .map(|&(ref host, port)| format!("{}:{}", host, port)) + ); + + let ancestors = if embed.host == "127.0.0.1" { let localhost = address("localhost", embed.port); - format!("frame-ancestors {} {} {} {};", - std, - localhost, - domain, - proxy, - ) + ancestors.chain(iter::once(localhost)).join(" ") } else { - format!("frame-ancestors {} {} {};", - std, - domain, - proxy, - ) - } + ancestors.join(" ") + }; + + format!("frame-ancestors {};", ancestors) }, None => format!("frame-ancestors 'self';"), }.into_bytes(), diff --git a/dapps/src/lib.rs b/dapps/src/lib.rs index 49efcd201..0cb7024cc 100644 --- a/dapps/src/lib.rs +++ b/dapps/src/lib.rs @@ -21,8 +21,10 @@ 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; @@ -74,6 +76,7 @@ use std::collections::HashMap; use jsonrpc_http_server::{self as http, hyper, Origin}; use fetch::Fetch; +use futures_cpupool::CpuPool; use parity_reactor::Remote; pub use hash_fetch::urlhint::ContractClient; @@ -82,10 +85,9 @@ pub use hash_fetch::urlhint::ContractClient; pub trait SyncStatus: Send + Sync { /// Returns true if there is a major sync happening. fn is_major_importing(&self) -> bool; -} -impl SyncStatus for F where F: Fn() -> bool + Send + Sync { - fn is_major_importing(&self) -> bool { self() } + /// Returns number of connected and ideal peers. + fn peers(&self) -> (usize, usize); } /// Validates Web Proxy tokens @@ -127,21 +129,29 @@ impl Middleware { } /// Creates new middleware for UI server. - pub fn ui( + pub fn ui( + ntp_server: &str, + pool: CpuPool, remote: Remote, + dapps_domain: &str, registrar: Arc, sync_status: Arc, fetch: F, - dapps_domain: String, ) -> Self { let content_fetcher = Arc::new(apps::fetcher::ContentFetcher::new( hash_fetch::urlhint::URLHintContract::new(registrar), - sync_status, + sync_status.clone(), remote.clone(), fetch.clone(), ).embeddable_on(None).allow_dapps(false)); let special = { - let mut special = special_endpoints(content_fetcher.clone()); + let mut special = special_endpoints( + ntp_server, + pool, + content_fetcher.clone(), + remote.clone(), + sync_status.clone(), + ); special.insert(router::SpecialEndpoint::Home, Some(apps::ui())); special }; @@ -150,7 +160,7 @@ impl Middleware { None, special, None, - dapps_domain, + dapps_domain.to_owned(), ); Middleware { @@ -160,39 +170,48 @@ impl Middleware { } /// Creates new Dapps server middleware. - pub fn dapps( + pub fn dapps( + ntp_server: &str, + pool: CpuPool, remote: Remote, ui_address: Option<(String, u16)>, + extra_embed_on: Vec<(String, u16)>, dapps_path: PathBuf, extra_dapps: Vec, - dapps_domain: String, + dapps_domain: &str, registrar: Arc, sync_status: Arc, web_proxy_tokens: Arc, fetch: F, ) -> Self { - let embeddable = as_embeddable(ui_address.clone(), dapps_domain.clone()); + let embeddable = as_embeddable(ui_address, extra_embed_on, dapps_domain); let content_fetcher = Arc::new(apps::fetcher::ContentFetcher::new( hash_fetch::urlhint::URLHintContract::new(registrar), - sync_status, + sync_status.clone(), remote.clone(), fetch.clone(), ).embeddable_on(embeddable.clone()).allow_dapps(true)); let endpoints = apps::all_endpoints( dapps_path, extra_dapps, - dapps_domain.clone(), - ui_address.clone(), + dapps_domain, + embeddable.clone(), web_proxy_tokens, remote.clone(), fetch.clone(), ); let special = { - let mut special = special_endpoints(content_fetcher.clone()); + let mut special = special_endpoints( + ntp_server, + pool, + content_fetcher.clone(), + remote.clone(), + sync_status, + ); special.insert( router::SpecialEndpoint::Home, - Some(apps::ui_redirection(ui_address.clone(), dapps_domain.clone())), + Some(apps::ui_redirection(embeddable.clone())), ); special }; @@ -202,7 +221,7 @@ impl Middleware { Some(endpoints.clone()), special, embeddable, - dapps_domain, + dapps_domain.to_owned(), ); Middleware { @@ -218,11 +237,22 @@ impl http::RequestMiddleware for Middleware { } } -fn special_endpoints(content_fetcher: Arc) -> HashMap>> { +fn special_endpoints( + ntp_server: &str, + pool: CpuPool, + 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))); + special.insert(router::SpecialEndpoint::Api, Some(api::RestApi::new( + content_fetcher, + sync_status, + api::TimeChecker::new(ntp_server.into(), pool), + remote, + ))); special } @@ -230,8 +260,17 @@ fn address(host: &str, port: u16) -> String { format!("{}:{}", host, port) } -fn as_embeddable(ui_address: Option<(String, u16)>, dapps_domain: String) -> Option { - ui_address.map(|(host, port)| ParentFrameSettings { host, port, dapps_domain, }) +fn as_embeddable( + ui_address: Option<(String, u16)>, + extra_embed_on: Vec<(String, u16)>, + dapps_domain: &str, +) -> Option { + ui_address.map(|(host, port)| ParentFrameSettings { + host, + port, + extra_embed_on, + dapps_domain: dapps_domain.to_owned(), + }) } /// Random filename @@ -250,6 +289,8 @@ pub struct ParentFrameSettings { pub host: String, /// Port pub port: u16, + /// Additional pages the pages can be embedded on. + pub extra_embed_on: Vec<(String, u16)>, /// Dapps Domain (web3.site) pub dapps_domain: String, } diff --git a/dapps/src/proxypac.rs b/dapps/src/proxypac.rs index fda522770..5bffe649c 100644 --- a/dapps/src/proxypac.rs +++ b/dapps/src/proxypac.rs @@ -19,27 +19,24 @@ use endpoint::{Endpoint, Handler, EndpointPath}; use handlers::ContentHandler; use apps::HOME_PAGE; -use address; +use {address, Embeddable}; pub struct ProxyPac { - signer_address: Option<(String, u16)>, + embeddable: Embeddable, dapps_domain: String, } impl ProxyPac { - pub fn boxed(signer_address: Option<(String, u16)>, dapps_domain: String) -> Box { - Box::new(ProxyPac { - signer_address: signer_address, - dapps_domain: dapps_domain, - }) + pub fn boxed(embeddable: Embeddable, dapps_domain: String) -> Box { + Box::new(ProxyPac { embeddable, dapps_domain }) } } impl Endpoint for ProxyPac { fn to_handler(&self, path: EndpointPath) -> Box { - let signer = self.signer_address + let ui = self.embeddable .as_ref() - .map(|&(ref host, port)| address(host, port)) + .map(|ref parent| address(&parent.host, parent.port)) .unwrap_or_else(|| format!("{}:{}", path.host, path.port)); let content = format!( @@ -58,7 +55,7 @@ function FindProxyForURL(url, host) {{ return "DIRECT"; }} "#, - HOME_PAGE, self.dapps_domain, path.host, path.port, signer); + HOME_PAGE, self.dapps_domain, path.host, path.port, ui); Box::new(ContentHandler::ok(content, mime!(Application/Javascript))) } diff --git a/dapps/src/tests/helpers/mod.rs b/dapps/src/tests/helpers/mod.rs index 060420792..2d9d5f341 100644 --- a/dapps/src/tests/helpers/mod.rs +++ b/dapps/src/tests/helpers/mod.rs @@ -26,6 +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 parity_reactor::Remote; use {Middleware, SyncStatus, WebProxyTokens}; @@ -38,6 +39,12 @@ use self::fetch::FakeFetch; const SIGNER_PORT: u16 = 18180; +struct FakeSync(bool); +impl SyncStatus for FakeSync { + fn is_major_importing(&self) -> bool { self.0 } + fn peers(&self) -> (usize, usize) { (0, 5) } +} + fn init_logger() { // Initialize logger if let Ok(log) = env::var("RUST_LOG") { @@ -82,7 +89,7 @@ pub fn serve_with_registrar() -> (Server, Arc) { pub fn serve_with_registrar_and_sync() -> (Server, Arc) { init_server(|builder| { - builder.sync_status(Arc::new(|| true)) + builder.sync_status(Arc::new(FakeSync(true))) }, Default::default(), Remote::new_sync()) } @@ -148,7 +155,7 @@ impl ServerBuilder { ServerBuilder { dapps_path: dapps_path.as_ref().to_owned(), registrar: registrar, - sync_status: Arc::new(|| false), + sync_status: Arc::new(FakeSync(false)), web_proxy_tokens: Arc::new(|_| None), signer_address: None, allowed_hosts: DomainsValidation::Disabled, @@ -248,8 +255,11 @@ impl Server { fetch: F, ) -> Result { let middleware = Middleware::dapps( + "pool.ntp.org:123", + CpuPool::new(4), remote, signer_address, + vec![], dapps_path, extra_dapps, DAPPS_DOMAIN.into(), @@ -290,4 +300,3 @@ impl Drop for Server { self.server.take().unwrap().close() } } - diff --git a/ethash/src/compute.rs b/ethash/src/compute.rs index 992578041..fae47c505 100644 --- a/ethash/src/compute.rs +++ b/ethash/src/compute.rs @@ -25,7 +25,7 @@ use std::mem; use std::ptr; use sha3; use std::slice; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use std::io::{self, Read, Write}; use std::fs::{self, File}; @@ -86,6 +86,7 @@ impl Node { pub type H256 = [u8; 32]; pub struct Light { + cache_dir: PathBuf, block_number: u64, cache: Vec, seed_compute: Mutex, @@ -94,8 +95,8 @@ pub struct Light { /// Light cache structure impl Light { /// Create a new light cache for a given block number - pub fn new(block_number: u64) -> Light { - light_new(block_number) + pub fn new>(cache_dir: T, block_number: u64) -> Light { + light_new(cache_dir, block_number) } /// Calculate the light boundary data @@ -105,17 +106,15 @@ impl Light { light_compute(self, header_hash, nonce) } - pub fn file_path(seed_hash: H256) -> PathBuf { - let mut home = ::std::env::home_dir().unwrap(); - home.push(".ethash"); - home.push("light"); - home.push(to_hex(&seed_hash)); - home + pub fn file_path>(cache_dir: T, seed_hash: H256) -> PathBuf { + let mut cache_dir = cache_dir.as_ref().to_path_buf(); + cache_dir.push(to_hex(&seed_hash)); + cache_dir } - pub fn from_file(block_number: u64) -> io::Result { + pub fn from_file>(cache_dir: T, block_number: u64) -> io::Result { let seed_compute = SeedHashCompute::new(); - let path = Light::file_path(seed_compute.get_seedhash(block_number)); + let path = Light::file_path(&cache_dir, seed_compute.get_seedhash(block_number)); let mut file = File::open(path)?; let cache_size = get_cache_size(block_number); @@ -128,19 +127,22 @@ impl Light { let buf = unsafe { slice::from_raw_parts_mut(nodes.as_mut_ptr() as *mut u8, cache_size) }; file.read_exact(buf)?; Ok(Light { + block_number, + cache_dir: cache_dir.as_ref().to_path_buf(), cache: nodes, - block_number: block_number, seed_compute: Mutex::new(seed_compute), }) } pub fn to_file(&self) -> io::Result { let seed_compute = self.seed_compute.lock(); - let path = Light::file_path(seed_compute.get_seedhash(self.block_number)); + let path = Light::file_path(&self.cache_dir, seed_compute.get_seedhash(self.block_number)); if self.block_number >= ETHASH_EPOCH_LENGTH * 2 { let deprecated = Light::file_path( - seed_compute.get_seedhash(self.block_number - ETHASH_EPOCH_LENGTH * 2)); + &self.cache_dir, + seed_compute.get_seedhash(self.block_number - ETHASH_EPOCH_LENGTH * 2) + ); if deprecated.exists() { debug!(target: "ethash", "removing: {:?}", &deprecated); @@ -341,14 +343,12 @@ fn calculate_dag_item(node_index: u32, cache: &[Node]) -> Node { } } -fn light_new(block_number: u64) -> Light { +fn light_new>(cache_dir: T, block_number: u64) -> Light { let seed_compute = SeedHashCompute::new(); let seedhash = seed_compute.get_seedhash(block_number); let cache_size = get_cache_size(block_number); - if cache_size % NODE_BYTES != 0 { - panic!("Unaligned cache size"); - } + assert!(cache_size % NODE_BYTES == 0, "Unaligned cache size"); let num_nodes = cache_size / NODE_BYTES; let mut nodes = Vec::with_capacity(num_nodes); @@ -372,8 +372,9 @@ fn light_new(block_number: u64) -> Light { } Light { + block_number, + cache_dir: cache_dir.as_ref().to_path_buf(), cache: nodes, - block_number: block_number, seed_compute: Mutex::new(seed_compute), } } @@ -432,7 +433,7 @@ fn test_light_compute() { let boundary = [0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x3e, 0x9b, 0x6c, 0x69, 0xbc, 0x2c, 0xe2, 0xa2, 0x4a, 0x8e, 0x95, 0x69, 0xef, 0xc7, 0xd7, 0x1b, 0x33, 0x35, 0xdf, 0x36, 0x8c, 0x9a, 0xe9, 0x7e, 0x53, 0x84]; let nonce = 0xd7b3ac70a301a249; // difficulty = 0x085657254bd9u64; - let light = Light::new(486382); + let light = Light::new(&::std::env::temp_dir(), 486382); let result = light_compute(&light, &hash, nonce); assert_eq!(result.mix_hash[..], mix_hash[..]); assert_eq!(result.value[..], boundary[..]); @@ -471,15 +472,16 @@ fn test_seed_compute_after_newer() { #[test] fn test_drop_old_data() { - let first = Light::new(0).to_file().unwrap(); + let path = ::std::env::temp_dir(); + let first = Light::new(&path, 0).to_file().unwrap(); - let second = Light::new(ETHASH_EPOCH_LENGTH).to_file().unwrap(); + let second = Light::new(&path, ETHASH_EPOCH_LENGTH).to_file().unwrap(); assert!(fs::metadata(&first).is_ok()); - let _ = Light::new(ETHASH_EPOCH_LENGTH * 2).to_file(); + let _ = Light::new(&path, ETHASH_EPOCH_LENGTH * 2).to_file(); assert!(fs::metadata(&first).is_err()); assert!(fs::metadata(&second).is_ok()); - let _ = Light::new(ETHASH_EPOCH_LENGTH * 3).to_file(); + let _ = Light::new(&path, ETHASH_EPOCH_LENGTH * 3).to_file(); assert!(fs::metadata(&second).is_err()); } diff --git a/ethash/src/lib.rs b/ethash/src/lib.rs index 956da5b87..a598af6f7 100644 --- a/ethash/src/lib.rs +++ b/ethash/src/lib.rs @@ -25,6 +25,7 @@ extern crate log; mod compute; use std::mem; +use std::path::{Path, PathBuf}; use compute::Light; pub use compute::{ETHASH_EPOCH_LENGTH, H256, ProofOfWork, SeedHashCompute, quick_get_difficulty, slow_get_seedhash}; @@ -41,12 +42,14 @@ struct LightCache { /// Light/Full cache manager. pub struct EthashManager { cache: Mutex, + cache_dir: PathBuf, } impl EthashManager { /// Create a new new instance of ethash manager - pub fn new() -> EthashManager { + pub fn new>(cache_dir: T) -> EthashManager { EthashManager { + cache_dir: cache_dir.as_ref().to_path_buf(), cache: Mutex::new(LightCache { recent_epoch: None, recent: None, @@ -88,11 +91,11 @@ impl EthashManager { }; match light { None => { - let light = match Light::from_file(block_number) { + let light = match Light::from_file(&self.cache_dir, block_number) { Ok(light) => Arc::new(light), Err(e) => { debug!("Light cache file not found for {}:{}", block_number, e); - let light = Light::new(block_number); + let light = Light::new(&self.cache_dir, block_number); if let Err(e) = light.to_file() { warn!("Light cache file write error: {}", e); } @@ -112,7 +115,7 @@ impl EthashManager { #[test] fn test_lru() { - let ethash = EthashManager::new(); + let ethash = EthashManager::new(&::std::env::temp_dir()); let hash = [0u8; 32]; ethash.compute_light(1, &hash, 1); ethash.compute_light(50000, &hash, 1); diff --git a/ethcore/Cargo.toml b/ethcore/Cargo.toml index 0a2909dd2..90baf5c9b 100644 --- a/ethcore/Cargo.toml +++ b/ethcore/Cargo.toml @@ -52,6 +52,8 @@ semver = "0.6" stats = { path = "../util/stats" } time = "0.1" transient-hashmap = "0.4" +parity-wasm = "0.12" +wasm-utils = { git = "https://github.com/paritytech/wasm-utils" } [dev-dependencies] native-contracts = { path = "native_contracts", features = ["test_contracts"] } diff --git a/ethcore/light/src/cache.rs b/ethcore/light/src/cache.rs index 67ba11ce0..9a5a3638f 100644 --- a/ethcore/light/src/cache.rs +++ b/ethcore/light/src/cache.rs @@ -26,7 +26,7 @@ use ethcore::receipt::Receipt; use stats::Corpus; use time::{SteadyTime, Duration}; -use util::{U256, H256}; +use util::{U256, H256, HeapSizeOf}; use util::cache::MemoryLruCache; /// Configuration for how much data to cache. @@ -153,6 +153,22 @@ impl Cache { pub fn set_gas_price_corpus(&mut self, corpus: Corpus) { self.corpus = Some((corpus, SteadyTime::now())) } + + /// Get the memory used. + pub fn mem_used(&self) -> usize { + self.heap_size_of_children() + } +} + +impl HeapSizeOf for Cache { + fn heap_size_of_children(&self) -> usize { + self.headers.current_size() + + self.canon_hashes.current_size() + + self.bodies.current_size() + + self.receipts.current_size() + + self.chain_score.current_size() + // TODO: + corpus + } } #[cfg(test)] diff --git a/ethcore/light/src/client/mod.rs b/ethcore/light/src/client/mod.rs index 57cd61cec..ba580b905 100644 --- a/ethcore/light/src/client/mod.rs +++ b/ethcore/light/src/client/mod.rs @@ -44,7 +44,7 @@ mod header_chain; mod service; /// Configuration for the light client. -#[derive(Debug, Default, Clone)] +#[derive(Debug, Clone)] pub struct Config { /// Verification queue config. pub queue: queue::Config, @@ -56,6 +56,21 @@ pub struct Config { pub db_compaction: CompactionProfile, /// Should db have WAL enabled? pub db_wal: bool, + /// Should it do full verification of blocks? + pub verify_full: bool, +} + +impl Default for Config { + fn default() -> Config { + Config { + queue: Default::default(), + chain_column: None, + db_cache_size: None, + db_compaction: CompactionProfile::default(), + db_wal: true, + verify_full: true, + } + } } /// Trait for interacting with the header chain abstractly. @@ -109,6 +124,9 @@ pub trait LightChainClient: Send + Sync { /// Get the EIP-86 transition block number. fn eip86_transition(&self) -> u64; + + /// Get a report of import activity since the last call. + fn report(&self) -> ClientReport; } /// An actor listening to light chain events. @@ -141,6 +159,7 @@ pub struct Client { import_lock: Mutex<()>, db: Arc, listeners: RwLock>>, + verify_full: bool, } impl Client { @@ -156,6 +175,7 @@ impl Client { import_lock: Mutex::new(()), db: db, listeners: RwLock::new(vec![]), + verify_full: config.verify_full, }) } @@ -263,6 +283,14 @@ impl Client { for verified_header in self.queue.drain(MAX) { let (num, hash) = (verified_header.number(), verified_header.hash()); + if self.verify_full && !self.check_header(&mut bad, &verified_header) { + continue + } + + // TODO: `epoch_end_signal`, `is_epoch_end`. + // proofs we get from the network would be _complete_, whereas we need + // _incomplete_ signals + let mut tx = self.db.transaction(); let pending = match self.chain.insert(&mut tx, verified_header) { Ok(pending) => { @@ -273,14 +301,16 @@ impl Client { Err(e) => { debug!(target: "client", "Error importing header {:?}: {}", (num, hash), e); bad.push(hash); - break; + continue; } }; + self.db.write_buffered(tx); self.chain.apply_pending(pending); - if let Err(e) = self.db.flush() { - panic!("Database flush failed: {}. Check disk health and space.", e); - } + } + + if let Err(e) = self.db.flush() { + panic!("Database flush failed: {}. Check disk health and space.", e); } self.queue.mark_as_bad(&bad); @@ -291,7 +321,7 @@ impl Client { /// Get a report about blocks imported. pub fn report(&self) -> ClientReport { - ::std::mem::replace(&mut *self.report.write(), ClientReport::default()) + self.report.read().clone() } /// Get blockchain mem usage in bytes. @@ -350,6 +380,37 @@ impl Client { } } } + + // return true if should skip, false otherwise. may push onto bad if + // should skip. + fn check_header(&self, bad: &mut Vec, verified_header: &Header) -> bool { + let hash = verified_header.hash(); + let parent_header = match self.chain.block_header(BlockId::Hash(*verified_header.parent_hash())) { + Some(header) => header, + None => return false, // skip import of block with missing parent. + }; + + // Verify Block Family + let verify_family_result = self.engine.verify_block_family(&verified_header, &parent_header.decode(), None); + if let Err(e) = verify_family_result { + warn!(target: "client", "Stage 3 block verification failed for #{} ({})\nError: {:?}", + verified_header.number(), verified_header.hash(), e); + bad.push(hash); + return false; + }; + + // "external" verification. + let verify_external_result = self.engine.verify_block_external(&verified_header, None); + if let Err(e) = verify_external_result { + warn!(target: "client", "Stage 4 block verification failed for #{} ({})\nError: {:?}", + verified_header.number(), verified_header.hash(), e); + + bad.push(hash); + return false; + }; + + true + } } impl LightChainClient for Client { @@ -414,4 +475,8 @@ impl LightChainClient for Client { fn eip86_transition(&self) -> u64 { self.engine().params().eip86_transition } + + fn report(&self) -> ClientReport { + Client::report(self) + } } diff --git a/ethcore/res/ethereum/kovan.json b/ethcore/res/ethereum/kovan.json index 1c6a029d1..7cdb79e16 100644 --- a/ethcore/res/ethereum/kovan.json +++ b/ethcore/res/ethereum/kovan.json @@ -54,10 +54,10 @@ "0x00521965e7bd230323c423d96c657db5b79d099f": { "balance": "1606938044258990275541962092341162602522202993782792835301376" } }, "nodes": [ - "enode://c005dd308256c60fab247813d8bf6d6e81f9cd354287837eb1c2fcf294adaa913a3208e88900ef5c55a8cba7042c301d80503edec2ad3f92a72e241ee6743854@192.241.230.87:30303", - "enode://48caeceb2724f2f71406990aa81efe87f8c53f26441d891473da2ae50cc138f238addc0e46b5aee240db55de8c711daac53d7b32a3f13e30edb86a3ca7c2700b@138.68.143.220:30303", - "enode://85705212fd28ebdd56669fb55e958feb9d81f74fe76c82f867564b6c2995e69f596df0f588eba16f1a43b69ce06485d68231a0c83fed8469b41eba0e390c126f@139.59.146.42:30303", - "enode://2aa81bd0a761cd4f02c934dcf3f81c5b65953e51ab5ba03ceb1f125eb06418a1cdffb1c9d01871aa7bd456f3fce35e745608189ad1164f72b2161634b0c3f6ea@188.166.240.190:30303", - "enode://c5900cdd6d20795d58372f42dfbab9d664c27bb97e9c27972741942736e919122f9bac28e74cbc58e4ff195475ea90d9880b71a37af5b5a8cb41d843f765cff8@174.138.79.48:30303" + "enode://0518a3d35d4a7b3e8c433e7ffd2355d84a1304ceb5ef349787b556197f0c87fad09daed760635b97d52179d645d3e6d16a37d2cc0a9945c2ddf585684beb39ac@40.68.248.100:30303", + "enode://dcf984764db421fa0cd8dc7fc02ae378545723abb94d179f55325514cc30185eaea3dcefde6e358b7cdbe970c50b7c49e841618713a9a72d6f3f59ad9949ec6b@52.165.239.18:30303", + "enode://7e2e7f00784f516939f94e22bdc6cf96153603ca2b5df1c7cc0f90a38e7a2f218ffb1c05b156835e8b49086d11fdd1b3e2965be16baa55204167aa9bf536a4d9@52.243.47.56:30303", + "enode://d51b3e98bf35addf2f1d0ea1ffc90483e24d7c60b0fb3be1701e818f3d6778c06e53fdec737a534fe222956296f9d6e909baa025916a94601897e5c7136a7d95@40.71.221.215:30303", + "enode://419d42e300e8fd379ff6d045d93d7e66a091441e7b3c9f1d3d10088d8634ad37721e6bf86148f78c3f1b9f1360dc566ca8ee830b2d2079bc9f7171ea6152bb64@52.166.117.77:30303" ] } diff --git a/ethcore/res/wasm-tests b/ethcore/res/wasm-tests new file mode 160000 index 000000000..9ed630431 --- /dev/null +++ b/ethcore/res/wasm-tests @@ -0,0 +1 @@ +Subproject commit 9ed6304313fa949ed92aa0570fb2bc759fb6dc58 diff --git a/ethcore/src/action_params.rs b/ethcore/src/action_params.rs index 96f7b1136..2ec71c1f3 100644 --- a/ethcore/src/action_params.rs +++ b/ethcore/src/action_params.rs @@ -39,6 +39,16 @@ impl ActionValue { ActionValue::Transfer(x) | ActionValue::Apparent(x) => x } } + + /// Returns the transfer action value of the U256-convertable raw value + pub fn transfer>(transfer_value: T) -> ActionValue { + ActionValue::Transfer(transfer_value.into()) + } + + /// Returns the apparent action value of the U256-convertable raw value + pub fn apparent>(apparent_value: T) -> ActionValue { + ActionValue::Apparent(apparent_value.into()) + } } // TODO: should be a trait, possible to avoid cloning everything from a Transaction(/View). diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index dcb9ab9f8..c6be9daf9 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -112,6 +112,22 @@ impl ClientReport { } } +impl<'a> ::std::ops::Sub<&'a ClientReport> for ClientReport { + type Output = Self; + + fn sub(mut self, other: &'a ClientReport) -> Self { + let higher_mem = ::std::cmp::max(self.state_db_mem, other.state_db_mem); + let lower_mem = ::std::cmp::min(self.state_db_mem, other.state_db_mem); + + self.blocks_imported -= other.blocks_imported; + self.transactions_applied -= other.transactions_applied; + self.gas_processed = self.gas_processed - other.gas_processed; + self.state_db_mem = higher_mem - lower_mem; + + self + } +} + struct SleepState { last_activity: Option, last_autosleep: Option, @@ -1702,6 +1718,33 @@ impl MiningBlockChainClient for Client { open_block } + fn reopen_block(&self, block: ClosedBlock) -> OpenBlock { + let engine = &*self.engine; + let mut block = block.reopen(engine); + let max_uncles = engine.maximum_uncle_count(); + if block.uncles().len() < max_uncles { + let chain = self.chain.read(); + let h = chain.best_block_hash(); + // Add new uncles + let uncles = chain + .find_uncle_hashes(&h, engine.maximum_uncle_age()) + .unwrap_or_else(Vec::new); + + for h in uncles { + if !block.uncles().iter().any(|header| header.hash() == h) { + let uncle = chain.block_header(&h).expect("find_uncle_hashes only returns hashes for existing headers; qed"); + block.push_uncle(uncle).expect("pushing up to maximum_uncle_count; + push_uncle is not ok only if more than maximum_uncle_count is pushed; + so all push_uncle are Ok; + qed"); + if block.uncles().len() >= max_uncles { break } + } + } + + } + block + } + fn vm_factory(&self) -> &EvmFactory { &self.factories.vm } diff --git a/ethcore/src/client/test_client.rs b/ethcore/src/client/test_client.rs index e656e6c42..5dfdf5c25 100644 --- a/ethcore/src/client/test_client.rs +++ b/ethcore/src/client/test_client.rs @@ -44,7 +44,7 @@ use types::mode::Mode; use types::pruning_info::PruningInfo; use verification::queue::QueueInfo; -use block::{OpenBlock, SealedBlock}; +use block::{OpenBlock, SealedBlock, ClosedBlock}; use executive::Executed; use error::CallError; use trace::LocalizedTrace; @@ -381,6 +381,10 @@ impl MiningBlockChainClient for TestBlockChainClient { open_block } + fn reopen_block(&self, block: ClosedBlock) -> OpenBlock { + block.reopen(&*self.spec.engine) + } + fn vm_factory(&self) -> &EvmFactory { &self.vm_factory } diff --git a/ethcore/src/client/traits.rs b/ethcore/src/client/traits.rs index 879620f0a..da765ad07 100644 --- a/ethcore/src/client/traits.rs +++ b/ethcore/src/client/traits.rs @@ -19,7 +19,7 @@ use util::{U256, Address, H256, H2048, Bytes, Itertools}; use util::hashdb::DBValue; use blockchain::TreeRoute; use verification::queue::QueueInfo as BlockQueueInfo; -use block::{OpenBlock, SealedBlock}; +use block::{OpenBlock, SealedBlock, ClosedBlock}; use header::{BlockNumber}; use transaction::{LocalizedTransaction, PendingTransaction, SignedTransaction}; use transaction_import::TransactionImportResult; @@ -288,6 +288,9 @@ pub trait MiningBlockChainClient: BlockChainClient { extra_data: Bytes ) -> OpenBlock; + /// Reopens an OpenBlock and updates uncles. + fn reopen_block(&self, block: ClosedBlock) -> OpenBlock; + /// Returns EvmFactory. fn vm_factory(&self) -> &EvmFactory; diff --git a/ethcore/src/engines/basic_authority.rs b/ethcore/src/engines/basic_authority.rs index 4623352d1..44bdbf6c9 100644 --- a/ethcore/src/engines/basic_authority.rs +++ b/ethcore/src/engines/basic_authority.rs @@ -266,7 +266,7 @@ mod tests { /// Create a new test chain spec with `BasicAuthority` consensus engine. fn new_test_authority() -> Spec { let bytes: &[u8] = include_bytes!("../../res/basic_authority.json"); - Spec::load(bytes).expect("invalid chain spec") + Spec::load(::std::env::temp_dir(), bytes).expect("invalid chain spec") } #[test] diff --git a/ethcore/src/engines/mod.rs b/ethcore/src/engines/mod.rs index 632ba08c3..676bb1aae 100644 --- a/ethcore/src/engines/mod.rs +++ b/ethcore/src/engines/mod.rs @@ -376,6 +376,11 @@ pub trait Engine : Sync + Send { self.snapshot_components().is_some() } + /// If this engine supports wasm contracts. + fn supports_wasm(&self) -> bool { + self.params().wasm + } + /// Returns new contract address generation scheme at given block number. fn create_address_scheme(&self, number: BlockNumber) -> CreateContractAddress { if number >= self.params().eip86_transition { @@ -386,7 +391,6 @@ pub trait Engine : Sync + Send { } } - /// Common engine utilities pub mod common { use block::ExecutedBlock; diff --git a/ethcore/src/error.rs b/ethcore/src/error.rs index e3a32db01..066a9d31a 100644 --- a/ethcore/src/error.rs +++ b/ethcore/src/error.rs @@ -129,6 +129,8 @@ pub enum BlockError { UncleIsBrother(OutOfBounds), /// An uncle is already in the chain. UncleInChain(H256), + /// An uncle is included twice. + DuplicateUncle(H256), /// An uncle has a parent not in the chain. UncleParentNotInChain(H256), /// State root header field is invalid. @@ -188,6 +190,7 @@ impl fmt::Display for BlockError { UncleTooOld(ref oob) => format!("Uncle block is too old. {}", oob), UncleIsBrother(ref oob) => format!("Uncle from same generation as block. {}", oob), UncleInChain(ref hash) => format!("Uncle {} already in chain", hash), + DuplicateUncle(ref hash) => format!("Uncle {} already in the header", hash), UncleParentNotInChain(ref hash) => format!("Uncle {} has a parent not in the chain", hash), InvalidStateRoot(ref mis) => format!("Invalid state root in header: {}", mis), InvalidGasUsed(ref mis) => format!("Invalid gas used in header: {}", mis), diff --git a/ethcore/src/ethereum/ethash.rs b/ethcore/src/ethereum/ethash.rs index 3b48ab321..f29c0421a 100644 --- a/ethcore/src/ethereum/ethash.rs +++ b/ethcore/src/ethereum/ethash.rs @@ -14,6 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +use std::path::Path; use ethash::{quick_get_difficulty, slow_get_seedhash, EthashManager}; use util::*; use block::*; @@ -24,7 +25,7 @@ use header::{Header, BlockNumber}; use state::CleanupMode; use spec::CommonParams; use transaction::UnverifiedTransaction; -use engines::Engine; +use engines::{self, Engine}; use evm::Schedule; use ethjson; use rlp::{self, UntrustedRlp}; @@ -147,12 +148,17 @@ pub struct Ethash { impl Ethash { /// Create a new instance of Ethash engine - pub fn new(params: CommonParams, ethash_params: EthashParams, builtins: BTreeMap) -> Arc { + pub fn new>( + cache_dir: T, + params: CommonParams, + ethash_params: EthashParams, + builtins: BTreeMap, + ) -> Arc { Arc::new(Ethash { - params: params, - ethash_params: ethash_params, - builtins: builtins, - pow: EthashManager::new(), + params, + ethash_params, + builtins, + pow: EthashManager::new(cache_dir), }) } } @@ -165,7 +171,7 @@ impl Ethash { // for any block in the chain. // in the future, we might move the Ethash epoch // caching onto this mechanism as well. -impl ::engines::EpochVerifier for Arc { +impl engines::EpochVerifier for Arc { fn verify_light(&self, _header: &Header) -> Result<(), Error> { Ok(()) } fn verify_heavy(&self, header: &Header) -> Result<(), Error> { self.verify_block_unordered(header, None) @@ -262,7 +268,7 @@ impl Engine for Arc { _begins_epoch: bool, ) -> Result<(), Error> { let parent_hash = block.fields().header.parent_hash().clone(); - ::engines::common::push_last_hash(block, last_hashes, self, &parent_hash)?; + engines::common::push_last_hash(block, last_hashes, self, &parent_hash)?; if block.fields().header.number() == self.ethash_params.dao_hardfork_transition { let state = block.fields_mut().state; for child in &self.ethash_params.dao_hardfork_accounts { @@ -404,8 +410,8 @@ impl Engine for Arc { Ok(()) } - fn epoch_verifier<'a>(&self, _header: &Header, _proof: &'a [u8]) -> ::engines::ConstructedVerifier<'a> { - ::engines::ConstructedVerifier::Trusted(Box::new(self.clone())) + fn epoch_verifier<'a>(&self, _header: &Header, _proof: &'a [u8]) -> engines::ConstructedVerifier<'a> { + engines::ConstructedVerifier::Trusted(Box::new(self.clone())) } fn snapshot_components(&self) -> Option> { @@ -558,13 +564,18 @@ mod tests { use engines::Engine; use error::{BlockError, Error}; use header::Header; + use spec::Spec; use super::super::{new_morden, new_homestead_test}; use super::{Ethash, EthashParams, PARITY_GAS_LIMIT_DETERMINANT, ecip1017_eras_block_reward}; use rlp; + fn test_spec() -> Spec { + new_morden(&::std::env::temp_dir()) + } + #[test] fn on_close_block() { - let spec = new_morden(); + let spec = test_spec(); let engine = &*spec.engine; let genesis_header = spec.genesis_header(); let db = spec.ensure_db_good(get_temp_state_db(), &Default::default()).unwrap(); @@ -576,7 +587,7 @@ mod tests { #[test] fn on_close_block_with_uncle() { - let spec = new_morden(); + let spec = test_spec(); let engine = &*spec.engine; let genesis_header = spec.genesis_header(); let db = spec.ensure_db_good(get_temp_state_db(), &Default::default()).unwrap(); @@ -594,14 +605,14 @@ mod tests { #[test] fn has_valid_metadata() { - let engine = new_morden().engine; + let engine = test_spec().engine; assert!(!engine.name().is_empty()); assert!(engine.version().major >= 1); } #[test] fn can_return_schedule() { - let engine = new_morden().engine; + let engine = test_spec().engine; let schedule = engine.schedule(10000000); assert!(schedule.stack_limit > 0); @@ -611,8 +622,8 @@ mod tests { #[test] fn can_do_seal_verification_fail() { - let engine = new_morden().engine; - //let engine = Ethash::new_test(new_morden()); + let engine = test_spec().engine; + //let engine = Ethash::new_test(test_spec()); let header: Header = Header::default(); let verify_result = engine.verify_block_basic(&header, None); @@ -626,7 +637,7 @@ mod tests { #[test] fn can_do_difficulty_verification_fail() { - let engine = new_morden().engine; + let engine = test_spec().engine; let mut header: Header = Header::default(); header.set_seal(vec![rlp::encode(&H256::zero()).into_vec(), rlp::encode(&H64::zero()).into_vec()]); @@ -641,7 +652,7 @@ mod tests { #[test] fn can_do_proof_of_work_verification_fail() { - let engine = new_morden().engine; + let engine = test_spec().engine; let mut header: Header = Header::default(); header.set_seal(vec![rlp::encode(&H256::zero()).into_vec(), rlp::encode(&H64::zero()).into_vec()]); header.set_difficulty(U256::from_str("ffffffffffffffffffffffffffffffffffffffffffffaaaaaaaaaaaaaaaaaaaa").unwrap()); @@ -657,7 +668,7 @@ mod tests { #[test] fn can_do_seal_unordered_verification_fail() { - let engine = new_morden().engine; + let engine = test_spec().engine; let header: Header = Header::default(); let verify_result = engine.verify_block_unordered(&header, None); @@ -671,7 +682,7 @@ mod tests { #[test] fn can_do_seal256_verification_fail() { - let engine = new_morden().engine; + let engine = test_spec().engine; let mut header: Header = Header::default(); header.set_seal(vec![rlp::encode(&H256::zero()).into_vec(), rlp::encode(&H64::zero()).into_vec()]); let verify_result = engine.verify_block_unordered(&header, None); @@ -685,7 +696,7 @@ mod tests { #[test] fn can_do_proof_of_work_unordered_verification_fail() { - let engine = new_morden().engine; + let engine = test_spec().engine; let mut header: Header = Header::default(); header.set_seal(vec![rlp::encode(&H256::from("b251bd2e0283d0658f2cadfdc8ca619b5de94eca5742725e2e757dd13ed7503d")).into_vec(), rlp::encode(&H64::zero()).into_vec()]); header.set_difficulty(U256::from_str("ffffffffffffffffffffffffffffffffffffffffffffaaaaaaaaaaaaaaaaaaaa").unwrap()); @@ -701,7 +712,7 @@ mod tests { #[test] fn can_verify_block_family_genesis_fail() { - let engine = new_morden().engine; + let engine = test_spec().engine; let header: Header = Header::default(); let parent_header: Header = Header::default(); @@ -716,7 +727,7 @@ mod tests { #[test] fn can_verify_block_family_difficulty_fail() { - let engine = new_morden().engine; + let engine = test_spec().engine; let mut header: Header = Header::default(); header.set_number(2); let mut parent_header: Header = Header::default(); @@ -733,7 +744,7 @@ mod tests { #[test] fn can_verify_block_family_gas_fail() { - let engine = new_morden().engine; + let engine = test_spec().engine; let mut header: Header = Header::default(); header.set_number(2); header.set_difficulty(U256::from_str("0000000000000000000000000000000000000000000000000000000000020000").unwrap()); @@ -763,7 +774,7 @@ mod tests { fn difficulty_frontier() { let spec = new_homestead_test(); let ethparams = get_default_ethash_params(); - let ethash = Ethash::new(spec.params().clone(), ethparams, BTreeMap::new()); + let ethash = Ethash::new(&::std::env::temp_dir(), spec.params().clone(), ethparams, BTreeMap::new()); let mut parent_header = Header::default(); parent_header.set_number(1000000); @@ -781,7 +792,7 @@ mod tests { fn difficulty_homestead() { let spec = new_homestead_test(); let ethparams = get_default_ethash_params(); - let ethash = Ethash::new(spec.params().clone(), ethparams, BTreeMap::new()); + let ethash = Ethash::new(&::std::env::temp_dir(), spec.params().clone(), ethparams, BTreeMap::new()); let mut parent_header = Header::default(); parent_header.set_number(1500000); @@ -838,7 +849,7 @@ mod tests { ecip1010_pause_transition: 3000000, ..get_default_ethash_params() }; - let ethash = Ethash::new(spec.params().clone(), ethparams, BTreeMap::new()); + let ethash = Ethash::new(&::std::env::temp_dir(), spec.params().clone(), ethparams, BTreeMap::new()); let mut parent_header = Header::default(); parent_header.set_number(3500000); @@ -872,7 +883,7 @@ mod tests { ecip1010_continue_transition: 5000000, ..get_default_ethash_params() }; - let ethash = Ethash::new(spec.params().clone(), ethparams, BTreeMap::new()); + let ethash = Ethash::new(&::std::env::temp_dir(), spec.params().clone(), ethparams, BTreeMap::new()); let mut parent_header = Header::default(); parent_header.set_number(5000102); @@ -917,7 +928,8 @@ mod tests { #[test] fn gas_limit_is_multiple_of_determinant() { let spec = new_homestead_test(); - let ethash = Ethash::new(spec.params().clone(), get_default_ethash_params(), BTreeMap::new()); + let ethparams = get_default_ethash_params(); + let ethash = Ethash::new(&::std::env::temp_dir(), spec.params().clone(), ethparams, BTreeMap::new()); let mut parent = Header::new(); let mut header = Header::new(); header.set_number(1); @@ -961,7 +973,7 @@ mod tests { fn difficulty_max_timestamp() { let spec = new_homestead_test(); let ethparams = get_default_ethash_params(); - let ethash = Ethash::new(spec.params().clone(), ethparams, BTreeMap::new()); + let ethash = Ethash::new(&::std::env::temp_dir(), spec.params().clone(), ethparams, BTreeMap::new()); let mut parent_header = Header::default(); parent_header.set_number(1000000); @@ -989,7 +1001,7 @@ mod tests { header.set_number(parent_header.number() + 1); header.set_gas_limit(100_001.into()); header.set_difficulty(ethparams.minimum_difficulty); - let ethash = Ethash::new(spec.params().clone(), ethparams, BTreeMap::new()); + let ethash = Ethash::new(&::std::env::temp_dir(), spec.params().clone(), ethparams, BTreeMap::new()); assert!(ethash.verify_block_family(&header, &parent_header, None).is_ok()); parent_header.set_number(9); @@ -1044,7 +1056,7 @@ mod tests { nonce: U256::zero(), }.sign(keypair.secret(), None).into(); - let ethash = Ethash::new(spec.params().clone(), ethparams, BTreeMap::new()); + let ethash = Ethash::new(&::std::env::temp_dir(), spec.params().clone(), ethparams, BTreeMap::new()); assert!(ethash.verify_transaction_basic(&tx1, &header).is_ok()); assert!(ethash.verify_transaction_basic(&tx2, &header).is_ok()); diff --git a/ethcore/src/ethereum/mod.rs b/ethcore/src/ethereum/mod.rs index f94c39b33..e731ef7db 100644 --- a/ethcore/src/ethereum/mod.rs +++ b/ethcore/src/ethereum/mod.rs @@ -27,6 +27,7 @@ pub mod denominations; pub use self::ethash::{Ethash}; pub use self::denominations::*; +use std::path::Path; use super::spec::*; /// Most recent fork block that we support on Mainnet. @@ -38,51 +39,56 @@ pub const FORK_SUPPORTED_ROPSTEN: u64 = 10; /// Most recent fork block that we support on Kovan. pub const FORK_SUPPORTED_KOVAN: u64 = 0; -fn load(b: &[u8]) -> Spec { - Spec::load(b).expect("chain spec is invalid") +fn load<'a, T: 'a + Into>>(cache_dir: T, b: &[u8]) -> Spec { + match cache_dir.into() { + Some(path) => Spec::load(path, b), + None => Spec::load(&::std::env::temp_dir(), b) + }.expect("chain spec is invalid") } /// Create a new Foundation Olympic chain spec. -pub fn new_olympic() -> Spec { load(include_bytes!("../../res/ethereum/olympic.json")) } +pub fn new_olympic(cache_dir: &Path) -> Spec { load(cache_dir, include_bytes!("../../res/ethereum/olympic.json")) } /// Create a new Foundation Mainnet chain spec. -pub fn new_foundation() -> Spec { load(include_bytes!("../../res/ethereum/foundation.json")) } +pub fn new_foundation(cache_dir: &Path) -> Spec { load(cache_dir, include_bytes!("../../res/ethereum/foundation.json")) } /// Create a new Classic Mainnet chain spec without the DAO hardfork. -pub fn new_classic() -> Spec { load(include_bytes!("../../res/ethereum/classic.json")) } +pub fn new_classic(cache_dir: &Path) -> Spec { load(cache_dir, include_bytes!("../../res/ethereum/classic.json")) } /// Create a new Expanse mainnet chain spec. -pub fn new_expanse() -> Spec { load(include_bytes!("../../res/ethereum/expanse.json")) } +pub fn new_expanse(cache_dir: &Path) -> Spec { load(cache_dir, include_bytes!("../../res/ethereum/expanse.json")) } /// Create a new Kovan testnet chain spec. -pub fn new_kovan() -> Spec { load(include_bytes!("../../res/ethereum/kovan.json")) } - -/// Create a new Foundation Frontier-era chain spec as though it never changes to Homestead. -pub fn new_frontier_test() -> Spec { load(include_bytes!("../../res/ethereum/frontier_test.json")) } - -/// Create a new Foundation Homestead-era chain spec as though it never changed from Frontier. -pub fn new_homestead_test() -> Spec { load(include_bytes!("../../res/ethereum/homestead_test.json")) } - -/// Create a new Foundation Homestead-EIP150-era chain spec as though it never changed from Homestead/Frontier. -pub fn new_eip150_test() -> Spec { load(include_bytes!("../../res/ethereum/eip150_test.json")) } - -/// Create a new Foundation Homestead-EIP161-era chain spec as though it never changed from Homestead/Frontier. -pub fn new_eip161_test() -> Spec { load(include_bytes!("../../res/ethereum/eip161_test.json")) } - -/// Create a new Foundation Frontier/Homestead/DAO chain spec with transition points at #5 and #8. -pub fn new_transition_test() -> Spec { load(include_bytes!("../../res/ethereum/transition_test.json")) } - -/// Create a new Foundation Mainnet chain spec without genesis accounts. -pub fn new_mainnet_like() -> Spec { load(include_bytes!("../../res/ethereum/frontier_like_test.json")) } - -/// Create a new Foundation Metropolis era spec. -pub fn new_metropolis_test() -> Spec { load(include_bytes!("../../res/ethereum/metropolis_test.json")) } +pub fn new_kovan(cache_dir: &Path) -> Spec { load(cache_dir, include_bytes!("../../res/ethereum/kovan.json")) } /// Create a new Foundation Ropsten chain spec. -pub fn new_ropsten() -> Spec { load(include_bytes!("../../res/ethereum/ropsten.json")) } +pub fn new_ropsten(cache_dir: &Path) -> Spec { load(cache_dir, include_bytes!("../../res/ethereum/ropsten.json")) } /// Create a new Morden chain spec. -pub fn new_morden() -> Spec { load(include_bytes!("../../res/ethereum/morden.json")) } +pub fn new_morden(cache_dir: &Path) -> Spec { load(cache_dir, include_bytes!("../../res/ethereum/morden.json")) } + +// For tests + +/// Create a new Foundation Frontier-era chain spec as though it never changes to Homestead. +pub fn new_frontier_test() -> Spec { load(None, include_bytes!("../../res/ethereum/frontier_test.json")) } + +/// Create a new Foundation Homestead-era chain spec as though it never changed from Frontier. +pub fn new_homestead_test() -> Spec { load(None, include_bytes!("../../res/ethereum/homestead_test.json")) } + +/// Create a new Foundation Homestead-EIP150-era chain spec as though it never changed from Homestead/Frontier. +pub fn new_eip150_test() -> Spec { load(None, include_bytes!("../../res/ethereum/eip150_test.json")) } + +/// Create a new Foundation Homestead-EIP161-era chain spec as though it never changed from Homestead/Frontier. +pub fn new_eip161_test() -> Spec { load(None, include_bytes!("../../res/ethereum/eip161_test.json")) } + +/// Create a new Foundation Frontier/Homestead/DAO chain spec with transition points at #5 and #8. +pub fn new_transition_test() -> Spec { load(None, include_bytes!("../../res/ethereum/transition_test.json")) } + +/// Create a new Foundation Mainnet chain spec without genesis accounts. +pub fn new_mainnet_like() -> Spec { load(None, include_bytes!("../../res/ethereum/frontier_like_test.json")) } + +/// Create a new Foundation Metropolis era spec. +pub fn new_metropolis_test() -> Spec { load(None, include_bytes!("../../res/ethereum/metropolis_test.json")) } #[cfg(test)] mod tests { @@ -94,7 +100,7 @@ mod tests { #[test] fn ensure_db_good() { - let spec = new_morden(); + let spec = new_morden(&::std::env::temp_dir()); let engine = &spec.engine; let genesis_header = spec.genesis_header(); let db = spec.ensure_db_good(get_temp_state_db(), &Default::default()).unwrap(); @@ -109,7 +115,7 @@ mod tests { #[test] fn morden() { - let morden = new_morden(); + let morden = new_morden(&::std::env::temp_dir()); assert_eq!(morden.state_root(), "f3f4696bbf3b3b07775128eb7a3763279a394e382130f27c21e70233e04946a9".into()); let genesis = morden.genesis_block(); @@ -120,7 +126,7 @@ mod tests { #[test] fn frontier() { - let frontier = new_foundation(); + let frontier = new_foundation(&::std::env::temp_dir()); assert_eq!(frontier.state_root(), "d7f8974fb5ac78d9ac099b9ad5018bedc2ce0a72dad1827a1709da30580f0544".into()); let genesis = frontier.genesis_block(); @@ -128,4 +134,23 @@ mod tests { let _ = frontier.engine; } + + #[test] + fn all_spec_files_valid() { + let tmp = ::std::env::temp_dir(); + new_olympic(&tmp); + new_foundation(&tmp); + new_classic(&tmp); + new_expanse(&tmp); + new_kovan(&tmp); + new_ropsten(&tmp); + new_morden(&tmp); + new_frontier_test(); + new_homestead_test(); + new_eip150_test(); + new_eip161_test(); + new_transition_test(); + new_mainnet_like(); + new_metropolis_test(); + } } diff --git a/ethcore/src/evm/evm.rs b/ethcore/src/evm/evm.rs index 31ecb13f8..7a1b9952d 100644 --- a/ethcore/src/evm/evm.rs +++ b/ethcore/src/evm/evm.rs @@ -21,6 +21,7 @@ use util::{U128, U256, U512, trie}; use action_params::ActionParams; use evm::Ext; use builtin; +use super::wasm; /// Evm errors. #[derive(Debug, Clone, PartialEq)] @@ -66,6 +67,8 @@ pub enum Error { MutableCallInStaticContext, /// Likely to cause consensus issues. Internal(String), + /// Wasm runtime error + Wasm(String), } impl From> for Error { @@ -80,6 +83,12 @@ impl From for Error { } } +impl From for Error { + fn from(err: wasm::RuntimeError) -> Self { + Error::Wasm(format!("Runtime error: {:?}", err)) + } +} + impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { use self::Error::*; @@ -92,6 +101,7 @@ impl fmt::Display for Error { BuiltIn(name) => write!(f, "Built-in failed: {}", name), Internal(ref msg) => write!(f, "Internal error: {}", msg), MutableCallInStaticContext => write!(f, "Mutable call in static context"), + Wasm(ref msg) => write!(f, "Internal error: {}", msg), } } } diff --git a/ethcore/src/evm/ext.rs b/ethcore/src/evm/ext.rs index c66d1d3ff..3a8dafc37 100644 --- a/ethcore/src/evm/ext.rs +++ b/ethcore/src/evm/ext.rs @@ -129,8 +129,11 @@ pub trait Ext { /// Increments sstore refunds count by 1. fn inc_sstore_clears(&mut self); + /// Decide if any more operations should be traced. Passthrough for the VM trace. + fn trace_next_instruction(&mut self, _pc: usize, _instruction: u8) -> bool { false } + /// Prepare to trace an operation. Passthrough for the VM trace. - fn trace_prepare_execute(&mut self, _pc: usize, _instruction: u8, _gas_cost: &U256) -> bool { false } + fn trace_prepare_execute(&mut self, _pc: usize, _instruction: u8, _gas_cost: U256) {} /// Trace the finalised execution of a single instruction. fn trace_executed(&mut self, _gas_used: U256, _stack_push: &[U256], _mem_diff: Option<(usize, &[u8])>, _store_diff: Option<(U256, U256)>) {} diff --git a/ethcore/src/evm/interpreter/mod.rs b/ethcore/src/evm/interpreter/mod.rs index 7b0becdb7..304d93b54 100644 --- a/ethcore/src/evm/interpreter/mod.rs +++ b/ethcore/src/evm/interpreter/mod.rs @@ -111,6 +111,7 @@ impl evm::Evm for Interpreter { self.mem.clear(); let mut informant = informant::EvmInformant::new(ext.depth()); + let mut do_trace = true; let code = ¶ms.code.as_ref().expect("exec always called with code; qed"); let mut valid_jump_destinations = None; @@ -124,13 +125,17 @@ impl evm::Evm for Interpreter { let instruction = code[reader.position]; reader.position += 1; + // TODO: make compile-time removable if too much of a performance hit. + do_trace = do_trace && ext.trace_next_instruction(reader.position - 1, instruction); + let info = &infos[instruction as usize]; self.verify_instruction(ext, instruction, info, &stack)?; // Calculate gas cost let requirements = gasometer.requirements(ext, instruction, info, &stack, self.mem.size())?; - // TODO: make compile-time removable if too much of a performance hit. - let trace_executed = ext.trace_prepare_execute(reader.position - 1, instruction, &requirements.gas_cost.as_u256()); + if do_trace { + ext.trace_prepare_execute(reader.position - 1, instruction, requirements.gas_cost.as_u256()); + } gasometer.verify_gas(&requirements.gas_cost)?; self.mem.expand(requirements.memory_required_size); @@ -139,7 +144,7 @@ impl evm::Evm for Interpreter { evm_debug!({ informant.before_instruction(reader.position, instruction, info, &gasometer.current_gas, &stack) }); - let (mem_written, store_written) = match trace_executed { + let (mem_written, store_written) = match do_trace { true => (Self::mem_written(instruction, &stack), Self::store_written(instruction, &stack)), false => (None, None), }; @@ -155,7 +160,7 @@ impl evm::Evm for Interpreter { gasometer.current_gas = gasometer.current_gas + *gas; } - if trace_executed { + 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); } diff --git a/ethcore/src/evm/mod.rs b/ethcore/src/evm/mod.rs index a48004290..db334ffa0 100644 --- a/ethcore/src/evm/mod.rs +++ b/ethcore/src/evm/mod.rs @@ -22,6 +22,7 @@ pub mod interpreter; #[macro_use] pub mod factory; pub mod schedule; +pub mod wasm; mod vmtype; mod instructions; diff --git a/ethcore/src/evm/tests.rs b/ethcore/src/evm/tests.rs index b93361392..61e0b62be 100644 --- a/ethcore/src/evm/tests.rs +++ b/ethcore/src/evm/tests.rs @@ -39,13 +39,13 @@ pub enum FakeCallType { #[derive(PartialEq, Eq, Hash, Debug)] pub struct FakeCall { - call_type: FakeCallType, - gas: U256, - sender_address: Option
, - receive_address: Option
, - value: Option, - data: Bytes, - code_address: Option
, + pub call_type: FakeCallType, + pub gas: U256, + pub sender_address: Option
, + pub receive_address: Option
, + pub value: Option, + pub data: Bytes, + pub code_address: Option
, } /// Fake externalities test structure. @@ -53,17 +53,17 @@ pub struct FakeCall { /// Can't do recursive calls. #[derive(Default)] pub struct FakeExt { + pub store: HashMap, + pub suicides: HashSet
, + pub calls: HashSet, sstore_clears: usize, depth: usize, - store: HashMap, blockhashes: HashMap, codes: HashMap>, logs: Vec, - _suicides: HashSet
, info: EnvInfo, schedule: Schedule, balances: HashMap, - calls: HashSet, } // similar to the normal `finalize` function, but ignoring NeedsReturn. @@ -173,8 +173,9 @@ impl Ext for FakeExt { unimplemented!(); } - fn suicide(&mut self, _refund_address: &Address) -> evm::Result<()> { - unimplemented!(); + fn suicide(&mut self, refund_address: &Address) -> evm::Result<()> { + self.suicides.insert(refund_address.clone()); + Ok(()) } fn schedule(&self) -> &Schedule { diff --git a/ethcore/src/evm/wasm/call_args.rs b/ethcore/src/evm/wasm/call_args.rs new file mode 100644 index 000000000..b4cce4982 --- /dev/null +++ b/ethcore/src/evm/wasm/call_args.rs @@ -0,0 +1,62 @@ +// 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 . + +//! Wasm evm call arguments helper + +use util::{U256, H160}; + +/// Input part of the wasm call descriptor +pub struct CallArgs { + /// Receiver of the transaction + pub address: [u8; 20], + + /// Sender of the transaction + pub sender: [u8; 20], + + /// Original transaction initiator + pub origin: [u8; 20], + + /// Transfer value + pub value: [u8; 32], + + /// call/create params + pub data: Vec, +} + +impl CallArgs { + /// New contract call payload with known parameters + pub fn new(address: H160, sender: H160, origin: H160, value: U256, data: Vec) -> Self { + let mut descriptor = CallArgs { + address: [0u8; 20], + sender: [0u8; 20], + origin: [0u8; 20], + value: [0u8; 32], + data: data, + }; + + descriptor.address.copy_from_slice(&*address); + descriptor.sender.copy_from_slice(&*sender); + descriptor.origin.copy_from_slice(&*origin); + value.to_big_endian(&mut descriptor.value); + + descriptor + } + + /// Total call payload length in linear memory + pub fn len(&self) -> u32 { + self.data.len() as u32 + 92 + } +} \ No newline at end of file diff --git a/ethcore/src/evm/wasm/env.rs b/ethcore/src/evm/wasm/env.rs new file mode 100644 index 000000000..cabd38bd9 --- /dev/null +++ b/ethcore/src/evm/wasm/env.rs @@ -0,0 +1,119 @@ +// 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 . + +//! Wasm env module bindings + +use parity_wasm::elements::ValueType::*; +use parity_wasm::interpreter::UserFunctionDescriptor; +use parity_wasm::interpreter::UserFunctionDescriptor::*; + +pub const SIGNATURES: &'static [UserFunctionDescriptor] = &[ + Static( + "_storage_read", + &[I32; 2], + Some(I32), + ), + Static( + "_storage_write", + &[I32; 2], + Some(I32), + ), + Static( + "_malloc", + &[I32], + Some(I32), + ), + Static( + "_free", + &[I32], + None, + ), + Static( + "gas", + &[I32], + None, + ), + Static( + "_debug", + &[I32; 2], + None, + ), + Static( + "_suicide", + &[I32], + None, + ), + Static( + "_create", + &[I32; 4], + Some(I32), + ), + Static( + "abort", + &[I32], + None, + ), + Static( + "_abort", + &[], + None, + ), + Static( + "invoke_vii", + &[I32; 3], + None, + ), + Static( + "invoke_vi", + &[I32; 2], + None, + ), + Static( + "invoke_v", + &[I32], + None, + ), + Static( + "invoke_iii", + &[I32; 3], + Some(I32), + ), + Static( + "___resumeException", + &[I32], + None, + ), + Static( + "_rust_begin_unwind", + &[I32; 4], + None, + ), + Static( + "___cxa_find_matching_catch_2", + &[], + Some(I32), + ), + Static( + "___gxx_personality_v0", + &[I32; 6], + Some(I32), + ), + Static( + "_emscripten_memcpy_big", + &[I32; 3], + Some(I32), + ) +]; diff --git a/ethcore/src/evm/wasm/mod.rs b/ethcore/src/evm/wasm/mod.rs new file mode 100644 index 000000000..3965cc529 --- /dev/null +++ b/ethcore/src/evm/wasm/mod.rs @@ -0,0 +1,159 @@ +// 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 . + +//! Wasm Interpreter + +mod runtime; +mod ptr; +mod call_args; +mod result; +#[cfg(test)] +mod tests; +mod env; + +use std::sync::Arc; + +const DEFAULT_STACK_SPACE: u32 = 5 * 1024 * 1024; + +use parity_wasm::{interpreter, elements}; +use parity_wasm::interpreter::ModuleInstanceInterface; +use wasm_utils; + +use evm::{self, GasLeft, ReturnData}; +use action_params::ActionParams; +use self::runtime::Runtime; + +pub use self::runtime::Error as RuntimeError; + +const DEFAULT_RESULT_BUFFER: usize = 1024; + +/// Wasm interpreter instance +pub struct WasmInterpreter { + program: interpreter::ProgramInstance, + result: Vec, +} + +impl WasmInterpreter { + /// New wasm interpreter instance + pub fn new() -> Result { + Ok(WasmInterpreter { + program: interpreter::ProgramInstance::new()?, + result: Vec::with_capacity(DEFAULT_RESULT_BUFFER), + }) + } +} + +impl evm::Evm for WasmInterpreter { + + fn exec(&mut self, params: ActionParams, ext: &mut evm::Ext) -> evm::Result { + use parity_wasm::elements::Deserialize; + + let code = params.code.expect("exec is only called on contract with code; qed"); + + trace!(target: "wasm", "Started wasm interpreter with code.len={:?}", code.len()); + + let env_instance = self.program.module("env") + // prefer explicit panic here + .expect("Wasm program to contain env module"); + + let env_memory = env_instance.memory(interpreter::ItemIndex::Internal(0)) + // prefer explicit panic here + .expect("Linear memory to exist in wasm runtime"); + + if params.gas > ::std::u64::MAX.into() { + return Err(evm::Error::Wasm("Wasm interpreter cannot run contracts with gas >= 2^64".to_owned())); + } + + let mut runtime = Runtime::with_params( + ext, + env_memory, + DEFAULT_STACK_SPACE, + params.gas.low_u64(), + ); + + let mut cursor = ::std::io::Cursor::new(&*code); + + let contract_module = wasm_utils::inject_gas_counter( + elements::Module::deserialize( + &mut cursor + ).map_err(|err| { + evm::Error::Wasm(format!("Error deserializing contract code ({:?})", err)) + })? + ); + + let d_ptr = runtime.write_descriptor( + call_args::CallArgs::new( + params.address, + params.sender, + params.origin, + params.value.value(), + params.data.unwrap_or(Vec::with_capacity(0)), + ) + )?; + + { + let execution_params = interpreter::ExecutionParams::with_external( + "env".into(), + Arc::new( + interpreter::env_native_module(env_instance, native_bindings(&mut runtime)) + .map_err(|err| { + // todo: prefer explicit panic here also? + evm::Error::Wasm(format!("Error instantiating native bindings: {:?}", err)) + })? + ) + ).add_argument(interpreter::RuntimeValue::I32(d_ptr.as_raw() as i32)); + + let module_instance = self.program.add_module("contract", contract_module, Some(&execution_params.externals)) + .map_err(|err| { + trace!(target: "wasm", "Error adding contract module: {:?}", err); + evm::Error::from(RuntimeError::Interpreter(err)) + })?; + + module_instance.execute_export("_call", execution_params) + .map_err(|err| { + trace!(target: "wasm", "Error executing contract: {:?}", err); + evm::Error::from(RuntimeError::Interpreter(err)) + })?; + } + + let result = result::WasmResult::new(d_ptr); + if result.peek_empty(&*runtime.memory())? { + trace!(target: "wasm", "Contract execution result is empty."); + Ok(GasLeft::Known(runtime.gas_left()?.into())) + } else { + self.result.clear(); + // todo: use memory views to avoid copy + self.result.extend(result.pop(&*runtime.memory())?); + let len = self.result.len(); + Ok(GasLeft::NeedsReturn { + gas_left: runtime.gas_left()?.into(), + data: ReturnData::new( + ::std::mem::replace(&mut self.result, Vec::with_capacity(DEFAULT_RESULT_BUFFER)), + 0, + len, + ), + apply_state: true, + }) + } + } +} + +fn native_bindings<'a>(runtime: &'a mut Runtime) -> interpreter::UserFunctions<'a> { + interpreter::UserFunctions { + executor: runtime, + functions: ::std::borrow::Cow::from(env::SIGNATURES), + } +} \ No newline at end of file diff --git a/ethcore/src/evm/wasm/ptr.rs b/ethcore/src/evm/wasm/ptr.rs new file mode 100644 index 000000000..11edbad70 --- /dev/null +++ b/ethcore/src/evm/wasm/ptr.rs @@ -0,0 +1,52 @@ +// 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 . + +//! Wasm bound-checked ptr + +use parity_wasm::interpreter; + +/// Bound-checked wrapper for webassembly memory +pub struct WasmPtr(u32); + +/// Error in bound check +#[derive(Debug)] +pub enum Error { + AccessViolation, +} + +impl From for WasmPtr { + fn from(raw: u32) -> Self { + WasmPtr(raw) + } +} + +impl WasmPtr { + // todo: use memory view when they are on + /// Check memory range and return data with given length starting from the current pointer value + pub fn slice(&self, len: u32, mem: &interpreter::MemoryInstance) -> Result, Error> { + mem.get(self.0, len as usize).map_err(|_| Error::AccessViolation) + } + + // todo: maybe 2gb limit can be enhanced + /// Convert i32 from wasm stack to the wrapped pointer + pub fn from_i32(raw_ptr: i32) -> Result { + if raw_ptr < 0 { return Err(Error::AccessViolation); } + Ok(WasmPtr(raw_ptr as u32)) + } + + /// Return pointer raw value + pub fn as_raw(&self) -> u32 { self.0 } +} \ No newline at end of file diff --git a/ethcore/src/evm/wasm/result.rs b/ethcore/src/evm/wasm/result.rs new file mode 100644 index 000000000..3d1e51f64 --- /dev/null +++ b/ethcore/src/evm/wasm/result.rs @@ -0,0 +1,51 @@ +// 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 . + +//! Wasm evm results helper + +use byteorder::{LittleEndian, ByteOrder}; + +use parity_wasm::interpreter; + +use super::ptr::WasmPtr; +use super::runtime::Error as RuntimeError; + +/// Wrapper for wasm contract call result +pub struct WasmResult { + ptr: WasmPtr, +} + +impl WasmResult { + /// New call result from given ptr + pub fn new(descriptor_ptr: WasmPtr) -> WasmResult { + WasmResult { ptr: descriptor_ptr } + } + + /// Check if the result contains any data + pub fn peek_empty(&self, mem: &interpreter::MemoryInstance) -> Result { + let result_len = LittleEndian::read_u32(&self.ptr.slice(16, mem)?[12..16]); + Ok(result_len == 0) + } + + /// Consume the result ptr and return the actual data from wasm linear memory + pub fn pop(self, mem: &interpreter::MemoryInstance) -> Result, RuntimeError> { + let result_ptr = LittleEndian::read_u32(&self.ptr.slice(16, mem)?[8..12]); + let result_len = LittleEndian::read_u32(&self.ptr.slice(16, mem)?[12..16]); + trace!(target: "wasm", "contract result: {} bytes at @{}", result_len, result_ptr); + + Ok(mem.get(result_ptr, result_len as usize)?) + } +} \ No newline at end of file diff --git a/ethcore/src/evm/wasm/runtime.rs b/ethcore/src/evm/wasm/runtime.rs new file mode 100644 index 000000000..eb1fc55fb --- /dev/null +++ b/ethcore/src/evm/wasm/runtime.rs @@ -0,0 +1,356 @@ +// 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 . + +//! Wasm evm program runtime intstance + +use std::sync::Arc; + +use byteorder::{LittleEndian, ByteOrder}; + +use evm; + +use parity_wasm::interpreter; +use util::{Address, H256, U256}; + +use super::ptr::{WasmPtr, Error as PtrError}; +use super::call_args::CallArgs; + +/// Wasm runtime error +#[derive(Debug)] +pub enum Error { + /// Storage error + Storage, + /// Allocator error + Allocator, + /// Invalid gas state during the call + InvalidGasState, + /// Memory access violation + AccessViolation, + /// Interpreter runtime error + Interpreter(interpreter::Error), +} + +impl From for Error { + fn from(err: interpreter::Error) -> Self { + Error::Interpreter(err) + } +} + +impl From for Error { + fn from(err: PtrError) -> Self { + match err { + PtrError::AccessViolation => Error::AccessViolation, + } + } +} + +/// Runtime enviroment data for wasm contract execution +pub struct Runtime<'a> { + gas_counter: u64, + gas_limit: u64, + dynamic_top: u32, + ext: &'a mut evm::Ext, + memory: Arc, +} + +impl<'a> Runtime<'a> { + /// New runtime for wasm contract with specified params + pub fn with_params<'b>( + ext: &'b mut evm::Ext, + memory: Arc, + stack_space: u32, + gas_limit: u64, + ) -> Runtime<'b> { + Runtime { + gas_counter: 0, + gas_limit: gas_limit, + dynamic_top: stack_space, + memory: memory, + ext: ext, + } + } + + /// Write to the storage from wasm memory + pub fn storage_write(&mut self, context: interpreter::CallerContext) + -> Result, interpreter::Error> + { + let mut context = context; + let val = self.pop_h256(&mut context)?; + let key = self.pop_h256(&mut context)?; + trace!(target: "wasm", "storage_write: value {} at @{}", &val, &key); + + self.ext.set_storage(key, val) + .map_err(|_| interpreter::Error::Trap("Storage update error".to_owned()))?; + + Ok(Some(0i32.into())) + } + + /// Read from the storage to wasm memory + pub fn storage_read(&mut self, context: interpreter::CallerContext) + -> Result, interpreter::Error> + { + let mut context = context; + let val_ptr = context.value_stack.pop_as::()?; + let key = self.pop_h256(&mut context)?; + + let val = self.ext.storage_at(&key) + .map_err(|_| interpreter::Error::Trap("Storage read error".to_owned()))?; + + self.memory.set(val_ptr as u32, &*val)?; + + Ok(Some(0.into())) + } + + /// Pass suicide to state runtime + pub fn suicide(&mut self, context: interpreter::CallerContext) + -> Result, interpreter::Error> + { + let mut context = context; + let refund_address = self.pop_address(&mut context)?; + + self.ext.suicide(&refund_address) + .map_err(|_| interpreter::Error::Trap("Suicide error".to_owned()))?; + + Ok(None) + } + + /// Invoke create in the state runtime + pub fn create(&mut self, context: interpreter::CallerContext) + -> Result, interpreter::Error> + { + // + // method signature: + // fn create(endowment: *const u8, code_ptr: *const u8, code_len: u32, result_ptr: *mut u8) -> i32; + // + + trace!(target: "wasm", "runtime: create contract"); + let mut context = context; + let result_ptr = context.value_stack.pop_as::()? as u32; + trace!(target: "wasm", " result_ptr: {:?}", result_ptr); + let code_len = context.value_stack.pop_as::()? as u32; + trace!(target: "wasm", " code_len: {:?}", code_len); + let code_ptr = context.value_stack.pop_as::()? as u32; + trace!(target: "wasm", " code_ptr: {:?}", code_ptr); + let endowment = self.pop_u256(&mut context)?; + trace!(target: "wasm", " val: {:?}", endowment); + + let code = self.memory.get(code_ptr, code_len as usize)?; + + let gas_left = self.gas_left() + .map_err(|_| interpreter::Error::Trap("Gas state error".to_owned()))? + .into(); + + match self.ext.create(&gas_left, &endowment, &code, evm::CreateContractAddress::FromSenderAndCodeHash) { + evm::ContractCreateResult::Created(address, gas_left) => { + self.memory.set(result_ptr, &*address)?; + self.gas_counter = self.gas_limit - gas_left.low_u64(); + trace!(target: "wasm", "runtime: create contract success (@{:?})", address); + Ok(Some(0i32.into())) + }, + evm::ContractCreateResult::Failed => { + trace!(target: "wasm", "runtime: create contract fail"); + Ok(Some((-1i32).into())) + } + } + } + + /// Allocate memory using the wasm stack params + pub fn malloc(&mut self, context: interpreter::CallerContext) + -> Result, interpreter::Error> + { + let amount = context.value_stack.pop_as::()? as u32; + let previous_top = self.dynamic_top; + self.dynamic_top = previous_top + amount; + Ok(Some((previous_top as i32).into())) + } + + /// Allocate memory in wasm memory instance + pub fn alloc(&mut self, amount: u32) -> Result { + let previous_top = self.dynamic_top; + self.dynamic_top = previous_top + amount; + Ok(previous_top.into()) + } + + /// Report gas cost with the params passed in wasm stack + fn gas(&mut self, context: interpreter::CallerContext) + -> Result, interpreter::Error> + { + let amount = context.value_stack.pop_as::()? as u64; + if self.charge_gas(amount) { + Ok(None) + } else { + Err(interpreter::Error::Trap(format!("Gas exceeds limits of {}", self.gas_limit))) + } + } + + fn charge_gas(&mut self, amount: u64) -> bool { + let prev = self.gas_counter; + if prev + amount > self.gas_limit { + // exceeds gas + false + } else { + self.gas_counter = prev + amount; + true + } + } + + fn h256_at(&self, ptr: WasmPtr) -> Result { + Ok(H256::from_slice(&ptr.slice(32, &*self.memory) + .map_err(|_| interpreter::Error::Trap("Memory access violation".to_owned()))? + )) + } + + fn pop_h256(&self, context: &mut interpreter::CallerContext) -> Result { + let ptr = WasmPtr::from_i32(context.value_stack.pop_as::()?) + .map_err(|_| interpreter::Error::Trap("Memory access violation".to_owned()))?; + self.h256_at(ptr) + } + + fn pop_u256(&self, context: &mut interpreter::CallerContext) -> Result { + let ptr = WasmPtr::from_i32(context.value_stack.pop_as::()?) + .map_err(|_| interpreter::Error::Trap("Memory access violation".to_owned()))?; + self.h256_at(ptr).map(Into::into) + } + + fn address_at(&self, ptr: WasmPtr) -> Result { + Ok(Address::from_slice(&ptr.slice(20, &*self.memory) + .map_err(|_| interpreter::Error::Trap("Memory access violation".to_owned()))? + )) + } + + fn pop_address(&self, context: &mut interpreter::CallerContext) -> Result { + let ptr = WasmPtr::from_i32(context.value_stack.pop_as::()?) + .map_err(|_| interpreter::Error::Trap("Memory access violation".to_owned()))?; + self.address_at(ptr) + } + + fn user_trap(&mut self, _context: interpreter::CallerContext) + -> Result, interpreter::Error> + { + Err(interpreter::Error::Trap("unknown trap".to_owned())) + } + + fn user_noop(&mut self, + _context: interpreter::CallerContext + ) -> Result, interpreter::Error> { + Ok(None) + } + + /// Write call descriptor to wasm memory + pub fn write_descriptor(&mut self, call_args: CallArgs) -> Result { + let d_ptr = self.alloc(16)?; + + let args_len = call_args.len(); + let args_ptr = self.alloc(args_len)?; + + // write call descriptor + // call descriptor is [args_ptr, args_len, return_ptr, return_len] + // all are 4 byte length, last 2 are zeroed + let mut d_buf = [0u8; 16]; + LittleEndian::write_u32(&mut d_buf[0..4], args_ptr); + LittleEndian::write_u32(&mut d_buf[4..8], args_len); + self.memory.set(d_ptr, &d_buf)?; + + // write call args to memory + self.memory.set(args_ptr, &call_args.address)?; + self.memory.set(args_ptr+20, &call_args.sender)?; + self.memory.set(args_ptr+40, &call_args.origin)?; + self.memory.set(args_ptr+60, &call_args.value)?; + self.memory.set(args_ptr+92, &call_args.data)?; + + Ok(d_ptr.into()) + } + + fn debug_log(&mut self, context: interpreter::CallerContext) + -> Result, interpreter::Error> + { + let msg_len = context.value_stack.pop_as::()? as u32; + let msg_ptr = context.value_stack.pop_as::()? as u32; + + let msg = String::from_utf8(self.memory.get(msg_ptr, msg_len as usize)?) + .map_err(|_| interpreter::Error::Trap("Debug log utf-8 decoding error".to_owned()))?; + + trace!(target: "wasm", "Contract debug message: {}", msg); + + Ok(None) + } + + /// Query current gas left for execution + pub fn gas_left(&self) -> Result { + if self.gas_counter > self.gas_limit { return Err(Error::InvalidGasState); } + Ok(self.gas_limit - self.gas_counter) + } + + /// Shared memory reference + pub fn memory(&self) -> &interpreter::MemoryInstance { + &*self.memory + } + + fn mem_copy(&self, context: interpreter::CallerContext) + -> Result, interpreter::Error> + { + let len = context.value_stack.pop_as::()? as u32; + let dst = context.value_stack.pop_as::()? as u32; + let src = context.value_stack.pop_as::()? as u32; + + let mem = self.memory().get(src, len as usize)?; + self.memory().set(dst, &mem)?; + + Ok(Some(0i32.into())) + } +} + +impl<'a> interpreter::UserFunctionExecutor for Runtime<'a> { + fn execute(&mut self, name: &str, context: interpreter::CallerContext) + -> Result, interpreter::Error> + { + match name { + "_malloc" => { + self.malloc(context) + }, + "_free" => { + // Since it is arena allocator, free does nothing + // todo: update if changed + self.user_noop(context) + }, + "_storage_read" => { + self.storage_read(context) + }, + "_storage_write" => { + self.storage_write(context) + }, + "_suicide" => { + self.suicide(context) + }, + "_create" => { + self.create(context) + }, + "_debug" => { + self.debug_log(context) + }, + "gas" => { + self.gas(context) + }, + "_emscripten_memcpy_big" => { + self.mem_copy(context) + }, + _ => { + trace!("Unknown env func: '{}'", name); + self.user_trap(context) + } + } + } +} \ No newline at end of file diff --git a/ethcore/src/evm/wasm/tests.rs b/ethcore/src/evm/wasm/tests.rs new file mode 100644 index 000000000..7fd98db08 --- /dev/null +++ b/ethcore/src/evm/wasm/tests.rs @@ -0,0 +1,279 @@ +use std::path::PathBuf; +use std::fs::File; +use std::io::Read; +use std::sync::Arc; + +use ethcore_logger::init_log; +use super::super::tests::{FakeExt, FakeCall, FakeCallType}; +use super::WasmInterpreter; +use evm::{self, Evm, GasLeft}; +use action_params::{ActionParams, ActionValue}; +use util::{U256, H256, Address}; + +fn load_sample(name: &str) -> Vec { + let mut path = PathBuf::from("./res/wasm-tests/compiled"); + path.push(name); + let mut file = File::open(path).expect(&format!("File {} for test to exist", name)); + let mut data = vec![]; + file.read_to_end(&mut data).expect(&format!("Test {} to load ok", name)); + data +} + +fn test_finalize(res: Result) -> Result { + match res { + Ok(GasLeft::Known(gas)) => Ok(gas), + Ok(GasLeft::NeedsReturn{..}) => unimplemented!(), // since ret is unimplemented. + Err(e) => Err(e), + } +} + +fn wasm_interpreter() -> WasmInterpreter { + WasmInterpreter::new().expect("wasm interpreter to create without errors") +} + +/// Empty contract does almost nothing except producing 1 (one) local node debug log message +#[test] +fn empty() { + init_log(); + + let code = load_sample("empty.wasm"); + let address: Address = "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6".parse().unwrap(); + + let mut params = ActionParams::default(); + params.address = address.clone(); + params.gas = U256::from(100_000); + params.code = Some(Arc::new(code)); + let mut ext = FakeExt::new(); + + let gas_left = { + let mut interpreter = wasm_interpreter(); + test_finalize(interpreter.exec(params, &mut ext)).unwrap() + }; + + assert_eq!(gas_left, U256::from(99_996)); +} + +// This test checks if the contract deserializes payload header properly. +// Contract is provided with receiver(address), sender, origin and transaction value +// logger.wasm writes all these provided fixed header fields to some arbitrary storage keys. +#[test] +fn logger() { + init_log(); + + let code = load_sample("logger.wasm"); + let address: Address = "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6".parse().unwrap(); + let sender: Address = "0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d".parse().unwrap(); + let origin: Address = "0102030405060708090a0b0c0d0e0f1011121314".parse().unwrap(); + + let mut params = ActionParams::default(); + params.address = address.clone(); + params.sender = sender.clone(); + params.origin = origin.clone(); + params.gas = U256::from(100_000); + params.value = ActionValue::transfer(1_000_000_000); + params.code = Some(Arc::new(code)); + let mut ext = FakeExt::new(); + + let gas_left = { + let mut interpreter = wasm_interpreter(); + test_finalize(interpreter.exec(params, &mut ext)).unwrap() + }; + + println!("ext.store: {:?}", ext.store); + assert_eq!(gas_left, U256::from(99581)); + let address_val: H256 = address.into(); + assert_eq!( + ext.store.get(&"0100000000000000000000000000000000000000000000000000000000000000".parse().unwrap()).expect("storage key to exist"), + &address_val, + "Logger sets 0x01 key to the provided address" + ); + let sender_val: H256 = sender.into(); + assert_eq!( + ext.store.get(&"0200000000000000000000000000000000000000000000000000000000000000".parse().unwrap()).expect("storage key to exist"), + &sender_val, + "Logger sets 0x02 key to the provided sender" + ); + let origin_val: H256 = origin.into(); + assert_eq!( + ext.store.get(&"0300000000000000000000000000000000000000000000000000000000000000".parse().unwrap()).expect("storage key to exist"), + &origin_val, + "Logger sets 0x03 key to the provided origin" + ); + assert_eq!( + U256::from(ext.store.get(&"0400000000000000000000000000000000000000000000000000000000000000".parse().unwrap()).expect("storage key to exist")), + U256::from(1_000_000_000), + "Logger sets 0x04 key to the trasferred value" + ); +} + +// This test checks if the contract can allocate memory and pass pointer to the result stream properly. +// 1. Contract is being provided with the call descriptor ptr +// 2. Descriptor ptr is 16 byte length +// 3. The last 8 bytes of call descriptor is the space for the contract to fill [result_ptr[4], result_len[4]] +// if it has any result. +#[test] +fn identity() { + init_log(); + + let code = load_sample("identity.wasm"); + let sender: Address = "01030507090b0d0f11131517191b1d1f21232527".parse().unwrap(); + + let mut params = ActionParams::default(); + params.sender = sender.clone(); + params.gas = U256::from(100_000); + params.code = Some(Arc::new(code)); + let mut ext = FakeExt::new(); + + let (gas_left, result) = { + let mut interpreter = wasm_interpreter(); + let result = interpreter.exec(params, &mut ext).expect("Interpreter to execute without any errors"); + match result { + GasLeft::Known(_) => { panic!("Identity contract should return payload"); }, + GasLeft::NeedsReturn { gas_left: gas, data: result, apply_state: _apply } => (gas, result.to_vec()), + } + }; + + assert_eq!(gas_left, U256::from(99_689)); + + assert_eq!( + Address::from_slice(&result), + sender, + "Idenity test contract does not return the sender passed" + ); +} + +// Dispersion test sends byte array and expect the contract to 'disperse' the original elements with +// their modulo 19 dopant. +// The result is always twice as long as the input. +// This also tests byte-perfect memory allocation and in/out ptr lifecycle. +#[test] +fn dispersion() { + init_log(); + + let code = load_sample("dispersion.wasm"); + + let mut params = ActionParams::default(); + params.gas = U256::from(100_000); + params.code = Some(Arc::new(code)); + params.data = Some(vec![ + 0u8, 125, 197, 255, 19 + ]); + let mut ext = FakeExt::new(); + + let (gas_left, result) = { + let mut interpreter = wasm_interpreter(); + let result = interpreter.exec(params, &mut ext).expect("Interpreter to execute without any errors"); + match result { + GasLeft::Known(_) => { panic!("Dispersion routine should return payload"); }, + GasLeft::NeedsReturn { gas_left: gas, data: result, apply_state: _apply } => (gas, result.to_vec()), + } + }; + + assert_eq!(gas_left, U256::from(99_402)); + + assert_eq!( + result, + vec![0u8, 0, 125, 11, 197, 7, 255, 8, 19, 0] + ); +} + +#[test] +fn suicide_not() { + init_log(); + + let code = load_sample("suicidal.wasm"); + + let mut params = ActionParams::default(); + params.gas = U256::from(100_000); + params.code = Some(Arc::new(code)); + params.data = Some(vec![ + 0u8 + ]); + let mut ext = FakeExt::new(); + + let (gas_left, result) = { + let mut interpreter = wasm_interpreter(); + let result = interpreter.exec(params, &mut ext).expect("Interpreter to execute without any errors"); + match result { + GasLeft::Known(_) => { panic!("Suicidal contract should return payload when had not actualy killed himself"); }, + GasLeft::NeedsReturn { gas_left: gas, data: result, apply_state: _apply } => (gas, result.to_vec()), + } + }; + + assert_eq!(gas_left, U256::from(99_703)); + + assert_eq!( + result, + vec![0u8] + ); +} + +#[test] +fn suicide() { + init_log(); + + let code = load_sample("suicidal.wasm"); + + let refund: Address = "01030507090b0d0f11131517191b1d1f21232527".parse().unwrap(); + let mut params = ActionParams::default(); + params.gas = U256::from(100_000); + params.code = Some(Arc::new(code)); + + let mut args = vec![127u8]; + args.extend(refund.to_vec()); + params.data = Some(args); + + let mut ext = FakeExt::new(); + + let gas_left = { + let mut interpreter = wasm_interpreter(); + let result = interpreter.exec(params, &mut ext).expect("Interpreter to execute without any errors"); + match result { + GasLeft::Known(gas) => gas, + GasLeft::NeedsReturn { .. } => { + panic!("Suicidal contract should not return anything when had killed itself"); + }, + } + }; + + assert_eq!(gas_left, U256::from(99_747)); + assert!(ext.suicides.contains(&refund)); +} + +#[test] +fn create() { + init_log(); + + let mut params = ActionParams::default(); + params.gas = U256::from(100_000); + params.code = Some(Arc::new(load_sample("creator.wasm"))); + params.data = Some(vec![0u8, 2, 4, 8, 16, 32, 64, 128]); + params.value = ActionValue::transfer(1_000_000_000); + + let mut ext = FakeExt::new(); + + let gas_left = { + let mut interpreter = wasm_interpreter(); + let result = interpreter.exec(params, &mut ext).expect("Interpreter to execute without any errors"); + match result { + GasLeft::Known(gas) => gas, + GasLeft::NeedsReturn { .. } => { + panic!("Create contract should not return anthing because ext always fails on creation"); + }, + } + }; + + trace!(target: "wasm", "fake_calls: {:?}", &ext.calls); + assert!(ext.calls.contains( + &FakeCall { + call_type: FakeCallType::Create, + gas: U256::from(99_778), + sender_address: None, + receive_address: None, + value: Some(1_000_000_000.into()), + data: vec![0u8, 2, 4, 8, 16, 32, 64, 128], + code_address: None, + } + )); + assert_eq!(gas_left, U256::from(99_768)); +} \ No newline at end of file diff --git a/ethcore/src/executive.rs b/ethcore/src/executive.rs index 0d62831d6..d7a6fbc28 100644 --- a/ethcore/src/executive.rs +++ b/ethcore/src/executive.rs @@ -22,7 +22,7 @@ use engines::Engine; use types::executed::CallType; use env_info::EnvInfo; use error::ExecutionError; -use evm::{self, Ext, Finalize, CreateContractAddress, FinalizationResult, ReturnData, CleanDustMode}; +use evm::{self, wasm, Factory, Ext, Finalize, CreateContractAddress, FinalizationResult, ReturnData, CleanDustMode}; use externalities::*; use trace::{FlatTrace, Tracer, NoopTracer, ExecutiveTracer, VMTrace, VMTracer, ExecutiveVMTracer, NoopVMTracer}; use transaction::{Action, SignedTransaction}; @@ -34,6 +34,8 @@ pub use types::executed::{Executed, ExecutionResult}; /// Maybe something like here: `https://github.com/ethereum/libethereum/blob/4db169b8504f2b87f7d5a481819cfb959fc65f6c/libethereum/ExtVM.cpp` const STACK_SIZE_PER_DEPTH: usize = 24*1024; +const WASM_MAGIC_NUMBER: &'static [u8; 4] = b"\0asm"; + /// Returns new address created from address, nonce, and code hash pub fn contract_address(address_scheme: CreateContractAddress, sender: &Address, nonce: &U256, code: &[u8]) -> (Address, Option) { use rlp::RlpStream; @@ -72,6 +74,20 @@ pub struct TransactOptions { pub check_nonce: bool, } +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) { + Box::new( + wasm::WasmInterpreter::new() + // prefer to fail fast + .expect("Failed to create wasm runtime") + ) + } else { + vm_factory.create(params.gas) + } +} + /// Transaction executor. pub struct Executive<'a, B: 'a + StateBackend, E: 'a + Engine + ?Sized> { state: &'a mut State, @@ -263,18 +279,19 @@ impl<'a, B: 'a + StateBackend, E: Engine + ?Sized> Executive<'a, B, E> { let vm_factory = self.state.vm_factory(); let mut ext = self.as_externalities(OriginInfo::from(¶ms), unconfirmed_substate, output_policy, tracer, vm_tracer, static_call); trace!(target: "executive", "ext.schedule.have_delegate_call: {}", ext.schedule().have_delegate_call); - return vm_factory.create(params.gas).exec(params, &mut ext).finalize(ext); + return executor(self.engine, &vm_factory, ¶ms).exec(params, &mut ext).finalize(ext); } // Start in new thread to reset stack // TODO [todr] No thread builder yet, so we need to reset once for a while // https://github.com/aturon/crossbeam/issues/16 crossbeam::scope(|scope| { + let engine = self.engine; let vm_factory = self.state.vm_factory(); let mut ext = self.as_externalities(OriginInfo::from(¶ms), unconfirmed_substate, output_policy, tracer, vm_tracer, static_call); scope.spawn(move || { - vm_factory.create(params.gas).exec(params, &mut ext).finalize(ext) + executor(engine, &vm_factory, ¶ms).exec(params, &mut ext).finalize(ext) }) }).join() } @@ -376,7 +393,7 @@ impl<'a, B: 'a + StateBackend, E: Engine + ?Sized> Executive<'a, B, E> { self.exec_vm(params, &mut unconfirmed_substate, OutputPolicy::Return(output, trace_output.as_mut()), &mut subtracer, &mut subvmtracer) }; - vm_tracer.done_subtrace(subvmtracer, res.is_ok()); + vm_tracer.done_subtrace(subvmtracer); trace!(target: "executive", "res={:?}", res); @@ -457,7 +474,7 @@ impl<'a, B: 'a + StateBackend, E: Engine + ?Sized> Executive<'a, B, E> { self.exec_vm(params, &mut unconfirmed_substate, OutputPolicy::InitContract(trace_output.as_mut()), &mut subtracer, &mut subvmtracer) }; - vm_tracer.done_subtrace(subvmtracer, res.is_ok()); + vm_tracer.done_subtrace(subvmtracer); match res { Ok(ref res) => tracer.trace_create( @@ -562,6 +579,7 @@ impl<'a, B: 'a + StateBackend, E: Engine + ?Sized> Executive<'a, B, E> { | Err(evm::Error::BadInstruction {.. }) | Err(evm::Error::StackUnderflow {..}) | Err(evm::Error::BuiltIn {..}) + | Err(evm::Error::Wasm {..}) | Err(evm::Error::OutOfStack {..}) | Err(evm::Error::MutableCallInStaticContext) | Ok(FinalizationResult { apply_state: false, .. }) => { diff --git a/ethcore/src/externalities.rs b/ethcore/src/externalities.rs index 3d30abf2a..0f0310649 100644 --- a/ethcore/src/externalities.rs +++ b/ethcore/src/externalities.rs @@ -379,7 +379,11 @@ impl<'a, T: 'a, V: 'a, B: 'a, E: 'a> Ext for Externalities<'a, T, V, B, E> self.substate.sstore_clears_count = self.substate.sstore_clears_count + U256::one(); } - fn trace_prepare_execute(&mut self, pc: usize, instruction: u8, gas_cost: &U256) -> bool { + fn trace_next_instruction(&mut self, pc: usize, instruction: u8) -> bool { + self.vm_tracer.trace_next_instruction(pc, instruction) + } + + fn trace_prepare_execute(&mut self, pc: usize, instruction: u8, gas_cost: U256) { self.vm_tracer.trace_prepare_execute(pc, instruction, gas_cost) } diff --git a/ethcore/src/lib.rs b/ethcore/src/lib.rs index 746a737bf..7ffa61288 100644 --- a/ethcore/src/lib.rs +++ b/ethcore/src/lib.rs @@ -105,6 +105,8 @@ extern crate semver; extern crate stats; extern crate time; extern crate transient_hashmap; +extern crate parity_wasm; +extern crate wasm_utils; #[macro_use] extern crate log; diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index df28265cc..139aea2ab 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -88,6 +88,8 @@ pub struct MinerOptions { pub reseal_on_external_tx: bool, /// Reseal on receipt of new local transactions. pub reseal_on_own_tx: bool, + /// Reseal when new uncle block has been imported. + pub reseal_on_uncle: bool, /// Minimum period between transaction-inspired reseals. pub reseal_min_period: Duration, /// Maximum period between blocks (enables force sealing after that). @@ -119,6 +121,7 @@ impl Default for MinerOptions { force_sealing: false, reseal_on_external_tx: false, reseal_on_own_tx: true, + reseal_on_uncle: false, tx_gas_limit: !U256::zero(), tx_queue_size: 1024, tx_queue_gas_limit: GasLimit::Auto, @@ -347,7 +350,7 @@ impl Miner { Some(old_block) => { trace!(target: "miner", "prepare_block: Already have previous work; updating and returning"); // add transactions to old_block - old_block.reopen(&*self.engine) + chain.reopen_block(old_block) } None => { // block not found - create it. @@ -366,7 +369,6 @@ impl Miner { let mut transactions_to_penalize = HashSet::new(); let block_number = open_block.block().fields().header.number(); - // TODO Push new uncles too. let mut tx_count: usize = 0; let tx_total = transactions.len(); for tx in transactions { @@ -1153,11 +1155,10 @@ impl MinerService for Miner { }) } - fn chain_new_blocks(&self, chain: &MiningBlockChainClient, _imported: &[H256], _invalid: &[H256], enacted: &[H256], retracted: &[H256]) { + fn chain_new_blocks(&self, chain: &MiningBlockChainClient, imported: &[H256], _invalid: &[H256], enacted: &[H256], retracted: &[H256]) { trace!(target: "miner", "chain_new_blocks"); - // 1. We ignore blocks that were `imported` (because it means that they are not in canon-chain, and transactions - // should be still available in the queue. + // 1. We ignore blocks that were `imported` unless resealing on new uncles is enabled. // 2. We ignore blocks that are `invalid` because it doesn't have any meaning in terms of the transactions that // are in those blocks @@ -1192,7 +1193,7 @@ impl MinerService for Miner { transaction_queue.remove_old(&fetch_account, time); } - if enacted.len() > 0 { + if enacted.len() > 0 || (imported.len() > 0 && self.options.reseal_on_uncle) { // -------------------------------------------------------------------------- // | NOTE Code below requires transaction_queue and sealing_work locks. | // | Make sure to release the locks before calling that method. | @@ -1312,6 +1313,7 @@ mod tests { force_sealing: false, reseal_on_external_tx: false, reseal_on_own_tx: true, + reseal_on_uncle: false, reseal_min_period: Duration::from_secs(5), reseal_max_period: Duration::from_secs(120), tx_gas_limit: !U256::zero(), diff --git a/ethcore/src/miner/mod.rs b/ethcore/src/miner/mod.rs index 90bf34f6b..25d131f77 100644 --- a/ethcore/src/miner/mod.rs +++ b/ethcore/src/miner/mod.rs @@ -32,7 +32,7 @@ //! use ethcore::miner::{Miner, MinerService}; //! //! fn main() { -//! let miner: Miner = Miner::with_spec(ðereum::new_foundation()); +//! let miner: Miner = Miner::with_spec(ðereum::new_foundation(&env::temp_dir())); //! // get status //! assert_eq!(miner.status().transactions_in_pending_queue, 0); //! diff --git a/ethcore/src/snapshot/tests/proof_of_authority.rs b/ethcore/src/snapshot/tests/proof_of_authority.rs index 4428ea2ad..a62dbaf10 100644 --- a/ethcore/src/snapshot/tests/proof_of_authority.rs +++ b/ethcore/src/snapshot/tests/proof_of_authority.rs @@ -59,7 +59,7 @@ lazy_static! { /// `native_contracts::test_contracts::ValidatorSet` provides a native wrapper for the ABi. fn spec_fixed_to_contract() -> Spec { let data = include_bytes!("test_validator_contract.json"); - Spec::load(&data[..]).unwrap() + Spec::load(&::std::env::temp_dir(), &data[..]).unwrap() } // creates an account provider, filling it with accounts from all the given diff --git a/ethcore/src/spec/spec.rs b/ethcore/src/spec/spec.rs index c34652792..d97aca7ff 100644 --- a/ethcore/src/spec/spec.rs +++ b/ethcore/src/spec/spec.rs @@ -80,9 +80,11 @@ pub struct CommonParams { /// Number of first block where dust cleanup rules (EIP-168 and EIP169) begin. pub dust_protection_transition: BlockNumber, /// Nonce cap increase per block. Nonce cap is only checked if dust protection is enabled. - pub nonce_cap_increment : u64, + pub nonce_cap_increment: u64, /// Enable dust cleanup for contracts. - pub remove_dust_contracts : bool, + pub remove_dust_contracts: bool, + /// Wasm support + pub wasm: bool, } impl From for CommonParams { @@ -110,6 +112,7 @@ impl From for CommonParams { dust_protection_transition: p.dust_protection_transition.map_or(BlockNumber::max_value(), Into::into), nonce_cap_increment: p.nonce_cap_increment.map_or(64, Into::into), remove_dust_contracts: p.remove_dust_contracts.unwrap_or(false), + wasm: p.wasm.unwrap_or(false), } } } @@ -158,7 +161,7 @@ pub struct Spec { genesis_state: PodState, } -fn load_from(s: ethjson::spec::Spec) -> Result { +fn load_from>(cache_dir: T, s: ethjson::spec::Spec) -> Result { let builtins = s.accounts.builtins().into_iter().map(|p| (p.0.into(), From::from(p.1))).collect(); let g = Genesis::from(s.genesis); let GenericSeal(seal_rlp) = g.seal.into(); @@ -166,7 +169,7 @@ fn load_from(s: ethjson::spec::Spec) -> Result { let mut s = Spec { name: s.name.clone().into(), - engine: Spec::engine(s.engine, params, builtins), + engine: Spec::engine(cache_dir, s.engine, params, builtins), data_dir: s.data_dir.unwrap_or(s.name).into(), nodes: s.nodes.unwrap_or_else(Vec::new), parent_hash: g.parent_hash, @@ -195,18 +198,26 @@ fn load_from(s: ethjson::spec::Spec) -> Result { macro_rules! load_bundled { ($e:expr) => { - Spec::load(include_bytes!(concat!("../../res/", $e, ".json")) as &[u8]).expect(concat!("Chain spec ", $e, " is invalid.")) + Spec::load( + &::std::env::temp_dir(), + include_bytes!(concat!("../../res/", $e, ".json")) as &[u8] + ).expect(concat!("Chain spec ", $e, " is invalid.")) }; } impl Spec { /// Convert engine spec into a arc'd Engine of the right underlying type. /// TODO avoid this hard-coded nastiness - use dynamic-linked plugin framework instead. - fn engine(engine_spec: ethjson::spec::Engine, params: CommonParams, builtins: BTreeMap) -> Arc { + fn engine>( + cache_dir: T, + engine_spec: ethjson::spec::Engine, + params: CommonParams, + builtins: BTreeMap, + ) -> Arc { match engine_spec { ethjson::spec::Engine::Null => Arc::new(NullEngine::new(params, builtins)), ethjson::spec::Engine::InstantSeal(instant) => Arc::new(InstantSeal::new(params, instant.params.registrar.map_or_else(Address::new, Into::into), builtins)), - ethjson::spec::Engine::Ethash(ethash) => Arc::new(ethereum::Ethash::new(params, From::from(ethash.params), builtins)), + ethjson::spec::Engine::Ethash(ethash) => Arc::new(ethereum::Ethash::new(cache_dir, params, From::from(ethash.params), builtins)), ethjson::spec::Engine::BasicAuthority(basic_authority) => Arc::new(BasicAuthority::new(params, From::from(basic_authority.params), builtins)), ethjson::spec::Engine::AuthorityRound(authority_round) => AuthorityRound::new(params, From::from(authority_round.params), builtins).expect("Failed to start AuthorityRound consensus engine."), ethjson::spec::Engine::Tendermint(tendermint) => Tendermint::new(params, From::from(tendermint.params), builtins).expect("Failed to start the Tendermint consensus engine."), @@ -397,13 +408,13 @@ impl Spec { /// Loads spec from json file. Provide factories for executing contracts and ensuring /// storage goes to the right place. - pub fn load(reader: R) -> Result where R: Read { + pub fn load, R>(cache_dir: T, reader: R) -> Result where R: Read { fn fmt(f: F) -> String { format!("Spec json is invalid: {}", f) } ethjson::spec::Spec::load(reader).map_err(fmt) - .and_then(|x| load_from(x).map_err(fmt)) + .and_then(|x| load_from(cache_dir, x).map_err(fmt)) } /// Create a new Spec which conforms to the Frontier-era Morden chain except that it's a NullEngine consensus. @@ -453,7 +464,20 @@ mod tests { // https://github.com/paritytech/parity/issues/1840 #[test] fn test_load_empty() { - assert!(Spec::load(&[] as &[u8]).is_err()); + assert!(Spec::load(::std::env::temp_dir(), &[] as &[u8]).is_err()); + } + + #[test] + fn all_spec_files_valid() { + Spec::new_test(); + Spec::new_null(); + Spec::new_test_constructor(); + Spec::new_instant(); + Spec::new_test_round(); + Spec::new_test_tendermint(); + Spec::new_validator_safe_contract(); + Spec::new_validator_contract(); + Spec::new_validator_multi(); } #[test] diff --git a/ethcore/src/tests/client.rs b/ethcore/src/tests/client.rs index 6aef92ec5..3b32f9094 100644 --- a/ethcore/src/tests/client.rs +++ b/ethcore/src/tests/client.rs @@ -52,7 +52,7 @@ fn imports_from_empty() { #[test] fn should_return_registrar() { let dir = RandomTempPath::new(); - let spec = ethereum::new_morden(); + let spec = ethereum::new_morden(&dir); let db_config = DatabaseConfig::with_columns(::db::NUM_COLUMNS); let client_db = Arc::new(Database::open(&db_config, dir.as_path().to_str().unwrap()).unwrap()); diff --git a/ethcore/src/trace/executive_tracer.rs b/ethcore/src/trace/executive_tracer.rs index fb2f91a2f..9dddd214a 100644 --- a/ethcore/src/trace/executive_tracer.rs +++ b/ethcore/src/trace/executive_tracer.rs @@ -192,14 +192,15 @@ impl ExecutiveVMTracer { } impl VMTracer for ExecutiveVMTracer { - fn trace_prepare_execute(&mut self, pc: usize, instruction: u8, gas_cost: &U256) -> bool { + fn trace_next_instruction(&mut self, _pc: usize, _instruction: u8) -> bool { true } + + fn trace_prepare_execute(&mut self, pc: usize, instruction: u8, gas_cost: U256) { self.data.operations.push(VMOperation { pc: pc, instruction: instruction, - gas_cost: gas_cost.clone(), + gas_cost: gas_cost, executed: None, }); - true } fn trace_executed(&mut self, gas_used: U256, stack_push: &[U256], mem_diff: Option<(usize, &[u8])>, store_diff: Option<(U256, U256)>) { @@ -221,7 +222,7 @@ impl VMTracer for ExecutiveVMTracer { }} } - fn done_subtrace(&mut self, sub: Self, _is_successful: bool) { + fn done_subtrace(&mut self, sub: Self) { self.data.subs.push(sub.data); } diff --git a/ethcore/src/trace/mod.rs b/ethcore/src/trace/mod.rs index a7eea218b..c6320b34c 100644 --- a/ethcore/src/trace/mod.rs +++ b/ethcore/src/trace/mod.rs @@ -87,18 +87,23 @@ pub trait Tracer: Send { /// Used by executive to build VM traces. pub trait VMTracer: Send { - /// Trace the preparation to execute a single instruction. - /// @returns true if `trace_executed` should be called. - fn trace_prepare_execute(&mut self, _pc: usize, _instruction: u8, _gas_cost: &U256) -> bool { false } - /// Trace the finalised execution of a single instruction. + /// Trace the progression of interpreter to next instruction. + /// If tracer returns `false` it won't be called again. + /// @returns true if `trace_prepare_execute` and `trace_executed` should be called. + fn trace_next_instruction(&mut self, _pc: usize, _instruction: u8) -> bool { false } + + /// Trace the preparation to execute a single valid instruction. + fn trace_prepare_execute(&mut self, _pc: usize, _instruction: u8, _gas_cost: U256) {} + + /// Trace the finalised execution of a single valid instruction. fn trace_executed(&mut self, _gas_used: U256, _stack_push: &[U256], _mem_diff: Option<(usize, &[u8])>, _store_diff: Option<(U256, U256)>) {} /// Spawn subtracer which will be used to trace deeper levels of execution. fn prepare_subtrace(&self, code: &[u8]) -> Self where Self: Sized; /// Finalize subtracer. - fn done_subtrace(&mut self, sub: Self, is_successful: bool) where Self: Sized; + fn done_subtrace(&mut self, sub: Self) where Self: Sized; /// Consumes self and returns the VM trace. fn drain(self) -> Option; diff --git a/ethcore/src/trace/noop_tracer.rs b/ethcore/src/trace/noop_tracer.rs index d8502a4a8..694d1b094 100644 --- a/ethcore/src/trace/noop_tracer.rs +++ b/ethcore/src/trace/noop_tracer.rs @@ -71,18 +71,15 @@ impl Tracer for NoopTracer { pub struct NoopVMTracer; impl VMTracer for NoopVMTracer { - /// Trace the preparation to execute a single instruction. - fn trace_prepare_execute(&mut self, _pc: usize, _instruction: u8, _gas_cost: &U256) -> bool { false } + fn trace_next_instruction(&mut self, _pc: usize, _instruction: u8) -> bool { false } + + fn trace_prepare_execute(&mut self, _pc: usize, _instruction: u8, _gas_cost: U256) {} - /// Trace the finalised execution of a single instruction. fn trace_executed(&mut self, _gas_used: U256, _stack_push: &[U256], _mem_diff: Option<(usize, &[u8])>, _store_diff: Option<(U256, U256)>) {} - /// Spawn subtracer which will be used to trace deeper levels of execution. fn prepare_subtrace(&self, _code: &[u8]) -> Self { NoopVMTracer } - /// Spawn subtracer which will be used to trace deeper levels of execution. - fn done_subtrace(&mut self, _sub: Self, _is_successful: bool) {} + fn done_subtrace(&mut self, _sub: Self) {} - /// Consumes self and returns all VM traces. fn drain(self) -> Option { None } } diff --git a/ethcore/src/types/trace_types/error.rs b/ethcore/src/types/trace_types/error.rs index 0348fc2a4..f10f2d1bb 100644 --- a/ethcore/src/types/trace_types/error.rs +++ b/ethcore/src/types/trace_types/error.rs @@ -42,6 +42,8 @@ pub enum Error { Internal, /// When execution tries to modify the state in static context MutableCallInStaticContext, + /// Wasm error + Wasm, } impl<'a> From<&'a EvmError> for Error { @@ -53,6 +55,7 @@ impl<'a> From<&'a EvmError> for Error { EvmError::StackUnderflow { .. } => Error::StackUnderflow, EvmError::OutOfStack { .. } => Error::OutOfStack, EvmError::BuiltIn { .. } => Error::BuiltIn, + EvmError::Wasm { .. } => Error::Wasm, EvmError::Internal(_) => Error::Internal, EvmError::MutableCallInStaticContext => Error::MutableCallInStaticContext, } @@ -75,6 +78,7 @@ impl fmt::Display for Error { StackUnderflow => "Stack underflow", OutOfStack => "Out of stack", BuiltIn => "Built-in failed", + Wasm => "Wasm runtime error", Internal => "Internal error", MutableCallInStaticContext => "Mutable Call In Static Context", }; @@ -94,6 +98,7 @@ impl Encodable for Error { Internal => 5, BuiltIn => 6, MutableCallInStaticContext => 7, + Wasm => 8, }; s.append_internal(&value); @@ -113,6 +118,7 @@ impl Decodable for Error { 5 => Ok(Internal), 6 => Ok(BuiltIn), 7 => Ok(MutableCallInStaticContext), + 8 => Ok(Wasm), _ => Err(DecoderError::Custom("Invalid error type")), } } diff --git a/ethcore/src/types/verification_queue_info.rs b/ethcore/src/types/verification_queue_info.rs index 566e37280..570277f09 100644 --- a/ethcore/src/types/verification_queue_info.rs +++ b/ethcore/src/types/verification_queue_info.rs @@ -17,7 +17,7 @@ //! Verification queue info types /// Verification queue status -#[derive(Debug)] +#[derive(Debug, Clone)] #[cfg_attr(feature = "ipc", binary)] pub struct VerificationQueueInfo { /// Number of queued items pending verification diff --git a/ethcore/src/verification/verification.rs b/ethcore/src/verification/verification.rs index d244c2af1..1be636adc 100644 --- a/ethcore/src/verification/verification.rs +++ b/ethcore/src/verification/verification.rs @@ -132,12 +132,17 @@ pub fn verify_block_family(header: &Header, bytes: &[u8], engine: &Engine, bc: & } } + let mut verified = HashSet::new(); for uncle in UntrustedRlp::new(bytes).at(2)?.iter().map(|rlp| rlp.as_val::
()) { let uncle = uncle?; if excluded.contains(&uncle.hash()) { return Err(From::from(BlockError::UncleInChain(uncle.hash()))) } + if verified.contains(&uncle.hash()) { + return Err(From::from(BlockError::DuplicateUncle(uncle.hash()))) + } + // m_currentBlock.number() - uncle.number() m_cB.n - uP.n() // 1 2 // 2 @@ -180,6 +185,7 @@ pub fn verify_block_family(header: &Header, bytes: &[u8], engine: &Engine, bc: & verify_parent(&uncle, &uncle_parent)?; engine.verify_block_family(&uncle, &uncle_parent, Some(bytes))?; + verified.insert(uncle.hash()); } } Ok(()) @@ -568,6 +574,11 @@ mod tests { check_fail(family_test(&create_test_block_with_data(&header, &good_transactions, &bad_uncles), engine, &bc), TooManyUncles(OutOfBounds { max: Some(engine.maximum_uncle_count()), min: None, found: bad_uncles.len() })); + header = good.clone(); + bad_uncles = vec![ good_uncle1.clone(), good_uncle1.clone() ]; + check_fail(family_test(&create_test_block_with_data(&header, &good_transactions, &bad_uncles), engine, &bc), + DuplicateUncle(good_uncle1.hash())); + // TODO: some additional uncle checks } diff --git a/evmbin/src/display/json.rs b/evmbin/src/display/json.rs index 1791640d6..13cba90bb 100644 --- a/evmbin/src/display/json.rs +++ b/evmbin/src/display/json.rs @@ -81,13 +81,18 @@ impl vm::Informant for Informant { } impl trace::VMTracer for Informant { - fn trace_prepare_execute(&mut self, pc: usize, instruction: u8, gas_cost: &U256) -> bool { + fn trace_next_instruction(&mut self, pc: usize, instruction: u8) -> bool { self.pc = pc; self.instruction = instruction; - self.gas_cost = *gas_cost; true } + fn trace_prepare_execute(&mut self, pc: usize, instruction: u8, gas_cost: U256) { + self.pc = pc; + self.instruction = instruction; + self.gas_cost = gas_cost; + } + fn trace_executed(&mut self, gas_used: U256, stack_push: &[U256], mem_diff: Option<(usize, &[u8])>, store_diff: Option<(U256, U256)>) { let info = evm::INSTRUCTIONS[self.instruction as usize]; @@ -127,17 +132,9 @@ impl trace::VMTracer for Informant { vm } - fn done_subtrace(&mut self, mut sub: Self, is_successful: bool) where Self: Sized { + fn done_subtrace(&mut self, mut sub: Self) { if sub.depth == 1 { // print last line with final state: - if is_successful { - sub.pc += 1; - sub.instruction = 0; - } else { - let push_bytes = evm::push_bytes(sub.instruction); - sub.pc += if push_bytes > 0 { push_bytes + 1 } else { 0 }; - sub.instruction = if sub.pc < sub.code.len() { sub.code[sub.pc] } else { 0 }; - } sub.gas_cost = 0.into(); let gas_used = sub.gas_used; trace::VMTracer::trace_executed(&mut sub, gas_used, &[], None, None); diff --git a/evmbin/src/display/simple.rs b/evmbin/src/display/simple.rs index 46a57bab0..9f8f7ee14 100644 --- a/evmbin/src/display/simple.rs +++ b/evmbin/src/display/simple.rs @@ -44,6 +44,6 @@ impl vm::Informant for Informant { impl trace::VMTracer for Informant { fn prepare_subtrace(&self, _code: &[u8]) -> Self where Self: Sized { Default::default() } - fn done_subtrace(&mut self, _sub: Self, _is_successful: bool) where Self: Sized {} + fn done_subtrace(&mut self, _sub: Self) {} fn drain(self) -> Option { None } } diff --git a/evmbin/src/main.rs b/evmbin/src/main.rs index 305b2cbd4..ac060d586 100644 --- a/evmbin/src/main.rs +++ b/evmbin/src/main.rs @@ -134,10 +134,10 @@ impl Args { Ok(match self.flag_spec { Some(ref filename) => { let file = fs::File::open(filename).map_err(|e| format!("{}", e))?; - spec::Spec::load(file)? + spec::Spec::load(::std::env::temp_dir(), file)? }, None => { - spec::Spec::new_instant() + ethcore::ethereum::new_foundation(&::std::env::temp_dir()) }, }) } diff --git a/js/package.json b/js/package.json index bc71029d9..75c2f56f5 100644 --- a/js/package.json +++ b/js/package.json @@ -124,6 +124,7 @@ "istanbul": "1.0.0-alpha.2", "jsdom": "9.11.0", "json-loader": "0.5.4", + "markdown-loader": "2.0.0", "mocha": "3.2.0", "mock-local-storage": "1.0.2", "mock-socket": "6.0.4", diff --git a/js/src/embed.js b/js/src/embed.js index b59f55cd1..5e8bf7ffe 100644 --- a/js/src/embed.js +++ b/js/src/embed.js @@ -94,6 +94,7 @@ class FrameSecureApi extends SecureApi { const transport = window.secureTransport || new FakeTransport(); const uiUrl = transport.uiUrl || 'http://127.0.0.1:8180'; +transport.uiUrlWithProtocol = uiUrl; transport.uiUrl = uiUrl.replace('http://', '').replace('https://', ''); const api = new FrameSecureApi(transport); diff --git a/js/src/modals/CreateAccount/NewQr/newQr.js b/js/src/modals/CreateAccount/NewQr/newQr.js index 362fd4d61..a1301ecd6 100644 --- a/js/src/modals/CreateAccount/NewQr/newQr.js +++ b/js/src/modals/CreateAccount/NewQr/newQr.js @@ -14,6 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +import { observer } from 'mobx-react'; import React, { Component, PropTypes } from 'react'; import { FormattedMessage } from 'react-intl'; @@ -21,6 +22,7 @@ import { Form, Input, InputAddress, QrScan } from '~/ui'; import ChangeVault from '../ChangeVault'; +@observer export default class NewQr extends Component { static propTypes = { createStore: PropTypes.object.isRequired, diff --git a/js/src/modals/FirstRun/TnC/tnc.js b/js/src/modals/FirstRun/TnC/tnc.js index 7fe60ff59..01651f0f4 100644 --- a/js/src/modals/FirstRun/TnC/tnc.js +++ b/js/src/modals/FirstRun/TnC/tnc.js @@ -16,10 +16,17 @@ import React, { Component, PropTypes } from 'react'; import { FormattedMessage } from 'react-intl'; +import ReactMarkdown from 'react-markdown'; import { Checkbox } from 'material-ui'; import styles from '../firstRun.css'; +let tnc = ''; + +if (process.env.NODE_ENV !== 'test') { + tnc = require('./tnc.md'); +} + export default class TnC extends Component { static propTypes = { hasAccepted: PropTypes.bool.isRequired, @@ -31,139 +38,10 @@ export default class TnC extends Component { return (
-

SECURITY WARNINGS

-
    -
  • You are responsible for your own computer security. If your machine is compromised you will lose your ether, access to any contracts and maybe more.
  • -
  • You are responsible for your own actions. If you mess something up or break any laws while using this software, it is your fault, and your fault only.
  • -
- -

LEGAL WARNING

-

SHORT VERSION

-

Disclaimer of Liability and Warranties

-
    -
  • The user expressly knows and agrees that the user is using Parity at the user’s sole risk.
  • -
  • The user represents that the user has an adequate understanding of the risks, usage and intricacies of cryptographic tokens and blockchain-based open source software, eth platform and eth.
  • -
  • The user acknowledges and agrees that, to the fullest extent permitted by any applicable law, the disclaimers of liability contained herein apply to any and all damages or injury whatsoever caused by or related to risks of, use of, or inability to use, Parity under any cause or action whatsoever of any kind in any jurisdiction, including, without limitation, actions for breach of warranty, breach of contract or tort (including negligence) and that Eth Core Limited shall be not liable for any indirect, incidental, special, exemplary or consequential damages, including for loss of profits, goodwill or data.
  • -
  • Some jurisdictions do not allow the exclusion of certain warranties or the limitation or exclusion of liability for certain types of damages. Therefore, some of the above limitations in this section may not apply to a user. In particular, nothing in these terms shall affect the statutory rights of any user or exclude injury arising from any wilful misconduct or fraud of Eth Core Limited.
  • -
  • All rights reserved by Parity. Licensed to the public under the GPL v3 https://www.gnu.org/licenses/gpl-3.0.txt
  • -
- -

LONG VERSION

-

The following Terms and Conditions (“Terms”) govern the use of Parity Technologies Limited’s open source software product (“Parity”). Prior to any use of the Parity or any of Parity Technologies Limited’s products (“Parity’s Products”), the user or anyone on whose behalf the software is used for directly or indirectly (“User”) confirms that they understand and expressly agree to all of the Terms. All capitalized terms in this agreement will be given the same effect and meaning as in the Terms. The group of developers and other personnel that is now, or will be, employed by, or contracted with, or affiliated with, Parity Technologies Limited (“Parity”) is termed the “Parity Team”.

- -

Acknowledgement of Risks

-

The user acknowledges the following serious risks to any use Parity and expressly agrees not to hold liable Parity or the Parity Team should any of these risks occur:

- -

Risk of Security Weaknesses in the Parity Core Infrastructure Software

-

Parity rests on open-source software, and although it is professionally developed in line with industry standards (which include external audits of the code base), there is a risk that Parity or the Parity Team, may have introduce unintentional weaknesses or bugs into the core infrastructural elements of Parity causing the system to lose ETH stored in one or more User accounts or other accounts or lose sums of other valued tokens.

- -

Risk of Weaknesses or Exploitable Breakthroughs in the Field of Cryptography

-

Cryptography is an art, not a science. And the state of the art can advance over time Advances in code cracking, or technical advances such as the development of quantum computers, could present risks to cryptocurrencies and Parity, which could result in the theft or loss of ETH. To the extent possible, Parity intends to update the protocol underlying Parity to account for any advances in cryptography and to incorporate additional security measures, but it cannot predict the future of cryptography or guaranty that any security updates will be made, timely or successful.

- -

Risk of Ether Mining Attacks

-

As with other cryptocurrencies, the blockchain used by Parity is susceptible to mining attacks, including but not limited to double-spend attacks, majority mining power attacks, “selfish-mining” attacks, and race condition attacks. Any successful attacks present a risk to the Ethereum ecosystem, expected proper execution and sequencing of ETH transactions, and expected proper execution and sequencing of contract computations. Despite the efforts of the Parity and the Parity Team, known or novel mining attacks may be successful.

- -

Risk of Rapid Adoption and Insufficiency of Computational Application Processing Power on the Ethereum Network

-

If Parity is rapidly adopted, the demand for transaction processing and decentralized application computations could rise dramatically and at a pace that exceeds the rate with which ETH miners can bring online additional mining power. Under such a scenario, the entire Ethereum ecosystem could become destabilized, due to the increased cost of running decentralized applications. In turn, this could dampen interest in the Ethereum ecosystem and ETH. Insufficiency of computational resources and an associated rise in the price of ETH could result in businesses being unable to acquire scarce computational resources to run their decentralized applications. This would represent revenue losses to businesses or worst case, cause businesses to cease operations because such operations have become uneconomical due to distortions in the crypto-economy.

- -

Risk of temporary network incoherence

-

We recommend any groups handling large or important transactions to maintain a voluntary 24 hour waiting period on any ether deposited. In case the integrity of the network is at risk due to issues in the clients, we will endeavour to publish patches in a timely fashion to address the issues. We will endeavour to provide solutions within the voluntary 24 hour waiting period.

- -

Use of Parity by you

-

You agree to use Parity only for purposes that are permitted by (a) these Terms and (b) any applicable law, regulation or generally accepted practices or guidelines in the relevant jurisdictions (including any laws regarding the export of data or software to and from the United Kingdom or other relevant countries).

-

You agree that you will not engage in any activity that interferes with or disrupts Parity’s or Parity’s Products’ functioning (or the networks which are connected to Parity).

-

Unless you have been specifically permitted to do so in a separate agreement with Parity, you agree that you will not reproduce, duplicate, copy, sell, trade or resell the Parity’s Products for any purpose unless than in accordance to the terms of the software licence terms available here: https://www.gnu.org/licenses/gpl-3.0.txt (“Software Licence Terms”).

-

You agree that you are solely responsible for (and that Parity has no responsibility to you or to any third party for) any breach of your obligations under these terms and for the consequences (including any loss or damage which Parity may suffer) of any such breach.

- -

Privacy and your personal information

-

You agree to the use of your data (if any is gathered) in accordance with Parity’s privacy policies: https://parity.io/legal.html. This policy explains how Parity treats your personal information (if any is gathered), and protects your privacy, when you use Parity’s Products.

- -

Content in Parity

-

You understand that all information and data (such as smart contracts, data files, written text, computer software, music, audio files or other sounds, photographs, videos or other images) which you may have access to as part of, or through your use of, Parity’s Product are the sole responsibility of the person from which such content originated. All such information is referred to below as the “Content”.

-

You should be aware that Content presented to you through Parity or Parity’s Product may be protected by intellectual property rights which are owned by thisrd parties who may provide that Content to Parity (or by other persons or companies on their behalf). You may not modify, rent, lease, loan, sell, distribute or create derivative works based on this Content (either in whole or in part) unless you have been specifically told that you may do so by Parity or by the owners of that Content, in a separate agreement.

-

You understand that by using Parity or Parity’s Products you may be exposed to Content that you may find offensive, indecent or objectionable and that, in this respect, you use Parity or Parity’s Products at your own risk.

-

You agree that you are solely responsible for (and that Parity has no responsibility to you or to any third party for) any Content that you create, transmit or display while using Parity or Parity’s Products and for the consequences of your actions (including any loss or damage which Parity may suffer) by doing so.

- -

Proprietary rights

-

You acknowledge and agree that Parity own all legal right, title and interest in and to the Parity and Parity’s Products, including any intellectual property rights which subsist in Parity and Parity’s Products (whether those rights happen to be registered or not, and wherever in the world those rights may exist).

-

Unless you have agreed otherwise in writing with Parity, nothing in the Terms gives you a right to use any of Parity’s trade names, trade marks, service marks, logos, domain names, and other distinctive brand features.

-

If you have been given an explicit right to use any of these brand features in a separate written agreement with Parity, then you agree that your use of such features shall be in compliance with that agreement, any applicable provisions of these terms, and Parity’s brand feature use guidelines as updated from time to time. These guidelines can be viewed online at https://parity.io/press.html.

-

Parity acknowledges and agrees that it obtains no right, title or interest from you (or your licensors) under these terms in or to any content that you submit, post, transmit or display on, or through, Parity, including any intellectual property rights which subsist in that content (whether those rights happen to be registered or not, and wherever in the world those rights may exist). Unless you have agreed otherwise in writing with Parity, you agree that you are responsible for protecting and enforcing those rights and that Parity has no obligation to do so on your behalf.

-

You agree that you shall not remove, obscure, or alter any proprietary rights notices (including copyright and trade mark notices) which may be affixed to or contained within Parity or Parity’s Products.

-

Unless you have been expressly authorized to do so in writing by Parity, you agree that in using Parity, you will not use any trade mark, service mark, trade name, logo of any company or organization in a way that is likely or intended to cause confusion about the owner or authorized user of such marks, names or logos.

- -

License Restrictions from Parity

-

You may not (and you may not permit anyone else to) copy, modify, create a derivative work of, reverse engineer, decompile or otherwise attempt to extract the source code of the Parity, Parity’s Products or any part thereof, unless this is expressly permitted by our Software Licence Terms or required by law, or unless you have been specifically told that you may do so by Parity, in writing.

-

Unless Parity has given you specific written permission to do so, you may not assign (or grant a sub-licence of) your rights to use Parity’s Products, grant a security interest in or over your rights to use the Parity’s Products, or otherwise transfer any part of your rights to use the Parity’s Products.

- -

Content licence from you

-

You retain copyright and any other rights you already hold in content which you submit, post or display on or through, Parity.

- -

Ending your relationship with Parity

-

The Terms will continue to apply until terminated by either you or Parity as set out below.

-

Parity may at any time, terminate its legal agreement with you if:

-
    -
  1. you have breached any provision of these Terms (or have acted in manner which clearly shows that you do not intend to, or are unable to comply with the provisions of these terms); or
  2. -
  3. Parity is required to do so by law (for example, where the provision of Parity’s Product to you is, or becomes, unlawful); or
  4. -
  5. the partner with whom Parity offered products or services to you has terminated its relationship with Parity or ceased to offer products or services to you; or
  6. -
  7. Parity is transitioning to no longer providing products or services to users in the country in which you are resident or from which you use the service; or
  8. -
  9. the provision of products or services to you by Parity is, in Parity’s opinion, no longer commercially viable.
  10. -
  11. When these Terms come to an end, all of the legal rights, obligations and liabilities that you and Parity have benefited from, been subject to (or which have accrued over time whilst the Terms have been in force) or which are expressed to continue indefinitely, shall be unaffected by this cessation, and the England and Wales jurisdiction choice shall continue to apply to such rights, obligations and liabilities indefinitely.
  12. -
- -

ACKNOWLEDGEMENT AND ACCEPTANCE OF ALL RISKS, EXCLUSION OF WARRANTIES

-

THE USER EXPRESSLY KNOWS AND AGREES THAT THE USER IS USING PARITY OR PARITY’S PRODUCTS AT THE USER’S SOLE RISK. THE USER REPRESENTS THAT THE USER HAS AN ADEQUATE UNDERSTANDING OF THE RISKS, USAGE AND INTRICACIES OF CRYPTOGRAPHIC TOKENS AND BLOCKCHAIN-BASED OPEN SOURCE SOFTWARE, PARITY.

-

YOU EXPRESSLY UNDERSTAND AND AGREE THAT YOUR USE OF PARITY’S PRODUCTS IS AT YOUR SOLE RISK AND THAT PARITY’S PRODUCTS ARE PROVIDED "AS IS" AND “AS AVAILABLE.”

-

IN PARTICULAR, PARITY, ITS SUBSIDIARIES AND AFFILIATES, AND ITS LICENSORS DO NOT REPRESENT OR WARRANT TO YOU THAT:

-

(A) YOUR USE OF PARITY OR PARITY’S PRODUCTS WILL MEET YOUR REQUIREMENTS,

-

(B) YOUR USE OF PARITY OR PARITY’S PRODUCTS WILL BE UNINTERRUPTED, TIMELY, SECURE OR FREE FROM ERROR,

-

(C) ANY INFORMATION OBTAINED BY YOU AS A RESULT OF YOUR USE OF PARITY OR PARITY’S PRODUCTS WILL BE ACCURATE OR RELIABLE, AND

-

(D) THAT DEFECTS IN THE OPERATION OR FUNCTIONALITY OF ANY SOFTWARE PROVIDED TO YOU AS PART OF PARITY’S PRODUCTS WILL BE CORRECTED.

-

ANY MATERIAL DOWNLOADED OR OTHERWISE OBTAINED THROUGH THE USE OF PARITY OR PARITY’S PRODUCTS IS DONE AT YOUR OWN DISCRETION AND RISK AND THAT YOU WILL BE SOLELY RESPONSIBLE FOR ANY DAMAGE TO YOUR COMPUTER SYSTEM OR OTHER DEVICE OR LOSS OF DATA OR ECONOMIC LOSS THAT RESULTS FROM THE DOWNLOAD OF ANY SUCH MATERIAL.

-

NO ADVICE OR INFORMATION, WHETHER ORAL OR WRITTEN, OBTAINED BY YOU FROM PARITY OR THROUGH OR FROM PARITY’S PRODUCTS SHALL CREATE ANY WARRANTY NOT EXPRESSLY STATED IN THE TERMS.

-

PARITY FURTHER EXPRESSLY DISCLAIMS ALL WARRANTIES AND CONDITIONS OF ANY KIND, WHETHER EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO THE IMPLIED WARRANTIES AND CONDITIONS OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.

- -

EXCLUSION AND LIMITATION OF LIABILITY

-

THE USER ACKNOWLEDGES AND AGREES THAT, TO THE FULLEST EXTENT PERMITTED BY ANY APPLICABLE LAW, THE DISCLAIMERS AND EXCLUSION OF LIABILITY CONTAINED HEREIN APPLY TO ANY AND ALL DAMAGES OR INJURY WHATSOEVER CAUSED BY OR RELATED TO RISKS OF, USE OF, OR INABILITY TO USE, PARITY UNDER ANY CAUSE OF ACTION WHATSOEVER OF ANY KIND IN ANY JURISDICTION, INCLUDING, WITHOUT LIMITATION, ACTIONS FOR BREACH OF WARRANTY, BREACH OF CONTRACT OR TORT (INCLUDING NEGLIGENCE) AND THAT NEITHER PARITY NOR THE PARITY TEAM SHALL BE LIABLE FOR ANY INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES, INCLUDING FOR LOSS OF PROFITS, GOODWILL OR DATA.

-

SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF CERTAIN WARRANTIES OR THE LIMITATION OR EXCLUSION OF LIABILITY FOR CERTAIN TYPES OF DAMAGES. THEREFORE, SOME OF THE ABOVE LIMITATIONS IN THIS SECTION MAY NOT APPLY TO A USER. IN PARTICULAR, NOTHING IN THESE TERMS SHALL AFFECT THE STATUTORY RIGHTS OF ANY USER OR EXCLUDE INJURY ARISING FROM ANY WILLFUL MISCONDUCT OR FRAUD OF PARITY.

-

SUBJECT TO ANY LIABILITY WHICH MAY NOT BE EXCLUDED, YOU EXPRESSLY UNDERSTAND AND AGREE THAT PARITY, ITS SUBSIDIARIES AND AFFILIATES, AND ITS LICENSORS SHALL NOT BE LIABLE TO YOU FOR:

-

(A) ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL CONSEQUENTIAL OR EXEMPLARY DAMAGES WHICH MAY BE INCURRED BY YOU, HOWEVER CAUSED AND UNDER ANY THEORY OF LIABILITY. THIS SHALL INCLUDE, BUT NOT BE LIMITED TO, ANY LOSS OF PROFIT (WHETHER INCURRED DIRECTLY OR INDIRECTLY), ANY LOSS OF GOODWILL OR BUSINESS REPUTATION, ANY LOSS OF DATA SUFFERED, COST OF PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, OR OTHER INTANGIBLE LOSS;

-

(B) ANY LOSS OR DAMAGE WHICH MAY BE INCURRED BY YOU, INCLUDING BUT NOT LIMITED TO LOSS OR DAMAGE AS A RESULT OF:

-

(I) ANY RELIANCE PLACED BY YOU ON THE COMPLETENESS, ACCURACY OR EXISTENCE OF ANY ADVERTISING, OR AS A RESULT OF ANY RELATIONSHIP OR TRANSACTION BETWEEN YOU AND ANY ADVERTISER OR SPONSOR WHOSE ADVERTISING APPEARS ON PARITY’S PRODUCTS;

-

(II) ANY CHANGES WHICH PARITY MAY MAKE TO PARITY’S PRODUCTS, OR FOR ANY PERMANENT OR TEMPORARY CESSATION IN THE PROVISION OF PARITY’S PRODUCTS (OR ANY FEATURES WITHIN PARITY’S PRODUCTS);

-

(III) THE DELETION OF, CORRUPTION OF, OR FAILURE TO STORE, ANY CONTENT AND OTHER COMMUNICATIONS DATA MAINTAINED OR TRANSMITTED BY OR THROUGH YOUR USE OF PARITY’S PRODUCTS;

-

(IV) YOUR FAILURE TO PROVIDE PARITY WITH ACCURATE ACCOUNT INFORMATION (IF THIS IS REQUIRED);

-

(V) YOUR FAILURE TO KEEP YOUR PASSWORD OR ACCOUNT DETAILS SECURE AND CONFIDENTIAL;

-

THE LIMITATIONS ON PARITY’S LIABILITY TO YOU SHALL APPLY WHETHER OR NOT PARITY HAS BEEN ADVISED OF OR SHOULD HAVE BEEN AWARE OF THE POSSIBILITY OF ANY SUCH LOSSES ARISING.

- -

Copyright and trade mark policies

-

It is Parity’s policy to respond to notices of alleged copyright infringement that comply with applicable international intellectual property law (including, in the United States, the Digital Millennium Copyright Act) and if Parity is put on notice and it is under Parity’s control and terminating the accounts of repeat infringers.

- -

Other content

-

Services provided may include hyperlinks to other web sites, smart contracts or content or resources. Parity may have no control over any web sites or resources which are provided by companies or persons other than Parity.

-

You acknowledge and agree that Parity is not responsible for the availability of any such external sites or resources, and does not endorse any advertising, products or other materials on or available from such web sites or resources.

-

You acknowledge and agree that Parity is not liable for any loss or damage which may be incurred by you as a result of the availability of those external sites or resources, or as a result of any reliance placed by you on the completeness, accuracy or existence of any advertising, products or other materials on, or available from, such web sites or resources.

- -

Changes to the Terms

-

Parity may make changes to these from time to time. When these changes are made, Parity will make a new copy of these terms available at https://parity.io/legal.html and any new terms will be made available to you from within, or through, the affected Parity’s Product.

-

You understand and agree that if you use Parity or Parity’s Products after the date on which the Terms have changed, Parity will treat your use as acceptance of the updated terms.

- -

General legal terms

-

Sometimes when you use Parity or Parity’s Products, you may (as a result of, or in connection with your use of these products) use a service or download a piece of software, or smart contract, or purchase goods, which are provided by another person or company. Your use of these other services, software, smart contract or goods may be subject to separate terms between you and the company or person concerned. If so, these Terms do not affect your legal relationship with these other companies or individuals.

-

These Terms constitute the whole legal agreement between you and Parity and govern your use of Parity and Parity’s Products (but excluding any products or services which Parity may provide to you under a separate written agreement), and completely replace any prior agreements between you and Parity in relation to Parity and Parity’s Products.

-

You agree that Parity may provide you with notices, including those regarding changes to the Terms, by postings on the affected Parity’s Product.

-

You agree that if Parity does not exercise or enforce any legal right or remedy which is contained in these Terms (or which Parity has the benefit of under any applicable law), this will not be taken to be a formal waiver of Parity’s rights and that those rights or remedies will still be available to Parity.

-

If any court of law, having the jurisdiction to decide on this matter, rules that any provision of these Terms is invalid, then that provision will be removed from the Terms without affecting the rest of the Terms. The remaining provisions of the Terms will continue to be valid and enforceable.

-

You acknowledge and agree that each member of the group of companies of which Parity is the parent shall be third party beneficiaries to these Terms and that such other companies shall be entitled to directly enforce, and rely upon, any provision of the Terms which confers a benefit on (or rights in favor of) them. Other than this, no other person or company shall be third party beneficiaries to these Terms.

-

These Terms, and your relationship with Parity under these Terms, shall be governed by the laws of England and Wales, United Kingdom without regard to its conflict of laws provisions. You and Parity agree to submit to the exclusive jurisdiction of the courts located within England, United Kingdom to resolve any legal matter arising from these Terms (subject to the Dispute Resolution clause below). Notwithstanding this, you agree that Parity shall still be allowed to apply for injunctive remedies (or an equivalent type of urgent legal relief) in any jurisdiction.

- -

Dispute Resolution

-

All disputes or claims arising out of, relating to, or in connection with the Terms, the breach thereof, or use of Parity shall be finally settled under the Rules of Arbitration of the International Chamber of Commerce by one or more arbitrators appointed in accordance with said Rules. All claims between the parties relating to these Terms that are capable of being resolved by arbitration, whether sounding in contract, tort, or otherwise, shall be submitted to ICC arbitration. Prior to commencing arbitration, the parties have a duty to negotiate in good faith and attempt to resolve their dispute in a manner other than by submission to ICC arbitration. The arbitration panel shall consist of one arbitrator only, unless the ICC Court of Arbitration determines that the dispute is such as to warrant three arbitrators. If the Court determines that one arbitrator is sufficient, then such arbitrator shall be a UK resident. If the Court determines that three arbitrators are necessary, then each party shall have 30 days to nominate an arbitrator of its choice - in the case of the Claimant, measured from receipt of notification of the ICC Court’s decision to have three arbitrators; in the case of Respondent, measured from receipt of notification of Claimant’s nomination. All nominations must be UK residents. If a party fails to nominate an arbitrator, the Court will do so. The Court shall also appoint the chairman. All arbitrators shall be and remain “independent” of the parties involved in the arbitration. The place of arbitration shall be England, United Kingdom. The language of the arbitration shall be English. In deciding the merits of the dispute, the tribunal shall apply the laws of England and Wales and any discovery shall be limited and shall not involve any depositions or any other examinations outside of a formal hearing. The tribunal shall not assume the powers of amiable compositeur or decide the case ex aequo et bono. In the final award, the tribunal shall fix the costs of the arbitration and decide which of the parties shall bear such costs in what proportion. Every award shall be binding on the parties. The parties undertake to carry out the award without delay and waive their right to any form of recourse against the award in so far as such waiver can validly be made.

- -

Additional Terms for Enterprise Use

-

If you are a business entity, then the individual accepting on behalf of the entity (for the avoidance of doubt, for business entities, in these Terms, "you" means the entity) represents and warrants that he or she has the authority to act on your behalf, that you represent that you are duly authorized to do business in the country or countries where you operate, and that your employees, officers, representatives, and other agents accessing Parity’s Products are duly authorized to access Parity and to legally bind you to these Terms.

-

Subject to these Terms and subject to the Software Licence Terms, Parity grants you a non-exclusive, non-transferable licence to install and use Parity solely on machines intended for use by your employees, officers, representatives, and agents in connection with your business entity, and provided that their use of Parity will be subject to these Terms and Parity’s Products software licence terms.

- + { - const status = { netPeers, syncing }; + .then(([ syncing, netPeers, health ]) => { + const status = { netPeers, syncing, health }; + + health.overall = this._overallStatus(health); if (!isEqual(status, this._status)) { this._store.dispatch(statusCollection(status)); @@ -216,6 +223,33 @@ export default class Status { }); } + _overallStatus = (health) => { + const all = [health.peers, health.sync, health.time].filter(x => x); + const statuses = all.map(x => x.status); + const bad = statuses.find(x => x === STATUS_BAD); + const needsAttention = statuses.find(x => x === STATUS_WARN); + const message = all.map(x => x.message).filter(x => x); + + if (all.length) { + return { + status: bad || needsAttention || STATUS_OK, + message + }; + } + + return { + status: STATUS_BAD, + message: ['Unable to fetch node health.'] + }; + } + + _fetchHealth = () => { + // Support Parity-Extension. + const uiUrl = this._api.transport.uiUrlWithProtocol || ''; + + return fetch(`${uiUrl}/api/health`).then(res => res.json()); + } + /** * The data fetched here should not change * unless Parity is restarted. They are thus diff --git a/js/src/redux/providers/statusReducer.js b/js/src/redux/providers/statusReducer.js index 3a6968548..23da8616f 100644 --- a/js/src/redux/providers/statusReducer.js +++ b/js/src/redux/providers/statusReducer.js @@ -18,11 +18,28 @@ import BigNumber from 'bignumber.js'; import { handleActions } from 'redux-actions'; const DEFAULT_NETCHAIN = '(unknown)'; +const DEFAULT_STATUS = 'needsAttention'; const initialState = { blockNumber: new BigNumber(0), blockTimestamp: new Date(), clientVersion: '', gasLimit: new BigNumber(0), + health: { + peers: { + status: DEFAULT_STATUS + }, + sync: { + status: DEFAULT_STATUS + }, + time: { + status: DEFAULT_STATUS + }, + overall: { + isReady: false, + status: DEFAULT_STATUS, + message: [] + } + }, netChain: DEFAULT_NETCHAIN, netPeers: { active: new BigNumber(0), diff --git a/js/src/ui/Actionbar/actionbar.js b/js/src/ui/Actionbar/actionbar.js index d7274b999..7e5c40fbb 100644 --- a/js/src/ui/Actionbar/actionbar.js +++ b/js/src/ui/Actionbar/actionbar.js @@ -31,7 +31,8 @@ export default class Actionbar extends Component { title: nodeOrStringProptype(), buttons: PropTypes.array, children: PropTypes.node, - className: PropTypes.string + className: PropTypes.string, + health: PropTypes.node }; static defaultProps = { diff --git a/js/src/ui/StatusIndicator/index.js b/js/src/ui/StatusIndicator/index.js new file mode 100644 index 000000000..7d2978dc4 --- /dev/null +++ b/js/src/ui/StatusIndicator/index.js @@ -0,0 +1,17 @@ +// 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 './statusIndicator'; diff --git a/js/src/ui/StatusIndicator/statusIndicator.css b/js/src/ui/StatusIndicator/statusIndicator.css new file mode 100644 index 000000000..fa8c0bfcf --- /dev/null +++ b/js/src/ui/StatusIndicator/statusIndicator.css @@ -0,0 +1,88 @@ +/* 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 . +*/ +.status { + display: inline-block; +} + +.radial,.signal { + display: inline-block; + margin: .2em; + width: 1em; + height: 1em; +} + +.radial { + border-radius: 100%; + border-top: 1px solid rgba(255, 255, 255, 0.5); + background-image: radial-gradient(ellipse at top, rgba(255, 255, 255, 0.38) 0%, rgba(255, 255, 255, 0) 100%); + + &.ok { + background-color: #070; + } + &.bad { + background-color: #c00; + } + &.needsAttention { + background-color: #dc0; + } +} + +.signal { + width: 2em; + width: calc(.9em + 5px); + text-transform: initial; + vertical-align: bottom; + margin-top: -1em; + + > .bar { + display: inline-block; + border: 1px solid #444; + box-shadow: 0 0 1px rgba(0, 0, 0, 0.8); + width: .3em; + height: 1em; + opacity: 0.7; + background-color: rgba(0, 0, 0, 0.6); + vertical-align: bottom; + + &.active { + opacity: 1.0; + background-image: linear-gradient(0, rgba(255, 255, 255, 0.38) 0%, rgba(255, 255, 255, 0) 100%); + } + + &.bad { + height: .4em; + border-right: 0; + } + &.needsAttention { + height: .6em; + border-right: 0; + } + &.ok { + height: 1em; + } + } + + &.bad > .bar.active { + background-color: #c00; + } + &.ok > .bar.active { + background-color: #080; + } + &.needsAttention > .bar.active { + background-color: #dc0; + } +} diff --git a/js/src/ui/StatusIndicator/statusIndicator.js b/js/src/ui/StatusIndicator/statusIndicator.js new file mode 100644 index 000000000..cacce2a36 --- /dev/null +++ b/js/src/ui/StatusIndicator/statusIndicator.js @@ -0,0 +1,70 @@ +// 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 React, { Component, PropTypes } from 'react'; +import ReactTooltip from 'react-tooltip'; + +import styles from './statusIndicator.css'; + +const statuses = ['bad', 'needsAttention', 'ok']; + +export default class StatusIndicator extends Component { + static propTypes = { + type: PropTypes.oneOf(['radial', 'signal']), + id: PropTypes.string.isRequired, + status: PropTypes.oneOf(statuses).isRequired, + title: PropTypes.arrayOf(PropTypes.node), + tooltipPlacement: PropTypes.oneOf(['left', 'top', 'bottom', 'right']) + }; + + static defaultProps = { + type: 'signal', + title: [] + }; + + render () { + const { id, status, title, type, tooltipPlacement } = this.props; + const tooltip = title.find(x => !x.isEmpty) ? ( + + { title.map(x => (
{ x }
)) } +
+ ) : null; + + return ( + + + { type === 'signal' && statuses.map(this.renderBar) } + + {tooltip} + + ); + } + + renderBar = (signal) => { + const idx = statuses.indexOf(this.props.status); + const isActive = statuses.indexOf(signal) <= idx; + const activeClass = isActive ? styles.active : ''; + + return ( + + ); + } +} diff --git a/js/src/ui/index.js b/js/src/ui/index.js index 1cb0e468b..7e07bad7b 100644 --- a/js/src/ui/index.js +++ b/js/src/ui/index.js @@ -52,6 +52,7 @@ export SectionList from './SectionList'; export SelectionList from './SelectionList'; export ShortenedHash from './ShortenedHash'; export SignerIcon from './SignerIcon'; +export StatusIndicator from './StatusIndicator'; export Tags from './Tags'; export Title from './Title'; export Tooltips, { Tooltip } from './Tooltips'; diff --git a/js/src/views/Accounts/accounts.js b/js/src/views/Accounts/accounts.js index a4f0e1126..2d37b7a4b 100644 --- a/js/src/views/Accounts/accounts.js +++ b/js/src/views/Accounts/accounts.js @@ -43,6 +43,7 @@ class Accounts extends Component { accountsInfo: PropTypes.object.isRequired, availability: PropTypes.string.isRequired, hasAccounts: PropTypes.bool.isRequired, + health: PropTypes.object.isRequired, setVisibleAccounts: PropTypes.func.isRequired } @@ -496,12 +497,14 @@ class Accounts extends Component { function mapStateToProps (state) { const { accounts, accountsInfo, hasAccounts } = state.personal; const { availability = 'unknown' } = state.nodeStatus.nodeKind || {}; + const { health } = state.nodeStatus; return { accounts, accountsInfo, availability, - hasAccounts + hasAccounts, + health }; } diff --git a/js/src/views/Application/Status/status.js b/js/src/views/Application/Status/status.js index 303449363..f44f48eb3 100644 --- a/js/src/views/Application/Status/status.js +++ b/js/src/views/Application/Status/status.js @@ -18,7 +18,7 @@ import React, { Component, PropTypes } from 'react'; import { FormattedMessage } from 'react-intl'; import { connect } from 'react-redux'; -import { BlockStatus } from '~/ui'; +import { BlockStatus, StatusIndicator } from '~/ui'; import styles from './status.css'; @@ -28,11 +28,12 @@ class Status extends Component { isTest: PropTypes.bool, netChain: PropTypes.string, netPeers: PropTypes.object, + health: PropTypes.object, upgradeStore: PropTypes.object.isRequired } render () { - const { clientVersion, isTest, netChain, netPeers } = this.props; + const { clientVersion, isTest, netChain, netPeers, health } = this.props; return (
@@ -44,13 +45,20 @@ class Status extends Component { { this.renderUpgradeButton() }
- +
+ +
+ + +
{ netChain }
-
- { netPeers.connected.toFormat() }/{ netPeers.max.toFormat() } peers -
); @@ -102,14 +110,7 @@ class Status extends Component { ); } - return ( -
- -
- ); + return; } renderUpgradeButton () { @@ -136,10 +137,11 @@ class Status extends Component { } function mapStateToProps (state) { - const { clientVersion, netPeers, netChain, isTest } = state.nodeStatus; + const { clientVersion, health, netPeers, netChain, isTest } = state.nodeStatus; return { clientVersion, + health, netPeers, netChain, isTest diff --git a/js/src/views/Application/TabBar/tabBar.css b/js/src/views/Application/TabBar/tabBar.css index 2903f3ed3..fa8478f8b 100644 --- a/js/src/views/Application/TabBar/tabBar.css +++ b/js/src/views/Application/TabBar/tabBar.css @@ -81,6 +81,16 @@ white-space: nowrap; } +.indicatorTab { + font-size: 1.5rem; + flex: 0; +} + +.indicator { + padding: 20px 12px 0; + opacity: 0.8; +} + .first { margin-left: -24px; } diff --git a/js/src/views/Application/TabBar/tabBar.js b/js/src/views/Application/TabBar/tabBar.js index 6388427e5..7ac0d8eac 100644 --- a/js/src/views/Application/TabBar/tabBar.js +++ b/js/src/views/Application/TabBar/tabBar.js @@ -21,7 +21,7 @@ import { Link } from 'react-router'; import { Toolbar, ToolbarGroup } from 'material-ui/Toolbar'; import { isEqual } from 'lodash'; -import { Tooltip } from '~/ui'; +import { Tooltip, StatusIndicator } from '~/ui'; import Tab from './Tab'; import styles from './tabBar.css'; @@ -33,6 +33,7 @@ class TabBar extends Component { static propTypes = { pending: PropTypes.array, + health: PropTypes.object.isRequired, views: PropTypes.array.isRequired }; @@ -41,12 +42,29 @@ class TabBar extends Component { }; render () { + const { health } = this.props; + return (
+ +
+ +
+ { this.renderTabItems() } { const { availability = 'unknown' } = state.nodeStatus.nodeKind || {}; const { views } = state.settings; + const { health } = state.nodeStatus; const viewIds = Object .keys(views) @@ -114,7 +133,7 @@ function mapStateToProps (initState) { }); if (isEqual(viewIds, filteredViewIds)) { - return { views: filteredViews }; + return { views: filteredViews, health }; } filteredViewIds = viewIds; @@ -123,7 +142,7 @@ function mapStateToProps (initState) { id })); - return { views: filteredViews }; + return { views: filteredViews, health }; }; } diff --git a/js/src/views/Application/TabBar/tabBar.spec.js b/js/src/views/Application/TabBar/tabBar.spec.js index ee6845768..f629bca1f 100644 --- a/js/src/views/Application/TabBar/tabBar.spec.js +++ b/js/src/views/Application/TabBar/tabBar.spec.js @@ -37,6 +37,12 @@ function createStore () { nodeStatus: { nodeKind: { 'availability': 'personal' + }, + health: { + overall: { + status: 'ok', + message: [] + } } } }; diff --git a/js/src/views/ParityBar/parityBar.js b/js/src/views/ParityBar/parityBar.js index 618a1d05a..f6ae8d48e 100644 --- a/js/src/views/ParityBar/parityBar.js +++ b/js/src/views/ParityBar/parityBar.js @@ -24,7 +24,7 @@ import { connect } from 'react-redux'; import store from 'store'; import imagesEthcoreBlock from '~/../assets/images/parity-logo-white-no-text.svg'; -import { AccountCard, Badge, Button, ContainerTitle, IdentityIcon, ParityBackground, SelectionList } from '~/ui'; +import { AccountCard, Badge, Button, ContainerTitle, IdentityIcon, ParityBackground, SelectionList, StatusIndicator } from '~/ui'; import { CancelIcon, FingerprintIcon } from '~/ui/Icons'; import DappsStore from '~/views/Dapps/dappsStore'; import { Embedded as Signer } from '~/views/Signer'; @@ -50,7 +50,8 @@ class ParityBar extends Component { static propTypes = { dapp: PropTypes.bool, externalLink: PropTypes.string, - pending: PropTypes.array + pending: PropTypes.array, + health: PropTypes.object }; state = { @@ -210,7 +211,7 @@ class ParityBar extends Component { } renderBar () { - const { dapp } = this.props; + const { dapp, health } = this.props; if (!dapp) { return null; @@ -218,6 +219,13 @@ class ParityBar extends Component { return (
+
+ ); +}; + +HealthItem.propTypes = { + id: PropTypes.string.isRequired, + title: PropTypes.node.isRequired, + details: PropTypes.oneOfType([ + PropTypes.string, + PropTypes.node + ]).isRequired, + item: PropTypes.object.isRequired +}; + +class Health extends Component { + static contextTypes = { + api: PropTypes.object.isRequired + }; + + static propTypes = { + peers: PropTypes.object.isRequired, + sync: PropTypes.object.isRequired, + time: PropTypes.object.isRequired + }; + + state = {}; + + render () { + const { peers, sync, time } = this.props; + const [yes, no] = [( + + ), ( + + )]; + + return ( + + + +
+ } + /> +
+
+
+ + } + details={ !sync.details ? yes : no } + item={ sync } + /> +
+
+ + } + details={ (peers.details || []).join('/') } + item={ peers } + /> +
+
+ + } + details={ `${time.details || 0} ms` } + item={ time } + /> +
+
+
+ + ); + } +} + +function mapStateToProps (state) { + return state.nodeStatus.health; +} + +export default connect( + mapStateToProps, + null +)(Health); diff --git a/js/src/views/Status/Health/index.js b/js/src/views/Status/Health/index.js new file mode 100644 index 000000000..3fefcb039 --- /dev/null +++ b/js/src/views/Status/Health/index.js @@ -0,0 +1,17 @@ +// 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 './health'; diff --git a/js/src/views/Status/NodeStatus/nodeStatus.css b/js/src/views/Status/NodeStatus/nodeStatus.css index e96e3bc04..e96dad03d 100644 --- a/js/src/views/Status/NodeStatus/nodeStatus.css +++ b/js/src/views/Status/NodeStatus/nodeStatus.css @@ -44,7 +44,7 @@ } .col, -.col3, .col4_5, .col6, .col12 { +.col3, .col4, .col4_5, .col6, .col12 { float: left; padding: 0 1em; box-sizing: border-box; @@ -57,6 +57,13 @@ width: calc(100% / 12 * 3); } +.col4 { + width: 33.3%; + width: -webkit-calc(100% / 12 * 4); + width: -moz-calc(100% / 12 * 4); + width: calc(100% / 12 * 4); +} + .col4_5 { width: 37.5%; width: -webkit-calc(100% / 12 * 4.5); diff --git a/js/src/views/Status/status.js b/js/src/views/Status/status.js index b1892631e..bac520a46 100644 --- a/js/src/views/Status/status.js +++ b/js/src/views/Status/status.js @@ -20,6 +20,7 @@ import { FormattedMessage } from 'react-intl'; import { Page } from '~/ui'; import Debug from './Debug'; +import Health from './Health'; import Peers from './Peers'; import NodeStatus from './NodeStatus'; @@ -35,6 +36,7 @@ export default () => ( } >
+ diff --git a/js/src/views/SyncWarning/syncWarning.css b/js/src/views/SyncWarning/syncWarning.css index 828036499..324f7d3fa 100644 --- a/js/src/views/SyncWarning/syncWarning.css +++ b/js/src/views/SyncWarning/syncWarning.css @@ -58,3 +58,7 @@ margin: 0.5em 0; } } + +.status { + font-size: 4rem; +} diff --git a/js/src/views/SyncWarning/syncWarning.js b/js/src/views/SyncWarning/syncWarning.js index 67deff075..cf448a9bb 100644 --- a/js/src/views/SyncWarning/syncWarning.js +++ b/js/src/views/SyncWarning/syncWarning.js @@ -20,7 +20,7 @@ import { FormattedMessage } from 'react-intl'; import { connect } from 'react-redux'; import store from 'store'; -import { Button } from '~/ui'; +import { Button, StatusIndicator } from '~/ui'; import styles from './syncWarning.css'; @@ -38,7 +38,8 @@ export const showSyncWarning = () => { class SyncWarning extends Component { static propTypes = { - isSyncing: PropTypes.bool + isOk: PropTypes.bool.isRequired, + health: PropTypes.object.isRequired }; state = { @@ -47,10 +48,10 @@ class SyncWarning extends Component { }; render () { - const { isSyncing } = this.props; + const { isOk, health } = this.props; const { dontShowAgain, show } = this.state; - if (!isSyncing || isSyncing === null || !show) { + if (isOk || !show) { return null; } @@ -59,18 +60,19 @@ class SyncWarning extends Component {
- - +
+ +
+ + { + health.overall.message.map(message => ( +

{ message }

+ )) + }
{ return { nodeStatus: { - syncing + health: { + overall: { + status: syncing ? 'needsAttention' : 'ok', + message: [] + } + } } }; } diff --git a/js/webpack/app.js b/js/webpack/app.js index 4a8b129a2..b076e66e2 100644 --- a/js/webpack/app.js +++ b/js/webpack/app.js @@ -91,7 +91,19 @@ module.exports = { } ] }, - + { + test: /\.md$/, + use: [ + { + loader: 'html-loader', + options: {} + }, + { + loader: 'markdown-loader', + options: {} + } + ] + }, { test: /\.css$/, include: [ /src/ ], diff --git a/json/src/spec/params.rs b/json/src/spec/params.rs index 8103515cb..0c0b1b01e 100644 --- a/json/src/spec/params.rs +++ b/json/src/spec/params.rs @@ -89,6 +89,8 @@ pub struct Params { pub nonce_cap_increment: Option, /// See `CommonParams` docs. pub remove_dust_contracts : Option, + /// Wasm support flag + pub wasm: Option, } #[cfg(test)] diff --git a/logger/src/lib.rs b/logger/src/lib.rs index b1999b2a7..7b57f383f 100644 --- a/logger/src/lib.rs +++ b/logger/src/lib.rs @@ -67,10 +67,11 @@ pub fn setup_log(config: &Config) -> Result, String> { let mut levels = String::new(); let mut builder = LogBuilder::new(); - // Disable ws info logging by default. + // Disable info logging by default for some modules: builder.filter(Some("ws"), LogLevelFilter::Warn); - // Disable rustls info logging by default. + builder.filter(Some("reqwest"), LogLevelFilter::Warn); builder.filter(Some("rustls"), LogLevelFilter::Warn); + // Enable info for others. builder.filter(None, LogLevelFilter::Info); if let Ok(lvl) = env::var("RUST_LOG") { diff --git a/parity/account.rs b/parity/account.rs index 3b2fd3c2d..ed4f9b40a 100644 --- a/parity/account.rs +++ b/parity/account.rs @@ -71,7 +71,7 @@ pub fn execute(cmd: AccountCmd) -> Result { } fn keys_dir(path: String, spec: SpecType) -> Result { - let spec = spec.spec()?; + let spec = spec.spec(&::std::env::temp_dir())?; let mut path = PathBuf::from(&path); path.push(spec.data_dir); RootDiskDirectory::create(path).map_err(|e| format!("Could not open keys directory: {}", e)) diff --git a/parity/blockchain.rs b/parity/blockchain.rs index 26de04f95..fae9a3bfc 100644 --- a/parity/blockchain.rs +++ b/parity/blockchain.rs @@ -29,7 +29,7 @@ use ethcore::error::ImportError; use ethcore::miner::Miner; use ethcore::verification::queue::VerifierSettings; use cache::CacheConfig; -use informant::{Informant, MillisecondDuration}; +use informant::{Informant, FullNodeInformantData, MillisecondDuration}; use params::{SpecType, Pruning, Switch, tracing_switch_to_bool, fatdb_switch_to_bool}; use helpers::{to_client_config, execute_upgrades}; use dir::Directories; @@ -148,7 +148,7 @@ fn execute_import(cmd: ImportBlockchain) -> Result<(), String> { let timer = Instant::now(); // load spec file - let spec = cmd.spec.spec()?; + let spec = cmd.spec.spec(&cmd.dirs.cache)?; // load genesis hash let genesis_hash = spec.genesis_header().hash(); @@ -238,7 +238,17 @@ fn execute_import(cmd: ImportBlockchain) -> Result<(), String> { } }; - let informant = Arc::new(Informant::new(client.clone(), None, None, None, None, cmd.with_color)); + let informant = Arc::new(Informant::new( + FullNodeInformantData { + client: client.clone(), + sync: None, + net: None, + }, + None, + None, + cmd.with_color, + )); + service.register_io_handler(informant).map_err(|_| "Unable to register informant handler".to_owned())?; let do_import = |bytes| { @@ -320,7 +330,7 @@ fn start_client( ) -> Result { // load spec file - let spec = spec.spec()?; + let spec = spec.spec(&dirs.cache)?; // load genesis hash let genesis_hash = spec.genesis_header().hash(); @@ -517,7 +527,7 @@ fn execute_export_state(cmd: ExportState) -> Result<(), String> { } pub fn kill_db(cmd: KillBlockchain) -> Result<(), String> { - let spec = cmd.spec.spec()?; + let spec = cmd.spec.spec(&cmd.dirs.cache)?; let genesis_hash = spec.genesis_header().hash(); let db_dirs = cmd.dirs.database(genesis_hash, None, spec.data_dir); let user_defaults_path = db_dirs.user_defaults_path(); diff --git a/parity/cli/config.toml b/parity/cli/config.toml index c3617077e..39e5686f6 100644 --- a/parity/cli/config.toml +++ b/parity/cli/config.toml @@ -78,6 +78,7 @@ disable_periodic = true jit = false [misc] +ntp_server = "pool.ntp.org:123" logging = "own_tx=trace" log_file = "/var/log/parity.log" color = true diff --git a/parity/cli/mod.rs b/parity/cli/mod.rs index 198622966..08e38bf19 100644 --- a/parity/cli/mod.rs +++ b/parity/cli/mod.rs @@ -180,8 +180,10 @@ usage! { or |c: &Config| otry!(c.rpc).apis.as_ref().map(|vec| vec.join(",")), flag_jsonrpc_hosts: String = "none", or |c: &Config| otry!(c.rpc).hosts.as_ref().map(|vec| vec.join(",")), - flag_jsonrpc_threads: Option = None, - or |c: &Config| otry!(c.rpc).threads.map(Some), + flag_jsonrpc_server_threads: Option = None, + or |c: &Config| otry!(c.rpc).server_threads.map(Some), + flag_jsonrpc_threads: usize = 0usize, + or |c: &Config| otry!(c.rpc).processing_threads, // WS flag_no_ws: bool = false, @@ -250,6 +252,8 @@ usage! { or |c: &Config| otry!(c.mining).force_sealing.clone(), flag_reseal_on_txs: String = "own", or |c: &Config| otry!(c.mining).reseal_on_txs.clone(), + flag_reseal_on_uncle: bool = false, + or |c: &Config| otry!(c.mining).reseal_on_uncle.clone(), flag_reseal_min_period: u64 = 2000u64, or |c: &Config| otry!(c.mining).reseal_min_period.clone(), flag_reseal_max_period: u64 = 120000u64, @@ -350,6 +354,8 @@ usage! { or |c: &Config| otry!(c.vm).jit.clone(), // -- Miscellaneous Options + flag_ntp_server: String = "pool.ntp.org:123", + or |c: &Config| otry!(c.misc).ntp_server.clone(), flag_logging: Option = None, or |c: &Config| otry!(c.misc).logging.clone().map(Some), flag_log_file: Option = None, @@ -466,7 +472,8 @@ struct Rpc { cors: Option, apis: Option>, hosts: Option>, - threads: Option, + server_threads: Option, + processing_threads: Option, } #[derive(Default, Debug, PartialEq, Deserialize)] @@ -524,6 +531,7 @@ struct Mining { author: Option, engine_signer: Option, force_sealing: Option, + reseal_on_uncle: Option, reseal_on_txs: Option, reseal_min_period: Option, reseal_max_period: Option, @@ -584,6 +592,7 @@ struct VM { #[derive(Default, Debug, PartialEq, Deserialize)] struct Misc { + ntp_server: Option, logging: Option, log_file: Option, color: Option, @@ -746,7 +755,8 @@ mod tests { flag_jsonrpc_cors: Some("null".into()), flag_jsonrpc_apis: "web3,eth,net,parity,traces,rpc,secretstore".into(), flag_jsonrpc_hosts: "none".into(), - flag_jsonrpc_threads: None, + flag_jsonrpc_server_threads: None, + flag_jsonrpc_threads: 0, // WS flag_no_ws: false, @@ -788,6 +798,7 @@ mod tests { flag_reseal_on_txs: "all".into(), flag_reseal_min_period: 4000u64, flag_reseal_max_period: 60000u64, + flag_reseal_on_uncle: false, flag_work_queue_size: 20usize, flag_tx_gas_limit: Some("6283184".into()), flag_tx_time_limit: Some(100u64), @@ -882,6 +893,7 @@ mod tests { flag_dapps_apis_all: None, // -- Miscellaneous Options + flag_ntp_server: "pool.ntp.org:123".into(), flag_version: false, flag_logging: Some("own_tx=trace".into()), flag_log_file: Some("/var/log/parity.log".into()), @@ -973,7 +985,8 @@ mod tests { cors: None, apis: None, hosts: None, - threads: None, + server_threads: None, + processing_threads: None, }), ipc: Some(Ipc { disable: None, @@ -1012,6 +1025,7 @@ mod tests { engine_signer: Some("0xdeadbeefcafe0000000000000000000000000001".into()), force_sealing: Some(true), reseal_on_txs: Some("all".into()), + reseal_on_uncle: None, reseal_min_period: Some(4000), reseal_max_period: Some(60000), work_queue_size: None, @@ -1056,6 +1070,7 @@ mod tests { jit: Some(false), }), misc: Some(Misc { + ntp_server: Some("pool.ntp.org:123".into()), logging: Some("own_tx=trace".into()), log_file: Some("/var/log/parity.log".into()), color: Some(true), diff --git a/parity/cli/usage.txt b/parity/cli/usage.txt index 40be1856f..e6fd5fdd7 100644 --- a/parity/cli/usage.txt +++ b/parity/cli/usage.txt @@ -176,9 +176,12 @@ API and Console Options: is additional security against some attack vectors. Special options: "all", "none", (default: {flag_jsonrpc_hosts}). - --jsonrpc-threads THREADS Enables experimental faster implementation of JSON-RPC server. + --jsonrpc-server-threads NUM Enables experimental faster implementation of JSON-RPC server. Requires Dapps server to be disabled - using --no-dapps. (default: {flag_jsonrpc_threads:?}) + using --no-dapps. (default: {flag_jsonrpc_server_threads:?}) + --jsonrpc-threads THREADS Turn on additional processing threads in all RPC servers. + Setting this to non-zero value allows parallel cpu-heavy queries + execution. (default: {flag_jsonrpc_threads}) --no-ws Disable the WebSockets server. (default: {flag_no_ws}) --ws-port PORT Specify the port portion of the WebSockets server @@ -262,6 +265,9 @@ Sealing/Mining Options: ext - reseal only on a new external transaction; all - reseal on all new transactions (default: {flag_reseal_on_txs}). + --reseal-on-uncle Force the node to author new blocks when a new uncle + block is imported. + (default: {flag_reseal_on_uncle}) --reseal-min-period MS Specify the minimum time between reseals from incoming transactions. MS is time measured in milliseconds (default: {flag_reseal_min_period}). @@ -461,6 +467,8 @@ Internal Options: --can-restart Executable will auto-restart if exiting with 69. Miscellaneous Options: + --ntp-server HOST NTP server to provide current time (host:port). Used to verify node health. + (default: {flag_ntp_server}) -l --logging LOGGING Specify the logging level. Must conform to the same format as RUST_LOG. (default: {flag_logging:?}) --log-file FILENAME Specify a filename into which logging should be diff --git a/parity/configuration.rs b/parity/configuration.rs index 81117db05..819835e01 100644 --- a/parity/configuration.rs +++ b/parity/configuration.rs @@ -34,7 +34,7 @@ use rpc::{IpcConfiguration, HttpConfiguration, WsConfiguration, UiConfiguration} use rpc_apis::ApiSet; use parity_rpc::NetworkSettings; use cache::CacheConfig; -use helpers::{to_duration, to_mode, to_block_id, to_u256, to_pending_set, to_price, replace_home, replace_home_for_db, +use helpers::{to_duration, to_mode, to_block_id, to_u256, to_pending_set, to_price, replace_home, replace_home_and_local, geth_ipc_path, parity_ipc_path, to_bootnodes, to_addresses, to_address, to_gas_limit, to_queue_strategy}; use params::{SpecType, ResealPolicy, AccountsConfig, GasPricerConfig, MinerExtras, Pruning, Switch}; use ethcore_logger::Config as LogConfig; @@ -137,7 +137,7 @@ impl Configuration { let secretstore_conf = self.secretstore_config()?; let format = self.format()?; - if self.args.flag_jsonrpc_threads.is_some() && dapps_conf.enabled { + if self.args.flag_jsonrpc_server_threads.is_some() && dapps_conf.enabled { dapps_conf.enabled = false; writeln!(&mut stderr(), "Warning: Disabling Dapps server because fast RPC server was enabled.").expect("Error writing to stderr.") } @@ -526,6 +526,7 @@ impl Configuration { force_sealing: self.args.flag_force_sealing, reseal_on_external_tx: reseal.external, reseal_on_own_tx: reseal.own, + reseal_on_uncle: self.args.flag_reseal_on_uncle, tx_gas_limit: match self.args.flag_tx_gas_limit { Some(ref d) => to_u256(d)?, None => U256::max_value(), @@ -555,6 +556,7 @@ impl Configuration { fn ui_config(&self) -> UiConfiguration { UiConfiguration { enabled: self.ui_enabled(), + ntp_server: self.args.flag_ntp_server.clone(), interface: self.ui_interface(), port: self.args.flag_ports_shift + self.args.flag_ui_port, hosts: self.ui_hosts(), @@ -564,12 +566,18 @@ impl Configuration { fn dapps_config(&self) -> DappsConfiguration { DappsConfiguration { enabled: self.dapps_enabled(), + ntp_server: self.args.flag_ntp_server.clone(), dapps_path: PathBuf::from(self.directories().dapps), extra_dapps: if self.args.cmd_dapp { self.args.arg_path.iter().map(|path| PathBuf::from(path)).collect() } else { vec![] }, + extra_embed_on: if self.args.flag_ui_no_validation { + vec![("localhost".to_owned(), 3000)] + } else { + vec![] + }, } } @@ -662,7 +670,7 @@ impl Configuration { let mut buffer = String::new(); let mut node_file = File::open(path).map_err(|e| format!("Error opening reserved nodes file: {}", e))?; node_file.read_to_string(&mut buffer).map_err(|_| "Error reading reserved node file")?; - let lines = buffer.lines().map(|s| s.trim().to_owned()).filter(|s| !s.is_empty()).collect::>(); + let lines = buffer.lines().map(|s| s.trim().to_owned()).filter(|s| !s.is_empty() && !s.starts_with("#")).collect::>(); if let Some(invalid) = lines.iter().find(|s| !is_valid_node_url(s)) { return Err(format!("Invalid node address format given for a boot node: {}", invalid)); } @@ -824,11 +832,12 @@ impl Configuration { }, hosts: self.rpc_hosts(), cors: self.rpc_cors(), - threads: match self.args.flag_jsonrpc_threads { + server_threads: match self.args.flag_jsonrpc_server_threads { Some(threads) if threads > 0 => Some(threads), None => None, - _ => return Err("--jsonrpc-threads number needs to be positive.".into()), - } + _ => return Err("--jsonrpc-server-threads number needs to be positive.".into()), + }, + processing_threads: self.args.flag_jsonrpc_threads, }; Ok(conf) @@ -893,14 +902,20 @@ impl Configuration { let local_path = default_local_path(); let base_path = self.args.flag_base_path.as_ref().or_else(|| self.args.flag_datadir.as_ref()).map_or_else(|| default_data_path(), |s| s.clone()); let data_path = replace_home("", &base_path); - let base_db_path = if self.args.flag_base_path.is_some() && self.args.flag_db_path.is_none() { - // If base_path is set and db_path is not we default to base path subdir instead of LOCAL. + let is_using_base_path = self.args.flag_base_path.is_some(); + // If base_path is set and db_path is not we default to base path subdir instead of LOCAL. + let base_db_path = if is_using_base_path && self.args.flag_db_path.is_none() { "$BASE/chains" } else { self.args.flag_db_path.as_ref().map_or(dir::CHAINS_PATH, |s| &s) }; + let cache_path = if is_using_base_path { + "$BASE/cache".into() + } else { + replace_home_and_local(&data_path, &local_path, &dir::CACHE_PATH) + }; - let db_path = replace_home_for_db(&data_path, &local_path, &base_db_path); + let db_path = replace_home_and_local(&data_path, &local_path, &base_db_path); let keys_path = replace_home(&data_path, &self.args.flag_keys_path); let dapps_path = replace_home(&data_path, &self.args.flag_dapps_path); let secretstore_path = replace_home(&data_path, &self.args.flag_secretstore_path); @@ -924,6 +939,7 @@ impl Configuration { Directories { keys: keys_path, base: data_path, + cache: cache_path, db: db_path, dapps: dapps_path, signer: ui_path, @@ -1256,6 +1272,7 @@ mod tests { support_token_api: true }, UiConfiguration { enabled: true, + ntp_server: "pool.ntp.org:123".into(), interface: "127.0.0.1".into(), port: 8180, hosts: Some(vec![]), @@ -1496,6 +1513,7 @@ mod tests { assert_eq!(conf0.directories().signer, "signer".to_owned()); assert_eq!(conf0.ui_config(), UiConfiguration { enabled: true, + ntp_server: "pool.ntp.org:123".into(), interface: "127.0.0.1".into(), port: 8180, hosts: Some(vec![]), @@ -1504,14 +1522,17 @@ mod tests { assert_eq!(conf1.directories().signer, "signer".to_owned()); assert_eq!(conf1.ui_config(), UiConfiguration { enabled: true, + ntp_server: "pool.ntp.org:123".into(), interface: "127.0.0.1".into(), port: 8180, hosts: Some(vec![]), }); + assert_eq!(conf1.dapps_config().extra_embed_on, vec![("localhost".to_owned(), 3000)]); assert_eq!(conf1.ws_config().unwrap().hosts, None); assert_eq!(conf2.directories().signer, "signer".to_owned()); assert_eq!(conf2.ui_config(), UiConfiguration { enabled: true, + ntp_server: "pool.ntp.org:123".into(), interface: "127.0.0.1".into(), port: 3123, hosts: Some(vec![]), @@ -1520,6 +1541,7 @@ mod tests { assert_eq!(conf3.directories().signer, "signer".to_owned()); assert_eq!(conf3.ui_config(), UiConfiguration { enabled: true, + ntp_server: "pool.ntp.org:123".into(), interface: "test".into(), port: 8180, hosts: Some(vec![]), @@ -1554,6 +1576,19 @@ mod tests { assert!(conf.init_reserved_nodes().is_ok()); } + #[test] + fn should_ignore_comments_in_reserved_peers() { + let temp = RandomTempPath::new(); + create_dir(temp.as_str().to_owned()).unwrap(); + let filename = temp.as_str().to_owned() + "/peers_comments"; + File::create(filename.clone()).unwrap().write_all(b"# Sample comment\nenode://6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@172.0.0.1:30303\n").unwrap(); + let args = vec!["parity", "--reserved-peers", &filename]; + let conf = Configuration::parse(&args, None).unwrap(); + let reserved_nodes = conf.init_reserved_nodes(); + assert!(reserved_nodes.is_ok()); + assert_eq!(reserved_nodes.unwrap().len(), 1); + } + #[test] fn test_dev_chain() { let args = vec!["parity", "--chain", "dev"]; diff --git a/parity/dapps.rs b/parity/dapps.rs index 7e1cf82c1..4460e05bc 100644 --- a/parity/dapps.rs +++ b/parity/dapps.rs @@ -22,6 +22,7 @@ use ethcore::client::{Client, BlockChainClient, BlockId}; use ethcore::transaction::{Transaction, Action}; use ethsync::LightSync; use futures::{future, IntoFuture, Future, BoxFuture}; +use futures_cpupool::CpuPool; use hash_fetch::fetch::Client as FetchClient; use hash_fetch::urlhint::ContractClient; use helpers::replace_home; @@ -35,8 +36,10 @@ use util::{Bytes, Address}; #[derive(Debug, PartialEq, Clone)] pub struct Configuration { pub enabled: bool, + pub ntp_server: String, pub dapps_path: PathBuf, pub extra_dapps: Vec, + pub extra_embed_on: Vec<(String, u16)>, } impl Default for Configuration { @@ -44,8 +47,10 @@ impl Default for Configuration { let data_dir = default_data_path(); Configuration { enabled: true, + ntp_server: "pool.ntp.org:123".into(), dapps_path: replace_home(&data_dir, "$BASE/dapps").into(), extra_dapps: vec![], + extra_embed_on: vec![], } } } @@ -140,6 +145,7 @@ pub struct Dependencies { pub sync_status: Arc, pub contract_client: Arc, pub remote: parity_reactor::TokioRemote, + pub pool: CpuPool, pub fetch: FetchClient, pub signer: Arc, pub ui_address: Option<(String, u16)>, @@ -152,20 +158,23 @@ pub fn new(configuration: Configuration, deps: Dependencies) -> Result Result, String> { +pub fn new_ui(enabled: bool, ntp_server: &str, deps: Dependencies) -> Result, String> { if !enabled { return Ok(None); } server::ui_middleware( deps, - rpc::DAPPS_DOMAIN.into(), + ntp_server, + rpc::DAPPS_DOMAIN, ).map(Some) } @@ -192,16 +201,19 @@ mod server { pub fn dapps_middleware( _deps: Dependencies, + _ntp_server: &str, _dapps_path: PathBuf, _extra_dapps: Vec, - _dapps_domain: String, + _dapps_domain: &str, + _extra_embed_on: Vec<(String, u16)>, ) -> Result { Err("Your Parity version has been compiled without WebApps support.".into()) } pub fn ui_middleware( _deps: Dependencies, - _dapps_domain: String, + _ntp_server: &str, + _dapps_domain: &str, ) -> Result { Err("Your Parity version has been compiled without UI support.".into()) } @@ -226,17 +238,22 @@ mod server { pub fn dapps_middleware( deps: Dependencies, + ntp_server: &str, dapps_path: PathBuf, extra_dapps: Vec, - dapps_domain: String, + dapps_domain: &str, + extra_embed_on: Vec<(String, u16)>, ) -> Result { let signer = deps.signer; let parity_remote = parity_reactor::Remote::new(deps.remote.clone()); let web_proxy_tokens = Arc::new(move |token| signer.web_proxy_access_token_domain(&token)); Ok(parity_dapps::Middleware::dapps( + ntp_server, + deps.pool, parity_remote, deps.ui_address, + extra_embed_on, dapps_path, extra_dapps, dapps_domain, @@ -249,15 +266,18 @@ mod server { pub fn ui_middleware( deps: Dependencies, - dapps_domain: String, + ntp_server: &str, + dapps_domain: &str, ) -> Result { let parity_remote = parity_reactor::Remote::new(deps.remote.clone()); Ok(parity_dapps::Middleware::ui( + ntp_server, + deps.pool, parity_remote, + dapps_domain, deps.contract_client, deps.sync_status, deps.fetch, - dapps_domain, )) } diff --git a/parity/dir.rs b/parity/dir.rs index 434254774..d254886b9 100644 --- a/parity/dir.rs +++ b/parity/dir.rs @@ -18,7 +18,7 @@ use std::fs; use std::path::{PathBuf, Path}; use util::{H64, H256}; use util::journaldb::Algorithm; -use helpers::{replace_home, replace_home_for_db}; +use helpers::{replace_home, replace_home_and_local}; use app_dirs::{AppInfo, get_app_root, AppDataType}; #[cfg(target_os = "macos")] const AUTHOR: &'static str = "Parity"; @@ -34,6 +34,9 @@ use app_dirs::{AppInfo, get_app_root, AppDataType}; #[cfg(target_os = "windows")] pub const CHAINS_PATH: &'static str = "$LOCAL/chains"; #[cfg(not(target_os = "windows"))] pub const CHAINS_PATH: &'static str = "$BASE/chains"; +#[cfg(target_os = "windows")] pub const CACHE_PATH: &'static str = "$LOCAL/cache"; +#[cfg(not(target_os = "windows"))] pub const CACHE_PATH: &'static str = "$BASE/cache"; + // this const is irrelevent cause we do have migrations now, // but we still use it for backwards compatibility const LEGACY_CLIENT_DB_VER_STR: &'static str = "5.3"; @@ -42,6 +45,7 @@ const LEGACY_CLIENT_DB_VER_STR: &'static str = "5.3"; pub struct Directories { pub base: String, pub db: String, + pub cache: String, pub keys: String, pub signer: String, pub dapps: String, @@ -54,7 +58,8 @@ impl Default for Directories { let local_dir = default_local_path(); Directories { base: replace_home(&data_dir, "$BASE"), - db: replace_home_for_db(&data_dir, &local_dir, CHAINS_PATH), + db: replace_home_and_local(&data_dir, &local_dir, CHAINS_PATH), + cache: replace_home_and_local(&data_dir, &local_dir, CACHE_PATH), keys: replace_home(&data_dir, "$BASE/keys"), signer: replace_home(&data_dir, "$BASE/signer"), dapps: replace_home(&data_dir, "$BASE/dapps"), @@ -67,6 +72,7 @@ impl Directories { pub fn create_dirs(&self, dapps_enabled: bool, signer_enabled: bool, secretstore_enabled: bool) -> Result<(), String> { fs::create_dir_all(&self.base).map_err(|e| e.to_string())?; fs::create_dir_all(&self.db).map_err(|e| e.to_string())?; + fs::create_dir_all(&self.cache).map_err(|e| e.to_string())?; fs::create_dir_all(&self.keys).map_err(|e| e.to_string())?; if signer_enabled { fs::create_dir_all(&self.signer).map_err(|e| e.to_string())?; @@ -231,7 +237,7 @@ pub fn default_hypervisor_path() -> String { #[cfg(test)] mod tests { use super::Directories; - use helpers::{replace_home, replace_home_for_db}; + use helpers::{replace_home, replace_home_and_local}; #[test] fn test_default_directories() { @@ -239,10 +245,14 @@ mod tests { let local_dir = super::default_local_path(); let expected = Directories { base: replace_home(&data_dir, "$BASE"), - db: replace_home_for_db(&data_dir, &local_dir, + db: replace_home_and_local(&data_dir, &local_dir, if cfg!(target_os = "windows") { "$LOCAL/chains" } else { "$BASE/chains" } ), + cache: replace_home_and_local(&data_dir, &local_dir, + if cfg!(target_os = "windows") { "$LOCAL/cache" } + else { "$BASE/cache" } + ), keys: replace_home(&data_dir, "$BASE/keys"), signer: replace_home(&data_dir, "$BASE/signer"), dapps: replace_home(&data_dir, "$BASE/dapps"), diff --git a/parity/helpers.rs b/parity/helpers.rs index 31129f18b..7d28f44fd 100644 --- a/parity/helpers.rs +++ b/parity/helpers.rs @@ -140,7 +140,7 @@ pub fn replace_home(base: &str, arg: &str) -> String { r.replace("/", &::std::path::MAIN_SEPARATOR.to_string()) } -pub fn replace_home_for_db(base: &str, local: &str, arg: &str) -> String { +pub fn replace_home_and_local(base: &str, local: &str, arg: &str) -> String { let r = replace_home(base, arg); r.replace("$LOCAL", local) } diff --git a/parity/informant.rs b/parity/informant.rs index 3dfd5147d..7e1e4ed4d 100644 --- a/parity/informant.rs +++ b/parity/informant.rs @@ -21,32 +21,21 @@ use self::ansi_term::Style; use std::sync::{Arc}; use std::sync::atomic::{AtomicUsize, AtomicBool, Ordering as AtomicOrdering}; use std::time::{Instant, Duration}; + +use ethcore::client::*; +use ethcore::header::BlockNumber; +use ethcore::service::ClientIoMessage; +use ethcore::snapshot::{RestorationStatus, SnapshotService as SS}; +use ethcore::snapshot::service::Service as SnapshotService; +use ethsync::{LightSyncProvider, LightSync, SyncProvider, ManageNetwork}; use io::{TimerToken, IoContext, IoHandler}; use isatty::{stdout_isatty}; -use ethsync::{SyncProvider, ManageNetwork}; -use util::{RwLock, Mutex, H256, Colour, Bytes}; -use ethcore::client::*; -use ethcore::service::ClientIoMessage; -use ethcore::snapshot::service::Service as SnapshotService; -use ethcore::snapshot::{RestorationStatus, SnapshotService as SS}; +use light::Cache as LightDataCache; +use light::client::LightChainClient; use number_prefix::{binary_prefix, Standalone, Prefixed}; use parity_rpc::{is_major_importing}; use parity_rpc::informant::RpcStats; - -pub struct Informant { - report: RwLock>, - last_tick: RwLock, - with_color: bool, - client: Arc, - snapshot: Option>, - sync: Option>, - net: Option>, - rpc_stats: Option>, - last_import: Mutex, - skipped: AtomicUsize, - skipped_txs: AtomicUsize, - in_shutdown: AtomicBool, -} +use util::{RwLock, Mutex, H256, Colour, Bytes}; /// Format byte counts to standard denominations. pub fn format_bytes(b: usize) -> String { @@ -68,29 +57,188 @@ impl MillisecondDuration for Duration { } } -impl Informant { +#[derive(Default)] +struct CacheSizes { + sizes: ::std::collections::BTreeMap<&'static str, usize>, +} + +impl CacheSizes { + fn insert(&mut self, key: &'static str, bytes: usize) { + self.sizes.insert(key, bytes); + } + + fn display(&self, style: Style, paint: F) -> String + where F: Fn(Style, String) -> String + { + use std::fmt::Write; + + let mut buf = String::new(); + for (name, &size) in &self.sizes { + + write!(buf, " {:>8} {}", paint(style, format_bytes(size)), name) + .expect("writing to string won't fail unless OOM; qed") + } + + buf + } +} + +pub struct SyncInfo { + last_imported_block_number: BlockNumber, + last_imported_old_block_number: Option, + num_peers: usize, + max_peers: u32, +} + +pub struct Report { + importing: bool, + chain_info: BlockChainInfo, + client_report: ClientReport, + queue_info: BlockQueueInfo, + cache_sizes: CacheSizes, + sync_info: Option, +} + +/// Something which can provide data to the informant. +pub trait InformantData: Send + Sync { + /// Whether it executes transactions + fn executes_transactions(&self) -> bool; + + /// Whether it is currently importing (also included in `Report`) + fn is_major_importing(&self) -> bool; + + /// Generate a report of blockchain status, memory usage, and sync info. + fn report(&self) -> Report; +} + +/// Informant data for a full node. +pub struct FullNodeInformantData { + pub client: Arc, + pub sync: Option>, + pub net: Option>, +} + +impl InformantData for FullNodeInformantData { + fn executes_transactions(&self) -> bool { true } + + fn is_major_importing(&self) -> bool { + let state = self.sync.as_ref().map(|sync| sync.status().state); + is_major_importing(state, self.client.queue_info()) + } + + fn report(&self) -> Report { + let (client_report, queue_info, blockchain_cache_info) = + (self.client.report(), self.client.queue_info(), self.client.blockchain_cache_info()); + + let chain_info = self.client.chain_info(); + + let mut cache_sizes = CacheSizes::default(); + cache_sizes.insert("db", client_report.state_db_mem); + cache_sizes.insert("queue", queue_info.mem_used); + cache_sizes.insert("chain", blockchain_cache_info.total()); + + let (importing, sync_info) = match (self.sync.as_ref(), self.net.as_ref()) { + (Some(sync), Some(net)) => { + let status = sync.status(); + let net_config = net.network_config(); + + cache_sizes.insert("sync", status.mem_used); + + let importing = is_major_importing(Some(status.state), queue_info.clone()); + (importing, Some(SyncInfo { + last_imported_block_number: status.last_imported_block_number.unwrap_or(chain_info.best_block_number), + last_imported_old_block_number: status.last_imported_old_block_number, + num_peers: status.num_peers, + max_peers: status.current_max_peers(net_config.min_peers, net_config.max_peers), + })) + } + _ => (is_major_importing(None, queue_info.clone()), None), + }; + + Report { + importing, + chain_info, + client_report, + queue_info, + cache_sizes, + sync_info, + } + } +} + +/// Informant data for a light node -- note that the network is required. +pub struct LightNodeInformantData { + pub client: Arc, + pub sync: Arc, + pub cache: Arc>, +} + +impl InformantData for LightNodeInformantData { + fn executes_transactions(&self) -> bool { false } + + fn is_major_importing(&self) -> bool { + self.sync.is_major_importing() + } + + fn report(&self) -> Report { + let (client_report, queue_info, chain_info) = + (self.client.report(), self.client.queue_info(), self.client.chain_info()); + + let mut cache_sizes = CacheSizes::default(); + cache_sizes.insert("queue", queue_info.mem_used); + cache_sizes.insert("cache", self.cache.lock().mem_used()); + + let peer_numbers = self.sync.peer_numbers(); + let sync_info = Some(SyncInfo { + last_imported_block_number: chain_info.best_block_number, + last_imported_old_block_number: None, + num_peers: peer_numbers.connected, + max_peers: peer_numbers.max as u32, + }); + + Report { + importing: self.sync.is_major_importing(), + chain_info, + client_report, + queue_info, + cache_sizes, + sync_info, + } + } +} + +pub struct Informant { + last_tick: RwLock, + with_color: bool, + target: T, + snapshot: Option>, + rpc_stats: Option>, + last_import: Mutex, + skipped: AtomicUsize, + skipped_txs: AtomicUsize, + in_shutdown: AtomicBool, + last_report: Mutex, +} + +impl Informant { /// Make a new instance potentially `with_color` output. pub fn new( - client: Arc, - sync: Option>, - net: Option>, + target: T, snapshot: Option>, rpc_stats: Option>, with_color: bool, ) -> Self { Informant { - report: RwLock::new(None), last_tick: RwLock::new(Instant::now()), with_color: with_color, - client: client, + target: target, snapshot: snapshot, - sync: sync, - net: net, rpc_stats: rpc_stats, last_import: Mutex::new(Instant::now()), skipped: AtomicUsize::new(0), skipped_txs: AtomicUsize::new(0), in_shutdown: AtomicBool::new(false), + last_report: Mutex::new(Default::default()), } } @@ -106,14 +254,24 @@ impl Informant { return; } - let chain_info = self.client.chain_info(); - let queue_info = self.client.queue_info(); - let cache_info = self.client.blockchain_cache_info(); - let network_config = self.net.as_ref().map(|n| n.network_config()); - let sync_status = self.sync.as_ref().map(|s| s.status()); + let Report { + importing, + chain_info, + client_report, + queue_info, + cache_sizes, + sync_info, + } = self.target.report(); + + let client_report = { + let mut last_report = self.last_report.lock(); + let diffed = client_report.clone() - &*last_report; + *last_report = client_report.clone(); + diffed + }; + let rpc_stats = self.rpc_stats.as_ref(); - let importing = is_major_importing(sync_status.map(|s| s.state), self.client.queue_info()); let (snapshot_sync, snapshot_current, snapshot_total) = self.snapshot.as_ref().map_or((false, 0, 0), |s| match s.status() { RestorationStatus::Ongoing { state_chunks, block_chunks, state_chunks_done, block_chunks_done } => @@ -128,9 +286,6 @@ impl Informant { *self.last_tick.write() = Instant::now(); - let mut write_report = self.report.write(); - let report = self.client.report(); - let paint = |c: Style, t: String| match self.with_color && stdout_isatty() { true => format!("{}", c.paint(t)), false => t, @@ -142,13 +297,16 @@ impl Informant { false => format!("Syncing {} {} {} {}+{} Qed", paint(White.bold(), format!("{:>8}", format!("#{}", chain_info.best_block_number))), paint(White.bold(), format!("{}", chain_info.best_block_hash)), - { - let last_report = match *write_report { Some(ref last_report) => last_report.clone(), _ => ClientReport::default() }; + if self.target.executes_transactions() { format!("{} blk/s {} tx/s {} Mgas/s", - paint(Yellow.bold(), format!("{:4}", ((report.blocks_imported - last_report.blocks_imported) * 1000) as u64 / elapsed.as_milliseconds())), - paint(Yellow.bold(), format!("{:4}", ((report.transactions_applied - last_report.transactions_applied) * 1000) as u64 / elapsed.as_milliseconds())), - paint(Yellow.bold(), format!("{:3}", ((report.gas_processed - last_report.gas_processed) / From::from(elapsed.as_milliseconds() * 1000)).low_u64())) - ) + paint(Yellow.bold(), format!("{:4}", (client_report.blocks_imported * 1000) as u64 / elapsed.as_milliseconds())), + paint(Yellow.bold(), format!("{:4}", (client_report.transactions_applied * 1000) as u64 / elapsed.as_milliseconds())), + paint(Yellow.bold(), format!("{:3}", (client_report.gas_processed / From::from(elapsed.as_milliseconds() * 1000)).low_u64())) + ) + } else { + format!("{} hdr/s", + paint(Yellow.bold(), format!("{:4}", (client_report.blocks_imported * 1000) as u64 / elapsed.as_milliseconds())) + ) }, paint(Green.bold(), format!("{:5}", queue_info.unverified_queue_size)), paint(Green.bold(), format!("{:5}", queue_info.verified_queue_size)) @@ -157,29 +315,21 @@ impl Informant { }, false => String::new(), }, - match (&sync_status, &network_config) { - (&Some(ref sync_info), &Some(ref net_config)) => format!("{}{}/{} peers", + match sync_info.as_ref() { + Some(ref sync_info) => format!("{}{}/{} peers", match importing { - true => format!("{} ", paint(Green.bold(), format!("{:>8}", format!("#{}", sync_info.last_imported_block_number.unwrap_or(chain_info.best_block_number))))), + true => format!("{} ", paint(Green.bold(), format!("{:>8}", format!("#{}", sync_info.last_imported_block_number)))), false => match sync_info.last_imported_old_block_number { Some(number) => format!("{} ", paint(Yellow.bold(), format!("{:>8}", format!("#{}", number)))), None => String::new(), } }, paint(Cyan.bold(), format!("{:2}", sync_info.num_peers)), - paint(Cyan.bold(), format!("{:2}", sync_info.current_max_peers(net_config.min_peers, net_config.max_peers))), + paint(Cyan.bold(), format!("{:2}", sync_info.max_peers)), ), _ => String::new(), }, - format!("{} db {} chain {} queue{}", - paint(Blue.bold(), format!("{:>8}", format_bytes(report.state_db_mem))), - paint(Blue.bold(), format!("{:>8}", format_bytes(cache_info.total()))), - paint(Blue.bold(), format!("{:>8}", format_bytes(queue_info.mem_used))), - match sync_status { - Some(ref sync_info) => format!(" {} sync", paint(Blue.bold(), format!("{:>8}", format_bytes(sync_info.mem_used)))), - _ => String::new(), - } - ), + cache_sizes.display(Blue.bold(), &paint), match rpc_stats { Some(ref rpc_stats) => format!( "RPC: {} conn, {} req/s, {} µs", @@ -190,25 +340,24 @@ impl Informant { _ => String::new(), }, ); - - *write_report = Some(report); } } -impl ChainNotify for Informant { +impl ChainNotify for Informant { fn new_blocks(&self, imported: Vec, _invalid: Vec, _enacted: Vec, _retracted: Vec, _sealed: Vec, _proposed: Vec, duration: u64) { let mut last_import = self.last_import.lock(); - let sync_state = self.sync.as_ref().map(|s| s.status().state); - let importing = is_major_importing(sync_state, self.client.queue_info()); + let client = &self.target.client; + + let importing = self.target.is_major_importing(); let ripe = Instant::now() > *last_import + Duration::from_secs(1) && !importing; let txs_imported = imported.iter() .take(imported.len().saturating_sub(if ripe { 1 } else { 0 })) - .filter_map(|h| self.client.block(BlockId::Hash(*h))) + .filter_map(|h| client.block(BlockId::Hash(*h))) .map(|b| b.transactions_count()) .sum(); if ripe { - if let Some(block) = imported.last().and_then(|h| self.client.block(BlockId::Hash(*h))) { + if let Some(block) = imported.last().and_then(|h| client.block(BlockId::Hash(*h))) { let header_view = block.header_view(); let size = block.rlp().as_raw().len(); let (skipped, skipped_txs) = (self.skipped.load(AtomicOrdering::Relaxed) + imported.len() - 1, self.skipped_txs.load(AtomicOrdering::Relaxed) + txs_imported); @@ -241,7 +390,7 @@ impl ChainNotify for Informant { const INFO_TIMER: TimerToken = 0; -impl IoHandler for Informant { +impl IoHandler for Informant { fn initialize(&self, io: &IoContext) { io.register_timer(INFO_TIMER, 5000).expect("Error registering timer"); } diff --git a/parity/main.rs b/parity/main.rs index 0287e4bdd..21a7cc2d4 100644 --- a/parity/main.rs +++ b/parity/main.rs @@ -29,6 +29,7 @@ extern crate docopt; extern crate env_logger; extern crate fdlimit; extern crate futures; +extern crate futures_cpupool; extern crate isatty; extern crate jsonrpc_core; extern crate num_cpus; diff --git a/parity/params.rs b/parity/params.rs index 20a24ee1c..507d1a9cb 100644 --- a/parity/params.rs +++ b/parity/params.rs @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -use std::{str, fs, fmt}; +use std::{str, fs, fmt, path}; use std::time::Duration; use util::{Address, U256, version_data}; use util::journaldb::Algorithm; @@ -79,19 +79,20 @@ impl fmt::Display for SpecType { } impl SpecType { - pub fn spec(&self) -> Result { + pub fn spec>(&self, cache_dir: T) -> Result { + let cache_dir = cache_dir.as_ref(); match *self { - SpecType::Foundation => Ok(ethereum::new_foundation()), - SpecType::Morden => Ok(ethereum::new_morden()), - SpecType::Ropsten => Ok(ethereum::new_ropsten()), - SpecType::Olympic => Ok(ethereum::new_olympic()), - SpecType::Classic => Ok(ethereum::new_classic()), - SpecType::Expanse => Ok(ethereum::new_expanse()), - SpecType::Kovan => Ok(ethereum::new_kovan()), + SpecType::Foundation => Ok(ethereum::new_foundation(cache_dir)), + SpecType::Morden => Ok(ethereum::new_morden(cache_dir)), + SpecType::Ropsten => Ok(ethereum::new_ropsten(cache_dir)), + SpecType::Olympic => Ok(ethereum::new_olympic(cache_dir)), + SpecType::Classic => Ok(ethereum::new_classic(cache_dir)), + SpecType::Expanse => Ok(ethereum::new_expanse(cache_dir)), + SpecType::Kovan => Ok(ethereum::new_kovan(cache_dir)), SpecType::Dev => Ok(Spec::new_instant()), SpecType::Custom(ref filename) => { - let file = fs::File::open(filename).map_err(|_| "Could not load specification file.")?; - Spec::load(file) + let file = fs::File::open(filename).map_err(|e| format!("Could not load specification file at {}: {}", filename, e))?; + Spec::load(cache_dir, file) } } } diff --git a/parity/rpc.rs b/parity/rpc.rs index 53d203cde..b79e23b79 100644 --- a/parity/rpc.rs +++ b/parity/rpc.rs @@ -30,6 +30,7 @@ use rpc_apis::{self, ApiSet}; pub use parity_rpc::{IpcServer, HttpServer, RequestMiddleware}; pub use parity_rpc::ws::Server as WsServer; +pub use parity_rpc::informant::CpuPool; pub const DAPPS_DOMAIN: &'static str = "web3.site"; @@ -42,7 +43,8 @@ pub struct HttpConfiguration { pub apis: ApiSet, pub cors: Option>, pub hosts: Option>, - pub threads: Option, + pub server_threads: Option, + pub processing_threads: usize, } impl HttpConfiguration { @@ -63,7 +65,8 @@ impl Default for HttpConfiguration { apis: ApiSet::UnsafeContext, cors: None, hosts: Some(Vec::new()), - threads: None, + server_threads: None, + processing_threads: 0, } } } @@ -71,6 +74,7 @@ impl Default for HttpConfiguration { #[derive(Debug, PartialEq, Clone)] pub struct UiConfiguration { pub enabled: bool, + pub ntp_server: String, pub interface: String, pub port: u16, pub hosts: Option>, @@ -94,7 +98,8 @@ impl From for HttpConfiguration { apis: rpc_apis::ApiSet::SafeContext, cors: None, hosts: conf.hosts, - threads: None, + server_threads: None, + processing_threads: 0, } } } @@ -103,6 +108,7 @@ impl Default for UiConfiguration { fn default() -> Self { UiConfiguration { enabled: true && cfg!(feature = "ui-enabled"), + ntp_server: "pool.ntp.org:123".into(), port: 8180, interface: "127.0.0.1".into(), hosts: Some(vec![]), @@ -176,6 +182,7 @@ pub struct Dependencies { pub apis: Arc, pub remote: TokioRemote, pub stats: Arc, + pub pool: Option, } pub fn new_ws( @@ -192,13 +199,14 @@ pub fn new_ws( let addr = url.parse().map_err(|_| format!("Invalid WebSockets listen host/port given: {}", url))?; - let full_handler = setup_apis(rpc_apis::ApiSet::SafeContext, deps); + let pool = deps.pool.clone(); + let full_handler = setup_apis(rpc_apis::ApiSet::SafeContext, deps, pool.clone()); let handler = { let mut handler = MetaIoHandler::with_middleware(( rpc::WsDispatcher::new(full_handler), - Middleware::new(deps.stats.clone(), deps.apis.activity_notifier()) + Middleware::new(deps.stats.clone(), deps.apis.activity_notifier(), pool) )); - let apis = conf.apis.list_apis().into_iter().collect::>(); + let apis = conf.apis.list_apis(); deps.apis.extend_with_set(&mut handler, &apis); handler @@ -252,7 +260,8 @@ pub fn new_http( let http_address = (conf.interface, conf.port); let url = format!("{}:{}", http_address.0, http_address.1); let addr = url.parse().map_err(|_| format!("Invalid {} listen host/port given: {}", id, url))?; - let handler = setup_apis(conf.apis, deps); + let pool = deps.pool.clone(); + let handler = setup_apis(conf.apis, deps, pool); let remote = deps.remote.clone(); let cors_domains = into_domains(conf.cors); @@ -265,7 +274,7 @@ pub fn new_http( handler, remote, rpc::RpcExtractor, - match (conf.threads, middleware) { + match (conf.server_threads, middleware) { (Some(threads), None) => rpc::HttpSettings::Threads(threads), (None, middleware) => rpc::HttpSettings::Dapps(middleware), (Some(_), Some(_)) => { @@ -291,7 +300,8 @@ pub fn new_ipc( return Ok(None); } - let handler = setup_apis(conf.apis, dependencies); + let pool = dependencies.pool.clone(); + let handler = setup_apis(conf.apis, dependencies, pool); let remote = dependencies.remote.clone(); match rpc::start_ipc(&conf.socket_addr, handler, remote, rpc::RpcExtractor) { Ok(server) => Ok(Some(server)), @@ -318,13 +328,13 @@ fn with_domain(items: Option>, domain: &str, addresses: &[Option<(St }) } -fn setup_apis(apis: ApiSet, deps: &Dependencies) -> MetaIoHandler> +fn setup_apis(apis: ApiSet, deps: &Dependencies, pool: Option) -> MetaIoHandler> where D: rpc_apis::Dependencies { let mut handler = MetaIoHandler::with_middleware( - Middleware::new(deps.stats.clone(), deps.apis.activity_notifier()) + Middleware::new(deps.stats.clone(), deps.apis.activity_notifier(), pool) ); - let apis = apis.list_apis().into_iter().collect::>(); + let apis = apis.list_apis(); deps.apis.extend_with_set(&mut handler, &apis); handler diff --git a/parity/rpc_apis.rs b/parity/rpc_apis.rs index d1b0253d5..bcac866ff 100644 --- a/parity/rpc_apis.rs +++ b/parity/rpc_apis.rs @@ -106,6 +106,8 @@ pub enum ApiSet { All, // Local "unsafe" context and accounts access IpcContext, + // APIs for Parity Generic Pub-Sub + PubSub, // Fixed list of APis List(HashSet), } @@ -153,7 +155,7 @@ impl FromStr for ApiSet { } } -fn to_modules(apis: &[Api]) -> BTreeMap { +fn to_modules(apis: &HashSet) -> BTreeMap { let mut modules = BTreeMap::new(); for api in apis { let (name, version) = match *api { @@ -187,7 +189,7 @@ pub trait Dependencies { fn extend_with_set( &self, handler: &mut MetaIoHandler, - apis: &[Api], + apis: &HashSet, ) where S: core::Middleware; } @@ -217,7 +219,7 @@ impl FullDependencies { fn extend_api( &self, handler: &mut MetaIoHandler, - apis: &[Api], + apis: &HashSet, for_generic_pubsub: bool, ) where S: core::Middleware { use parity_rpc::v1::*; @@ -305,7 +307,8 @@ impl FullDependencies { Api::ParityPubSub => { if !for_generic_pubsub { let mut rpc = MetaIoHandler::default(); - self.extend_api(&mut rpc, apis, true); + let apis = ApiSet::List(apis.clone()).retain(ApiSet::PubSub).list_apis(); + self.extend_api(&mut rpc, &apis, true); handler.extend_with(PubSubClient::new(rpc, self.remote.clone()).to_delegate()); } }, @@ -349,7 +352,7 @@ impl Dependencies for FullDependencies { fn extend_with_set( &self, handler: &mut MetaIoHandler, - apis: &[Api], + apis: &HashSet, ) where S: core::Middleware { self.extend_api(handler, apis, false) } @@ -386,7 +389,7 @@ impl LightDependencies { fn extend_api>( &self, handler: &mut MetaIoHandler, - apis: &[Api], + apis: &HashSet, for_generic_pubsub: bool, ) { use parity_rpc::v1::*; @@ -486,7 +489,8 @@ impl LightDependencies { Api::ParityPubSub => { if !for_generic_pubsub { let mut rpc = MetaIoHandler::default(); - self.extend_api(&mut rpc, apis, true); + let apis = ApiSet::List(apis.clone()).retain(ApiSet::PubSub).list_apis(); + self.extend_api(&mut rpc, &apis, true); handler.extend_with(PubSubClient::new(rpc, self.remote.clone()).to_delegate()); } }, @@ -525,7 +529,7 @@ impl Dependencies for LightDependencies { fn extend_with_set( &self, handler: &mut MetaIoHandler, - apis: &[Api], + apis: &HashSet, ) where S: core::Middleware { self.extend_api(handler, apis, false) } @@ -538,9 +542,9 @@ impl ApiSet { } pub fn list_apis(&self) -> HashSet { - let mut public_list = vec![ + let mut public_list = [ Api::Web3, Api::Net, Api::Eth, Api::EthPubSub, Api::Parity, Api::Rpc, Api::SecretStore, - ].into_iter().collect(); + ].into_iter().cloned().collect(); match *self { ApiSet::List(ref apis) => apis.clone(), ApiSet::PublicContext => public_list, @@ -572,6 +576,13 @@ impl ApiSet { public_list.insert(Api::Personal); public_list }, + ApiSet::PubSub => [ + Api::Eth, + Api::Parity, + Api::ParityAccounts, + Api::ParitySet, + Api::Traces, + ].into_iter().cloned().collect() } } } diff --git a/parity/run.rs b/parity/run.rs index bcb18aeeb..98a595b65 100644 --- a/parity/run.rs +++ b/parity/run.rs @@ -16,26 +16,26 @@ use std::sync::Arc; use std::net::{TcpListener}; + use ctrlc::CtrlC; -use fdlimit::raise_fd_limit; -use parity_rpc::{NetworkSettings, informant, is_major_importing}; -use ethsync::NetworkConfiguration; -use util::{Colour, version, Mutex, Condvar}; use ethcore_logger::{Config as LogConfig, RotatingLogger}; -use ethcore::miner::{StratumOptions, Stratum}; -use ethcore::client::{Client, Mode, DatabaseCompactionProfile, VMType, BlockChainClient}; -use ethcore::service::ClientService; use ethcore::account_provider::{AccountProvider, AccountProviderSettings}; +use ethcore::client::{Client, Mode, DatabaseCompactionProfile, VMType, BlockChainClient}; +use ethcore::ethstore::ethkey; use ethcore::miner::{Miner, MinerService, ExternalMiner, MinerOptions}; +use ethcore::miner::{StratumOptions, Stratum}; +use ethcore::service::ClientService; use ethcore::snapshot; use ethcore::verification::queue::VerifierSettings; -use ethcore::ethstore::ethkey; -use light::Cache as LightDataCache; -use ethsync::SyncConfig; -use informant::Informant; -use updater::{UpdatePolicy, Updater}; -use parity_reactor::EventLoop; +use ethsync::{self, SyncConfig}; +use fdlimit::raise_fd_limit; use hash_fetch::fetch::{Fetch, Client as FetchClient}; +use informant::{Informant, LightNodeInformantData, FullNodeInformantData}; +use light::Cache as LightDataCache; +use parity_reactor::EventLoop; +use parity_rpc::{NetworkSettings, informant, is_major_importing}; +use updater::{UpdatePolicy, Updater}; +use util::{Colour, version, Mutex, Condvar}; use params::{ SpecType, Pruning, AccountsConfig, GasPricerConfig, MinerExtras, Switch, @@ -83,7 +83,7 @@ pub struct RunCmd { pub ws_conf: rpc::WsConfiguration, pub http_conf: rpc::HttpConfiguration, pub ipc_conf: rpc::IpcConfiguration, - pub net_conf: NetworkConfiguration, + pub net_conf: ethsync::NetworkConfiguration, pub network_id: Option, pub warp_sync: bool, pub public_node: bool, @@ -168,7 +168,7 @@ fn execute_light(cmd: RunCmd, can_restart: bool, logger: Arc) -> use util::RwLock; // load spec - let spec = cmd.spec.spec()?; + let spec = cmd.spec.spec(&cmd.dirs.cache)?; // load genesis hash let genesis_hash = spec.genesis_header().hash(); @@ -209,6 +209,7 @@ fn execute_light(cmd: RunCmd, can_restart: bool, logger: Arc) -> db_cache_size: Some(cmd.cache_config.blockchain() as usize * 1024 * 1024), db_compaction: compaction, db_wal: cmd.wal, + verify_full: true, }; config.queue.max_mem_use = cmd.cache_config.queue() as usize * 1024 * 1024; @@ -235,7 +236,7 @@ fn execute_light(cmd: RunCmd, can_restart: bool, logger: Arc) -> network_config: net_conf.into_basic().map_err(|e| format!("Failed to produce network config: {}", e))?, client: Arc::new(provider), network_id: cmd.network_id.unwrap_or(spec.network_id()), - subprotocol_name: ::ethsync::LIGHT_PROTOCOL, + subprotocol_name: ethsync::LIGHT_PROTOCOL, handlers: vec![on_demand.clone()], }; let light_sync = LightSync::new(sync_params).map_err(|e| format!("Error starting network: {}", e))?; @@ -275,9 +276,18 @@ fn execute_light(cmd: RunCmd, can_restart: bool, logger: Arc) -> on_demand: on_demand.clone(), }); - let sync = light_sync.clone(); + struct LightSyncStatus(Arc); + impl dapps::SyncStatus for LightSyncStatus { + fn is_major_importing(&self) -> bool { self.0.is_major_importing() } + fn peers(&self) -> (usize, usize) { + let peers = ethsync::LightSyncProvider::peer_numbers(&*self.0); + (peers.connected, peers.max) + } + } + dapps::Dependencies { - sync_status: Arc::new(move || sync.is_major_importing()), + sync_status: Arc::new(LightSyncStatus(light_sync.clone())), + pool: fetch.pool(), contract_client: contract_client, remote: event_loop.raw_remote(), fetch: fetch.clone(), @@ -287,7 +297,7 @@ fn execute_light(cmd: RunCmd, can_restart: bool, logger: Arc) -> }; let dapps_middleware = dapps::new(cmd.dapps_conf.clone(), dapps_deps.clone())?; - let ui_middleware = dapps::new_ui(cmd.ui_conf.enabled, dapps_deps)?; + let ui_middleware = dapps::new_ui(cmd.ui_conf.enabled, &cmd.ui_conf.ntp_server, dapps_deps)?; // start RPCs let dapps_service = dapps::service(&dapps_middleware); @@ -300,7 +310,7 @@ fn execute_light(cmd: RunCmd, can_restart: bool, logger: Arc) -> logger: logger, settings: Arc::new(cmd.net_settings), on_demand: on_demand, - cache: cache, + cache: cache.clone(), transaction_queue: txq, dapps_service: dapps_service, dapps_address: cmd.dapps_conf.address(cmd.http_conf.address()), @@ -314,6 +324,11 @@ fn execute_light(cmd: RunCmd, can_restart: bool, logger: Arc) -> apis: deps_for_rpc_apis.clone(), remote: event_loop.raw_remote(), stats: rpc_stats.clone(), + pool: if cmd.http_conf.processing_threads > 0 { + Some(rpc::CpuPool::new(cmd.http_conf.processing_threads)) + } else { + None + }, }; // start rpc servers @@ -322,16 +337,25 @@ fn execute_light(cmd: RunCmd, can_restart: bool, logger: Arc) -> let _ipc_server = rpc::new_ipc(cmd.ipc_conf, &dependencies)?; let _ui_server = rpc::new_http("Parity Wallet (UI)", "ui", cmd.ui_conf.clone().into(), &dependencies, ui_middleware)?; - // minimal informant thread. Just prints block number every 5 seconds. - // TODO: integrate with informant.rs - let informant_client = service.client().clone(); - ::std::thread::spawn(move || loop { - info!("#{}", informant_client.best_block_header().number()); - ::std::thread::sleep(::std::time::Duration::from_secs(5)); - }); + // the informant + let informant = Arc::new(Informant::new( + LightNodeInformantData { + client: service.client().clone(), + sync: light_sync.clone(), + cache: cache, + }, + None, + Some(rpc_stats), + cmd.logger_config.color, + )); - // wait for ctrl-c. - Ok(wait_for_exit(None, None, can_restart)) + service.register_handler(informant.clone()).map_err(|_| "Unable to register informant handler".to_owned())?; + + // wait for ctrl-c and then shut down the informant. + let res = wait_for_exit(None, None, can_restart); + informant.shutdown(); + + Ok(res) } pub fn execute(cmd: RunCmd, can_restart: bool, logger: Arc) -> Result<(bool, Option), String> { @@ -352,7 +376,7 @@ pub fn execute(cmd: RunCmd, can_restart: bool, logger: Arc) -> R } // load spec - let spec = cmd.spec.spec()?; + let spec = cmd.spec.spec(&cmd.dirs.cache)?; // load genesis hash let genesis_hash = spec.genesis_header().hash(); @@ -570,7 +594,7 @@ pub fn execute(cmd: RunCmd, can_restart: bool, logger: Arc) -> R let (sync_provider, manage_network, chain_notify) = modules::sync( &mut hypervisor, sync_config, - net_conf.into(), + net_conf.clone().into(), client.clone(), snapshot_service.clone(), client.clone(), @@ -614,8 +638,20 @@ pub fn execute(cmd: RunCmd, can_restart: bool, logger: Arc) -> R let (sync, client) = (sync_provider.clone(), client.clone()); let contract_client = Arc::new(::dapps::FullRegistrar { client: client.clone() }); + struct SyncStatus(Arc, Arc, ethsync::NetworkConfiguration); + impl dapps::SyncStatus for SyncStatus { + fn is_major_importing(&self) -> bool { + is_major_importing(Some(self.0.status().state), self.1.queue_info()) + } + fn peers(&self) -> (usize, usize) { + let status = self.0.status(); + (status.num_peers, status.current_max_peers(self.2.min_peers, self.2.max_peers) as usize) + } + } + dapps::Dependencies { - sync_status: Arc::new(move || is_major_importing(Some(sync.status().state), client.queue_info())), + sync_status: Arc::new(SyncStatus(sync, client, net_conf)), + pool: fetch.pool(), contract_client: contract_client, remote: event_loop.raw_remote(), fetch: fetch.clone(), @@ -624,7 +660,7 @@ pub fn execute(cmd: RunCmd, can_restart: bool, logger: Arc) -> R } }; let dapps_middleware = dapps::new(cmd.dapps_conf.clone(), dapps_deps.clone())?; - let ui_middleware = dapps::new_ui(cmd.ui_conf.enabled, dapps_deps)?; + let ui_middleware = dapps::new_ui(cmd.ui_conf.enabled, &cmd.ui_conf.ntp_server, dapps_deps)?; let dapps_service = dapps::service(&dapps_middleware); let deps_for_rpc_apis = Arc::new(rpc_apis::FullDependencies { @@ -652,6 +688,12 @@ pub fn execute(cmd: RunCmd, can_restart: bool, logger: Arc) -> R apis: deps_for_rpc_apis.clone(), remote: event_loop.raw_remote(), stats: rpc_stats.clone(), + pool: if cmd.http_conf.processing_threads > 0 { + Some(rpc::CpuPool::new(cmd.http_conf.processing_threads)) + } else { + None + }, + }; // start rpc servers @@ -672,9 +714,11 @@ pub fn execute(cmd: RunCmd, can_restart: bool, logger: Arc) -> R // the informant let informant = Arc::new(Informant::new( - service.client(), - Some(sync_provider.clone()), - Some(manage_network.clone()), + FullNodeInformantData { + client: service.client(), + sync: Some(sync_provider.clone()), + net: Some(manage_network.clone()), + }, Some(snapshot_service.clone()), Some(rpc_stats.clone()), cmd.logger_config.color, diff --git a/parity/snapshot.rs b/parity/snapshot.rs index 0c4569901..dc786a2ea 100644 --- a/parity/snapshot.rs +++ b/parity/snapshot.rs @@ -133,7 +133,7 @@ impl SnapshotCommand { // shared portion of snapshot commands: start the client service fn start_service(self) -> Result { // load spec file - let spec = self.spec.spec()?; + let spec = self.spec.spec(&self.dirs.cache)?; // load genesis hash let genesis_hash = spec.genesis_header().hash(); diff --git a/rpc/Cargo.toml b/rpc/Cargo.toml index 3d0b1e7d3..05cfe1057 100644 --- a/rpc/Cargo.toml +++ b/rpc/Cargo.toml @@ -10,6 +10,7 @@ authors = ["Parity Technologies "] [dependencies] cid = "0.2" futures = "0.1" +futures-cpupool = "0.1" log = "0.3" multihash ="0.6" order-stat = "0.1" diff --git a/rpc/src/lib.rs b/rpc/src/lib.rs index 38dafc0a4..64e1fc463 100644 --- a/rpc/src/lib.rs +++ b/rpc/src/lib.rs @@ -23,6 +23,7 @@ extern crate cid; extern crate crypto as rust_crypto; extern crate futures; +extern crate futures_cpupool; extern crate multihash; extern crate order_stat; extern crate rand; diff --git a/rpc/src/v1/extractors.rs b/rpc/src/v1/extractors.rs index 67f121526..ab1fad4da 100644 --- a/rpc/src/v1/extractors.rs +++ b/rpc/src/v1/extractors.rs @@ -217,18 +217,26 @@ impl> WsDispatcher { } impl> core::Middleware for WsDispatcher { - fn on_request(&self, request: core::Request, meta: Metadata, process: F) -> core::FutureResponse where - F: FnOnce(core::Request, Metadata) -> core::FutureResponse, + type Future = core::futures::future::Either< + M::Future, + core::FutureResponse, + >; + + fn on_request(&self, request: core::Request, meta: Metadata, process: F) -> Self::Future where + F: FnOnce(core::Request, Metadata) -> X, + X: core::futures::Future, Error=()> + Send + 'static, { + use self::core::futures::future::Either::{A, B}; + let use_full = match &meta.origin { &Origin::Signer { .. } => true, _ => false, }; if use_full { - self.full_handler.handle_rpc_request(request, meta) + A(self.full_handler.handle_rpc_request(request, meta)) } else { - process(request, meta) + B(process(request, meta).boxed()) } } } diff --git a/rpc/src/v1/helpers/subscription_manager.rs b/rpc/src/v1/helpers/subscription_manager.rs index 1cf067901..c5e4216f4 100644 --- a/rpc/src/v1/helpers/subscription_manager.rs +++ b/rpc/src/v1/helpers/subscription_manager.rs @@ -17,6 +17,7 @@ //! Generic poll manager for Pub-Sub. use std::sync::Arc; +use std::sync::atomic::{self, AtomicBool}; use util::Mutex; use jsonrpc_core::futures::future::{self, Either}; @@ -34,7 +35,8 @@ struct Subscription { method: String, params: core::Params, sink: mpsc::Sender>, - last_result: Arc>>, + /// a flag if subscription is still active and last returned value + last_result: Arc<(AtomicBool, Mutex>)>, } /// A struct managing all subscriptions. @@ -68,10 +70,10 @@ impl> GenericPollManager { { let (sink, stream) = mpsc::channel(1); let subscription = Subscription { - metadata: metadata, - method: method, - params: params, - sink: sink, + metadata, + method, + params, + sink, last_result: Default::default(), }; let id = self.subscribers.insert(subscription); @@ -80,7 +82,9 @@ impl> GenericPollManager { pub fn unsubscribe(&mut self, id: &SubscriptionId) -> bool { debug!(target: "pubsub", "Removing subscription: {:?}", id); - self.subscribers.remove(id).is_some() + self.subscribers.remove(id).map(|subscription| { + subscription.last_result.0.store(true, atomic::Ordering::SeqCst); + }).is_some() } pub fn tick(&self) -> BoxFuture<(), ()> { @@ -100,7 +104,12 @@ impl> GenericPollManager { let sender = subscription.sink.clone(); let result = result.and_then(move |response| { - let mut last_result = last_result.lock(); + // quick check if the subscription is still valid + if last_result.0.load(atomic::Ordering::SeqCst) { + return Either::B(future::ok(())) + } + + let mut last_result = last_result.1.lock(); if *last_result != response && response.is_some() { let output = response.expect("Existence proved by the condition."); debug!(target: "pubsub", "Got new response, sending: {:?}", output); diff --git a/rpc/src/v1/informant.rs b/rpc/src/v1/informant.rs index c1bbbe6c2..160f0ea9f 100644 --- a/rpc/src/v1/informant.rs +++ b/rpc/src/v1/informant.rs @@ -21,10 +21,13 @@ use std::sync::Arc; use std::sync::atomic::{self, AtomicUsize}; use std::time; use futures::Future; +use futures_cpupool as pool; use jsonrpc_core as rpc; use order_stat; use util::RwLock; +pub use self::pool::CpuPool; + const RATE_SECONDS: usize = 10; const STATS_SAMPLES: usize = 60; @@ -184,14 +187,16 @@ pub trait ActivityNotifier: Send + Sync + 'static { pub struct Middleware { stats: Arc, notifier: T, + pool: Option, } impl Middleware { /// Create new Middleware with stats counter and activity notifier. - pub fn new(stats: Arc, notifier: T) -> Self { + pub fn new(stats: Arc, notifier: T, pool: Option) -> Self { Middleware { - stats: stats, - notifier: notifier, + stats, + notifier, + pool, } } @@ -201,19 +206,32 @@ impl Middleware { } impl rpc::Middleware for Middleware { - fn on_request(&self, request: rpc::Request, meta: M, process: F) -> rpc::FutureResponse where - F: FnOnce(rpc::Request, M) -> rpc::FutureResponse, + type Future = rpc::futures::future::Either< + pool::CpuFuture, ()>, + rpc::FutureResponse, + >; + + fn on_request(&self, request: rpc::Request, meta: M, process: F) -> Self::Future where + F: FnOnce(rpc::Request, M) -> X, + X: rpc::futures::Future, Error=()> + Send + 'static, { + use self::rpc::futures::future::Either::{A, B}; + let start = time::Instant::now(); - let response = process(request, meta); self.notifier.active(); + self.stats.count_request(); + let stats = self.stats.clone(); - stats.count_request(); - response.map(move |res| { + let future = process(request, meta).map(move |res| { stats.add_roundtrip(Self::as_micro(start.elapsed())); res - }).boxed() + }); + + match self.pool { + Some(ref pool) => A(pool.spawn(future)), + None => B(future.boxed()), + } } } diff --git a/rpc/src/v1/tests/eth.rs b/rpc/src/v1/tests/eth.rs index 5f70271ee..7ce1dcc29 100644 --- a/rpc/src/v1/tests/eth.rs +++ b/rpc/src/v1/tests/eth.rs @@ -15,6 +15,7 @@ // along with Parity. If not, see . //! rpc integration tests. +use std::env; use std::sync::Arc; use std::time::Duration; @@ -57,6 +58,7 @@ fn miner_service(spec: &Spec, accounts: Arc) -> Arc { force_sealing: true, reseal_on_external_tx: true, reseal_on_own_tx: true, + reseal_on_uncle: false, tx_queue_size: 1024, tx_gas_limit: !U256::zero(), tx_queue_strategy: PrioritizationStrategy::GasPriceOnly, @@ -318,7 +320,7 @@ const POSITIVE_NONCE_SPEC: &'static [u8] = br#"{ #[test] fn eth_transaction_count() { let secret = "8a283037bb19c4fed7b1c569e40c7dcff366165eb869110a1b11532963eb9cb2".parse().unwrap(); - let tester = EthTester::from_spec(Spec::load(TRANSACTION_COUNT_SPEC).expect("invalid chain spec")); + let tester = EthTester::from_spec(Spec::load(&env::temp_dir(), TRANSACTION_COUNT_SPEC).expect("invalid chain spec")); let address = tester.accounts.insert_account(secret, "").unwrap(); tester.accounts.unlock_account_permanently(address, "".into()).unwrap(); @@ -444,7 +446,7 @@ fn verify_transaction_counts(name: String, chain: BlockChain) { #[test] fn starting_nonce_test() { - let tester = EthTester::from_spec(Spec::load(POSITIVE_NONCE_SPEC).expect("invalid chain spec")); + let tester = EthTester::from_spec(Spec::load(&env::temp_dir(), POSITIVE_NONCE_SPEC).expect("invalid chain spec")); let address = Address::from(10); let sample = tester.handler.handle_request_sync(&(r#" diff --git a/sync/src/light_sync/tests/test_net.rs b/sync/src/light_sync/tests/test_net.rs index 525216a7e..f6d5eddf0 100644 --- a/sync/src/light_sync/tests/test_net.rs +++ b/sync/src/light_sync/tests/test_net.rs @@ -211,8 +211,12 @@ impl TestNet { pub fn light(n_light: usize, n_full: usize) -> Self { let mut peers = Vec::with_capacity(n_light + n_full); for _ in 0..n_light { + let mut config = ::light::client::Config::default(); + + // skip full verification because the blocks are bad. + config.verify_full = false; let cache = Arc::new(Mutex::new(Cache::new(Default::default(), Duration::hours(6)))); - let client = LightClient::in_memory(Default::default(), &Spec::new_test(), IoChannel::disconnected(), cache); + let client = LightClient::in_memory(config, &Spec::new_test(), IoChannel::disconnected(), cache); peers.push(Arc::new(Peer::new_light(Arc::new(client)))) } diff --git a/util/fetch/src/client.rs b/util/fetch/src/client.rs index 18c8d87d9..4aa85bd34 100644 --- a/util/fetch/src/client.rs +++ b/util/fetch/src/client.rs @@ -126,6 +126,11 @@ impl Client { *self.client.write() = (time::Instant::now(), client.clone()); Ok(client) } + + /// Returns a handle to underlying CpuPool of this client. + pub fn pool(&self) -> CpuPool { + self.pool.clone() + } } impl Fetch for Client { @@ -204,6 +209,15 @@ pub enum Error { Aborted, } +impl fmt::Display for Error { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + match *self { + Error::Aborted => write!(fmt, "The request has been aborted."), + Error::Fetch(ref err) => write!(fmt, "{}", err), + } + } +} + impl From for Error { fn from(error: reqwest::Error) -> Self { Error::Fetch(error)