Merge branch 'master' into issues/4673

This commit is contained in:
Joseph Mark 2017-07-12 16:06:23 +07:00
commit 2d52c7b42f
No known key found for this signature in database
GPG Key ID: 9BA8D1EE2E53C283
77 changed files with 1670 additions and 309 deletions

122
Cargo.lock generated
View File

@ -67,6 +67,29 @@ dependencies = [
"winapi 0.2.8 (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]] [[package]]
name = "base-x" name = "base-x"
version = "0.2.2" version = "0.2.2"
@ -159,6 +182,11 @@ dependencies = [
"rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", "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]] [[package]]
name = "byteorder" name = "byteorder"
version = "1.0.0" version = "1.0.0"
@ -225,6 +253,14 @@ dependencies = [
"unicode-normalization 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "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]] [[package]]
name = "cookie" name = "cookie"
version = "0.3.1" version = "0.3.1"
@ -275,6 +311,11 @@ dependencies = [
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", "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]] [[package]]
name = "daemonize" name = "daemonize"
version = "0.2.2" version = "0.2.2"
@ -283,6 +324,15 @@ dependencies = [
"libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", "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]] [[package]]
name = "deque" name = "deque"
version = "0.3.1" version = "0.3.1"
@ -335,6 +385,14 @@ dependencies = [
"regex 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "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]] [[package]]
name = "eth-secp256k1" name = "eth-secp256k1"
version = "0.5.6" version = "0.5.6"
@ -406,7 +464,7 @@ dependencies = [
"native-contracts 0.1.0", "native-contracts 0.1.0",
"num 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)", "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)", "num_cpus 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"parity-wasm 0.12.1 (git+http://github.com/nikvolf/parity-wasm)", "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)", "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
"rlp 0.2.0", "rlp 0.2.0",
"rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", "rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1061,7 +1119,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
name = "jsonrpc-core" name = "jsonrpc-core"
version = "7.0.0" 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 = [ dependencies = [
"futures 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", "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)", "log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1073,7 +1131,7 @@ dependencies = [
[[package]] [[package]]
name = "jsonrpc-http-server" name = "jsonrpc-http-server"
version = "7.0.0" 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 = [ dependencies = [
"hyper 0.10.0-a.0 (git+https://github.com/paritytech/hyper)", "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)", "jsonrpc-core 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)",
@ -1086,7 +1144,7 @@ dependencies = [
[[package]] [[package]]
name = "jsonrpc-ipc-server" name = "jsonrpc-ipc-server"
version = "7.0.0" 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 = [ dependencies = [
"bytes 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", "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)", "jsonrpc-core 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)",
@ -1099,7 +1157,7 @@ dependencies = [
[[package]] [[package]]
name = "jsonrpc-macros" name = "jsonrpc-macros"
version = "7.0.0" 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 = [ dependencies = [
"jsonrpc-core 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)", "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)", "jsonrpc-pubsub 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)",
@ -1109,7 +1167,7 @@ dependencies = [
[[package]] [[package]]
name = "jsonrpc-minihttp-server" name = "jsonrpc-minihttp-server"
version = "7.0.0" 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 = [ dependencies = [
"bytes 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", "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)", "jsonrpc-core 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)",
@ -1124,7 +1182,7 @@ dependencies = [
[[package]] [[package]]
name = "jsonrpc-pubsub" name = "jsonrpc-pubsub"
version = "7.0.0" 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 = [ dependencies = [
"jsonrpc-core 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)", "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)", "log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1134,8 +1192,9 @@ dependencies = [
[[package]] [[package]]
name = "jsonrpc-server-utils" name = "jsonrpc-server-utils"
version = "7.0.0" 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 = [ 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)", "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)", "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)", "log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1146,7 +1205,7 @@ dependencies = [
[[package]] [[package]]
name = "jsonrpc-tcp-server" name = "jsonrpc-tcp-server"
version = "7.0.0" 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 = [ dependencies = [
"bytes 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", "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)", "jsonrpc-core 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)",
@ -1160,11 +1219,13 @@ dependencies = [
[[package]] [[package]]
name = "jsonrpc-ws-server" name = "jsonrpc-ws-server"
version = "7.0.0" 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 = [ dependencies = [
"jsonrpc-core 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)", "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)", "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)", "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)", "ws 0.7.1 (git+https://github.com/tomusdrw/ws-rs)",
] ]
@ -1464,6 +1525,19 @@ name = "nom"
version = "1.2.2" version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index" 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]] [[package]]
name = "num" name = "num"
version = "0.1.32" version = "0.1.32"
@ -1624,6 +1698,7 @@ dependencies = [
"ethsync 1.7.0", "ethsync 1.7.0",
"fdlimit 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "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 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)", "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)", "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)", "log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1666,12 +1741,14 @@ dependencies = [
"ethcore-util 1.7.0", "ethcore-util 1.7.0",
"fetch 0.1.0", "fetch 0.1.0",
"futures 0.1.11 (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)",
"jsonrpc-core 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)", "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-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)", "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)", "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 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)", "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-dapps-glue 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
"parity-hash-fetch 1.7.0", "parity-hash-fetch 1.7.0",
"parity-reactor 0.1.0", "parity-reactor 0.1.0",
@ -1775,6 +1852,7 @@ dependencies = [
"ethsync 1.7.0", "ethsync 1.7.0",
"fetch 0.1.0", "fetch 0.1.0",
"futures 0.1.11 (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)",
"jsonrpc-core 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)", "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-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)", "jsonrpc-ipc-server 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)",
@ -1856,7 +1934,7 @@ dependencies = [
[[package]] [[package]]
name = "parity-ui-precompiled" name = "parity-ui-precompiled"
version = "1.4.0" version = "1.4.0"
source = "git+https://github.com/paritytech/js-precompiled.git#fa572f52beb3a7b6f6473a5a5cf07518d899c4d9" source = "git+https://github.com/paritytech/js-precompiled.git#b49a1d46cc6c545403d18579ef7ae9f9d14eea7e"
dependencies = [ dependencies = [
"parity-dapps-glue 1.7.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)",
] ]
@ -1883,7 +1961,7 @@ dependencies = [
[[package]] [[package]]
name = "parity-wasm" name = "parity-wasm"
version = "0.12.1" version = "0.12.1"
source = "git+http://github.com/nikvolf/parity-wasm#98311ec7333e0d3dc9e45c9df673cc79e41acb83" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ dependencies = [
"byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "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)", "log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
@ -2235,6 +2313,11 @@ dependencies = [
"time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", "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]] [[package]]
name = "rustc-hex" name = "rustc-hex"
version = "1.0.0" version = "1.0.0"
@ -2872,13 +2955,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
name = "wasm-utils" name = "wasm-utils"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/paritytech/wasm-utils#357a5deed635938e79553227bfab976959ca3523" source = "git+https://github.com/paritytech/wasm-utils#fee06b6d5826c2dc1fc1aa183b0c2c75e3e140c3"
dependencies = [ dependencies = [
"clap 2.24.2 (registry+https://github.com/rust-lang/crates.io-index)", "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)", "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)", "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)", "log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
"parity-wasm 0.12.1 (git+http://github.com/nikvolf/parity-wasm)", "parity-wasm 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]] [[package]]
@ -2957,6 +3040,8 @@ dependencies = [
"checksum arrayvec 0.3.20 (registry+https://github.com/rust-lang/crates.io-index)" = "d89f1b0e242270b5b797778af0c8d182a1a2ccac5d8d6fadf414223cc0fab096" "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 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 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 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 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" "checksum bigint 3.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d0673c930652d3d4d6dcd5c45b5db4fa5f8f33994d7323618c43c083b223e8c"
@ -2971,6 +3056,7 @@ dependencies = [
"checksum blastfig 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "09640e0509d97d5cdff03a9f5daf087a8e04c735c3b113a75139634a19cfc7b2" "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 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)" = "<none>" "checksum bn 0.4.4 (git+https://github.com/paritytech/bn)" = "<none>"
"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 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 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 cfg-if 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "de1e760d7b6535af4241fca8bd8adf68e2e7edacc6b29f5d399050c5e48cf88c"
@ -2978,13 +3064,16 @@ dependencies = [
"checksum clap 2.24.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6b8f69e518f967224e628896b54e41ff6acfb4dcfefc5076325c36525dac900f" "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 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 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 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 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 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 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 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)" = "<none>" "checksum ctrlc 1.1.1 (git+https://github.com/paritytech/rust-ctrlc.git)" = "<none>"
"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 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 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 difference 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b3304d19798a8e067e48d8e69b2c37f0b5e9b4e462504ad9e27e9f3fce02bba8"
"checksum docopt 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3b5b93718f8b3e5544fcc914c43de828ca6c6ace23e0332c6080a2977b49787a" "checksum docopt 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3b5b93718f8b3e5544fcc914c43de828ca6c6ace23e0332c6080a2977b49787a"
@ -2992,6 +3081,7 @@ dependencies = [
"checksum either 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3d2b503c86dad62aaf414ecf2b8c527439abedb3f8d812537f0b12bfd6f32a91" "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 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 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)" = "<none>" "checksum eth-secp256k1 0.5.6 (git+https://github.com/paritytech/rust-secp256k1)" = "<none>"
"checksum ethabi 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0c3d62319ee0f35abf20afe8859dd2668195912614346447bb2dee9fb8da7c62" "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" "checksum fdlimit 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b1ee15a7050e5580b3712877157068ea713b245b080ff302ae2ca973cfcd9baa"
@ -3062,6 +3152,7 @@ dependencies = [
"checksum net2 0.2.29 (registry+https://github.com/rust-lang/crates.io-index)" = "bc01404e7568680f1259aa5729539f221cb1e6d047a0d9053cab4be8a73b5d67" "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 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 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 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-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" "checksum num-complex 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)" = "ccac67baf893ac97474f8d70eff7761dabb1f6c66e71f8f1c67a6859218db810"
@ -3080,7 +3171,7 @@ dependencies = [
"checksum parity-dapps-glue 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e1d06f6ee0fda786df3784a96ee3f0629f529b91cbfb7d142f6410e6bcd1ce2c" "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)" = "<none>" "checksum parity-tokio-ipc 0.1.5 (git+https://github.com/nikvolf/parity-tokio-ipc)" = "<none>"
"checksum parity-ui-precompiled 1.4.0 (git+https://github.com/paritytech/js-precompiled.git)" = "<none>" "checksum parity-ui-precompiled 1.4.0 (git+https://github.com/paritytech/js-precompiled.git)" = "<none>"
"checksum parity-wasm 0.12.1 (git+http://github.com/nikvolf/parity-wasm)" = "<none>" "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 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 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" "checksum parking_lot_core 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fb1b97670a2ffadce7c397fb80a3d687c4f3060140b885621ef1653d0e5d5068"
@ -3117,6 +3208,7 @@ dependencies = [
"checksum rpassword 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5d3a99497c5c544e629cc8b359ae5ede321eba5fa8e5a8078f3ced727a976c3f" "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 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 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-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-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" "checksum rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "c5f5376ea5e30ce23c03eb77cbe4962b988deead10910c372b226388b594c084"

View File

@ -25,6 +25,7 @@ serde_json = "1.0"
serde_derive = "1.0" serde_derive = "1.0"
app_dirs = "1.1.1" app_dirs = "1.1.1"
futures = "0.1" futures = "0.1"
futures-cpupool = "0.1"
fdlimit = "0.1" fdlimit = "0.1"
ws2_32-sys = "0.2" ws2_32-sys = "0.2"
ctrlc = { git = "https://github.com/paritytech/rust-ctrlc.git" } ctrlc = { git = "https://github.com/paritytech/rust-ctrlc.git" }

View File

@ -81,14 +81,6 @@ Once you have rustup, install parity or download and build from source
---- ----
## Quick build and install
```bash
cargo install --git https://github.com/paritytech/parity.git parity
```
----
## Install from the snap store ## Install from the snap store
In any of the [supported Linux distros](https://snapcraft.io/docs/core/install): In any of the [supported Linux distros](https://snapcraft.io/docs/core/install):

View File

@ -11,11 +11,13 @@ authors = ["Parity Technologies <admin@parity.io>"]
base32 = "0.3" base32 = "0.3"
env_logger = "0.4" env_logger = "0.4"
futures = "0.1" futures = "0.1"
futures-cpupool = "0.1"
linked-hash-map = "0.3" linked-hash-map = "0.3"
log = "0.3" log = "0.3"
parity-dapps-glue = "1.7" parity-dapps-glue = "1.7"
mime = "0.2" mime = "0.2"
mime_guess = "1.6.1" mime_guess = "1.6.1"
ntp = "0.2.0"
rand = "0.3" rand = "0.3"
rustc-hex = "1.0" rustc-hex = "1.0"
serde = "1.0" serde = "1.0"

View File

@ -18,23 +18,36 @@ use std::sync::Arc;
use hyper::{server, net, Decoder, Encoder, Next, Control}; use hyper::{server, net, Decoder, Encoder, Next, Control};
use hyper::method::Method; use hyper::method::Method;
use hyper::status::StatusCode;
use api::types::ApiError; use api::{response, types};
use api::response; use api::time::TimeChecker;
use apps::fetcher::Fetcher; use apps::fetcher::Fetcher;
use handlers::{self, extract_url};
use handlers::extract_url;
use endpoint::{Endpoint, Handler, EndpointPath}; use endpoint::{Endpoint, Handler, EndpointPath};
use parity_reactor::Remote;
use {SyncStatus};
#[derive(Clone)] #[derive(Clone)]
pub struct RestApi { pub struct RestApi {
fetcher: Arc<Fetcher>, fetcher: Arc<Fetcher>,
sync_status: Arc<SyncStatus>,
time: TimeChecker,
remote: Remote,
} }
impl RestApi { impl RestApi {
pub fn new(fetcher: Arc<Fetcher>) -> Box<Endpoint> { pub fn new(
fetcher: Arc<Fetcher>,
sync_status: Arc<SyncStatus>,
time: TimeChecker,
remote: Remote,
) -> Box<Endpoint> {
Box::new(RestApi { Box::new(RestApi {
fetcher: fetcher, fetcher,
sync_status,
time,
remote,
}) })
} }
} }
@ -58,11 +71,11 @@ impl RestApiRouter {
path: Some(path), path: Some(path),
control: Some(control), control: Some(control),
api: api, api: api,
handler: response::as_json_error(&ApiError { handler: Box::new(response::as_json_error(StatusCode::NotFound, &types::ApiError {
code: "404".into(), code: "404".into(),
title: "Not Found".into(), title: "Not Found".into(),
detail: "Resource you requested has not been found.".into(), detail: "Resource you requested has not been found.".into(),
}), })),
} }
} }
@ -75,6 +88,78 @@ impl RestApiRouter {
_ => None _ => None
} }
} }
fn health(&self, control: Control) -> Box<Handler> {
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<net::HttpStream> for RestApiRouter { impl server::Handler<net::HttpStream> for RestApiRouter {
@ -103,6 +188,7 @@ impl server::Handler<net::HttpStream> for RestApiRouter {
let handler = endpoint.and_then(|v| match v { let handler = endpoint.and_then(|v| match v {
"ping" => Some(response::ping()), "ping" => Some(response::ping()),
"health" => Some(self.health(control)),
"content" => self.resolve_content(hash, path, control), "content" => self.resolve_content(hash, path, control),
_ => None _ => None
}); });

View File

@ -18,6 +18,8 @@
mod api; mod api;
mod response; mod response;
mod time;
mod types; mod types;
pub use self::api::RestApi; pub use self::api::RestApi;
pub use self::time::TimeChecker;

View File

@ -16,6 +16,8 @@
use serde::Serialize; use serde::Serialize;
use serde_json; use serde_json;
use hyper::status::StatusCode;
use endpoint::Handler; use endpoint::Handler;
use handlers::{ContentHandler, EchoHandler}; use handlers::{ContentHandler, EchoHandler};
@ -23,10 +25,16 @@ pub fn empty() -> Box<Handler> {
Box::new(ContentHandler::ok("".into(), mime!(Text/Plain))) Box::new(ContentHandler::ok("".into(), mime!(Text/Plain)))
} }
pub fn as_json_error<T: Serialize>(val: &T) -> Box<Handler> { pub fn as_json<T: Serialize>(status: StatusCode, val: &T) -> ContentHandler {
let json = serde_json::to_string(val) let json = serde_json::to_string(val)
.expect("serialization to string is infallible; qed"); .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<T: Serialize>(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<Handler> { pub fn ping() -> Box<Handler> {

264
dapps/src/api/time.rs Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
//! 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<io::Error> for Error {
fn from(err: io::Error) -> Self { Error::Io(format!("{}", err)) }
}
impl From<ntp::errors::Error> 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<Duration, Error>;
}
/// NTP client using the SNTP algorithm for calculating drift.
#[derive(Clone)]
pub struct SimpleNtp {
address: Arc<String>,
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<Duration, Error> {
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<N: Ntp = SimpleNtp> {
ntp: N,
last_result: Arc<RwLock<(time::Instant, VecDeque<Result<i64, Error>>)>>,
}
impl TimeChecker<SimpleNtp> {
/// 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<N: Ntp> TimeChecker<N> {
/// Updates the time
pub fn update(&self) -> BoxFuture<i64, Error> {
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<i64, Error> {
// 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<Item=&'a Result<i64, Error>>>(results: T) -> Result<i64, Error> {
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<Vec<Duration>>, Cell<u64>);
impl FakeNtp {
fn new() -> FakeNtp {
FakeNtp(
RefCell::new(vec![Duration::milliseconds(150)]),
Cell::new(0))
}
}
impl Ntp for FakeNtp {
fn drift(&self) -> BoxFuture<Duration, Error> {
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<FakeNtp> {
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);
}
}

View File

@ -14,11 +14,54 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
/// A structure representing any error in REST API.
#[derive(Debug, PartialEq, Serialize, Deserialize)] #[derive(Debug, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)] #[serde(deny_unknown_fields)]
pub struct ApiError { pub struct ApiError {
/// Error code.
pub code: String, pub code: String,
/// Human-readable error summary.
pub title: String, pub title: String,
/// More technical error details.
pub detail: String, 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<T> {
/// 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<bool>,
/// Time diff info.
pub time: HealthInfo<i64>,
}

View File

@ -25,6 +25,7 @@ use util::sha3::sha3;
use page::{LocalPageEndpoint, PageCache}; use page::{LocalPageEndpoint, PageCache};
use handlers::{ContentValidator, ValidatorResponse}; use handlers::{ContentValidator, ValidatorResponse};
use apps::manifest::{MANIFEST_FILENAME, deserialize_manifest, serialize_manifest, Manifest}; use apps::manifest::{MANIFEST_FILENAME, deserialize_manifest, serialize_manifest, Manifest};
use Embeddable;
type OnDone = Box<Fn(Option<LocalPageEndpoint>) + Send>; type OnDone = Box<Fn(Option<LocalPageEndpoint>) + Send>;
@ -116,16 +117,16 @@ pub struct Dapp {
id: String, id: String,
dapps_path: PathBuf, dapps_path: PathBuf,
on_done: OnDone, on_done: OnDone,
embeddable_on: Option<(String, u16)>, embeddable_on: Embeddable,
} }
impl Dapp { impl Dapp {
pub fn new(id: String, dapps_path: PathBuf, on_done: OnDone, embeddable_on: Option<(String, u16)>) -> Self { pub fn new(id: String, dapps_path: PathBuf, on_done: OnDone, embeddable_on: Embeddable) -> Self {
Dapp { Dapp {
id: id, id,
dapps_path: dapps_path, dapps_path,
on_done: on_done, on_done,
embeddable_on: embeddable_on, embeddable_on,
} }
} }

View File

@ -31,7 +31,7 @@ use parity_reactor::Remote;
use hyper; use hyper;
use hyper::status::StatusCode; use hyper::status::StatusCode;
use {SyncStatus, random_filename}; use {Embeddable, SyncStatus, random_filename};
use util::Mutex; use util::Mutex;
use page::LocalPageEndpoint; use page::LocalPageEndpoint;
use handlers::{ContentHandler, ContentFetcherHandler}; use handlers::{ContentHandler, ContentFetcherHandler};
@ -52,7 +52,7 @@ pub struct ContentFetcher<F: Fetch = FetchClient, R: URLHint + 'static = URLHint
resolver: R, resolver: R,
cache: Arc<Mutex<ContentCache>>, cache: Arc<Mutex<ContentCache>>,
sync: Arc<SyncStatus>, sync: Arc<SyncStatus>,
embeddable_on: Option<(String, u16)>, embeddable_on: Embeddable,
remote: Remote, remote: Remote,
fetch: F, fetch: F,
only_content: bool, only_content: bool,
@ -93,22 +93,22 @@ impl<R: URLHint + 'static, F: Fetch> ContentFetcher<F, R> {
self self
} }
pub fn embeddable_on(mut self, embeddable_on: Option<(String, u16)>) -> Self { pub fn embeddable_on(mut self, embeddable_on: Embeddable) -> Self {
self.embeddable_on = embeddable_on; self.embeddable_on = embeddable_on;
self self
} }
fn still_syncing(address: Option<(String, u16)>) -> Box<Handler> { fn still_syncing(embeddable: Embeddable) -> Box<Handler> {
Box::new(ContentHandler::error( Box::new(ContentHandler::error(
StatusCode::ServiceUnavailable, StatusCode::ServiceUnavailable,
"Sync In Progress", "Sync In Progress",
"Your node is still syncing. We cannot resolve any content before it's fully synced.", "Your node is still syncing. We cannot resolve any content before it's fully synced.",
Some("<a href=\"javascript:window.location.reload()\">Refresh</a>"), Some("<a href=\"javascript:window.location.reload()\">Refresh</a>"),
address, embeddable,
)) ))
} }
fn dapps_disabled(address: Option<(String, u16)>) -> Box<Handler> { fn dapps_disabled(address: Embeddable) -> Box<Handler> {
Box::new(ContentHandler::error( Box::new(ContentHandler::error(
StatusCode::ServiceUnavailable, StatusCode::ServiceUnavailable,
"Network Dapps Not Available", "Network Dapps Not Available",
@ -271,6 +271,7 @@ mod tests {
use endpoint::EndpointInfo; use endpoint::EndpointInfo;
use page::LocalPageEndpoint; use page::LocalPageEndpoint;
use super::{ContentFetcher, Fetcher}; use super::{ContentFetcher, Fetcher};
use {SyncStatus};
#[derive(Clone)] #[derive(Clone)]
struct FakeResolver; 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] #[test]
fn should_true_if_contains_the_app() { fn should_true_if_contains_the_app() {
// given // given
let path = env::temp_dir(); 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); .allow_dapps(true);
let handler = LocalPageEndpoint::new(path, EndpointInfo { let handler = LocalPageEndpoint::new(path, EndpointInfo {
name: "fake".into(), name: "fake".into(),

View File

@ -22,6 +22,7 @@ use std::path::{Path, PathBuf};
use page::{LocalPageEndpoint, PageCache}; use page::{LocalPageEndpoint, PageCache};
use endpoint::{Endpoint, EndpointInfo}; use endpoint::{Endpoint, EndpointInfo};
use apps::manifest::{MANIFEST_FILENAME, deserialize_manifest}; use apps::manifest::{MANIFEST_FILENAME, deserialize_manifest};
use Embeddable;
struct LocalDapp { struct LocalDapp {
id: String, id: String,
@ -60,14 +61,14 @@ fn read_manifest(name: &str, mut path: PathBuf) -> EndpointInfo {
/// Returns Dapp Id and Local Dapp Endpoint for given filesystem path. /// Returns Dapp Id and Local Dapp Endpoint for given filesystem path.
/// Parses the path to extract last component (for name). /// Parses the path to extract last component (for name).
/// `None` is returned when path is invalid or non-existent. /// `None` is returned when path is invalid or non-existent.
pub fn local_endpoint<P: AsRef<Path>>(path: P, signer_address: Option<(String, u16)>) -> Option<(String, Box<LocalPageEndpoint>)> { pub fn local_endpoint<P: AsRef<Path>>(path: P, embeddable: Embeddable) -> Option<(String, Box<LocalPageEndpoint>)> {
let path = path.as_ref().to_owned(); let path = path.as_ref().to_owned();
path.canonicalize().ok().and_then(|path| { path.canonicalize().ok().and_then(|path| {
let name = path.file_name().and_then(|name| name.to_str()); let name = path.file_name().and_then(|name| name.to_str());
name.map(|name| { name.map(|name| {
let dapp = local_dapp(name.into(), path.clone()); let dapp = local_dapp(name.into(), path.clone());
(dapp.id, Box::new(LocalPageEndpoint::new( (dapp.id, Box::new(LocalPageEndpoint::new(
dapp.path, dapp.info, PageCache::Disabled, signer_address.clone()) dapp.path, dapp.info, PageCache::Disabled, embeddable.clone())
)) ))
}) })
}) })
@ -86,12 +87,12 @@ fn local_dapp(name: String, path: PathBuf) -> LocalDapp {
/// Returns endpoints for Local Dapps found for given filesystem path. /// Returns endpoints for Local Dapps found for given filesystem path.
/// Scans the directory and collects `LocalPageEndpoints`. /// Scans the directory and collects `LocalPageEndpoints`.
pub fn local_endpoints<P: AsRef<Path>>(dapps_path: P, signer_address: Option<(String, u16)>) -> BTreeMap<String, Box<Endpoint>> { pub fn local_endpoints<P: AsRef<Path>>(dapps_path: P, embeddable: Embeddable) -> BTreeMap<String, Box<Endpoint>> {
let mut pages = BTreeMap::<String, Box<Endpoint>>::new(); let mut pages = BTreeMap::<String, Box<Endpoint>>::new();
for dapp in local_dapps(dapps_path.as_ref()) { for dapp in local_dapps(dapps_path.as_ref()) {
pages.insert( pages.insert(
dapp.id, dapp.id,
Box::new(LocalPageEndpoint::new(dapp.path, dapp.info, PageCache::Disabled, signer_address.clone())) Box::new(LocalPageEndpoint::new(dapp.path, dapp.info, PageCache::Disabled, embeddable.clone()))
); );
} }
pages pages

View File

@ -26,7 +26,7 @@ use fetch::Fetch;
use parity_dapps::WebApp; use parity_dapps::WebApp;
use parity_reactor::Remote; use parity_reactor::Remote;
use parity_ui; use parity_ui;
use {WebProxyTokens}; use {WebProxyTokens, ParentFrameSettings};
mod app; mod app;
mod cache; mod cache;
@ -52,23 +52,23 @@ pub fn ui() -> Box<Endpoint> {
Box::new(PageEndpoint::with_fallback_to_index(parity_ui::App::default())) Box::new(PageEndpoint::with_fallback_to_index(parity_ui::App::default()))
} }
pub fn ui_redirection(ui_address: Option<(String, u16)>) -> Box<Endpoint> { pub fn ui_redirection(embeddable: Option<ParentFrameSettings>) -> Box<Endpoint> {
Box::new(ui::Redirection::new(ui_address)) Box::new(ui::Redirection::new(embeddable))
} }
pub fn all_endpoints<F: Fetch>( pub fn all_endpoints<F: Fetch>(
dapps_path: PathBuf, dapps_path: PathBuf,
extra_dapps: Vec<PathBuf>, extra_dapps: Vec<PathBuf>,
dapps_domain: String, dapps_domain: &str,
ui_address: Option<(String, u16)>, embeddable: Option<ParentFrameSettings>,
web_proxy_tokens: Arc<WebProxyTokens>, web_proxy_tokens: Arc<WebProxyTokens>,
remote: Remote, remote: Remote,
fetch: F, fetch: F,
) -> Endpoints { ) -> Endpoints {
// fetch fs dapps at first to avoid overwriting builtins // fetch fs dapps at first to avoid overwriting builtins
let mut pages = fs::local_endpoints(dapps_path, ui_address.clone()); let mut pages = fs::local_endpoints(dapps_path, embeddable.clone());
for path in extra_dapps { for path in extra_dapps {
if let Some((id, endpoint)) = fs::local_endpoint(path.clone(), ui_address.clone()) { if let Some((id, endpoint)) = fs::local_endpoint(path.clone(), embeddable.clone()) {
pages.insert(id, endpoint); pages.insert(id, endpoint);
} else { } else {
warn!(target: "dapps", "Ignoring invalid dapp at {}", path.display()); warn!(target: "dapps", "Ignoring invalid dapp at {}", path.display());
@ -76,9 +76,9 @@ pub fn all_endpoints<F: Fetch>(
} }
// NOTE [ToDr] Dapps will be currently embeded on 8180 // NOTE [ToDr] Dapps will be currently embeded on 8180
insert::<parity_ui::App>(&mut pages, "ui", Embeddable::Yes(ui_address.clone())); insert::<parity_ui::App>(&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(ui_address.clone(), web_proxy_tokens.clone(), remote.clone(), fetch.clone())); pages.insert(WEB_PATH.into(), Web::boxed(embeddable.clone(), web_proxy_tokens.clone(), remote.clone(), fetch.clone()));
Arc::new(pages) Arc::new(pages)
} }
@ -91,7 +91,7 @@ fn insert<T : WebApp + Default + 'static>(pages: &mut BTreeMap<String, Box<Endpo
} }
enum Embeddable { enum Embeddable {
Yes(Option<(String, u16)>), Yes(Option<ParentFrameSettings>),
#[allow(dead_code)] #[allow(dead_code)]
No, No,
} }

View File

@ -19,28 +19,28 @@
use hyper::{Control, StatusCode}; use hyper::{Control, StatusCode};
use endpoint::{Endpoint, Handler, EndpointPath}; use endpoint::{Endpoint, Handler, EndpointPath};
use {address, handlers}; use {handlers, Embeddable};
/// Redirection to UI server. /// Redirection to UI server.
pub struct Redirection { pub struct Redirection {
signer_address: Option<(String, u16)>, embeddable_on: Embeddable,
} }
impl Redirection { impl Redirection {
pub fn new( pub fn new(
signer_address: Option<(String, u16)>, embeddable_on: Embeddable,
) -> Self { ) -> Self {
Redirection { Redirection {
signer_address: signer_address, embeddable_on,
} }
} }
} }
impl Endpoint for Redirection { impl Endpoint for Redirection {
fn to_async_handler(&self, _path: EndpointPath, _control: Control) -> Box<Handler> { fn to_async_handler(&self, _path: EndpointPath, _control: Control) -> Box<Handler> {
if let Some(ref signer_address) = self.signer_address { if let Some(ref frame) = self.embeddable_on {
trace!(target: "dapps", "Redirecting to signer interface."); trace!(target: "dapps", "Redirecting to signer interface.");
handlers::Redirection::boxed(&format!("http://{}", address(signer_address))) handlers::Redirection::boxed(&format!("http://{}:{}", &frame.host, frame.port))
} else { } else {
trace!(target: "dapps", "Signer disabled, returning 404."); trace!(target: "dapps", "Signer disabled, returning 404.");
Box::new(handlers::ContentHandler::error( Box::new(handlers::ContentHandler::error(
@ -48,7 +48,7 @@ impl Endpoint for Redirection {
"404 Not Found", "404 Not Found",
"Your homepage is not available when Trusted Signer is disabled.", "Your homepage is not available when Trusted Signer is disabled.",
Some("You can still access dapps by writing a correct address, though. Re-enable Signer to get your homepage back."), Some("You can still access dapps by writing a correct address, though. Re-enable Signer to get your homepage back."),
self.signer_address.clone(), None,
)) ))
} }
} }

112
dapps/src/handlers/async.rs Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
//! 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<F, T, M> {
Initial(F, M, Remote, Control),
Waiting(mpsc::Receiver<Result<T, ()>>, M),
Done(ContentHandler),
Invalid,
}
pub struct AsyncHandler<F, T, M> {
state: State<F, T, M>,
}
impl<F, T, M> AsyncHandler<F, T, M> {
pub fn new(future: F, map: M, remote: Remote, control: Control) -> Self {
AsyncHandler {
state: State::Initial(future, map, remote, control),
}
}
}
impl<F, T, E, M> server::Handler<HttpStream> for AsyncHandler<F, Result<T, E>, M> where
F: Future<Item=T, Error=E> + Send + 'static,
M: FnOnce(Result<Result<T, E>, ()>) -> ContentHandler,
T: Send + 'static,
E: Send + 'static,
{
fn on_request(&mut self, _request: server::Request<HttpStream>) -> 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<HttpStream>) -> 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<HttpStream>) -> Next {
if let State::Done(ref mut handler) = self.state {
handler.on_response_writable(encoder)
} else {
Next::end()
}
}
}

View File

@ -24,6 +24,7 @@ use hyper::status::StatusCode;
use util::version; use util::version;
use handlers::add_security_headers; use handlers::add_security_headers;
use Embeddable;
#[derive(Clone)] #[derive(Clone)]
pub struct ContentHandler { pub struct ContentHandler {
@ -31,7 +32,7 @@ pub struct ContentHandler {
content: String, content: String,
mimetype: Mime, mimetype: Mime,
write_pos: usize, write_pos: usize,
safe_to_embed_on: Option<(String, u16)>, safe_to_embed_on: Embeddable,
} }
impl ContentHandler { impl ContentHandler {
@ -39,15 +40,17 @@ impl ContentHandler {
Self::new(StatusCode::Ok, content, mimetype) Self::new(StatusCode::Ok, content, mimetype)
} }
pub fn not_found(content: String, mimetype: Mime) -> Self { pub fn html(code: StatusCode, content: String, embeddable_on: Embeddable) -> Self {
Self::new(StatusCode::NotFound, content, mimetype)
}
pub fn html(code: StatusCode, content: String, embeddable_on: Option<(String, u16)>) -> Self {
Self::new_embeddable(code, content, mime!(Text/Html), embeddable_on) Self::new_embeddable(code, content, mime!(Text/Html), embeddable_on)
} }
pub fn error(code: StatusCode, title: &str, message: &str, details: Option<&str>, embeddable_on: Option<(String, u16)>) -> Self { pub fn error(
code: StatusCode,
title: &str,
message: &str,
details: Option<&str>,
embeddable_on: Embeddable,
) -> Self {
Self::html(code, format!( Self::html(code, format!(
include_str!("../error_tpl.html"), include_str!("../error_tpl.html"),
title=title, title=title,
@ -61,13 +64,18 @@ impl ContentHandler {
Self::new_embeddable(code, content, mimetype, None) Self::new_embeddable(code, content, mimetype, None)
} }
pub fn new_embeddable(code: StatusCode, content: String, mimetype: Mime, embeddable_on: Option<(String, u16)>) -> Self { pub fn new_embeddable(
code: StatusCode,
content: String,
mimetype: Mime,
safe_to_embed_on: Embeddable,
) -> Self {
ContentHandler { ContentHandler {
code: code, code,
content: content, content,
mimetype: mimetype, mimetype,
write_pos: 0, write_pos: 0,
safe_to_embed_on: embeddable_on, safe_to_embed_on,
} }
} }
} }
@ -84,7 +92,7 @@ impl server::Handler<HttpStream> for ContentHandler {
fn on_response(&mut self, res: &mut server::Response) -> Next { fn on_response(&mut self, res: &mut server::Response) -> Next {
res.set_status(self.code); res.set_status(self.code);
res.headers_mut().set(header::ContentType(self.mimetype.clone())); res.headers_mut().set(header::ContentType(self.mimetype.clone()));
add_security_headers(&mut res.headers_mut(), self.safe_to_embed_on.clone()); add_security_headers(&mut res.headers_mut(), self.safe_to_embed_on.take());
Next::write() Next::write()
} }

View File

@ -33,6 +33,7 @@ use hyper::status::StatusCode;
use endpoint::EndpointPath; use endpoint::EndpointPath;
use handlers::{ContentHandler, StreamingHandler}; use handlers::{ContentHandler, StreamingHandler};
use page::{LocalPageEndpoint, PageHandlerWaiting}; use page::{LocalPageEndpoint, PageHandlerWaiting};
use {Embeddable};
const FETCH_TIMEOUT: u64 = 300; const FETCH_TIMEOUT: u64 = 300;
@ -179,7 +180,7 @@ impl server::Handler<HttpStream> for WaitingHandler {
#[derive(Clone)] #[derive(Clone)]
struct Errors { struct Errors {
embeddable_on: Option<(String, u16)>, embeddable_on: Embeddable,
} }
impl Errors { impl Errors {
@ -241,20 +242,20 @@ impl<H: ContentValidator, F: Fetch> ContentFetcherHandler<H, F> {
path: EndpointPath, path: EndpointPath,
control: Control, control: Control,
installer: H, installer: H,
embeddable_on: Option<(String, u16)>, embeddable_on: Embeddable,
remote: Remote, remote: Remote,
fetch: F, fetch: F,
) -> Self { ) -> Self {
ContentFetcherHandler { ContentFetcherHandler {
fetch_control: FetchControl::default(), fetch_control: FetchControl::default(),
control: control, control,
remote: remote, remote,
fetch: fetch, fetch,
status: FetchState::NotStarted(url), status: FetchState::NotStarted(url),
installer: Some(installer), installer: Some(installer),
path: path, path,
errors: Errors { errors: Errors {
embeddable_on: embeddable_on, embeddable_on,
}, },
} }
} }

View File

@ -16,34 +16,34 @@
//! Hyper handlers implementations. //! Hyper handlers implementations.
mod async;
mod content; mod content;
mod echo; mod echo;
mod fetch; mod fetch;
mod redirect; mod redirect;
mod streaming; mod streaming;
pub use self::async::AsyncHandler;
pub use self::content::ContentHandler; pub use self::content::ContentHandler;
pub use self::echo::EchoHandler; pub use self::echo::EchoHandler;
pub use self::fetch::{ContentFetcherHandler, ContentValidator, FetchControl, ValidatorResponse}; pub use self::fetch::{ContentFetcherHandler, ContentValidator, FetchControl, ValidatorResponse};
pub use self::redirect::Redirection; pub use self::redirect::Redirection;
pub use self::streaming::StreamingHandler; pub use self::streaming::StreamingHandler;
use std::iter;
use util::Itertools;
use url::Url; use url::Url;
use hyper::{server, header, net, uri}; use hyper::{server, header, net, uri};
use address; use {apps, address, Embeddable};
/// Adds security-related headers to the Response. /// Adds security-related headers to the Response.
pub fn add_security_headers(headers: &mut header::Headers, embeddable_on: Option<(String, u16)>) { pub fn add_security_headers(headers: &mut header::Headers, embeddable_on: Embeddable) {
headers.set_raw("X-XSS-Protection", vec![b"1; mode=block".to_vec()]); headers.set_raw("X-XSS-Protection", vec![b"1; mode=block".to_vec()]);
headers.set_raw("X-Content-Type-Options", vec![b"nosniff".to_vec()]); headers.set_raw("X-Content-Type-Options", vec![b"nosniff".to_vec()]);
// Embedding header: // Embedding header:
if let Some(ref embeddable_on) = embeddable_on { if let None = embeddable_on {
headers.set_raw("X-Frame-Options", vec![
format!("ALLOW-FROM http://{}", address(embeddable_on)).into_bytes()
]);
} else {
// TODO [ToDr] Should we be more strict here (DENY?)?
headers.set_raw("X-Frame-Options", vec![b"SAMEORIGIN".to_vec()]); headers.set_raw("X-Frame-Options", vec![b"SAMEORIGIN".to_vec()]);
} }
@ -60,7 +60,7 @@ pub fn add_security_headers(headers: &mut header::Headers, embeddable_on: Option
b"child-src 'self' http: https:;".to_vec(), b"child-src 'self' http: https:;".to_vec(),
// We allow data: blob: and HTTP(s) images. // We allow data: blob: and HTTP(s) images.
// We could get rid of wildcarding HTTP and only allow RPC server URL. // We could get rid of wildcarding HTTP and only allow RPC server URL.
// (http require for local dapps icons) // (http required for local dapps icons)
b"img-src 'self' 'unsafe-inline' data: blob: http: https:;".to_vec(), b"img-src 'self' 'unsafe-inline' data: blob: http: https:;".to_vec(),
// Allow style from data: blob: and HTTPS. // Allow style from data: blob: and HTTPS.
b"style-src 'self' 'unsafe-inline' data: blob: https:;".to_vec(), b"style-src 'self' 'unsafe-inline' data: blob: https:;".to_vec(),
@ -78,10 +78,27 @@ pub fn add_security_headers(headers: &mut header::Headers, embeddable_on: Option
b"block-all-mixed-content;".to_vec(), b"block-all-mixed-content;".to_vec(),
// Specify if the site can be embedded. // Specify if the site can be embedded.
match embeddable_on { match embeddable_on {
Some((ref host, ref port)) if host == "127.0.0.1" => { Some(ref embed) => {
format!("frame-ancestors {} {};", address(&(host.to_owned(), *port)), address(&("localhost".to_owned(), *port))) let std = address(&embed.host, embed.port);
let proxy = format!("{}.{}", apps::HOME_PAGE, embed.dapps_domain);
let domain = format!("*.{}:{}", embed.dapps_domain, embed.port);
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);
ancestors.chain(iter::once(localhost)).join(" ")
} else {
ancestors.join(" ")
};
format!("frame-ancestors {};", ancestors)
}, },
Some(ref embed) => format!("frame-ancestors {};", address(embed)),
None => format!("frame-ancestors 'self';"), None => format!("frame-ancestors 'self';"),
}.into_bytes(), }.into_bytes(),
]); ]);

View File

@ -24,6 +24,7 @@ use hyper::mime::Mime;
use hyper::status::StatusCode; use hyper::status::StatusCode;
use handlers::add_security_headers; use handlers::add_security_headers;
use Embeddable;
const BUFFER_SIZE: usize = 1024; const BUFFER_SIZE: usize = 1024;
@ -33,11 +34,11 @@ pub struct StreamingHandler<R: io::Read> {
status: StatusCode, status: StatusCode,
content: io::BufReader<R>, content: io::BufReader<R>,
mimetype: Mime, mimetype: Mime,
safe_to_embed_on: Option<(String, u16)>, safe_to_embed_on: Embeddable,
} }
impl<R: io::Read> StreamingHandler<R> { impl<R: io::Read> StreamingHandler<R> {
pub fn new(content: R, status: StatusCode, mimetype: Mime, embeddable_on: Option<(String, u16)>) -> Self { pub fn new(content: R, status: StatusCode, mimetype: Mime, embeddable_on: Embeddable) -> Self {
StreamingHandler { StreamingHandler {
buffer: [0; BUFFER_SIZE], buffer: [0; BUFFER_SIZE],
buffer_leftover: 0, buffer_leftover: 0,
@ -68,7 +69,7 @@ impl<R: io::Read> server::Handler<HttpStream> for StreamingHandler<R> {
fn on_response(&mut self, res: &mut server::Response) -> Next { fn on_response(&mut self, res: &mut server::Response) -> Next {
res.set_status(self.status); res.set_status(self.status);
res.headers_mut().set(header::ContentType(self.mimetype.clone())); res.headers_mut().set(header::ContentType(self.mimetype.clone()));
add_security_headers(&mut res.headers_mut(), self.safe_to_embed_on.clone()); add_security_headers(&mut res.headers_mut(), self.safe_to_embed_on.take());
Next::write() Next::write()
} }

View File

@ -21,8 +21,10 @@
extern crate base32; extern crate base32;
extern crate futures; extern crate futures;
extern crate futures_cpupool;
extern crate linked_hash_map; extern crate linked_hash_map;
extern crate mime_guess; extern crate mime_guess;
extern crate ntp;
extern crate rand; extern crate rand;
extern crate rustc_hex; extern crate rustc_hex;
extern crate serde; extern crate serde;
@ -74,6 +76,7 @@ use std::collections::HashMap;
use jsonrpc_http_server::{self as http, hyper, Origin}; use jsonrpc_http_server::{self as http, hyper, Origin};
use fetch::Fetch; use fetch::Fetch;
use futures_cpupool::CpuPool;
use parity_reactor::Remote; use parity_reactor::Remote;
pub use hash_fetch::urlhint::ContractClient; pub use hash_fetch::urlhint::ContractClient;
@ -82,10 +85,9 @@ pub use hash_fetch::urlhint::ContractClient;
pub trait SyncStatus: Send + Sync { pub trait SyncStatus: Send + Sync {
/// Returns true if there is a major sync happening. /// Returns true if there is a major sync happening.
fn is_major_importing(&self) -> bool; fn is_major_importing(&self) -> bool;
}
impl<F> SyncStatus for F where F: Fn() -> bool + Send + Sync { /// Returns number of connected and ideal peers.
fn is_major_importing(&self) -> bool { self() } fn peers(&self) -> (usize, usize);
} }
/// Validates Web Proxy tokens /// Validates Web Proxy tokens
@ -127,21 +129,29 @@ impl Middleware {
} }
/// Creates new middleware for UI server. /// Creates new middleware for UI server.
pub fn ui<F: Fetch + Clone>( pub fn ui<F: Fetch>(
ntp_server: &str,
pool: CpuPool,
remote: Remote, remote: Remote,
dapps_domain: &str,
registrar: Arc<ContractClient>, registrar: Arc<ContractClient>,
sync_status: Arc<SyncStatus>, sync_status: Arc<SyncStatus>,
fetch: F, fetch: F,
dapps_domain: String,
) -> Self { ) -> Self {
let content_fetcher = Arc::new(apps::fetcher::ContentFetcher::new( let content_fetcher = Arc::new(apps::fetcher::ContentFetcher::new(
hash_fetch::urlhint::URLHintContract::new(registrar), hash_fetch::urlhint::URLHintContract::new(registrar),
sync_status, sync_status.clone(),
remote.clone(), remote.clone(),
fetch.clone(), fetch.clone(),
).embeddable_on(None).allow_dapps(false)); ).embeddable_on(None).allow_dapps(false));
let special = { 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.insert(router::SpecialEndpoint::Home, Some(apps::ui()));
special special
}; };
@ -150,7 +160,7 @@ impl Middleware {
None, None,
special, special,
None, None,
dapps_domain, dapps_domain.to_owned(),
); );
Middleware { Middleware {
@ -160,36 +170,49 @@ impl Middleware {
} }
/// Creates new Dapps server middleware. /// Creates new Dapps server middleware.
pub fn dapps<F: Fetch + Clone>( pub fn dapps<F: Fetch>(
ntp_server: &str,
pool: CpuPool,
remote: Remote, remote: Remote,
ui_address: Option<(String, u16)>, ui_address: Option<(String, u16)>,
extra_embed_on: Vec<(String, u16)>,
dapps_path: PathBuf, dapps_path: PathBuf,
extra_dapps: Vec<PathBuf>, extra_dapps: Vec<PathBuf>,
dapps_domain: String, dapps_domain: &str,
registrar: Arc<ContractClient>, registrar: Arc<ContractClient>,
sync_status: Arc<SyncStatus>, sync_status: Arc<SyncStatus>,
web_proxy_tokens: Arc<WebProxyTokens>, web_proxy_tokens: Arc<WebProxyTokens>,
fetch: F, fetch: F,
) -> Self { ) -> Self {
let embeddable = as_embeddable(ui_address, extra_embed_on, dapps_domain);
let content_fetcher = Arc::new(apps::fetcher::ContentFetcher::new( let content_fetcher = Arc::new(apps::fetcher::ContentFetcher::new(
hash_fetch::urlhint::URLHintContract::new(registrar), hash_fetch::urlhint::URLHintContract::new(registrar),
sync_status, sync_status.clone(),
remote.clone(), remote.clone(),
fetch.clone(), fetch.clone(),
).embeddable_on(ui_address.clone()).allow_dapps(true)); ).embeddable_on(embeddable.clone()).allow_dapps(true));
let endpoints = apps::all_endpoints( let endpoints = apps::all_endpoints(
dapps_path, dapps_path,
extra_dapps, extra_dapps,
dapps_domain.clone(), dapps_domain,
ui_address.clone(), embeddable.clone(),
web_proxy_tokens, web_proxy_tokens,
remote.clone(), remote.clone(),
fetch.clone(), fetch.clone(),
); );
let special = { let special = {
let mut special = special_endpoints(content_fetcher.clone()); let mut special = special_endpoints(
special.insert(router::SpecialEndpoint::Home, Some(apps::ui_redirection(ui_address.clone()))); ntp_server,
pool,
content_fetcher.clone(),
remote.clone(),
sync_status,
);
special.insert(
router::SpecialEndpoint::Home,
Some(apps::ui_redirection(embeddable.clone())),
);
special special
}; };
@ -197,8 +220,8 @@ impl Middleware {
content_fetcher, content_fetcher,
Some(endpoints.clone()), Some(endpoints.clone()),
special, special,
ui_address, embeddable,
dapps_domain, dapps_domain.to_owned(),
); );
Middleware { Middleware {
@ -214,16 +237,40 @@ impl http::RequestMiddleware for Middleware {
} }
} }
fn special_endpoints(content_fetcher: Arc<apps::fetcher::Fetcher>) -> HashMap<router::SpecialEndpoint, Option<Box<endpoint::Endpoint>>> { fn special_endpoints(
ntp_server: &str,
pool: CpuPool,
content_fetcher: Arc<apps::fetcher::Fetcher>,
remote: Remote,
sync_status: Arc<SyncStatus>,
) -> HashMap<router::SpecialEndpoint, Option<Box<endpoint::Endpoint>>> {
let mut special = HashMap::new(); let mut special = HashMap::new();
special.insert(router::SpecialEndpoint::Rpc, None); special.insert(router::SpecialEndpoint::Rpc, None);
special.insert(router::SpecialEndpoint::Utils, Some(apps::utils())); 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 special
} }
fn address(address: &(String, u16)) -> String { fn address(host: &str, port: u16) -> String {
format!("{}:{}", address.0, address.1) format!("{}:{}", host, port)
}
fn as_embeddable(
ui_address: Option<(String, u16)>,
extra_embed_on: Vec<(String, u16)>,
dapps_domain: &str,
) -> Option<ParentFrameSettings> {
ui_address.map(|(host, port)| ParentFrameSettings {
host,
port,
extra_embed_on,
dapps_domain: dapps_domain.to_owned(),
})
} }
/// Random filename /// Random filename
@ -232,3 +279,18 @@ fn random_filename() -> String {
let mut rng = ::rand::OsRng::new().unwrap(); let mut rng = ::rand::OsRng::new().unwrap();
rng.gen_ascii_chars().take(12).collect() rng.gen_ascii_chars().take(12).collect()
} }
type Embeddable = Option<ParentFrameSettings>;
/// Parent frame host and port allowed to embed the content.
#[derive(Debug, Clone)]
pub struct ParentFrameSettings {
/// Hostname
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,
}

View File

@ -18,6 +18,7 @@ use page::{handler, PageCache};
use std::sync::Arc; use std::sync::Arc;
use endpoint::{Endpoint, EndpointInfo, EndpointPath, Handler}; use endpoint::{Endpoint, EndpointInfo, EndpointPath, Handler};
use parity_dapps::{WebApp, File, Info}; use parity_dapps::{WebApp, File, Info};
use Embeddable;
pub struct PageEndpoint<T : WebApp + 'static> { pub struct PageEndpoint<T : WebApp + 'static> {
/// Content of the files /// Content of the files
@ -25,7 +26,7 @@ pub struct PageEndpoint<T : WebApp + 'static> {
/// Prefix to strip from the path (when `None` deducted from `app_id`) /// Prefix to strip from the path (when `None` deducted from `app_id`)
pub prefix: Option<String>, pub prefix: Option<String>,
/// Safe to be loaded in frame by other origin. (use wisely!) /// Safe to be loaded in frame by other origin. (use wisely!)
safe_to_embed_on: Option<(String, u16)>, safe_to_embed_on: Embeddable,
info: EndpointInfo, info: EndpointInfo,
fallback_to_index_html: bool, fallback_to_index_html: bool,
} }
@ -73,7 +74,7 @@ impl<T: WebApp + 'static> PageEndpoint<T> {
/// Creates new `PageEndpoint` which can be safely used in iframe /// Creates new `PageEndpoint` which can be safely used in iframe
/// even from different origin. It might be dangerous (clickjacking). /// even from different origin. It might be dangerous (clickjacking).
/// Use wisely! /// Use wisely!
pub fn new_safe_to_embed(app: T, address: Option<(String, u16)>) -> Self { pub fn new_safe_to_embed(app: T, address: Embeddable) -> Self {
let info = app.info(); let info = app.info();
PageEndpoint { PageEndpoint {
app: Arc::new(app), app: Arc::new(app),

View File

@ -24,6 +24,7 @@ use hyper::status::StatusCode;
use hyper::{Decoder, Encoder, Next}; use hyper::{Decoder, Encoder, Next};
use endpoint::EndpointPath; use endpoint::EndpointPath;
use handlers::{ContentHandler, add_security_headers}; use handlers::{ContentHandler, add_security_headers};
use {Embeddable};
/// Represents a file that can be sent to client. /// Represents a file that can be sent to client.
/// Implementation should keep track of bytes already sent internally. /// Implementation should keep track of bytes already sent internally.
@ -59,7 +60,7 @@ pub enum ServedFile<T: Dapp> {
} }
impl<T: Dapp> ServedFile<T> { impl<T: Dapp> ServedFile<T> {
pub fn new(embeddable_on: Option<(String, u16)>) -> Self { pub fn new(embeddable_on: Embeddable) -> Self {
ServedFile::Error(ContentHandler::error( ServedFile::Error(ContentHandler::error(
StatusCode::NotFound, StatusCode::NotFound,
"404 Not Found", "404 Not Found",
@ -102,7 +103,7 @@ pub struct PageHandler<T: Dapp> {
/// Requested path. /// Requested path.
pub path: EndpointPath, pub path: EndpointPath,
/// Flag indicating if the file can be safely embeded (put in iframe). /// Flag indicating if the file can be safely embeded (put in iframe).
pub safe_to_embed_on: Option<(String, u16)>, pub safe_to_embed_on: Embeddable,
/// Cache settings for this page. /// Cache settings for this page.
pub cache: PageCache, pub cache: PageCache,
} }
@ -174,7 +175,7 @@ impl<T: Dapp> server::Handler<HttpStream> for PageHandler<T> {
} }
// Security headers: // Security headers:
add_security_headers(&mut res.headers_mut(), self.safe_to_embed_on.clone()); add_security_headers(&mut res.headers_mut(), self.safe_to_embed_on.take());
Next::write() Next::write()
}, },
ServedFile::Error(ref mut handler) => { ServedFile::Error(ref mut handler) => {

View File

@ -21,6 +21,7 @@ use std::path::{Path, PathBuf};
use page::handler::{self, PageCache, PageHandlerWaiting}; use page::handler::{self, PageCache, PageHandlerWaiting};
use endpoint::{Endpoint, EndpointInfo, EndpointPath, Handler}; use endpoint::{Endpoint, EndpointInfo, EndpointPath, Handler};
use mime::Mime; use mime::Mime;
use Embeddable;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct LocalPageEndpoint { pub struct LocalPageEndpoint {
@ -28,11 +29,11 @@ pub struct LocalPageEndpoint {
mime: Option<Mime>, mime: Option<Mime>,
info: Option<EndpointInfo>, info: Option<EndpointInfo>,
cache: PageCache, cache: PageCache,
embeddable_on: Option<(String, u16)>, embeddable_on: Embeddable,
} }
impl LocalPageEndpoint { impl LocalPageEndpoint {
pub fn new(path: PathBuf, info: EndpointInfo, cache: PageCache, embeddable_on: Option<(String, u16)>) -> Self { pub fn new(path: PathBuf, info: EndpointInfo, cache: PageCache, embeddable_on: Embeddable) -> Self {
LocalPageEndpoint { LocalPageEndpoint {
path: path, path: path,
mime: None, mime: None,

View File

@ -19,27 +19,24 @@
use endpoint::{Endpoint, Handler, EndpointPath}; use endpoint::{Endpoint, Handler, EndpointPath};
use handlers::ContentHandler; use handlers::ContentHandler;
use apps::HOME_PAGE; use apps::HOME_PAGE;
use address; use {address, Embeddable};
pub struct ProxyPac { pub struct ProxyPac {
signer_address: Option<(String, u16)>, embeddable: Embeddable,
dapps_domain: String, dapps_domain: String,
} }
impl ProxyPac { impl ProxyPac {
pub fn boxed(signer_address: Option<(String, u16)>, dapps_domain: String) -> Box<Endpoint> { pub fn boxed(embeddable: Embeddable, dapps_domain: String) -> Box<Endpoint> {
Box::new(ProxyPac { Box::new(ProxyPac { embeddable, dapps_domain })
signer_address: signer_address,
dapps_domain: dapps_domain,
})
} }
} }
impl Endpoint for ProxyPac { impl Endpoint for ProxyPac {
fn to_handler(&self, path: EndpointPath) -> Box<Handler> { fn to_handler(&self, path: EndpointPath) -> Box<Handler> {
let signer = self.signer_address let ui = self.embeddable
.as_ref() .as_ref()
.map(address) .map(|ref parent| address(&parent.host, parent.port))
.unwrap_or_else(|| format!("{}:{}", path.host, path.port)); .unwrap_or_else(|| format!("{}:{}", path.host, path.port));
let content = format!( let content = format!(
@ -58,7 +55,7 @@ function FindProxyForURL(url, host) {{
return "DIRECT"; 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))) Box::new(ContentHandler::ok(content, mime!(Application/Javascript)))
} }

View File

@ -30,6 +30,7 @@ use apps;
use apps::fetcher::Fetcher; use apps::fetcher::Fetcher;
use endpoint::{Endpoint, Endpoints, EndpointPath, Handler}; use endpoint::{Endpoint, Endpoints, EndpointPath, Handler};
use handlers; use handlers;
use Embeddable;
/// Special endpoints are accessible on every domain (every dapp) /// Special endpoints are accessible on every domain (every dapp)
#[derive(Debug, PartialEq, Hash, Eq)] #[derive(Debug, PartialEq, Hash, Eq)]
@ -45,7 +46,7 @@ pub struct Router {
endpoints: Option<Endpoints>, endpoints: Option<Endpoints>,
fetch: Arc<Fetcher>, fetch: Arc<Fetcher>,
special: HashMap<SpecialEndpoint, Option<Box<Endpoint>>>, special: HashMap<SpecialEndpoint, Option<Box<Endpoint>>>,
embeddable_on: Option<(String, u16)>, embeddable_on: Embeddable,
dapps_domain: String, dapps_domain: String,
} }
@ -148,7 +149,7 @@ impl Router {
content_fetcher: Arc<Fetcher>, content_fetcher: Arc<Fetcher>,
endpoints: Option<Endpoints>, endpoints: Option<Endpoints>,
special: HashMap<SpecialEndpoint, Option<Box<Endpoint>>>, special: HashMap<SpecialEndpoint, Option<Box<Endpoint>>>,
embeddable_on: Option<(String, u16)>, embeddable_on: Embeddable,
dapps_domain: String, dapps_domain: String,
) -> Self { ) -> Self {
Router { Router {

View File

@ -26,6 +26,7 @@ use jsonrpc_http_server::{self as http, Host, DomainsValidation};
use devtools::http_client; use devtools::http_client;
use hash_fetch::urlhint::ContractClient; use hash_fetch::urlhint::ContractClient;
use fetch::{Fetch, Client as FetchClient}; use fetch::{Fetch, Client as FetchClient};
use futures_cpupool::CpuPool;
use parity_reactor::Remote; use parity_reactor::Remote;
use {Middleware, SyncStatus, WebProxyTokens}; use {Middleware, SyncStatus, WebProxyTokens};
@ -38,6 +39,12 @@ use self::fetch::FakeFetch;
const SIGNER_PORT: u16 = 18180; 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() { fn init_logger() {
// Initialize logger // Initialize logger
if let Ok(log) = env::var("RUST_LOG") { if let Ok(log) = env::var("RUST_LOG") {
@ -82,7 +89,7 @@ pub fn serve_with_registrar() -> (Server, Arc<FakeRegistrar>) {
pub fn serve_with_registrar_and_sync() -> (Server, Arc<FakeRegistrar>) { pub fn serve_with_registrar_and_sync() -> (Server, Arc<FakeRegistrar>) {
init_server(|builder| { init_server(|builder| {
builder.sync_status(Arc::new(|| true)) builder.sync_status(Arc::new(FakeSync(true)))
}, Default::default(), Remote::new_sync()) }, Default::default(), Remote::new_sync())
} }
@ -148,7 +155,7 @@ impl ServerBuilder {
ServerBuilder { ServerBuilder {
dapps_path: dapps_path.as_ref().to_owned(), dapps_path: dapps_path.as_ref().to_owned(),
registrar: registrar, registrar: registrar,
sync_status: Arc::new(|| false), sync_status: Arc::new(FakeSync(false)),
web_proxy_tokens: Arc::new(|_| None), web_proxy_tokens: Arc::new(|_| None),
signer_address: None, signer_address: None,
allowed_hosts: DomainsValidation::Disabled, allowed_hosts: DomainsValidation::Disabled,
@ -248,8 +255,11 @@ impl Server {
fetch: F, fetch: F,
) -> Result<Server, http::Error> { ) -> Result<Server, http::Error> {
let middleware = Middleware::dapps( let middleware = Middleware::dapps(
"pool.ntp.org:123",
CpuPool::new(4),
remote, remote,
signer_address, signer_address,
vec![],
dapps_path, dapps_path,
extra_dapps, extra_dapps,
DAPPS_DOMAIN.into(), DAPPS_DOMAIN.into(),
@ -290,4 +300,3 @@ impl Drop for Server {
self.server.take().unwrap().close() self.server.take().unwrap().close()
} }
} }

View File

@ -31,9 +31,7 @@ use handlers::{
StreamingHandler, extract_url, StreamingHandler, extract_url,
}; };
use url::Url; use url::Url;
use WebProxyTokens; use {Embeddable, WebProxyTokens};
pub type Embeddable = Option<(String, u16)>;
pub struct Web<F> { pub struct Web<F> {
embeddable_on: Embeddable, embeddable_on: Embeddable,
@ -43,12 +41,17 @@ pub struct Web<F> {
} }
impl<F: Fetch> Web<F> { impl<F: Fetch> Web<F> {
pub fn boxed(embeddable_on: Embeddable, web_proxy_tokens: Arc<WebProxyTokens>, remote: Remote, fetch: F) -> Box<Endpoint> { pub fn boxed(
embeddable_on: Embeddable,
web_proxy_tokens: Arc<WebProxyTokens>,
remote: Remote,
fetch: F,
) -> Box<Endpoint> {
Box::new(Web { Box::new(Web {
embeddable_on: embeddable_on, embeddable_on,
web_proxy_tokens: web_proxy_tokens, web_proxy_tokens,
remote: remote, remote,
fetch: fetch, fetch,
}) })
} }
} }

View File

@ -102,12 +102,7 @@ pub fn request(address: &SocketAddr, request: &str) -> Response {
/// Check if all required security headers are present /// Check if all required security headers are present
pub fn assert_security_headers_present(headers: &[String], port: Option<u16>) { pub fn assert_security_headers_present(headers: &[String], port: Option<u16>) {
if let Some(port) = port { if let None = port {
assert!(
headers.iter().find(|header| header.as_str() == &format!("X-Frame-Options: ALLOW-FROM http://127.0.0.1:{}", port)).is_some(),
"X-Frame-Options: ALLOW-FROM missing: {:?}", headers
);
} else {
assert!( assert!(
headers.iter().find(|header| header.as_str() == "X-Frame-Options: SAMEORIGIN").is_some(), headers.iter().find(|header| header.as_str() == "X-Frame-Options: SAMEORIGIN").is_some(),
"X-Frame-Options: SAMEORIGIN missing: {:?}", headers "X-Frame-Options: SAMEORIGIN missing: {:?}", headers

View File

@ -52,7 +52,7 @@ semver = "0.6"
stats = { path = "../util/stats" } stats = { path = "../util/stats" }
time = "0.1" time = "0.1"
transient-hashmap = "0.4" transient-hashmap = "0.4"
parity-wasm = { git = "https://github.com/nikvolf/parity-wasm" } parity-wasm = "0.12"
wasm-utils = { git = "https://github.com/paritytech/wasm-utils" } wasm-utils = { git = "https://github.com/paritytech/wasm-utils" }
[dev-dependencies] [dev-dependencies]

View File

@ -64,7 +64,9 @@ pub enum SignError {
/// Low-level hardware device error. /// Low-level hardware device error.
Hardware(HardwareError), Hardware(HardwareError),
/// Low-level error from store /// Low-level error from store
SStore(SSError) SStore(SSError),
/// Inappropriate chain
InappropriateChain,
} }
impl fmt::Display for SignError { impl fmt::Display for SignError {
@ -74,6 +76,7 @@ impl fmt::Display for SignError {
SignError::NotFound => write!(f, "Account does not exist"), SignError::NotFound => write!(f, "Account does not exist"),
SignError::Hardware(ref e) => write!(f, "{}", e), SignError::Hardware(ref e) => write!(f, "{}", e),
SignError::SStore(ref e) => write!(f, "{}", e), SignError::SStore(ref e) => write!(f, "{}", e),
SignError::InappropriateChain => write!(f, "Inappropriate chain"),
} }
} }
} }

View File

@ -804,9 +804,15 @@ impl MinerService for Miner {
// | Make sure to release the locks before calling that method. | // | Make sure to release the locks before calling that method. |
// -------------------------------------------------------------------------- // --------------------------------------------------------------------------
self.engine.set_signer(ap.clone(), address, password); self.engine.set_signer(ap.clone(), address, password);
}
}
Ok(()) Ok(())
} else {
warn!(target: "miner", "No account provider");
Err(AccountError::NotFound)
}
} else {
warn!(target: "miner", "Cannot set engine signer on a PoW chain.");
Err(AccountError::InappropriateChain)
}
} }
fn set_extra_data(&self, extra_data: Bytes) { fn set_extra_data(&self, extra_data: Bytes) {
@ -1085,6 +1091,10 @@ impl MinerService for Miner {
self.transaction_queue.read().last_nonce(address) self.transaction_queue.read().last_nonce(address)
} }
fn can_produce_work_package(&self) -> bool {
self.engine.seals_internally().is_none()
}
/// Update sealing if required. /// Update sealing if required.
/// Prepare the block and work if the Engine does not seal internally. /// Prepare the block and work if the Engine does not seal internally.
fn update_sealing(&self, chain: &MiningBlockChainClient) { fn update_sealing(&self, chain: &MiningBlockChainClient) {
@ -1113,7 +1123,7 @@ impl MinerService for Miner {
} }
} }
fn is_sealing(&self) -> bool { fn is_currently_sealing(&self) -> bool {
self.sealing_work.lock().queue.is_in_use() self.sealing_work.lock().queue.is_in_use()
} }
@ -1272,7 +1282,7 @@ mod tests {
use header::BlockNumber; use header::BlockNumber;
use types::transaction::{SignedTransaction, Transaction, PendingTransaction, Action}; use types::transaction::{SignedTransaction, Transaction, PendingTransaction, Action};
use spec::Spec; use spec::Spec;
use tests::helpers::{generate_dummy_client}; use tests::helpers::{generate_dummy_client, generate_dummy_client_with_spec_and_accounts};
#[test] #[test]
fn should_prepare_block_to_seal() { fn should_prepare_block_to_seal() {
@ -1440,4 +1450,22 @@ mod tests {
assert!(miner.pending_block().is_none()); assert!(miner.pending_block().is_none());
assert_eq!(client.chain_info().best_block_number, 4 as BlockNumber); assert_eq!(client.chain_info().best_block_number, 4 as BlockNumber);
} }
#[test]
fn should_fail_setting_engine_signer_on_pow() {
let spec = Spec::new_pow_test_spec;
let tap = Arc::new(AccountProvider::transient_provider());
let addr = tap.insert_account("1".sha3().into(), "").unwrap();
let client = generate_dummy_client_with_spec_and_accounts(spec, Some(tap.clone()));
assert!(match client.miner().set_engine_signer(addr, "".into()) { Err(AccountError::InappropriateChain) => true, _ => false })
}
#[test]
fn should_fail_setting_engine_signer_without_account_provider() {
let spec = Spec::new_instant;
let tap = Arc::new(AccountProvider::transient_provider());
let addr = tap.insert_account("1".sha3().into(), "").unwrap();
let client = generate_dummy_client_with_spec_and_accounts(spec, None);
assert!(match client.miner().set_engine_signer(addr, "".into()) { Err(AccountError::NotFound) => true, _ => false });
}
} }

View File

@ -136,6 +136,9 @@ pub trait MinerService : Send + Sync {
/// Called when blocks are imported to chain, updates transactions queue. /// Called when blocks are imported to chain, updates transactions queue.
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]);
/// PoW chain - can produce work package
fn can_produce_work_package(&self) -> bool;
/// New chain head event. Restart mining operation. /// New chain head event. Restart mining operation.
fn update_sealing(&self, chain: &MiningBlockChainClient); fn update_sealing(&self, chain: &MiningBlockChainClient);
@ -176,7 +179,7 @@ pub trait MinerService : Send + Sync {
fn last_nonce(&self, address: &Address) -> Option<U256>; fn last_nonce(&self, address: &Address) -> Option<U256>;
/// Is it currently sealing? /// Is it currently sealing?
fn is_sealing(&self) -> bool; fn is_currently_sealing(&self) -> bool;
/// Suggested gas price. /// Suggested gas price.
fn sensible_gas_price(&self) -> U256; fn sensible_gas_price(&self) -> U256;

View File

@ -451,6 +451,9 @@ impl Spec {
/// Create a new Spec with BasicAuthority which uses multiple validator sets changing with height. /// Create a new Spec with BasicAuthority which uses multiple validator sets changing with height.
/// Account with secrets "0".sha3() is the validator for block 1 and with "1".sha3() onwards. /// Account with secrets "0".sha3() is the validator for block 1 and with "1".sha3() onwards.
pub fn new_validator_multi() -> Self { load_bundled!("validator_multi") } pub fn new_validator_multi() -> Self { load_bundled!("validator_multi") }
/// Create a new spec for a PoW chain
pub fn new_pow_test_spec() -> Self { load_bundled!("ethereum/olympic") }
} }
#[cfg(test)] #[cfg(test)]

View File

@ -134,10 +134,10 @@ impl Args {
Ok(match self.flag_spec { Ok(match self.flag_spec {
Some(ref filename) => { Some(ref filename) => {
let file = fs::File::open(filename).map_err(|e| format!("{}", e))?; let file = fs::File::open(filename).map_err(|e| format!("{}", e))?;
spec::Spec::load(file)? spec::Spec::load(::std::env::temp_dir(), file)?
}, },
None => { None => {
ethcore::ethereum::new_foundation() ethcore::ethereum::new_foundation(&::std::env::temp_dir())
}, },
}) })
} }

View File

@ -1,6 +1,6 @@
{ {
"name": "parity.js", "name": "parity.js",
"version": "1.7.98", "version": "1.7.99",
"main": "release/index.js", "main": "release/index.js",
"jsnext:main": "src/index.js", "jsnext:main": "src/index.js",
"author": "Parity Team <admin@parity.io>", "author": "Parity Team <admin@parity.io>",

View File

@ -21,6 +21,7 @@ export const ERROR_CODES = {
NO_WORK: -32001, NO_WORK: -32001,
NO_AUTHOR: -32002, NO_AUTHOR: -32002,
NO_NEW_WORK: -32003, NO_NEW_WORK: -32003,
NO_WORK_REQUIRED: -32004,
NOT_ENOUGH_DATA: -32006, NOT_ENOUGH_DATA: -32006,
UNKNOWN_ERROR: -32009, UNKNOWN_ERROR: -32009,
TRANSACTION_ERROR: -32010, TRANSACTION_ERROR: -32010,
@ -38,7 +39,7 @@ export const ERROR_CODES = {
COMPILATION_ERROR: -32050, COMPILATION_ERROR: -32050,
ENCRYPTION_ERROR: -32055, ENCRYPTION_ERROR: -32055,
FETCH_ERROR: -32060, FETCH_ERROR: -32060,
INVALID_PARAMS: -32602 INVALID_PARAMS: -32602,
}; };
export default class TransportError extends ExtendableError { export default class TransportError extends ExtendableError {

View File

@ -94,6 +94,7 @@ class FrameSecureApi extends SecureApi {
const transport = window.secureTransport || new FakeTransport(); const transport = window.secureTransport || new FakeTransport();
const uiUrl = transport.uiUrl || 'http://127.0.0.1:8180'; const uiUrl = transport.uiUrl || 'http://127.0.0.1:8180';
transport.uiUrlWithProtocol = uiUrl;
transport.uiUrl = uiUrl.replace('http://', '').replace('https://', ''); transport.uiUrl = uiUrl.replace('http://', '').replace('https://', '');
const api = new FrameSecureApi(transport); const api = new FrameSecureApi(transport);

View File

@ -25,6 +25,10 @@ import { statusBlockNumber, statusCollection } from './statusActions';
const log = getLogger(LOG_KEYS.Signer); const log = getLogger(LOG_KEYS.Signer);
let instance = null; let instance = null;
const STATUS_OK = 'ok';
const STATUS_WARN = 'needsAttention';
const STATUS_BAD = 'bad';
export default class Status { export default class Status {
_apiStatus = {}; _apiStatus = {};
_status = {}; _status = {};
@ -195,13 +199,19 @@ export default class Status {
const statusPromises = [ const statusPromises = [
this._api.eth.syncing(), this._api.eth.syncing(),
this._api.parity.netPeers() this._api.parity.netPeers(),
this._fetchHealth()
]; ];
return Promise return Promise
.all(statusPromises) .all(statusPromises)
.then(([ syncing, netPeers ]) => { .then(([ syncing, netPeers, health ]) => {
const status = { netPeers, syncing }; const status = { netPeers, syncing, health };
health.overall = this._overallStatus(health);
health.peers = health.peers || {};
health.sync = health.sync || {};
health.time = health.time || {};
if (!isEqual(status, this._status)) { if (!isEqual(status, this._status)) {
this._store.dispatch(statusCollection(status)); this._store.dispatch(statusCollection(status));
@ -216,6 +226,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 * The data fetched here should not change
* unless Parity is restarted. They are thus * unless Parity is restarted. They are thus

View File

@ -18,11 +18,28 @@ import BigNumber from 'bignumber.js';
import { handleActions } from 'redux-actions'; import { handleActions } from 'redux-actions';
const DEFAULT_NETCHAIN = '(unknown)'; const DEFAULT_NETCHAIN = '(unknown)';
const DEFAULT_STATUS = 'needsAttention';
const initialState = { const initialState = {
blockNumber: new BigNumber(0), blockNumber: new BigNumber(0),
blockTimestamp: new Date(), blockTimestamp: new Date(),
clientVersion: '', clientVersion: '',
gasLimit: new BigNumber(0), 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, netChain: DEFAULT_NETCHAIN,
netPeers: { netPeers: {
active: new BigNumber(0), active: new BigNumber(0),

View File

@ -31,7 +31,8 @@ export default class Actionbar extends Component {
title: nodeOrStringProptype(), title: nodeOrStringProptype(),
buttons: PropTypes.array, buttons: PropTypes.array,
children: PropTypes.node, children: PropTypes.node,
className: PropTypes.string className: PropTypes.string,
health: PropTypes.node
}; };
static defaultProps = { static defaultProps = {

View File

@ -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 <http://www.gnu.org/licenses/>.
export default from './statusIndicator';

View File

@ -0,0 +1,89 @@
/* 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 <http://www.gnu.org/licenses/>.
*/
.status {
display: inline-block;
}
.radial,.signal {
display: inline-block;
margin: .2em;
width: 1em;
height: 1em;
white-space: nowrap;
}
.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;
}
}

View File

@ -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 <http://www.gnu.org/licenses/>.
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) ? (
<ReactTooltip id={ `status-${id}` }>
{ title.map(x => (<div key={ x }>{ x }</div>)) }
</ReactTooltip>
) : null;
return (
<span className={ styles.status }>
<span className={ `${styles[type]} ${styles[status]}` }
data-tip={ title.length }
data-for={ `status-${id}` }
data-place={ tooltipPlacement }
data-effect='solid'
>
{ type === 'signal' && statuses.map(this.renderBar) }
</span>
{tooltip}
</span>
);
}
renderBar = (signal) => {
const idx = statuses.indexOf(this.props.status);
const isActive = statuses.indexOf(signal) <= idx;
const activeClass = isActive ? styles.active : '';
return (
<span key={ signal } className={ `${styles.bar} ${styles[signal]} ${activeClass}` } />
);
}
}

View File

@ -52,6 +52,7 @@ export SectionList from './SectionList';
export SelectionList from './SelectionList'; export SelectionList from './SelectionList';
export ShortenedHash from './ShortenedHash'; export ShortenedHash from './ShortenedHash';
export SignerIcon from './SignerIcon'; export SignerIcon from './SignerIcon';
export StatusIndicator from './StatusIndicator';
export Tags from './Tags'; export Tags from './Tags';
export Title from './Title'; export Title from './Title';
export Tooltips, { Tooltip } from './Tooltips'; export Tooltips, { Tooltip } from './Tooltips';

View File

@ -43,6 +43,7 @@ class Accounts extends Component {
accountsInfo: PropTypes.object.isRequired, accountsInfo: PropTypes.object.isRequired,
availability: PropTypes.string.isRequired, availability: PropTypes.string.isRequired,
hasAccounts: PropTypes.bool.isRequired, hasAccounts: PropTypes.bool.isRequired,
health: PropTypes.object.isRequired,
setVisibleAccounts: PropTypes.func.isRequired setVisibleAccounts: PropTypes.func.isRequired
} }
@ -496,12 +497,14 @@ class Accounts extends Component {
function mapStateToProps (state) { function mapStateToProps (state) {
const { accounts, accountsInfo, hasAccounts } = state.personal; const { accounts, accountsInfo, hasAccounts } = state.personal;
const { availability = 'unknown' } = state.nodeStatus.nodeKind || {}; const { availability = 'unknown' } = state.nodeStatus.nodeKind || {};
const { health } = state.nodeStatus;
return { return {
accounts, accounts,
accountsInfo, accountsInfo,
availability, availability,
hasAccounts hasAccounts,
health
}; };
} }

View File

@ -18,7 +18,7 @@ import React, { Component, PropTypes } from 'react';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { BlockStatus } from '~/ui'; import { BlockStatus, StatusIndicator } from '~/ui';
import styles from './status.css'; import styles from './status.css';
@ -28,11 +28,12 @@ class Status extends Component {
isTest: PropTypes.bool, isTest: PropTypes.bool,
netChain: PropTypes.string, netChain: PropTypes.string,
netPeers: PropTypes.object, netPeers: PropTypes.object,
health: PropTypes.object,
upgradeStore: PropTypes.object.isRequired upgradeStore: PropTypes.object.isRequired
} }
render () { render () {
const { clientVersion, isTest, netChain, netPeers } = this.props; const { clientVersion, isTest, netChain, netPeers, health } = this.props;
return ( return (
<div className={ styles.status }> <div className={ styles.status }>
@ -44,13 +45,20 @@ class Status extends Component {
{ this.renderUpgradeButton() } { this.renderUpgradeButton() }
</div> </div>
<div className={ styles.netinfo }> <div className={ styles.netinfo }>
<div>
<StatusIndicator
type='signal'
id='application.status.health'
status={ health.overall.status }
title={ health.overall.message }
/>
</div>
<span title={ `${netPeers.connected.toFormat()}/${netPeers.max.toFormat()} peers` }>
<BlockStatus /> <BlockStatus />
</span>
<div className={ `${styles.network} ${styles[isTest ? 'test' : 'live']}` }> <div className={ `${styles.network} ${styles[isTest ? 'test' : 'live']}` }>
{ netChain } { netChain }
</div> </div>
<div className={ styles.peers }>
{ netPeers.connected.toFormat() }/{ netPeers.max.toFormat() } peers
</div>
</div> </div>
</div> </div>
); );
@ -102,14 +110,7 @@ class Status extends Component {
); );
} }
return ( return;
<div>
<FormattedMessage
id='application.status.consensus.unknown'
defaultMessage='Upgrade status is unknown.'
/>
</div>
);
} }
renderUpgradeButton () { renderUpgradeButton () {
@ -136,10 +137,11 @@ class Status extends Component {
} }
function mapStateToProps (state) { function mapStateToProps (state) {
const { clientVersion, netPeers, netChain, isTest } = state.nodeStatus; const { clientVersion, health, netPeers, netChain, isTest } = state.nodeStatus;
return { return {
clientVersion, clientVersion,
health,
netPeers, netPeers,
netChain, netChain,
isTest isTest

View File

@ -81,6 +81,16 @@
white-space: nowrap; white-space: nowrap;
} }
.indicatorTab {
font-size: 1.5rem;
flex: 0;
}
.indicator {
padding: 20px 12px 0;
opacity: 0.8;
}
.first { .first {
margin-left: -24px; margin-left: -24px;
} }

View File

@ -21,7 +21,7 @@ import { Link } from 'react-router';
import { Toolbar, ToolbarGroup } from 'material-ui/Toolbar'; import { Toolbar, ToolbarGroup } from 'material-ui/Toolbar';
import { isEqual } from 'lodash'; import { isEqual } from 'lodash';
import { Tooltip } from '~/ui'; import { Tooltip, StatusIndicator } from '~/ui';
import Tab from './Tab'; import Tab from './Tab';
import styles from './tabBar.css'; import styles from './tabBar.css';
@ -33,6 +33,7 @@ class TabBar extends Component {
static propTypes = { static propTypes = {
pending: PropTypes.array, pending: PropTypes.array,
health: PropTypes.object.isRequired,
views: PropTypes.array.isRequired views: PropTypes.array.isRequired
}; };
@ -41,12 +42,29 @@ class TabBar extends Component {
}; };
render () { render () {
const { health } = this.props;
return ( return (
<Toolbar className={ styles.toolbar }> <Toolbar className={ styles.toolbar }>
<ToolbarGroup className={ styles.first }> <ToolbarGroup className={ styles.first }>
<div /> <div />
</ToolbarGroup> </ToolbarGroup>
<div className={ styles.tabs }> <div className={ styles.tabs }>
<Link
activeClassName={ styles.tabactive }
className={ `${styles.tabLink} ${styles.indicatorTab}` }
key='status'
to='/status'
>
<div className={ styles.indicator }>
<StatusIndicator
type='signal'
id='topbar.health'
status={ health.overall.status }
title={ health.overall.message }
/>
</div>
</Link>
{ this.renderTabItems() } { this.renderTabItems() }
<Tooltip <Tooltip
className={ styles.tabbarTooltip } className={ styles.tabbarTooltip }
@ -101,6 +119,7 @@ function mapStateToProps (initState) {
return (state) => { return (state) => {
const { availability = 'unknown' } = state.nodeStatus.nodeKind || {}; const { availability = 'unknown' } = state.nodeStatus.nodeKind || {};
const { views } = state.settings; const { views } = state.settings;
const { health } = state.nodeStatus;
const viewIds = Object const viewIds = Object
.keys(views) .keys(views)
@ -114,7 +133,7 @@ function mapStateToProps (initState) {
}); });
if (isEqual(viewIds, filteredViewIds)) { if (isEqual(viewIds, filteredViewIds)) {
return { views: filteredViews }; return { views: filteredViews, health };
} }
filteredViewIds = viewIds; filteredViewIds = viewIds;
@ -123,7 +142,7 @@ function mapStateToProps (initState) {
id id
})); }));
return { views: filteredViews }; return { views: filteredViews, health };
}; };
} }

View File

@ -37,6 +37,12 @@ function createStore () {
nodeStatus: { nodeStatus: {
nodeKind: { nodeKind: {
'availability': 'personal' 'availability': 'personal'
},
health: {
overall: {
status: 'ok',
message: []
}
} }
} }
}; };

View File

@ -24,7 +24,7 @@ import { connect } from 'react-redux';
import store from 'store'; import store from 'store';
import imagesEthcoreBlock from '~/../assets/images/parity-logo-white-no-text.svg'; 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 { CancelIcon, FingerprintIcon } from '~/ui/Icons';
import DappsStore from '~/views/Dapps/dappsStore'; import DappsStore from '~/views/Dapps/dappsStore';
import { Embedded as Signer } from '~/views/Signer'; import { Embedded as Signer } from '~/views/Signer';
@ -50,7 +50,8 @@ class ParityBar extends Component {
static propTypes = { static propTypes = {
dapp: PropTypes.bool, dapp: PropTypes.bool,
externalLink: PropTypes.string, externalLink: PropTypes.string,
pending: PropTypes.array pending: PropTypes.array,
health: PropTypes.object
}; };
state = { state = {
@ -210,7 +211,7 @@ class ParityBar extends Component {
} }
renderBar () { renderBar () {
const { dapp } = this.props; const { dapp, health } = this.props;
if (!dapp) { if (!dapp) {
return null; return null;
@ -218,6 +219,13 @@ class ParityBar extends Component {
return ( return (
<div className={ styles.cornercolor }> <div className={ styles.cornercolor }>
<StatusIndicator
type='signal'
id='paritybar.health'
status={ health.overall.status }
title={ health.overall.message }
tooltipPlacement='right'
/>
<Button <Button
className={ styles.iconButton } className={ styles.iconButton }
icon={ icon={
@ -699,9 +707,11 @@ class ParityBar extends Component {
function mapStateToProps (state) { function mapStateToProps (state) {
const { pending } = state.signer; const { pending } = state.signer;
const { health } = state.nodeStatus;
return { return {
pending pending,
health
}; };
} }

View File

@ -37,6 +37,14 @@ function createRedux (state = {}) {
}, },
signer: { signer: {
pending: [] pending: []
},
nodeStatus: {
health: {
overall: {
status: 'ok',
message: []
}
}
} }
}, state) }, state)
}; };

View File

@ -17,7 +17,7 @@
import React from 'react'; import React from 'react';
import imagesEthcoreBlock from '~/../assets/images/parity-logo-white-no-text.svg'; import imagesEthcoreBlock from '~/../assets/images/parity-logo-white-no-text.svg';
import { AccountsIcon, AddressesIcon, AppsIcon, ContactsIcon, FingerprintIcon, SettingsIcon, StatusIcon } from '~/ui/Icons'; import { AccountsIcon, AddressesIcon, AppsIcon, ContactsIcon, FingerprintIcon, SettingsIcon } from '~/ui/Icons';
import styles from './views.css'; import styles from './views.css';
@ -65,14 +65,6 @@ const defaultViews = {
value: 'contract' value: 'contract'
}, },
status: {
active: false,
onlyPersonal: true,
icon: <StatusIcon />,
route: '/status',
value: 'status'
},
signer: { signer: {
active: true, active: true,
fixed: true, fixed: true,

View File

@ -113,17 +113,6 @@ class Views extends Component {
/> />
) )
} }
{
this.renderView('status',
<FormattedMessage
id='settings.views.status.label'
/>,
<FormattedMessage
id='settings.views.status.description'
defaultMessage='See how the Parity node is performing in terms of connections to the network, logs from the actual running instance and details of mining (if enabled and configured).'
/>
)
}
{ {
this.renderView('signer', this.renderView('signer',
<FormattedMessage <FormattedMessage

View File

@ -0,0 +1,152 @@
// 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 <http://www.gnu.org/licenses/>.
import React, { Component, PropTypes } from 'react';
import { FormattedMessage } from 'react-intl';
import { connect } from 'react-redux';
import { Container, ContainerTitle, StatusIndicator } from '~/ui';
import grid from '../NodeStatus/nodeStatus.css';
const HealthItem = (props) => {
const status = props.item.status || 'needsAttention';
return (
<div>
<h3>
<StatusIndicator
id={ props.id }
title={ [
(<div>{ props.item.message }</div>)
] }
status={ status }
/>
{ props.title }
<small>&nbsp;({ props.details })</small>
</h3>
<p>
{ status !== 'ok' ? props.item.message : '' }
</p>
</div>
);
};
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] = [(
<FormattedMessage
id='status.health.yes'
defaultMessage='yes'
/>
), (
<FormattedMessage
id='status.health.no'
defaultMessage='no'
/>
)];
return (
<Container>
<ContainerTitle
title={
<div>
<FormattedMessage
id='status.health.title'
defaultMessage='Node Health'
/>
</div>
}
/>
<div className={ grid.container }>
<div className={ grid.row }>
<div className={ grid.col4 }>
<HealthItem
id='status.health.sync'
title={
<FormattedMessage
id='status.health.sync'
defaultMessage='Chain Synchronized'
/>
}
details={ !sync.details ? yes : no }
item={ sync }
/>
</div>
<div className={ grid.col4 }>
<HealthItem
id='status.health.peers'
title={
<FormattedMessage
id='status.health.peers'
defaultMessage='Connected Peers'
/>
}
details={ (peers.details || []).join('/') }
item={ peers }
/>
</div>
<div className={ grid.col4 }>
<HealthItem
id='status.health.time'
title={
<FormattedMessage
id='status.health.time'
defaultMessage='Time Synchronized'
/>
}
details={ `${time.details || 0} ms` }
item={ time }
/>
</div>
</div>
</div>
</Container>
);
}
}
function mapStateToProps (state) {
return state.nodeStatus.health;
}
export default connect(
mapStateToProps,
null
)(Health);

View File

@ -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 <http://www.gnu.org/licenses/>.
export default from './health';

View File

@ -44,7 +44,7 @@
} }
.col, .col,
.col3, .col4_5, .col6, .col12 { .col3, .col4, .col4_5, .col6, .col12 {
float: left; float: left;
padding: 0 1em; padding: 0 1em;
box-sizing: border-box; box-sizing: border-box;
@ -57,6 +57,13 @@
width: calc(100% / 12 * 3); 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 { .col4_5 {
width: 37.5%; width: 37.5%;
width: -webkit-calc(100% / 12 * 4.5); width: -webkit-calc(100% / 12 * 4.5);

View File

@ -20,6 +20,7 @@ import { FormattedMessage } from 'react-intl';
import { Page } from '~/ui'; import { Page } from '~/ui';
import Debug from './Debug'; import Debug from './Debug';
import Health from './Health';
import Peers from './Peers'; import Peers from './Peers';
import NodeStatus from './NodeStatus'; import NodeStatus from './NodeStatus';
@ -35,6 +36,7 @@ export default () => (
} }
> >
<div className={ styles.body }> <div className={ styles.body }>
<Health />
<NodeStatus /> <NodeStatus />
<Peers /> <Peers />
<Debug /> <Debug />

View File

@ -58,3 +58,7 @@
margin: 0.5em 0; margin: 0.5em 0;
} }
} }
.status {
font-size: 4rem;
}

View File

@ -20,7 +20,7 @@ import { FormattedMessage } from 'react-intl';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import store from 'store'; import store from 'store';
import { Button } from '~/ui'; import { Button, StatusIndicator } from '~/ui';
import styles from './syncWarning.css'; import styles from './syncWarning.css';
@ -38,7 +38,8 @@ export const showSyncWarning = () => {
class SyncWarning extends Component { class SyncWarning extends Component {
static propTypes = { static propTypes = {
isSyncing: PropTypes.bool isOk: PropTypes.bool.isRequired,
health: PropTypes.object.isRequired
}; };
state = { state = {
@ -47,10 +48,10 @@ class SyncWarning extends Component {
}; };
render () { render () {
const { isSyncing } = this.props; const { isOk, health } = this.props;
const { dontShowAgain, show } = this.state; const { dontShowAgain, show } = this.state;
if (!isSyncing || isSyncing === null || !show) { if (isOk || !show) {
return null; return null;
} }
@ -59,18 +60,19 @@ class SyncWarning extends Component {
<div className={ styles.overlay } /> <div className={ styles.overlay } />
<div className={ styles.modal }> <div className={ styles.modal }>
<div className={ styles.body }> <div className={ styles.body }>
<FormattedMessage <div className={ styles.status }>
id='syncWarning.message.line1' <StatusIndicator
defaultMessage={ ` type='signal'
Your Parity node is still syncing to the chain. id='healthWarning.indicator'
` } status={ health.overall.status }
/>
<FormattedMessage
id='syncWarning.message.line2'
defaultMessage={ `
Some of the shown information might be out-of-date.
` }
/> />
</div>
{
health.overall.message.map(message => (
<p key={ message }>{ message }</p>
))
}
<div className={ styles.button }> <div className={ styles.button }>
<Checkbox <Checkbox
@ -113,14 +115,13 @@ class SyncWarning extends Component {
} }
function mapStateToProps (state) { function mapStateToProps (state) {
const { syncing } = state.nodeStatus; const { health } = state.nodeStatus;
// syncing could be an Object, false, or null const isNotAvailableYet = health.overall.isReady;
const isSyncing = syncing const isOk = isNotAvailableYet || health.overall.status === 'ok';
? true
: syncing;
return { return {
isSyncing isOk,
health
}; };
} }

View File

@ -26,7 +26,12 @@ function createRedux (syncing = null) {
getState: () => { getState: () => {
return { return {
nodeStatus: { nodeStatus: {
syncing health: {
overall: {
status: syncing ? 'needsAttention' : 'ok',
message: []
}
}
} }
}; };
} }

View File

@ -67,10 +67,11 @@ pub fn setup_log(config: &Config) -> Result<Arc<RotatingLogger>, String> {
let mut levels = String::new(); let mut levels = String::new();
let mut builder = LogBuilder::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); builder.filter(Some("ws"), LogLevelFilter::Warn);
// Disable rustls info logging by default. builder.filter(Some("reqwest"), LogLevelFilter::Warn);
builder.filter(Some("rustls"), LogLevelFilter::Warn); builder.filter(Some("rustls"), LogLevelFilter::Warn);
// Enable info for others.
builder.filter(None, LogLevelFilter::Info); builder.filter(None, LogLevelFilter::Info);
if let Ok(lvl) = env::var("RUST_LOG") { if let Ok(lvl) = env::var("RUST_LOG") {

View File

@ -78,6 +78,7 @@ disable_periodic = true
jit = false jit = false
[misc] [misc]
ntp_server = "pool.ntp.org:123"
logging = "own_tx=trace" logging = "own_tx=trace"
log_file = "/var/log/parity.log" log_file = "/var/log/parity.log"
color = true color = true

View File

@ -183,8 +183,10 @@ usage! {
or |c: &Config| otry!(c.rpc).apis.as_ref().map(|vec| vec.join(",")), or |c: &Config| otry!(c.rpc).apis.as_ref().map(|vec| vec.join(",")),
flag_jsonrpc_hosts: String = "none", flag_jsonrpc_hosts: String = "none",
or |c: &Config| otry!(c.rpc).hosts.as_ref().map(|vec| vec.join(",")), or |c: &Config| otry!(c.rpc).hosts.as_ref().map(|vec| vec.join(",")),
flag_jsonrpc_threads: Option<usize> = None, flag_jsonrpc_server_threads: Option<usize> = None,
or |c: &Config| otry!(c.rpc).threads.map(Some), or |c: &Config| otry!(c.rpc).server_threads.map(Some),
flag_jsonrpc_threads: usize = 0usize,
or |c: &Config| otry!(c.rpc).processing_threads,
// WS // WS
flag_no_ws: bool = false, flag_no_ws: bool = false,
@ -355,6 +357,8 @@ usage! {
or |c: &Config| otry!(c.vm).jit.clone(), or |c: &Config| otry!(c.vm).jit.clone(),
// -- Miscellaneous Options // -- Miscellaneous Options
flag_ntp_server: String = "pool.ntp.org:123",
or |c: &Config| otry!(c.misc).ntp_server.clone(),
flag_logging: Option<String> = None, flag_logging: Option<String> = None,
or |c: &Config| otry!(c.misc).logging.clone().map(Some), or |c: &Config| otry!(c.misc).logging.clone().map(Some),
flag_log_file: Option<String> = None, flag_log_file: Option<String> = None,
@ -471,7 +475,8 @@ struct Rpc {
cors: Option<String>, cors: Option<String>,
apis: Option<Vec<String>>, apis: Option<Vec<String>>,
hosts: Option<Vec<String>>, hosts: Option<Vec<String>>,
threads: Option<usize>, server_threads: Option<usize>,
processing_threads: Option<usize>,
} }
#[derive(Default, Debug, PartialEq, Deserialize)] #[derive(Default, Debug, PartialEq, Deserialize)]
@ -590,6 +595,7 @@ struct VM {
#[derive(Default, Debug, PartialEq, Deserialize)] #[derive(Default, Debug, PartialEq, Deserialize)]
struct Misc { struct Misc {
ntp_server: Option<String>,
logging: Option<String>, logging: Option<String>,
log_file: Option<String>, log_file: Option<String>,
color: Option<bool>, color: Option<bool>,
@ -752,7 +758,8 @@ mod tests {
flag_jsonrpc_cors: Some("null".into()), flag_jsonrpc_cors: Some("null".into()),
flag_jsonrpc_apis: "web3,eth,net,parity,traces,rpc,secretstore".into(), flag_jsonrpc_apis: "web3,eth,net,parity,traces,rpc,secretstore".into(),
flag_jsonrpc_hosts: "none".into(), flag_jsonrpc_hosts: "none".into(),
flag_jsonrpc_threads: None, flag_jsonrpc_server_threads: None,
flag_jsonrpc_threads: 0,
// WS // WS
flag_no_ws: false, flag_no_ws: false,
@ -889,6 +896,7 @@ mod tests {
flag_dapps_apis_all: None, flag_dapps_apis_all: None,
// -- Miscellaneous Options // -- Miscellaneous Options
flag_ntp_server: "pool.ntp.org:123".into(),
flag_version: false, flag_version: false,
flag_logging: Some("own_tx=trace".into()), flag_logging: Some("own_tx=trace".into()),
flag_log_file: Some("/var/log/parity.log".into()), flag_log_file: Some("/var/log/parity.log".into()),
@ -980,7 +988,8 @@ mod tests {
cors: None, cors: None,
apis: None, apis: None,
hosts: None, hosts: None,
threads: None, server_threads: None,
processing_threads: None,
}), }),
ipc: Some(Ipc { ipc: Some(Ipc {
disable: None, disable: None,
@ -1064,6 +1073,7 @@ mod tests {
jit: Some(false), jit: Some(false),
}), }),
misc: Some(Misc { misc: Some(Misc {
ntp_server: Some("pool.ntp.org:123".into()),
logging: Some("own_tx=trace".into()), logging: Some("own_tx=trace".into()),
log_file: Some("/var/log/parity.log".into()), log_file: Some("/var/log/parity.log".into()),
color: Some(true), color: Some(true),

View File

@ -177,9 +177,12 @@ API and Console Options:
is additional security against some attack is additional security against some attack
vectors. Special options: "all", "none", vectors. Special options: "all", "none",
(default: {flag_jsonrpc_hosts}). (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 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}) --no-ws Disable the WebSockets server. (default: {flag_no_ws})
--ws-port PORT Specify the port portion of the WebSockets server --ws-port PORT Specify the port portion of the WebSockets server
@ -465,6 +468,8 @@ Internal Options:
--can-restart Executable will auto-restart if exiting with 69. --can-restart Executable will auto-restart if exiting with 69.
Miscellaneous Options: 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 -l --logging LOGGING Specify the logging level. Must conform to the same
format as RUST_LOG. (default: {flag_logging:?}) format as RUST_LOG. (default: {flag_logging:?})
--log-file FILENAME Specify a filename into which logging should be --log-file FILENAME Specify a filename into which logging should be

View File

@ -137,7 +137,7 @@ impl Configuration {
let secretstore_conf = self.secretstore_config()?; let secretstore_conf = self.secretstore_config()?;
let format = self.format()?; 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; dapps_conf.enabled = false;
writeln!(&mut stderr(), "Warning: Disabling Dapps server because fast RPC server was enabled.").expect("Error writing to stderr.") writeln!(&mut stderr(), "Warning: Disabling Dapps server because fast RPC server was enabled.").expect("Error writing to stderr.")
} }
@ -556,6 +556,7 @@ impl Configuration {
fn ui_config(&self) -> UiConfiguration { fn ui_config(&self) -> UiConfiguration {
UiConfiguration { UiConfiguration {
enabled: self.ui_enabled(), enabled: self.ui_enabled(),
ntp_server: self.args.flag_ntp_server.clone(),
interface: self.ui_interface(), interface: self.ui_interface(),
port: self.args.flag_ports_shift + self.args.flag_ui_port, port: self.args.flag_ports_shift + self.args.flag_ui_port,
hosts: self.ui_hosts(), hosts: self.ui_hosts(),
@ -565,12 +566,18 @@ impl Configuration {
fn dapps_config(&self) -> DappsConfiguration { fn dapps_config(&self) -> DappsConfiguration {
DappsConfiguration { DappsConfiguration {
enabled: self.dapps_enabled(), enabled: self.dapps_enabled(),
ntp_server: self.args.flag_ntp_server.clone(),
dapps_path: PathBuf::from(self.directories().dapps), dapps_path: PathBuf::from(self.directories().dapps),
extra_dapps: if self.args.cmd_dapp { extra_dapps: if self.args.cmd_dapp {
self.args.arg_path.iter().map(|path| PathBuf::from(path)).collect() self.args.arg_path.iter().map(|path| PathBuf::from(path)).collect()
} else { } else {
vec![] vec![]
}, },
extra_embed_on: if self.args.flag_ui_no_validation {
vec![("localhost".to_owned(), 3000)]
} else {
vec![]
},
} }
} }
@ -776,15 +783,11 @@ impl Configuration {
} }
fn ws_hosts(&self) -> Option<Vec<String>> { fn ws_hosts(&self) -> Option<Vec<String>> {
if self.args.flag_ui_no_validation {
return None;
}
self.hosts(&self.args.flag_ws_hosts, &self.ws_interface()) self.hosts(&self.args.flag_ws_hosts, &self.ws_interface())
} }
fn ws_origins(&self) -> Option<Vec<String>> { fn ws_origins(&self) -> Option<Vec<String>> {
if self.args.flag_unsafe_expose { if self.args.flag_unsafe_expose || self.args.flag_ui_no_validation {
return None; return None;
} }
@ -825,11 +828,12 @@ impl Configuration {
}, },
hosts: self.rpc_hosts(), hosts: self.rpc_hosts(),
cors: self.rpc_cors(), 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), Some(threads) if threads > 0 => Some(threads),
None => None, 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) Ok(conf)
@ -1264,6 +1268,7 @@ mod tests {
support_token_api: true support_token_api: true
}, UiConfiguration { }, UiConfiguration {
enabled: true, enabled: true,
ntp_server: "pool.ntp.org:123".into(),
interface: "127.0.0.1".into(), interface: "127.0.0.1".into(),
port: 8180, port: 8180,
hosts: Some(vec![]), hosts: Some(vec![]),
@ -1504,6 +1509,7 @@ mod tests {
assert_eq!(conf0.directories().signer, "signer".to_owned()); assert_eq!(conf0.directories().signer, "signer".to_owned());
assert_eq!(conf0.ui_config(), UiConfiguration { assert_eq!(conf0.ui_config(), UiConfiguration {
enabled: true, enabled: true,
ntp_server: "pool.ntp.org:123".into(),
interface: "127.0.0.1".into(), interface: "127.0.0.1".into(),
port: 8180, port: 8180,
hosts: Some(vec![]), hosts: Some(vec![]),
@ -1512,14 +1518,17 @@ mod tests {
assert_eq!(conf1.directories().signer, "signer".to_owned()); assert_eq!(conf1.directories().signer, "signer".to_owned());
assert_eq!(conf1.ui_config(), UiConfiguration { assert_eq!(conf1.ui_config(), UiConfiguration {
enabled: true, enabled: true,
ntp_server: "pool.ntp.org:123".into(),
interface: "127.0.0.1".into(), interface: "127.0.0.1".into(),
port: 8180, port: 8180,
hosts: Some(vec![]), hosts: Some(vec![]),
}); });
assert_eq!(conf1.ws_config().unwrap().hosts, None); assert_eq!(conf1.dapps_config().extra_embed_on, vec![("localhost".to_owned(), 3000)]);
assert_eq!(conf1.ws_config().unwrap().origins, None);
assert_eq!(conf2.directories().signer, "signer".to_owned()); assert_eq!(conf2.directories().signer, "signer".to_owned());
assert_eq!(conf2.ui_config(), UiConfiguration { assert_eq!(conf2.ui_config(), UiConfiguration {
enabled: true, enabled: true,
ntp_server: "pool.ntp.org:123".into(),
interface: "127.0.0.1".into(), interface: "127.0.0.1".into(),
port: 3123, port: 3123,
hosts: Some(vec![]), hosts: Some(vec![]),
@ -1528,6 +1537,7 @@ mod tests {
assert_eq!(conf3.directories().signer, "signer".to_owned()); assert_eq!(conf3.directories().signer, "signer".to_owned());
assert_eq!(conf3.ui_config(), UiConfiguration { assert_eq!(conf3.ui_config(), UiConfiguration {
enabled: true, enabled: true,
ntp_server: "pool.ntp.org:123".into(),
interface: "test".into(), interface: "test".into(),
port: 8180, port: 8180,
hosts: Some(vec![]), hosts: Some(vec![]),

View File

@ -22,6 +22,7 @@ use ethcore::client::{Client, BlockChainClient, BlockId};
use ethcore::transaction::{Transaction, Action}; use ethcore::transaction::{Transaction, Action};
use ethsync::LightSync; use ethsync::LightSync;
use futures::{future, IntoFuture, Future, BoxFuture}; use futures::{future, IntoFuture, Future, BoxFuture};
use futures_cpupool::CpuPool;
use hash_fetch::fetch::Client as FetchClient; use hash_fetch::fetch::Client as FetchClient;
use hash_fetch::urlhint::ContractClient; use hash_fetch::urlhint::ContractClient;
use helpers::replace_home; use helpers::replace_home;
@ -35,8 +36,10 @@ use util::{Bytes, Address};
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, PartialEq, Clone)]
pub struct Configuration { pub struct Configuration {
pub enabled: bool, pub enabled: bool,
pub ntp_server: String,
pub dapps_path: PathBuf, pub dapps_path: PathBuf,
pub extra_dapps: Vec<PathBuf>, pub extra_dapps: Vec<PathBuf>,
pub extra_embed_on: Vec<(String, u16)>,
} }
impl Default for Configuration { impl Default for Configuration {
@ -44,8 +47,10 @@ impl Default for Configuration {
let data_dir = default_data_path(); let data_dir = default_data_path();
Configuration { Configuration {
enabled: true, enabled: true,
ntp_server: "pool.ntp.org:123".into(),
dapps_path: replace_home(&data_dir, "$BASE/dapps").into(), dapps_path: replace_home(&data_dir, "$BASE/dapps").into(),
extra_dapps: vec![], extra_dapps: vec![],
extra_embed_on: vec![],
} }
} }
} }
@ -140,6 +145,7 @@ pub struct Dependencies {
pub sync_status: Arc<SyncStatus>, pub sync_status: Arc<SyncStatus>,
pub contract_client: Arc<ContractClient>, pub contract_client: Arc<ContractClient>,
pub remote: parity_reactor::TokioRemote, pub remote: parity_reactor::TokioRemote,
pub pool: CpuPool,
pub fetch: FetchClient, pub fetch: FetchClient,
pub signer: Arc<SignerService>, pub signer: Arc<SignerService>,
pub ui_address: Option<(String, u16)>, pub ui_address: Option<(String, u16)>,
@ -152,20 +158,23 @@ pub fn new(configuration: Configuration, deps: Dependencies) -> Result<Option<Mi
server::dapps_middleware( server::dapps_middleware(
deps, deps,
&configuration.ntp_server,
configuration.dapps_path, configuration.dapps_path,
configuration.extra_dapps, configuration.extra_dapps,
rpc::DAPPS_DOMAIN.into(), rpc::DAPPS_DOMAIN,
configuration.extra_embed_on,
).map(Some) ).map(Some)
} }
pub fn new_ui(enabled: bool, deps: Dependencies) -> Result<Option<Middleware>, String> { pub fn new_ui(enabled: bool, ntp_server: &str, deps: Dependencies) -> Result<Option<Middleware>, String> {
if !enabled { if !enabled {
return Ok(None); return Ok(None);
} }
server::ui_middleware( server::ui_middleware(
deps, deps,
rpc::DAPPS_DOMAIN.into(), ntp_server,
rpc::DAPPS_DOMAIN,
).map(Some) ).map(Some)
} }
@ -192,16 +201,19 @@ mod server {
pub fn dapps_middleware( pub fn dapps_middleware(
_deps: Dependencies, _deps: Dependencies,
_ntp_server: &str,
_dapps_path: PathBuf, _dapps_path: PathBuf,
_extra_dapps: Vec<PathBuf>, _extra_dapps: Vec<PathBuf>,
_dapps_domain: String, _dapps_domain: &str,
_extra_embed_on: Vec<(String, u16)>,
) -> Result<Middleware, String> { ) -> Result<Middleware, String> {
Err("Your Parity version has been compiled without WebApps support.".into()) Err("Your Parity version has been compiled without WebApps support.".into())
} }
pub fn ui_middleware( pub fn ui_middleware(
_deps: Dependencies, _deps: Dependencies,
_dapps_domain: String, _ntp_server: &str,
_dapps_domain: &str,
) -> Result<Middleware, String> { ) -> Result<Middleware, String> {
Err("Your Parity version has been compiled without UI support.".into()) Err("Your Parity version has been compiled without UI support.".into())
} }
@ -226,17 +238,22 @@ mod server {
pub fn dapps_middleware( pub fn dapps_middleware(
deps: Dependencies, deps: Dependencies,
ntp_server: &str,
dapps_path: PathBuf, dapps_path: PathBuf,
extra_dapps: Vec<PathBuf>, extra_dapps: Vec<PathBuf>,
dapps_domain: String, dapps_domain: &str,
extra_embed_on: Vec<(String, u16)>,
) -> Result<Middleware, String> { ) -> Result<Middleware, String> {
let signer = deps.signer; let signer = deps.signer;
let parity_remote = parity_reactor::Remote::new(deps.remote.clone()); 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)); let web_proxy_tokens = Arc::new(move |token| signer.web_proxy_access_token_domain(&token));
Ok(parity_dapps::Middleware::dapps( Ok(parity_dapps::Middleware::dapps(
ntp_server,
deps.pool,
parity_remote, parity_remote,
deps.ui_address, deps.ui_address,
extra_embed_on,
dapps_path, dapps_path,
extra_dapps, extra_dapps,
dapps_domain, dapps_domain,
@ -249,15 +266,18 @@ mod server {
pub fn ui_middleware( pub fn ui_middleware(
deps: Dependencies, deps: Dependencies,
dapps_domain: String, ntp_server: &str,
dapps_domain: &str,
) -> Result<Middleware, String> { ) -> Result<Middleware, String> {
let parity_remote = parity_reactor::Remote::new(deps.remote.clone()); let parity_remote = parity_reactor::Remote::new(deps.remote.clone());
Ok(parity_dapps::Middleware::ui( Ok(parity_dapps::Middleware::ui(
ntp_server,
deps.pool,
parity_remote, parity_remote,
dapps_domain,
deps.contract_client, deps.contract_client,
deps.sync_status, deps.sync_status,
deps.fetch, deps.fetch,
dapps_domain,
)) ))
} }

View File

@ -29,6 +29,7 @@ extern crate docopt;
extern crate env_logger; extern crate env_logger;
extern crate fdlimit; extern crate fdlimit;
extern crate futures; extern crate futures;
extern crate futures_cpupool;
extern crate isatty; extern crate isatty;
extern crate jsonrpc_core; extern crate jsonrpc_core;
extern crate num_cpus; extern crate num_cpus;

View File

@ -30,6 +30,7 @@ use rpc_apis::{self, ApiSet};
pub use parity_rpc::{IpcServer, HttpServer, RequestMiddleware}; pub use parity_rpc::{IpcServer, HttpServer, RequestMiddleware};
pub use parity_rpc::ws::Server as WsServer; pub use parity_rpc::ws::Server as WsServer;
pub use parity_rpc::informant::CpuPool;
pub const DAPPS_DOMAIN: &'static str = "web3.site"; pub const DAPPS_DOMAIN: &'static str = "web3.site";
@ -42,7 +43,8 @@ pub struct HttpConfiguration {
pub apis: ApiSet, pub apis: ApiSet,
pub cors: Option<Vec<String>>, pub cors: Option<Vec<String>>,
pub hosts: Option<Vec<String>>, pub hosts: Option<Vec<String>>,
pub threads: Option<usize>, pub server_threads: Option<usize>,
pub processing_threads: usize,
} }
impl HttpConfiguration { impl HttpConfiguration {
@ -63,7 +65,8 @@ impl Default for HttpConfiguration {
apis: ApiSet::UnsafeContext, apis: ApiSet::UnsafeContext,
cors: None, cors: None,
hosts: Some(Vec::new()), hosts: Some(Vec::new()),
threads: None, server_threads: None,
processing_threads: 0,
} }
} }
} }
@ -71,6 +74,7 @@ impl Default for HttpConfiguration {
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, PartialEq, Clone)]
pub struct UiConfiguration { pub struct UiConfiguration {
pub enabled: bool, pub enabled: bool,
pub ntp_server: String,
pub interface: String, pub interface: String,
pub port: u16, pub port: u16,
pub hosts: Option<Vec<String>>, pub hosts: Option<Vec<String>>,
@ -94,7 +98,8 @@ impl From<UiConfiguration> for HttpConfiguration {
apis: rpc_apis::ApiSet::SafeContext, apis: rpc_apis::ApiSet::SafeContext,
cors: None, cors: None,
hosts: conf.hosts, hosts: conf.hosts,
threads: None, server_threads: None,
processing_threads: 0,
} }
} }
} }
@ -103,6 +108,7 @@ impl Default for UiConfiguration {
fn default() -> Self { fn default() -> Self {
UiConfiguration { UiConfiguration {
enabled: true && cfg!(feature = "ui-enabled"), enabled: true && cfg!(feature = "ui-enabled"),
ntp_server: "pool.ntp.org:123".into(),
port: 8180, port: 8180,
interface: "127.0.0.1".into(), interface: "127.0.0.1".into(),
hosts: Some(vec![]), hosts: Some(vec![]),
@ -176,6 +182,7 @@ pub struct Dependencies<D: rpc_apis::Dependencies> {
pub apis: Arc<D>, pub apis: Arc<D>,
pub remote: TokioRemote, pub remote: TokioRemote,
pub stats: Arc<RpcStats>, pub stats: Arc<RpcStats>,
pub pool: Option<CpuPool>,
} }
pub fn new_ws<D: rpc_apis::Dependencies>( pub fn new_ws<D: rpc_apis::Dependencies>(
@ -192,11 +199,12 @@ pub fn new_ws<D: rpc_apis::Dependencies>(
let addr = url.parse().map_err(|_| format!("Invalid WebSockets listen host/port given: {}", url))?; 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 handler = {
let mut handler = MetaIoHandler::with_middleware(( let mut handler = MetaIoHandler::with_middleware((
rpc::WsDispatcher::new(full_handler), 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(); let apis = conf.apis.list_apis();
deps.apis.extend_with_set(&mut handler, &apis); deps.apis.extend_with_set(&mut handler, &apis);
@ -252,7 +260,8 @@ pub fn new_http<D: rpc_apis::Dependencies>(
let http_address = (conf.interface, conf.port); let http_address = (conf.interface, conf.port);
let url = format!("{}:{}", http_address.0, http_address.1); let url = format!("{}:{}", http_address.0, http_address.1);
let addr = url.parse().map_err(|_| format!("Invalid {} listen host/port given: {}", id, url))?; 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 remote = deps.remote.clone();
let cors_domains = into_domains(conf.cors); let cors_domains = into_domains(conf.cors);
@ -265,7 +274,7 @@ pub fn new_http<D: rpc_apis::Dependencies>(
handler, handler,
remote, remote,
rpc::RpcExtractor, rpc::RpcExtractor,
match (conf.threads, middleware) { match (conf.server_threads, middleware) {
(Some(threads), None) => rpc::HttpSettings::Threads(threads), (Some(threads), None) => rpc::HttpSettings::Threads(threads),
(None, middleware) => rpc::HttpSettings::Dapps(middleware), (None, middleware) => rpc::HttpSettings::Dapps(middleware),
(Some(_), Some(_)) => { (Some(_), Some(_)) => {
@ -291,7 +300,8 @@ pub fn new_ipc<D: rpc_apis::Dependencies>(
return Ok(None); 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(); let remote = dependencies.remote.clone();
match rpc::start_ipc(&conf.socket_addr, handler, remote, rpc::RpcExtractor) { match rpc::start_ipc(&conf.socket_addr, handler, remote, rpc::RpcExtractor) {
Ok(server) => Ok(Some(server)), Ok(server) => Ok(Some(server)),
@ -318,11 +328,11 @@ fn with_domain(items: Option<Vec<String>>, domain: &str, addresses: &[Option<(St
}) })
} }
fn setup_apis<D>(apis: ApiSet, deps: &Dependencies<D>) -> MetaIoHandler<Metadata, Middleware<D::Notifier>> fn setup_apis<D>(apis: ApiSet, deps: &Dependencies<D>, pool: Option<CpuPool>) -> MetaIoHandler<Metadata, Middleware<D::Notifier>>
where D: rpc_apis::Dependencies where D: rpc_apis::Dependencies
{ {
let mut handler = MetaIoHandler::with_middleware( 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(); let apis = apis.list_apis();
deps.apis.extend_with_set(&mut handler, &apis); deps.apis.extend_with_set(&mut handler, &apis);

View File

@ -27,8 +27,7 @@ use ethcore::miner::{StratumOptions, Stratum};
use ethcore::service::ClientService; use ethcore::service::ClientService;
use ethcore::snapshot; use ethcore::snapshot;
use ethcore::verification::queue::VerifierSettings; use ethcore::verification::queue::VerifierSettings;
use ethsync::NetworkConfiguration; use ethsync::{self, SyncConfig};
use ethsync::SyncConfig;
use fdlimit::raise_fd_limit; use fdlimit::raise_fd_limit;
use hash_fetch::fetch::{Fetch, Client as FetchClient}; use hash_fetch::fetch::{Fetch, Client as FetchClient};
use informant::{Informant, LightNodeInformantData, FullNodeInformantData}; use informant::{Informant, LightNodeInformantData, FullNodeInformantData};
@ -84,7 +83,7 @@ pub struct RunCmd {
pub ws_conf: rpc::WsConfiguration, pub ws_conf: rpc::WsConfiguration,
pub http_conf: rpc::HttpConfiguration, pub http_conf: rpc::HttpConfiguration,
pub ipc_conf: rpc::IpcConfiguration, pub ipc_conf: rpc::IpcConfiguration,
pub net_conf: NetworkConfiguration, pub net_conf: ethsync::NetworkConfiguration,
pub network_id: Option<u64>, pub network_id: Option<u64>,
pub warp_sync: bool, pub warp_sync: bool,
pub public_node: bool, pub public_node: bool,
@ -237,7 +236,7 @@ fn execute_light(cmd: RunCmd, can_restart: bool, logger: Arc<RotatingLogger>) ->
network_config: net_conf.into_basic().map_err(|e| format!("Failed to produce network config: {}", e))?, network_config: net_conf.into_basic().map_err(|e| format!("Failed to produce network config: {}", e))?,
client: Arc::new(provider), client: Arc::new(provider),
network_id: cmd.network_id.unwrap_or(spec.network_id()), 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()], handlers: vec![on_demand.clone()],
}; };
let light_sync = LightSync::new(sync_params).map_err(|e| format!("Error starting network: {}", e))?; let light_sync = LightSync::new(sync_params).map_err(|e| format!("Error starting network: {}", e))?;
@ -277,9 +276,18 @@ fn execute_light(cmd: RunCmd, can_restart: bool, logger: Arc<RotatingLogger>) ->
on_demand: on_demand.clone(), on_demand: on_demand.clone(),
}); });
let sync = light_sync.clone(); struct LightSyncStatus(Arc<LightSync>);
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 { 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, contract_client: contract_client,
remote: event_loop.raw_remote(), remote: event_loop.raw_remote(),
fetch: fetch.clone(), fetch: fetch.clone(),
@ -289,7 +297,7 @@ fn execute_light(cmd: RunCmd, can_restart: bool, logger: Arc<RotatingLogger>) ->
}; };
let dapps_middleware = dapps::new(cmd.dapps_conf.clone(), dapps_deps.clone())?; 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 // start RPCs
let dapps_service = dapps::service(&dapps_middleware); let dapps_service = dapps::service(&dapps_middleware);
@ -316,6 +324,11 @@ fn execute_light(cmd: RunCmd, can_restart: bool, logger: Arc<RotatingLogger>) ->
apis: deps_for_rpc_apis.clone(), apis: deps_for_rpc_apis.clone(),
remote: event_loop.raw_remote(), remote: event_loop.raw_remote(),
stats: rpc_stats.clone(), 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 // start rpc servers
@ -581,7 +594,7 @@ pub fn execute(cmd: RunCmd, can_restart: bool, logger: Arc<RotatingLogger>) -> R
let (sync_provider, manage_network, chain_notify) = modules::sync( let (sync_provider, manage_network, chain_notify) = modules::sync(
&mut hypervisor, &mut hypervisor,
sync_config, sync_config,
net_conf.into(), net_conf.clone().into(),
client.clone(), client.clone(),
snapshot_service.clone(), snapshot_service.clone(),
client.clone(), client.clone(),
@ -625,8 +638,20 @@ pub fn execute(cmd: RunCmd, can_restart: bool, logger: Arc<RotatingLogger>) -> R
let (sync, client) = (sync_provider.clone(), client.clone()); let (sync, client) = (sync_provider.clone(), client.clone());
let contract_client = Arc::new(::dapps::FullRegistrar { client: client.clone() }); let contract_client = Arc::new(::dapps::FullRegistrar { client: client.clone() });
struct SyncStatus(Arc<ethsync::SyncProvider>, Arc<Client>, 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 { 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, contract_client: contract_client,
remote: event_loop.raw_remote(), remote: event_loop.raw_remote(),
fetch: fetch.clone(), fetch: fetch.clone(),
@ -635,7 +660,7 @@ pub fn execute(cmd: RunCmd, can_restart: bool, logger: Arc<RotatingLogger>) -> R
} }
}; };
let dapps_middleware = dapps::new(cmd.dapps_conf.clone(), dapps_deps.clone())?; 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 dapps_service = dapps::service(&dapps_middleware);
let deps_for_rpc_apis = Arc::new(rpc_apis::FullDependencies { let deps_for_rpc_apis = Arc::new(rpc_apis::FullDependencies {
@ -663,6 +688,12 @@ pub fn execute(cmd: RunCmd, can_restart: bool, logger: Arc<RotatingLogger>) -> R
apis: deps_for_rpc_apis.clone(), apis: deps_for_rpc_apis.clone(),
remote: event_loop.raw_remote(), remote: event_loop.raw_remote(),
stats: rpc_stats.clone(), 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 // start rpc servers

View File

@ -10,6 +10,7 @@ authors = ["Parity Technologies <admin@parity.io>"]
[dependencies] [dependencies]
cid = "0.2" cid = "0.2"
futures = "0.1" futures = "0.1"
futures-cpupool = "0.1"
log = "0.3" log = "0.3"
multihash ="0.6" multihash ="0.6"
order-stat = "0.1" order-stat = "0.1"

View File

@ -23,6 +23,7 @@
extern crate cid; extern crate cid;
extern crate crypto as rust_crypto; extern crate crypto as rust_crypto;
extern crate futures; extern crate futures;
extern crate futures_cpupool;
extern crate multihash; extern crate multihash;
extern crate order_stat; extern crate order_stat;
extern crate rand; extern crate rand;

View File

@ -217,18 +217,26 @@ impl<M: core::Middleware<Metadata>> WsDispatcher<M> {
} }
impl<M: core::Middleware<Metadata>> core::Middleware<Metadata> for WsDispatcher<M> { impl<M: core::Middleware<Metadata>> core::Middleware<Metadata> for WsDispatcher<M> {
fn on_request<F>(&self, request: core::Request, meta: Metadata, process: F) -> core::FutureResponse where type Future = core::futures::future::Either<
F: FnOnce(core::Request, Metadata) -> core::FutureResponse, M::Future,
core::FutureResponse,
>;
fn on_request<F, X>(&self, request: core::Request, meta: Metadata, process: F) -> Self::Future where
F: FnOnce(core::Request, Metadata) -> X,
X: core::futures::Future<Item=Option<core::Response>, Error=()> + Send + 'static,
{ {
use self::core::futures::future::Either::{A, B};
let use_full = match &meta.origin { let use_full = match &meta.origin {
&Origin::Signer { .. } => true, &Origin::Signer { .. } => true,
_ => false, _ => false,
}; };
if use_full { if use_full {
self.full_handler.handle_rpc_request(request, meta) A(self.full_handler.handle_rpc_request(request, meta))
} else { } else {
process(request, meta) B(process(request, meta).boxed())
} }
} }
} }

View File

@ -28,6 +28,7 @@ mod codes {
pub const NO_WORK: i64 = -32001; pub const NO_WORK: i64 = -32001;
pub const NO_AUTHOR: i64 = -32002; pub const NO_AUTHOR: i64 = -32002;
pub const NO_NEW_WORK: i64 = -32003; pub const NO_NEW_WORK: i64 = -32003;
pub const NO_WORK_REQUIRED: i64 = -32004;
pub const UNKNOWN_ERROR: i64 = -32009; pub const UNKNOWN_ERROR: i64 = -32009;
pub const TRANSACTION_ERROR: i64 = -32010; pub const TRANSACTION_ERROR: i64 = -32010;
pub const EXECUTION_ERROR: i64 = -32015; pub const EXECUTION_ERROR: i64 = -32015;
@ -133,7 +134,7 @@ pub fn state_pruned() -> Error {
Error { Error {
code: ErrorCode::ServerError(codes::UNSUPPORTED_REQUEST), code: ErrorCode::ServerError(codes::UNSUPPORTED_REQUEST),
message: "This request is not supported because your node is running with state pruning. Run with --pruning=archive.".into(), message: "This request is not supported because your node is running with state pruning. Run with --pruning=archive.".into(),
data: None data: None,
} }
} }
@ -145,7 +146,7 @@ pub fn exceptional() -> Error {
Error { Error {
code: ErrorCode::ServerError(codes::EXCEPTION_ERROR), code: ErrorCode::ServerError(codes::EXCEPTION_ERROR),
message: "The execution failed due to an exception.".into(), message: "The execution failed due to an exception.".into(),
data: None data: None,
} }
} }
@ -153,7 +154,7 @@ pub fn no_work() -> Error {
Error { Error {
code: ErrorCode::ServerError(codes::NO_WORK), code: ErrorCode::ServerError(codes::NO_WORK),
message: "Still syncing.".into(), message: "Still syncing.".into(),
data: None data: None,
} }
} }
@ -161,7 +162,7 @@ pub fn no_new_work() -> Error {
Error { Error {
code: ErrorCode::ServerError(codes::NO_NEW_WORK), code: ErrorCode::ServerError(codes::NO_NEW_WORK),
message: "Work has not changed.".into(), message: "Work has not changed.".into(),
data: None data: None,
} }
} }
@ -169,7 +170,15 @@ pub fn no_author() -> Error {
Error { Error {
code: ErrorCode::ServerError(codes::NO_AUTHOR), code: ErrorCode::ServerError(codes::NO_AUTHOR),
message: "Author not configured. Run Parity with --author to configure.".into(), message: "Author not configured. Run Parity with --author to configure.".into(),
data: None data: None,
}
}
pub fn no_work_required() -> Error {
Error {
code: ErrorCode::ServerError(codes::NO_WORK_REQUIRED),
message: "External work is only required for Proof of Work engines.".into(),
data: None,
} }
} }

View File

@ -332,7 +332,7 @@ impl<C, SN: ?Sized, S: ?Sized, M, EM> Eth for EthClient<C, SN, S, M, EM> where
} }
fn is_mining(&self) -> Result<bool, Error> { fn is_mining(&self) -> Result<bool, Error> {
Ok(self.miner.is_sealing()) Ok(self.miner.is_currently_sealing())
} }
fn hashrate(&self) -> Result<RpcU256, Error> { fn hashrate(&self) -> Result<RpcU256, Error> {
@ -553,6 +553,11 @@ impl<C, SN: ?Sized, S: ?Sized, M, EM> Eth for EthClient<C, SN, S, M, EM> where
} }
fn work(&self, no_new_work_timeout: Trailing<u64>) -> Result<Work, Error> { fn work(&self, no_new_work_timeout: Trailing<u64>) -> Result<Work, Error> {
if !self.miner.can_produce_work_package() {
warn!(target: "miner", "Cannot give work package - engine seals internally.");
return Err(errors::no_work_required())
}
let no_new_work_timeout = no_new_work_timeout.unwrap_or_default(); let no_new_work_timeout = no_new_work_timeout.unwrap_or_default();
// check if we're still syncing and return empty strings in that case // check if we're still syncing and return empty strings in that case
@ -602,6 +607,11 @@ impl<C, SN: ?Sized, S: ?Sized, M, EM> Eth for EthClient<C, SN, S, M, EM> where
} }
fn submit_work(&self, nonce: RpcH64, pow_hash: RpcH256, mix_hash: RpcH256) -> Result<bool, Error> { fn submit_work(&self, nonce: RpcH64, pow_hash: RpcH256, mix_hash: RpcH256) -> Result<bool, Error> {
if !self.miner.can_produce_work_package() {
warn!(target: "miner", "Cannot submit work - engine seals internally.");
return Err(errors::no_work_required())
}
let nonce: H64 = nonce.into(); let nonce: H64 = nonce.into();
let pow_hash: H256 = pow_hash.into(); let pow_hash: H256 = pow_hash.into();
let mix_hash: H256 = mix_hash.into(); let mix_hash: H256 = mix_hash.into();

View File

@ -21,10 +21,13 @@ use std::sync::Arc;
use std::sync::atomic::{self, AtomicUsize}; use std::sync::atomic::{self, AtomicUsize};
use std::time; use std::time;
use futures::Future; use futures::Future;
use futures_cpupool as pool;
use jsonrpc_core as rpc; use jsonrpc_core as rpc;
use order_stat; use order_stat;
use util::RwLock; use util::RwLock;
pub use self::pool::CpuPool;
const RATE_SECONDS: usize = 10; const RATE_SECONDS: usize = 10;
const STATS_SAMPLES: usize = 60; const STATS_SAMPLES: usize = 60;
@ -184,14 +187,16 @@ pub trait ActivityNotifier: Send + Sync + 'static {
pub struct Middleware<T: ActivityNotifier = ClientNotifier> { pub struct Middleware<T: ActivityNotifier = ClientNotifier> {
stats: Arc<RpcStats>, stats: Arc<RpcStats>,
notifier: T, notifier: T,
pool: Option<CpuPool>,
} }
impl<T: ActivityNotifier> Middleware<T> { impl<T: ActivityNotifier> Middleware<T> {
/// Create new Middleware with stats counter and activity notifier. /// Create new Middleware with stats counter and activity notifier.
pub fn new(stats: Arc<RpcStats>, notifier: T) -> Self { pub fn new(stats: Arc<RpcStats>, notifier: T, pool: Option<CpuPool>) -> Self {
Middleware { Middleware {
stats: stats, stats,
notifier: notifier, notifier,
pool,
} }
} }
@ -201,19 +206,32 @@ impl<T: ActivityNotifier> Middleware<T> {
} }
impl<M: rpc::Metadata, T: ActivityNotifier> rpc::Middleware<M> for Middleware<T> { impl<M: rpc::Metadata, T: ActivityNotifier> rpc::Middleware<M> for Middleware<T> {
fn on_request<F>(&self, request: rpc::Request, meta: M, process: F) -> rpc::FutureResponse where type Future = rpc::futures::future::Either<
F: FnOnce(rpc::Request, M) -> rpc::FutureResponse, pool::CpuFuture<Option<rpc::Response>, ()>,
rpc::FutureResponse,
>;
fn on_request<F, X>(&self, request: rpc::Request, meta: M, process: F) -> Self::Future where
F: FnOnce(rpc::Request, M) -> X,
X: rpc::futures::Future<Item=Option<rpc::Response>, Error=()> + Send + 'static,
{ {
use self::rpc::futures::future::Either::{A, B};
let start = time::Instant::now(); let start = time::Instant::now();
let response = process(request, meta);
self.notifier.active(); self.notifier.active();
self.stats.count_request();
let stats = self.stats.clone(); let stats = self.stats.clone();
stats.count_request(); let future = process(request, meta).map(move |res| {
response.map(move |res| {
stats.add_roundtrip(Self::as_micro(start.elapsed())); stats.add_roundtrip(Self::as_micro(start.elapsed()));
res res
}).boxed() });
match self.pool {
Some(ref pool) => A(pool.spawn(future)),
None => B(future.boxed()),
}
} }
} }

View File

@ -207,6 +207,11 @@ impl MinerService for TestMinerService {
unimplemented!(); unimplemented!();
} }
/// PoW chain - can produce work package
fn can_produce_work_package(&self) -> bool {
true
}
/// New chain head event. Restart mining operation. /// New chain head event. Restart mining operation.
fn update_sealing(&self, _chain: &MiningBlockChainClient) { fn update_sealing(&self, _chain: &MiningBlockChainClient) {
unimplemented!(); unimplemented!();
@ -265,7 +270,7 @@ impl MinerService for TestMinerService {
self.last_nonces.read().get(address).cloned() self.last_nonces.read().get(address).cloned()
} }
fn is_sealing(&self) -> bool { fn is_currently_sealing(&self) -> bool {
false false
} }

View File

@ -126,6 +126,11 @@ impl Client {
*self.client.write() = (time::Instant::now(), client.clone()); *self.client.write() = (time::Instant::now(), client.clone());
Ok(client) Ok(client)
} }
/// Returns a handle to underlying CpuPool of this client.
pub fn pool(&self) -> CpuPool {
self.pool.clone()
}
} }
impl Fetch for Client { impl Fetch for Client {
@ -204,6 +209,15 @@ pub enum Error {
Aborted, 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<reqwest::Error> for Error { impl From<reqwest::Error> for Error {
fn from(error: reqwest::Error) -> Self { fn from(error: reqwest::Error) -> Self {
Error::Fetch(error) Error::Fetch(error)